# Channel adapter  -  Confluence page

> Detailed contract for the `confluence` channel of `/multi-agent:channels`. Split out of `channels.md` in v8.0.0; the parent doc keeps a one-line summary and a link here.

The Confluence adapter creates or updates a Confluence page under a chosen parent. Like the Jira adapter, it runs **once** per invocation  -  the page lives at the primary repo's component slug in multi-repo mode.

## Required body structure

Every Confluence page written by this adapter follows the same section order. Section headings render in `prefs.global.outputLanguage`; the template (this doc) is English because `promptLanguage="en"` is locked, but the runtime body is in the user's language.

| # | Section key | Heading (`tr`) | Heading (`en`) | Required? |
|---|---|---|---|---|
| 1 | `overview` | `## Genel Bakış` | `## Overview` | always |
| 2 | `flow` | `## Akış Diyagramı` | `## Flow Diagram` | when work involves a non-trivial sequence (auth, payment, multi-step UI, async pipeline) |
| 3 | `technical_details` | `## Teknik Detaylar` | `## Technical Details` | always |
| 4 | `api_contracts` | `## API Kontratları` | `## API Contracts` | when work added or modified HTTP endpoints, GraphQL ops, or RPC methods |
| 5 | `references` | `## İlgili Bağlantılar` | `## References` | when any context link exists |

> Headings in the source markdown are `##`; the storage-format converter rewrites them to `<h2>...</h2>`. Sub-headings (`###`) become `<h3>` and so on.

### Section content rules

**`overview`**  -  3-6 sentences. Audience is stakeholders/PM/designers, not engineers  -  explain the change in user/business terms first, then a single sentence on the technical approach. No marketing voice, no "we are proud to announce".

**`flow`**  -  when included, embed a flow diagram. Preferred form is a Mermaid block (Confluence renders Mermaid via the storage-format macro when the space has the plugin enabled); fallback is a numbered step list when Mermaid is unavailable. Template shape:

```markdown
## Flow Diagram

\`\`\`mermaid
sequenceDiagram
  participant <actor>
  participant <service>
  <actor>->><service>: <request>
  <service>-->><actor>: <response>
\`\`\`
```

Replace `<actor>` / `<service>` with the real names at write-time; do not invent placeholders that don't exist in the diff.

**`technical_details`**  -  bullet list or sub-sections covering implementation specifics: data flow, key components touched, edge cases handled, performance notes. Code identifiers stay verbatim across languages.

**`api_contracts`**  -  for each new/changed endpoint:

```markdown
### <METHOD> <path>

**Request**
\`\`\`json
{ "..." }
\`\`\`

**Response  -  200**
\`\`\`json
{ "..." }
\`\`\`

**Error responses**
- `400`  -  <when>
- `401`  -  <when>
- `404`  -  <when>
```

When the task referenced a Confluence API-contract source page (see Phase 0 link extraction / Phase 1 context injection), this section may consume that contract directly  -  verbatim where it matches, with a `(source: <confluence-page-url>)` annotation at the end of the section.

**`references`**  -  flat bullet list, mirrors the Jira `## References` shape (Swagger, Figma, Crashlytics, Fortify, source Confluence pages, PR URLs). Emit one line per link, never deduplicate across categories.

### Assembly order (per run)

```
1. Read agent-state.json (taskId, contextLinks, prUrls, language).
2. Build section bodies in markdown  -  overview first, then in the table order, skipping conditional sections that don't apply.
3. Run the assembled body through the `humanizer` skill (tone target: formal, stakeholder-readable).
4. Convert markdown → Confluence storage XML (see table below).
5. POST / PUT.
```

## Token check

1. Read `prefs.global.keychainMapping.confluence` → `~/.claude/lib/credential-store.sh get`.
2. Missing → trigger the inline Token Save Flow from `setup.md` (clipboard-based). User can save the token now or skip the run.
3. Expired (`401 Unauthorized`) → same Save Flow with the `expired` trigger.
4. Skipped → adapter returns `{status: "skipped", reason: "no token"}`, does NOT block other adapters.

## Parent page resolution

Recents from `prefs.projects[<project>].confluenceUrls` (LRU, max 5):

