# Render, publish, report (Phases 3, 3.5, 4, 5)

> Render, humanize, validate, publish and report. Loaded on demand by `/multi-agent:analysis` and by pipeline Phase 1. The humanizer pass and `validate-analysis-doc.mjs` are required here in every mode: a document that skipped either is not shippable.

### Phase 3 - Humanize & buffer drafts (no side effects)

1. **Language resolution**: read `prefs.global.outputLanguage` (`tr` or `en`, default `tr`). Use the `Output language matrix` table in `$HOME/.claude/multi-agent-refs/analysis-template.md` to swap headings and system strings.

1b. **Template selection** (Locked 32): read `state.analysisSpec.profile`. `global` renders against `$HOME/.claude/multi-agent-refs/analysis-template.md`; `corporate` renders against `$HOME/.claude/multi-agent-refs/analysis-template-corporate.md`. The evidence is the same either way - this step chooses the projection, nothing else. In the corporate profile the Part A backbone and the footer render even with zero evidence, carrying `N/A` or `EKLENECEK`, and each `EKLENECEK` emits its Section 20 row (Locked 33).

2. **Markdown render**: for each platform in `state.analysisSpec.platforms[]`, concatenate the per-platform spec into one markdown file. Tables in pipe-syntax. Numbering uses plain `## 1.`, `## 2.`, ... - omitted sections do **not** create gaps. Visible numbering is sequential 1..N over the rendered set. In the corporate profile the Part A and footer numbers are fixed and do not re-flow, since the backbone never drops; Part B and Part C follow the omission table as usual.

   **When `platforms[]` is empty** (Locked 35), render one platform-agnostic file instead of one per platform: the development layer (corporate Part C, global Sections 13, 14, 15) and the Pass B projection are skipped, Section 20 carries a row recording that they await a repo selection, and the front-matter `platform` key reads `none`. Everything that does not need a target repository still renders in full.

