Confluence API Doc
Publish API docs to Confluence — from the docs/api/*.yaml custom-YAML api-spec — one endpoint = one page, grouped under domain parents, with the service overview on the parent page. The full procedure (auth, source-select, page-tree mapping, the source→storage conversion rules, REST calls, round-trip normalization) is the single source in references/publish-reference.md — follow it; the steps below are the spine. Every push is gated on deterministic checks (pre-flight + round-trip) + an independent fresh-eyes pass + a completeness sweep, never on an HTTP 200.
ASSET_DIR = <skill base dir>/assets, SKILL_DIR = <skill base dir> (the skill-load message gives the "Base directory for this skill"). Input is the docs/api/*.yaml custom-YAML api-spec (authored by the api-spec skill).
Audience (non-negotiable)
Confluence is for other teams that call this API (BFF, mobile, partner). It is not a dump of the api-spec for the owning service's developers.
- Publish the wire contract and caller-visible behaviour only.
- Strip internal/dev prose at assemble time (P3) — do not push ticket framing, evidence paths, ALIGN logs, internal renames, changelog notes, or implementer-only cross-refs. Full strip list:
publish-reference.md§ Audience filter. - The api-spec may keep those for neo/traceability; Confluence must not.
The spine
- Gather — the source is the api-spec at
docs/api/*.yaml(_meta.yaml+<domain>/<endpoint>.yaml); if it does not exist → STOP (run/specto author it). Then take the Confluence parent-page URL → page ID. - Auth —
acli auth status→CONFLUENCE_URL+EMAIL; resolve the write token ($CONFLUENCE_API_TOKENor ask once) at push time. - Scan + sanitize — endpoint pages titled
<METHOD>: <path>, one per group; parent page = the service overview. Title from the endpoint'smethod+path; assemble the page body from the endpoint YAML (P3) through the Audience filter —description→ intro, field tables from params/body/responses, examples,business_logic,errors[]; parent body =_meta.overview+_meta.field_info+_meta.common_errors. Drop pure-devnotes[]; never publishcovers_ac. Skiphealth/. (Full rules:publish-reference.md§ Step P3 + Audience filter.) - Map — fetch existing children (
curl GET …?expand=space,children.page), match by exact title, plan create/update; create groups before endpoints. - Versions —
acli confluence page view --id <id> --include-version --json. - Convert — markdown → Confluence storage per
publish-reference.md§ P6 (code blocks → code macro/CDATA first, then inline rules; mind the nested-list rule). Stage each page in the gitignored.api-doc-publish/as both a<page>.jsonmanifest and a rawstorage/<page>.xml(the latter feeds the round-trip).
verify-L1 · Deterministic (pre-flight + round-trip)
L1a — pre-flight (before any push):
python3 <ASSET_DIR>/pubcheck.py .api-doc-publish/
Well-formedness · CDATA/table/list balance · bare &/< · source↔storage element counts. Loop fix→re-stage→re-run until exit 0, OR ~3 rounds → escalate. Never push storage that failed pre-flight.
Then Sync (REST create/update: domain-group pages → endpoint pages → parent page; version+1 on update; skip unchanged).
L1b — round-trip (after push): re-fetch each page (acli … --body-format storage --json) and compare to the staged storage:
python3 <ASSET_DIR>/pubcheck.py --roundtrip .api-doc-publish/storage/<page>.xml .api-doc-publish/refetched/<page>.xml
Canonical compare (ignores Confluence's benign rewrites; CDATA must match exactly). Structural drift → review; CDATA drift → a code example was mangled, investigate. One round of fixes, then escalate.
verify-L1.5 · Offer fresh-eyes (default yes)
Ask once via AskUserQuestion: "Run an independent fresh-eyes verify of the published pages? (default: yes)" — no → skip L2 (mark "skipped by user"); yes → L2.
verify-L2 · Fresh-eyes verifier (independent agent)
The pre-flight + round-trip prove the storage is well-formed and survived Confluence verbatim; they cannot judge whether the conversion preserved meaning. Dispatch a verifier that reads a sample of (source markdown ↔ converted storage) pairs:
Agent(subagent_type: "fresh-eyes", description: "verify confluence publish", prompt: """
# Role: Publish Verifier
Read first: <SKILL_DIR>/references/pub-verifier.md
SKILL_DIR = <skill base dir>
## Task
Independently judge (1) conversion fidelity and (2) audience fitness for a sample of
pages — semantic preservation the pre-flight counts and round-trip cannot see, plus
no leftover internal/dev prose. Read the source yaml + the staged storage in
.api-doc-publish/ yourself. Apply the Audience filter in publish-reference.md /
pub-verifier.md.
## Pages under review
<list a representative sample: the most table-heavy, code-heavy, and nested-list pages>
End with Status: DONE | DONE_WITH_CONCERNS | BLOCKED
""")
SKILL_DIR is mandatory. The verifier is read-only by tool grant — fresh-eyes holds no write/edit (harness without that type → general-purpose, read-only by instruction only) → you fix the conversion → re-stage → re-run L1a (and re-push + L1b if already pushed).
verify-L3 · Completeness sweep (omission critic)
L1/L2 inspect the pages that were converted; L3 catches a whole page missing entirely. Re-enumerate every docs/api/<domain>/<endpoint>.yaml endpoint + every domain group, and confirm each maps to a created/updated Confluence page in the report, and that the parent overview (_meta.overview) was synced. Report any group/endpoint silently skipped; fix → re-sync.
Output
## Confluence API Doc — publish
**Source:** docs/api api-spec **Parent page:** <id>
| Page | Type | Page ID | Status |
| --- | --- | --- | --- |
| (Service) Overview | Parent | … | Updated (v3→v4) |
| Consent | Domain group | … | Created |
| POST: /api/v1/consents | API page | … | Created |
**Totals:** N groups, M API pages — created K / updated U / skipped S / failed F
**Verification (three-layer):**
- L1 pre-flight ✅/❌ · round-trip: N/M clean, D drift (CDATA drift: …)
- L2 fresh-eyes: ✅ Clean / ⚠️ N findings fixed / ⏭ Skipped / ⏸ Not run
- L3 completeness sweep: ✅ all pages synced / ⚠️ N silent omissions fixed
What this skill is NOT
- Not a source generator — the
docs/api/*.yamlapi-spec is authored by theapi-specskill;openapi-doconly drift-checks Go against it. This skill reads the api-spec. - Not a Bruno OpenCollection generator — that is the
open-collectionskill. - Not a general Confluence page editor — it publishes the API-doc tree, nothing else.
- Not a mirror of every api-spec remark/note — internal/dev text stays in the repo; Confluence is consumer-facing only (Audience filter).
- An HTTP 200 is not proof the content is right — that is the round-trip + fresh-eyes job.