协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
md-to-confluence【开发与工程】Push markdown files to Confluence - update existing pages or create new ones under a parent page. Use this skill whenever the user wants to publish markdown to Confluence, update a Confluence page from a local .md file, create new Confluence pages, or batch-create pages. Also triggers on "push to confluence", "update confluence page", "create conflue…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Push local markdown to Confluence - update existing pages or create new ones. The bundled script handles pandoc conversion, image uploads, Confluence macro formatting, and wide mode. It avoids the many pitfalls of Confluence's storage format so you don't have to build HTML manually.
# Update existing page
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py <page_id> <markdown_file>
# Create new page under a parent
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py --create --parent-id <parent_id> --space <SPACE_KEY> <markdown_file> --wide
# Batch create
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py --create --parent-id <parent_id> --space <SPACE_KEY> file1.md file2.md --wide
The script auto-loads credentials from .env at the project root.
Before updating an existing page, ALWAYS run the preflight. It detects whether anyone edited the page directly on Confluence since your last sync — so you don't silently overwrite their changes.
python3 .claude/skills/md-to-confluence/scripts/confluence-preflight.py <page_id> <local.md>
Exit codes:
How snapshots work (Git-style 3-way diff):
.local/confluence-snapshots/<page_id>.md (auto-created, gitignored)First-time push (no baseline yet): preflight falls back to a 2-way diff and prints a warning. The baseline is auto-created after the push succeeds, so subsequent pushes have the full 3-way protection.
Use --show-diff to see full diffs inline; use --force only if you have
explicitly verified the Confluence-side changes and decided to overwrite.
Update - user has an existing page URL:
https://[your-instance].atlassian.net/wiki/spaces/[SPACE]/pages/1234567890/Page+Title
^^^^^^^^^^ page ID
Create - user wants a new page. You need:
--parent-id (from the parent page URL)--space key (the Confluence space key for your project)# Update with title change and wide mode
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py 1234567890 my-doc.md --title "New Title" --wide
# Create under parent with auto-title (extracted from first H1)
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py --create --parent-id 9876543210 --space [SPACE_KEY] guide.md --wide
# Dry run first
python3 .claude/skills/md-to-confluence/scripts/md-to-confluence.py 1234567890 my-doc.md --dry-run
After every push, verify these automatically:
# Title, the
script strips it. Verify: fetch body.storage and confirm no <h1> tag exists (Confluence adds
its own page title separately).> Source: [Confluence](...) line in markdown is a local-only reference.
The script strips it. Verify: fetch body.storage and confirm no "Source:" info panel or blockquote.body.storage
and confirm <th class="numberingColumn"> exists in every <table>.data-table-width="1800" and data-layout="wide" on every <table>.<ri:attachment ri:filename="..."> have
matching attachments on the page.@Name to <ac:link><ri:user> macros by looking up
account IDs from .local/team/users.md. Handles pandoc's citation span wrapping (<span class="citation" data-cites="Name">@Name</span>).
Verify: no @Name plain text or class="citation" remains.ac:name="toc" present.data-highlight-colour="#b3bac5" on versioning table <th> cells.
Verify: attribute present on non-numberingColumn headers.<time datetime="YYYY-MM-DD" />
macros (styled date pill). Verify: <time datetime= in HTML.# Quick eval: fetch page and check for common issues (9 checks)
python3 -c "
import http.client, json, os, base64
email = os.environ.get('CONFLUENCE_EMAIL', '')
token = os.environ.get('CONFLUENCE_TOKEN', '')
auth = base64.b64encode(f'{email}:{token}'.encode()).decode()
base_url = os.environ.get('CONFLUENCE_BASE_URL', '').replace('https://', '').rstrip('/')
conn = http.client.HTTPSConnection(base_url)
conn.request('GET', '/wiki/rest/api/content/PAGE_ID?expand=body.storage',
headers={'Authorization': f'Basic {auth}'})
html = json.loads(conn.getresponse().read())['body']['storage']['value']
checks = []
checks.append(('No duplicate H1', '<h1' not in html))
checks.append(('Numbering columns', 'numberingColumn' in html))
checks.append(('Wide tables', 'data-table-width' in html))
checks.append(('No plain @mentions', 'citation' not in html))
checks.append(('TOC macro', 'ac:name=\"toc\"' in html))
checks.append(('Versioning subtext', 'version control and releases management' in html))
checks.append(('Grey headers', 'data-highlight-colour' in html))
checks.append(('Date macros', '<time datetime' in html))
checks.append(('No broken images', 'UNKNOWN_ATTACHMENT' not in html))
for name, passed in checks:
print(f'{\"PASS\" if passed else \"FAIL\"}: {name}')
print(f'{sum(1 for _,p in checks if p)}/{len(checks)} checks passed')
"
--wrap=none - prevents premature line breaks that Confluence renders literally refs, uploads missing attachments, converts to <ac:image> macros at ac:width="1350" (fills the wide content area). After creating a page, re-pushes HTML so image refs resolve against uploaded attachments (prevents UNKNOWN_ATTACHMENT bug).**bold label:** become Confluence info/note panels<pre><code> becomes Confluence code macro with CDATAdata-table-width="1800" data-layout="wide" and data-number-column="true" for full width with numbered rows# column stripping - auto-strips manual | # |, | Sr |, | S# | first columns from tables before adding Confluence numbering (prevents duplicate row numbers). Keep # columns in markdown for local readability — the script handles it.numberingColumn class) to all tables — header gets empty <th>, data rows get sequential <td> numbers<h1> from HTML output since Confluence uses the page title as H1 (avoids duplicate heading)> Source: [Confluence](...) info panels/blockquotes that are only meaningful in local markdown<th> content is wrapped in <strong> so header row text renders bold--wide flag sets both draft and published content appearance properties@Name to Confluence user mention macros using .local/team/users.md lookup. Handles pandoc's citation span wrapping automatically.<time> date pills. Keep plain dates in markdown for local readability — the script converts them on push.Pandoc column widths are the #1 table gotcha. Pandoc generates equal-percentage <col> widths
(e.g., 7 columns = 14% each). This makes ALL columns the same width regardless of content — a
short "Type" column gets the same width as a long "Rationale" column. The script now replaces
pandoc's <colgroup> with proportional pixel-based widths using a heuristic: columns with short
header names (< 10 chars) like "Type", "#", "Count" get narrow widths; columns with known long-
content names like "Rationale", "Summary", "Reason", "Description" get wide widths. If the auto-
heuristic is wrong, fix column widths post-push by replacing the <colgroup> block via REST API
with <col style="width: Npx;" /> values that match the content. Confluence uses pixel widths
in <col> tags inside <colgroup>, NOT data-colwidth on <td>/<th> cells.
Manual # columns are auto-stripped. The script detects manual numbering columns (| # |,
| Sr |, | S# |, | S.No |) and removes them before adding Confluence's built-in
numberingColumn. Keep # columns in your markdown for local readability — the script handles
the deduplication automatically.
Pandoc line wrapping is the #2 gotcha. Without --wrap=none, pandoc breaks lines at ~72 chars
and Confluence renders those as actual line breaks. The script already handles this, but if you ever
call pandoc directly, always include --wrap=none.
Paragraph width is a Confluence design decision. Even in wide mode, paragraph text constrains to
~740px readable width. Only tables expand with data-layout="wide". This is how Confluence works -
it's not a bug.
Single newlines in markdown merge into one paragraph. Pandoc treats a single newline as a space,
not a line break. If you have Q&A pairs (**Q:**\nA:) or intro text followed by a numbered list,
they'll render on the same line in Confluence. Always use blank lines between elements that
should be separate paragraphs - e.g., between a question and its answer, or between intro text
and a numbered/bulleted list.
Duplicate titles cause errors. Confluence rejects creating a page if another page with the same title exists in the space. The script reports the 400 error clearly.
Fabric editor rejects inline code in tables. Confluence's new Fabric editor throws
BAD_REQUEST: Content contains unsupported extensions when HTML contains <code> tags inside
<table> cells. The fix: replace all backtick code spans in markdown with bold (**text**)
before running pandoc. The script should strip <code> tags from table cells and wrap the content
in <strong> instead. If the script fails with this error, manually replace backticks in the
markdown source and retry.
Fabric editor rejects --create but accepts updates. The v2 page creation API
(POST /wiki/api/v2/pages) uses the Fabric editor which rejects <code> tags in tables,
numberingColumn, and colgroup. However, updating an existing page via the v1 API
(PUT /wiki/rest/api/content/{id}) works fine and preserves all formatting. Workaround for
--create failures: create the page manually via v1 API with minimal HTML, then immediately
update it using the script. The script's update path handles numbering, colgroups, and all
post-processing correctly.
Relative links MUST be fixed in the source .md, not in post-processing HTML. When markdown
references other spec files via relative paths (e.g., ../011-p2p-peer-to-peer-sync/p2p-peer-to-peer-sync.md),
these render as broken links on Confluence. Fixing them only in the pushed HTML is NOT sufficient
because the next full push regenerates HTML from the unchanged .md, bringing broken links back.
Before pushing, grep the markdown for ../ relative links and replace them IN THE .MD FILE with
the full Confluence URL from each target spec's header line (> Source: [Confluence](url) or
> Confluence: url). This is a one-time fix per file - once the .md has absolute Confluence URLs,
all future pushes will be correct.
Check: grep '\.\.\/' <file.md> - if any matches, fix them before pushing.
For the full list of Confluence storage format details (image format, callout panels, code blocks,
status badges, @mentions, colored cells, TOC macros), read references/confluence-format-guide.md.
Whether a name appears as plain text ("Jane Doe") or with @ prefix ("@Jane Doe") in the
local .md file, it MUST render as a Confluence @mention macro on the pushed page. Plain text names
are not acceptable on Confluence.
The script handles @Name patterns automatically. For plain text names (e.g., in Attendees lines
of decisions.md), do a post-push find-replace:
body.storage via REST API.local/team/users.md<ac:link><ri:user ri:account-id="ACCOUNT_ID" /></ac:link>Sort names by length (longest first) to avoid partial matches (e.g., "Jane Doe" before "Jane").
This script does a full page replacement. Pandoc CANNOT preserve Confluence-native macros:
<ac:structured-macro ac:name="jira">) become broken UUID text<ac:image>) are lost<ac:structured-macro ac:name="status">) become plain textBefore pushing to ANY existing page, check:
confluence-update.py for surgical updates:
add-section — add a new section before/after a headingfind-replace — replace specific textadd-table-row — add a versioning rowinsert — add an info callout before specific textThis script is ONLY safe for:
--create)Real incident: A full push to a dashboard page destroyed 12 images, 2 status badges, and 15 Jira smart links. Required reverting to a previous version and re-adding content surgically.
Use Step 0 (confluence-preflight.py) — it's the enforced version of this check.
The preflight does a 3-way diff against the snapshot baseline and fails loudly
if anyone has edited the page on Confluence since your last sync.
Real incident: a full-page push via this script removed answers that a reviewer had typed directly on the Confluence page. The snapshot + preflight system exists to make this class of failure detectable before the push.
Scripts live INSIDE the skill folder, NOT in a shared .claude/scripts/ folder:
.claude/skills/md-to-confluence/scripts/md-to-confluence.py~/.claude/skills/md-to-confluence/scripts/md-to-confluence.pyNEVER use symlinks — always copy actual Python files. Symlinks break when the target moves.
Keep both locations in sync when modifying the script.
brew install pandoc).env with: CONFLUENCE_EMAIL, CONFLUENCE_TOKEN, CONFLUENCE_BASE_URL