3. **Humanizer pass (MANDATORY: actually invoke the `ai-common-toolkit:humanizer` skill on the rendered markdown - the punctuation grep alone does NOT satisfy this step)** (`technical-explanatory` tone for the scratch buffer; per-channel re-humanize happens in Phase 4 when actually emitting):
   ```
   ai-common-toolkit:humanizer skill input:
     language: <tr|en>
     tone: technical-explanatory
     stripFancyPunctuation: true
     content: <markdown>
   ```

   **Explicit punctuation policy** (enforced by `stripFancyPunctuation: true`): no em-dash (U+2014), no en-dash (U+2013), no horizontal ellipsis (U+2026), no curly quotes (U+2018, U+2019, U+201C, U+201D), no section sign (U+00A7). The humanizer replaces these with ASCII equivalents (`-`, `:`, `,`, `...`, `'`, `"`, and `bölüm` / `section` for the section sign per `outputLanguage`) before emit. Tables, code blocks, URLs, and front-matter YAML are exempt. Post-emit verification runs through `node $HOME/.claude/scripts/validate-analysis-doc.mjs <file>`, which checks this policy deterministically in Node on all three operating systems. Do NOT verify with `grep -P`: BSD grep (macOS, the pipeline's primary platform) has no `-P`, so that command never runs and "zero matches" is trivially true. The per-language readability rules (Turkish sentence shape, nominalization chains, which terms stay untranslated) live in the humanizer skill itself, not here; this paragraph owns only the punctuation policy the validator enforces. **Diacritics are PRESERVED, not stripped: this policy targets ONLY the listed fancy-punctuation codepoints. Turkish letters (ş/Ş, ç/Ç, ğ/Ğ, ı/I, İ, ö/Ö, ü/Ü) and all other `outputLanguage` letters MUST stay verbatim. Never ASCII-fold the prose - emit `Geliştirme Özeti`, `için`, `Kullanıcı Hikayeleri`, NOT `Gelistirme Ozeti`, `icin`, `Kullanici`. ASCII-folded Turkish is a humanizer-skipped smell and fails review.**

3b. **Build Section 21 References** (Locked 34): emit the table with

   ```bash
   node "$HOME/.claude/scripts/build-references.mjs" <state.json> --lang "$OUTPUT_LANGUAGE"
   ```

   and paste its output under the References heading. The model does not hand-write this table; it is a projection of `state.analysisSpec.evidence.*`, which is what keeps a source the run actually read from going unlisted and a remembered-but-unread source from appearing.

4. **Write scratch drafts**: create `/tmp/analysis-<feature-slug>-<UTC-iso8601>/` and write `<feature>-<platform>.md` for each selected platform, or a single `<feature>.md` when `platforms[]` is empty. Update `state.analysisSpec.outputs.draftDir` with the path.

5. **Surface the draft tree to the user**:
   ```
   Drafts ready (3 files, 6.4 KB):
     /tmp/analysis-UserProfile-20260514T1015/UserProfile-ios.md
     /tmp/analysis-UserProfile-20260514T1015/UserProfile-android.md
     /tmp/analysis-UserProfile-20260514T1015/UserProfile-backend.md
   ```
   User can inspect drafts before choosing output destinations in Phase 3.5.

### Phase 3.5 - Output destination picker

AskUserQuestion (multiSelect=true), at least one selection required. `Local file` pre-selected per Locked decision 5.

```
header: "Output"
question: <localized: "Where should the per-platform analyses be written?">
options:
  - label: "Local file"
    description: <resolved path, shown literally: "<repo>/analysis/<feature>-<platform>.md" per selected repo, or "~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md" when no repo was selected>
  - label: "Confluence page"
  - label: "Jira issue"
    description: <localized: "As a comment by default; writing the description is a separate, explicit choice">
```

Conditional follow-ups:
- If `Confluence` selected: ask `header="Parent page"`, free-text via Other for the parent page key or URL. One Confluence page per platform is created under this parent, each titled `<Feature> - <Platform>`.
- If `Jira` selected: if Step 5 Q4 produced Jira IDs, AskUserQuestion (single-select) to pick which one; otherwise ask via Other. Per Locked decision 9 + design choice "single description with platform separators", the chosen issue receives one combined body holding all per-platform sections under `h2. Platform: <X>` separators (wiki markup  -  neither the comment nor the description field renders Markdown; conversion happens at dispatch, see Phase 4).

  Then ask **where in the issue it goes**, because two of the three answers can destroy text somebody else wrote:

  ```
  header: "Jira write"
  question: <localized: "Where should the analysis go on {ISSUE-KEY}?">
  options:
    - label: "Comment"
      description: <localized: "Added as a new comment. Nothing existing is touched.">   # pre-selected
    - label: "Description - append"
      description: <localized: "Kept below the current description, separated by a rule.">
    - label: "Description - replace"
      description: <localized: "Replaces the current description. Its text is backed up first and the run tells you what was there.">
  ```

  `Comment` is pre-selected and is the default on any non-answer, including autopilot. The description of an issue is often the analyst's original requirement text, and an analysis run is not a reason to lose it. Result: `state.analysisSpec.outputs.jira = { key, target: "comment"|"description", mode: "append"|"replace" }`.

Result: `state.analysisSpec.outputs.requested[]`.

### Phase 4 - Dispatch & report

**Pre-dispatch gate (BLOCKING).** Before writing to any destination, run the deterministic doc validator on every per-platform draft:

```bash
for f in /tmp/analysis-<feature-slug>-<ts>/*.md; do
  node "$HOME/.claude/scripts/validate-analysis-doc.mjs" "$f" || GATE_FAILED=1
  node "$HOME/.claude/scripts/build-references.mjs" <state.json> --check "$f" || GATE_FAILED=1
done
```

`validate-analysis-doc.mjs` enforces the mechanically-checkable Locked decisions on the emitted markdown itself (front-matter completeness, never-omitted sections per Locked 2, humanizer punctuation per Locked 7, Full-mode business-rule traceability per Locked 31, and in the corporate profile the backbone presence and `EKLENECEK`-to-Section-20 pairing per Locked 33). `build-references.mjs --check` runs the References coverage gate (Locked 34): a source the run consumed but did not list, or a listed row with no evidence behind it, blocks dispatch. Any ERROR blocks dispatch: fix the draft and re-validate. Warnings are advisory (run with `--strict` to treat them as blocking). This turns the "fails the dispatch gate" prose into a real, model-independent check.

Iterate `state.analysisSpec.outputs.requested`. For each target:

| Target | Action |
|--------|--------|
| Local | For each per-platform draft, `cp /tmp/analysis-<feature-slug>-<ts>/<feature>-<platform>.md` into `analysis/<feature>-<platform>.md` in the matching repo's working tree. When multiple repos exist for the same platform, the file is duplicated into each and the dispatch report lists every destination. **When no repo was selected** (Locked 35) there is no working tree to be relative to, so the file lands in `~/Desktop/multiAgentAnalysis/<feature-name>/<feature>.md`. The current working directory is never written to: for a repo-less run it is arbitrary, and creating a folder in whatever directory the command happened to be invoked from is the kind of surprise that costs a tool its trust. The user can override the path through the picker's Other input. **No commit.** |
| Confluence | Re-humanize each per-platform draft with `formal-stakeholder` tone. One Confluence page per platform under the chosen parent, titled `<Feature> - <Platform>`. Cross-link siblings inside each page via `<ac:link><ri:page ri:content-title="<Feature> - <OtherPlatform>"/></ac:link>`. Markdown -> storage XML via `$HOME/.claude/multi-agent-refs/channels/confluence.md`. Re-emit on existing pages uses PUT with version bump. |
| Jira | Re-humanize the combined body with `informal-technical` tone. Concatenate per-platform drafts under `h2. Platform: iOS`, `h2. Platform: Android`, `h2. Platform: Backend`, `h2. Platform: Frontend` separators (in the order platforms were selected), then run the whole body through the markdown → Jira wiki conversion table in `$HOME/.claude/multi-agent-refs/channels/jira.md`  -  both the comment body and the `description` field render wiki markup, so raw `##`/`**`/backticks arrive as literal text. Write the converted body to a file and publish it with `$HOME/.claude/lib/jira-publish.sh`, never with a hand-rolled `curl`: <br><br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target comment` <br>`bash "$HOME/.claude/lib/jira-publish.sh" --issue "$KEY" --body-file "$F" --target description --mode append` <br><br>The script owns the parts that are easy to get wrong: it runs `jira-wiki-escape.mjs` on the body, resolves host + token without putting either in argv, and on the description path it GETs the current value, writes it to a backup under `~/.claude/logs/multi-agent/jira-backups/` and reports the path, appends below a `----` rule by default, and **refuses with exit 3** when `--mode replace` would discard a non-empty description unless `--confirm-overwrite` is passed. Exit 3 is reported to the user with the backup path, never retried with the flag added automatically  -  only the user's explicit "Description - replace" answer from Phase 3.5 supplies it. `--dry-run` previews the exact final body without writing. |

**Output capture**: fill `state.analysisSpec.outputs.localPaths[]` (one entry per per-platform-per-repo write), `outputs.confluencePageUrls[]` (one entry per platform), `outputs.jiraIssueKey` (single string).

### Phase 5 - Report & stop

After dispatch, print a summary to the user (in `outputLanguage`). Example shape:

**Operational reporting.** Analysis is a pipeline of its own now - its own phases, its own gates, its own report - so it emits a run record like any other run. Without this the panel shows dev runs only, and the command people reach for before writing code is invisible in the usage data.

```bash
STATE="$HOME/.claude/logs/multi-agent/$TASK_ID/agent-state.json"
mkdir -p "$(dirname "$STATE")"
printf '%s' "{\"taskId\":\"$TASK_ID\",\"command\":\"/multi-agent:analysis\",\"mode\":\"$PROFILE\",\"project\":${PROJECT_JSON:-null},\"status\":\"$STATUS\",\"finishedAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" \
  | node "$HOME/.claude/scripts/write-state.mjs" "$STATE" >/dev/null 2>&1 || true
node "$HOME/.claude/scripts/usage-report.mjs" --state "$STATE" >/dev/null 2>&1 || true
```

`mode` carries the profile (`global` / `corporate`), which is what distinguishes one analysis run from another. `project` is digested by the reporter before it leaves the machine, and is `null` on a stack-optional run - there is no repository to name. A failure here never affects the run: the reporter is best-effort and the summary above is already printed.

```
Feature: <featureName>
Language: <tr|en>
Platforms: iOS, Android, Backend
Repos: my-ios-app (write), my-android-app (write), my-backend-api (write)

Sources used:
  Figma:     2 frames
  Swagger:   1 file
  Confluence: 1 page (1 fetch error: auth_required)
  Jira:      1 issue
  Firebase:  3 events from schema, 1 from auto-detect
  Standards: 2 local files + 1 wiki page
    - /Users/.../<project>-Standards.md
    - /tmp/<repo>-wiki/Navigation.md
    - ~/.claude/rules/<framework>-qa.md (auto-detected)

Repo evidence (reuse vs new):
  iOS my-ios-app:        4 direct-match, 6 same-domain, 11 cross-cutting
  Android my-android-app: 2 direct-match, 8 same-domain, 14 cross-cutting
  Backend my-backend-api: 1 direct-match, 3 same-domain, 7 cross-cutting

Sections (per platform):
  iOS:      [1 Scope, 2 Design, 3 Localization, 4 API, 5 Deeplink/Push, 6 Business+Tests, 7 Dev Plan]
  Android:  [1 Scope, 2 Design, 3 Localization, 4 API, 5 Deeplink/Push, 6 Business+Tests, 7 Dev Plan]
  Backend:  [1 Scope, 4 API, 6 Business+Tests, 7 Dev Plan]    (Sections 2/3/5 omitted - no UI)

Outputs:
  - analysis/UserProfile-ios.md       (my-ios-app)
  - analysis/UserProfile-android.md   (my-android-app)
  - analysis/UserProfile-backend.md   (my-backend-api)
  - Confluence (3 pages):
      https://{CONFLUENCE_HOST}/pages/viewpage.action?pageId=...    (iOS)
      https://{CONFLUENCE_HOST}/pages/viewpage.action?pageId=...    (Android)
      https://{CONFLUENCE_HOST}/pages/viewpage.action?pageId=...    (Backend)
  - Jira: {JIRA_KEY}-12345 (description updated with 3 platform sections)
```

**Stop. Do not chain into a dev run. Do not open a worktree. Do not create a branch.**

**Open-question follow-up**: when any rendered file's Section 20 (Risks and Open Questions) has rows with status `Acik / Open`, append one line to the report: `<localized: "Section 20 has <N> open rows. Run /multi-agent:analysis-resolve to resolve them interactively before dispatching to dev.">`. This is a suggestion line only - never auto-invoke the resolver.


## Confluence write (on-request only)

Per the `analysis-output-confluence-on-request` memory, Confluence post is NEVER default-selected at Phase 3.5 (the output destination picker). Only when the user explicitly asks ("post this to Confluence", "create a page under the Coding Documentation parent", etc.) does the command:

1. Resolve the token via `~/.claude/lib/credential-store.sh get <key>`, where `<key>` is read from `prefs.global.keychainMapping.confluence` (per-user mapping; never hardcode the service name in this doc - see channel adapter doc `$HOME/.claude/multi-agent-refs/channels/confluence.md` for the lookup contract).
2. Use parent page URL from user input. No default parent is hardcoded here; the user picks one at the prompt (LRU recents come from `prefs.projects[<project>].confluenceUrls`).

   **Corporate profile exception.** When `state.analysisSpec.profile == "corporate"` and the bindings are configured, the destination is already settled and the prompt is skipped: the space comes from `prefs.global.analysisProfile.corporate.confluenceSpaceKey`, the parent from `prefs.global.analysisProfile.corporate.confluenceParentPageId`, and the page title is built from `prefs.global.analysisProfile.corporate.titleFormat` with `prefs.global.analysisProfile.corporate.titlePrefix` filling its `{prefix}` placeholder. A corporate analysis always lands in the same tree, so asking each time is a question whose answer never changes. Any of the four missing falls back to the prompt above rather than guessing, and a title that would collide with an existing page becomes an update (PUT with version bump), never a second page.
3. Convert markdown to storage XML using the table in `$HOME/.claude/multi-agent-refs/channels/confluence.md`.
4. Upload Figma frame screenshots as page attachments via `POST /rest/api/content/{pageId}/child/attachment` (cache the MCP asset locally first because the upstream URLs expire after 7 days).
5. Reference attachments inside the page body via `<ac:image><ri:attachment ri:filename="frame-<nodeId>.png"/></ac:image>`.
6. POST `/rest/api/content` to create the page (or PUT with version bump when updating). Surface the resulting page URL in the Phase 4 report.

Token miss handling: surface a single line `WARN: Confluence token not configured (prefs.global.keychainMapping.confluence). Run /multi-agent:setup or skip Confluence output.` and continue with local-only output.