```
Confluence parent page URL:
  Recent [1]: https://<host>/display/<SPACE>/ComponentLibrary   (enter to reuse)
  Recent [2]: https://<host>/display/<SPACE>/Releases
  [p] Paste new URL
  [s] Skip this run
```

`spaceKey` + `parentId` extracted from URL with these patterns:

| URL form | Extraction |
|---|---|
| `/display/<SPACE>/<title>` | `spaceKey=<SPACE>`, `parentId` looked up via `GET /rest/api/content?spaceKey=<SPACE>&title=<title>` |
| `?pageId=12345` | `parentId=12345`, `spaceKey` resolved from page's `space.key` |
| `/spaces/<SPACE>/pages/12345/<title>` | both fields direct |

## Page title

Auto-suggested as `"{jiraId}  -  {taskTitle}"`  -  user can edit at the prompt. The `taskTitle` comes from `state.taskTitle` if set, else from the Jira summary, else from the PR title.

## Body conversion (markdown → storage format)

| Markdown | Confluence storage XML |
|---|---|
| `### Heading` | `<h3>Heading</h3>` |
| Tables | `<table><tr><th>...</th>...</tr><tr><td>...</td>...</tr></table>` |
| Code fences ` ```lang ` | `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">lang</ac:parameter><ac:plain-text-body><![CDATA[...]]></ac:plain-text-body></ac:structured-macro>` |
| Inline `code` | `<code>code</code>` |
| Links `[t](u)` | `<a href="u">t</a>` |
| Lists | `<ul><li>...</li></ul>` / `<ol><li>...</li></ol>` |
| ` ```mermaid ` fences | `ac:name="mermaid"` macro (`md2confluence-v3.py` implements it, with a numbered-list fallback when the space lacks the plugin)  -  not the generic code macro |

Body is run through humanizer before XML conversion  -  Confluence tone is the most formal of the four channels (target audience: stakeholders, PMs, designers).

## POST contract

The paths below are relative to the API base, which depends on the Confluence flavor  -  resolve it the way `lib/fetch-confluence.sh` already does: hosts matching `*atlassian.net` (Cloud) use `https://<host>/wiki/rest/api`, everything else (Server / Data Center) uses `https://<host>/rest/api`. On Cloud the un-prefixed form 404s on every call. (Cloud also serves a v2 API at `/wiki/api/v2` with ADF bodies; this adapter stays on v1 + storage format, which both flavors accept.)

```
POST /rest/api/content
{
  "type": "page",
  "title": "<title>",
  "space": { "key": "<spaceKey>" },
  "ancestors": [{ "id": "<parentId>" }],
  "body": {
    "storage": {
      "value": "<storage-format-body>",
      "representation": "storage"
    }
  }
}
```

If a page with the same title already lives under that parent, the adapter switches to update mode: `GET /rest/api/content/<id>?expand=version` → `PUT /rest/api/content/<id>` with incremented `version.number`.

## Recents persistence

Successful POST → URL prepended to `prefs.projects[<project>].confluenceUrls` (dedup by URL, trim to 5 entries). Surfaces as `Recent [1]` on the next run.

## Hard rules (must not regress)

- Real newlines and no HTML entities in the source body  -  XML conversion happens at adapter time, never embed `\n` literals.
- Section order is fixed: `overview` → `flow` (cond.) → `technical_details` → `api_contracts` (cond.) → `references` (cond.). Conditional sections may be omitted but never reordered.
- Humanizer pass runs **after** body assembly and **before** storage-XML conversion. Tone: formal, stakeholder-readable. No "we are excited", no first-person plural.
- Body content language follows `prefs.global.outputLanguage`. Code identifiers, API paths, file paths, type names, JSON keys, and the storage-XML markup stay verbatim. The template (this doc) is English because `promptLanguage="en"` is locked.
- No decorative/emotive emoji or smileys (    ) anywhere in the page body. Stakeholder-readable technical prose only.
- Adapter failures are non-blocking. 401 / network timeout / parent-not-found → `failed` status, loop continues.
- If `--dry-run`, print the assembled storage XML to stdout and return without POST.
