@mmerterden/multi-agent-pipeline 14.2.1 → 14.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +10 -6
  3. package/README.tr.md +143 -0
  4. package/docs/architecture.md +23 -8
  5. package/docs/ecosystem.md +237 -0
  6. package/install/_plugin-skills.mjs +16 -2
  7. package/install/codex.mjs +9 -4
  8. package/install/templates/copilot-instructions.md +12 -9
  9. package/package.json +1 -1
  10. package/pipeline/commands/multi-agent/SKILL.md +2 -2
  11. package/pipeline/commands/multi-agent/analysis/SKILL.md +2 -2
  12. package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
  13. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -4
  14. package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
  15. package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
  16. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
  17. package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
  18. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
  19. package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
  20. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  21. package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
  22. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
  23. package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
  24. package/pipeline/commands/multi-agent/ship/SKILL.md +5 -1
  25. package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
  26. package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
  27. package/pipeline/lib/extract-conventions.sh +44 -15
  28. package/pipeline/lib/fetch-figma-annotations.sh +8 -1
  29. package/pipeline/lib/fetch-fortify.sh +23 -8
  30. package/pipeline/lib/figma-screenshot.sh +11 -1
  31. package/pipeline/lib/issue-fetcher.sh +76 -9
  32. package/pipeline/lib/md2confluence-v3.py +16 -2
  33. package/pipeline/lib/plan-todos.sh +5 -2
  34. package/pipeline/lib/post-pr-review.sh +8 -6
  35. package/pipeline/lib/shadow-git.sh +50 -9
  36. package/pipeline/lib/submodule-detector.sh +8 -1
  37. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  38. package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
  39. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  40. package/pipeline/multi-agent-refs/channels/jira.md +13 -2
  41. package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
  42. package/pipeline/multi-agent-refs/channels/pr.md +20 -0
  43. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  44. package/pipeline/multi-agent-refs/cross-cli-contract.md +3 -3
  45. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  46. package/pipeline/multi-agent-refs/generate-issue.md +2 -2
  47. package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
  48. package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
  49. package/pipeline/multi-agent-refs/phases/modes.md +20 -0
  50. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  51. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
  52. package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
  53. package/pipeline/multi-agent-refs/phases.md +6 -0
  54. package/pipeline/multi-agent-refs/rules.md +2 -0
  55. package/pipeline/schemas/prefs.schema.json +1 -1
  56. package/pipeline/scripts/audit-log-rotate.sh +10 -0
  57. package/pipeline/scripts/build-stack-plugins.mjs +8 -1
  58. package/pipeline/scripts/check-derived-drift.mjs +13 -1
  59. package/pipeline/scripts/diff-explain.mjs +41 -3
  60. package/pipeline/scripts/diff-risk-score.mjs +72 -8
  61. package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
  62. package/pipeline/scripts/learning-curve.mjs +8 -2
  63. package/pipeline/scripts/output-quality-check.sh +15 -4
  64. package/pipeline/scripts/phase-tracker.sh +21 -8
  65. package/pipeline/scripts/pre-commit-check.sh +69 -22
  66. package/pipeline/scripts/render-agent-log-cost.sh +8 -3
  67. package/pipeline/scripts/render-cost-summary.sh +42 -22
  68. package/pipeline/scripts/render-work-summary.sh +47 -13
  69. package/pipeline/scripts/review-scope.mjs +1 -1
  70. package/pipeline/scripts/run-aggregator.mjs +38 -14
  71. package/pipeline/scripts/smoke-schema-validation.sh +5 -1
  72. package/pipeline/scripts/test-gap-scan.mjs +45 -6
  73. package/pipeline/scripts/uninstall.mjs +39 -4
  74. package/pipeline/scripts/update-issue-progress.sh +12 -16
  75. package/pipeline/scripts/worktree-finalize.sh +23 -2
  76. package/pipeline/skills/.skills-index.json +11 -2
  77. package/pipeline/skills/shared/README.md +1 -1
  78. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
  79. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  80. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
  81. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
  82. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
  83. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
  84. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  85. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
  86. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
  87. package/pipeline/skills/shared/core/multi-agent-ship/SKILL.md +4 -0
  88. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  89. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
  90. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +144 -0
  91. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
  92. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
  93. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +175 -0
  94. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
  95. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
  96. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
  97. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
  98. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
  99. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
  100. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
  101. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
  102. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
  103. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
  104. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
  105. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
  106. package/pipeline/skills/skills-index.md +3 -2
@@ -75,6 +75,22 @@ Never use `Closes #N`, `Fixes #N`, `Resolves PROJ-X`. Issues require 4-approval
75
75
  5. Dispatch per the Behaviour-by-remote table.
76
76
  ```
77
77
 
78
+ ## Markup dialect (per surface, not per pipeline)
79
+
80
+ The PR body is **Markdown** on every supported remote - GitHub, Bitbucket Server, and GitLab all render Markdown in the description field. Emit the assembled markdown verbatim; there is no conversion step on this adapter.
81
+
82
+ Jira wiki markup in a PR body is a defect, not a style choice. The Jira adapter's conversion table (`channels/jira.md` "Wiki markup conversion") applies to the **Jira comment only** and must never be reached from here. Concretely, in a PR body:
83
+
84
+ | Never in a PR body | Renders as | Use instead |
85
+ |---|---|---|
86
+ | `h2. Title` / `h3. Title` | literal text `h2. Title` | `## Title` / `### Title` |
87
+ | `{{identifier}}` | literal braces `{{identifier}}` | `` `identifier` `` |
88
+ | `# item` for a numbered list | an H1 heading, one giant line per item | `1. item` |
89
+ | `{code:swift} ... {code}` | literal braces | fenced ` ```swift ` block |
90
+ | `*bold*` | italic in Markdown, not bold | `**bold**` |
91
+
92
+ The two adapters run in the same phase over the same source markdown, so the failure mode is a converter applied to the wrong target: the Jira comment is correct and the PR body ships raw wiki markup. Assemble once in markdown, then convert **only** on the Jira branch.
93
+
78
94
  ## Behaviour by remote
79
95
 
80
96
  | Remote | API | Reviewer handling |
@@ -106,6 +122,10 @@ curl -s -X PUT -u "$BB_USER:$BB_TOKEN" -H "Content-Type: application/json" \
106
122
  --data-binary @/tmp/channels-$TASK_ID-pr-payload.json "$PR_URL"
107
123
  ```
108
124
 
125
+ Verify after every PUT: refetch the PR and compare `reviewers | length` against the pre-PUT count. A drop means the payload lost the field - repair immediately.
126
+
127
+ `POST /pull-requests/{id}/participants` is **repair-only**, never the primary path. It accepts one user per call, so restoring N reviewers writes N separate "added 1 reviewer" rows into the PR activity feed, and Bitbucket activity entries cannot be deleted. A PR that opened with 22 reviewers in one clean create call and then shows 22 individual re-adds is a visible, permanent record of a dropped-reviewer PUT. Carry `reviewers` through the payload above instead.
128
+
109
129
  ## Multi-repo cross-links
110
130
 
111
131
  When `state.projects[].length > 1`, `channels-multi-repo.sh render-pr <state> <body> <repoName>` prepends a `## Related PRs` block:
@@ -62,6 +62,6 @@ Adapter implementation chooses the right git push target + commit message format
62
62
  - Wiki adapter is the **only** channel allowed to skip silently when its precondition gate fails. PR / Jira / Confluence always run when selected.
63
63
  - Per the `figma-pipeline.md` rules, every component wiki page must include a variant matrix, accessibility identifiers, analytics events, and 3-layer test summary.
64
64
  - Screenshots cover light/dark + LTR/RTL - wiki adapter refuses to commit if any quadrant is missing.
65
- - Body content language follows `prefs.global.outputLanguage`. Code identifiers, file paths, type names, design token names, and wiki markup stay verbatim across languages. The template (this doc) is English because `promptLanguage="en"` is locked.
65
+ - Body content language follows `prefs.global.outputLanguage`. Code identifiers, file paths, type names, design token names, and Markdown formatting stay verbatim across languages (wiki pages are `.md` files - "wiki markup" in this doc set means Jira's dialect, which never appears here). The template (this doc) is English because `promptLanguage="en"` is locked.
66
66
  - No decorative/emotive emoji or smileys (😊 🙂 🎉 👍 🚀 ✨) in the page prose. Only functional/structural marks a fixed template defines are allowed.
67
67
  - Autopilot always pauses at the channels menu (per `phase-7-report.md` autopilot contract) - even in autopilot mode the user gets to confirm wiki scope.
@@ -164,10 +164,10 @@ skills took the block from 11 skills / 4,710 bytes to 83 skills / 22,111 bytes
164
164
  only **75 of the 142** surfaced, **and an unrelated user-scope skill was evicted**.
165
165
  Removing the plugin brought it back.
166
166
 
167
- So shipping the 43 sub-commands as peer skills on Codex would silently lose
167
+ So shipping the 49 sub-commands as peer skills on Codex would silently lose
168
168
  pipeline commands next to any stack toolkit, with no error anywhere. The pipeline
169
169
  therefore contributes **exactly one** skill on Codex (`multi-agent`) and keeps the
170
- 43 sub-command specs as reference files that cost nothing until read.
170
+ 49 sub-command specs as reference files that cost nothing until read.
171
171
 
172
172
  **Do not "fix" this by adding per-command skills on Codex.** The layout is
173
173
  capability-derived, and `smoke-install-layout.sh` fails if the Codex skills tree
@@ -176,7 +176,7 @@ gains a second pipeline entry.
176
176
  ### Parity axis differs per host
177
177
 
178
178
  Claude Code and Copilot CLI are compared on their **skill directory sets**. Codex is
179
- compared on its **ref set**: the 43 command specs must all exist under
179
+ compared on its **ref set**: the 49 command specs must all exist under
180
180
  `~/.codex/multi-agent-refs/commands/<cmd>/SKILL.md`, and
181
181
  `smoke-codex-install.sh` asserts the count against the source tree. Comparing Codex
182
182
  on skill directories would demand exactly the layout that breaks it.
@@ -51,7 +51,7 @@ This is why the removal is safe:
51
51
  |---|---|---|
52
52
  | Phase 7 triage-memory ingest | `triage-output.json` | `[ -f ]`-guarded, so it degrades **silently**: the triage corpus and learnings ledger stop being fed and no error appears |
53
53
  | Phase 7 learnings-ledger distill | same file | same silent degradation |
54
- | `render-work-summary.sh` | `agent-state.json`, `phase-tracker.json` | exits 2, so the Work Summary vanishes from the PR body and the Jira comment |
54
+ | `render-work-summary.sh` | `agent-state.json`, `phase-tracker.json` (falls back to `logs/multi-agent/<task>/tracker-state.json`, which survives removal) | loses the salvaged copies but keeps the tracker via the logs fallback |
55
55
  | `:resume` | `agent-state.json` | cannot continue a Phase 7 pause |
56
56
  | `:status`, `:log` | `agent-state.json` | the task becomes invisible |
57
57
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **TLDR** - Shared 12-step flow for `/multi-agent:create-jira`. Asks the issue type (Task / Bug / Story), mines the target project's existing same-type issues to learn team conventions, detects the active sprint, drafts a standards-compliant issue from a fixed standard template with auto-sizing sections, asks the user about every genuinely unknown field, renders a full preview, and creates the Jira issue only after explicit approval. Creates exactly one Jira issue per run - no branches, no commits, no worktrees.
4
4
 
5
- Consumed by `generate.md`. This ref is never invoked directly.
5
+ Consumed by `create-jira/SKILL.md`. This ref is never invoked directly.
6
6
 
7
7
  ## Hard rules (must not regress)
8
8
 
@@ -10,7 +10,7 @@ Consumed by `generate.md`. This ref is never invoked directly.
10
10
  - **Never invent content.** Anything the user did not supply and mining could not derive stays an open question (step [8/12]) or is omitted entirely - never fabricated repro steps, environments, acceptance criteria, or test pass/fail results.
11
11
  - **Standard template is the baseline.** The description structure comes from the type's standard template (see [7/12]), not from a mined heading set. Mining informs summary prefix, labels, components, priority norm, sprint placement, and test-scenario style - never replaces the standard section skeleton.
12
12
  - **Auto-sizing.** A conditional section renders only when its trigger is present. No empty placeholder headings - if there is nothing to put in a conditional section, it does not appear.
13
- - **Issue content language follows `prefs.global.outputLanguage`.** Summary and description are written in the user's output language (intentional exception to the "external payloads stay English" default - the issue is authored FOR the user's team). Code identifiers, file paths, and URLs stay verbatim. AskUserQuestion `question`/`description` also follow `outputLanguage`; `label`/`header` stay English.
13
+ - **Issue content language follows `prefs.global.outputLanguage`.** Summary and description are written in the user's output language, like every other user-facing payload body (`rules.md` matrix). Code identifiers, file paths, and URLs stay verbatim. AskUserQuestion `question`/`description` also follow `outputLanguage`; `label`/`header` stay English.
14
14
  - **UTF-8 verbatim POST path** (same as `$HOME/.claude/multi-agent-refs/channels/jira.md`): description body goes to a file, `jq -n --rawfile` builds the payload, `curl --data-binary @file` ships it. Never round-trip through `unicode_escape`/`latin-1`, never hand-roll a re-encoding helper. Jira wiki markup (`h3.` headings, `[text|url]` links), real newlines, no HTML entities.
15
15
  - **Humanizer pass** on the description body after composition, before the preview render.
16
16
  - **Read-only until approval.** Steps 1-9 perform only GET requests. The first write of any kind is the `POST /rest/api/2/issue` in step [10/12].
@@ -42,7 +42,7 @@ Decision table:
42
42
  2. Resolve Jira token via `keychainMapping.jira`. Token missing → run Token Save Flow from `setup.md` inline (clipboard-based; user can add now or skip). Skip → treat as `never` for this run.
43
43
  3. Derive Jira fields from the GitHub issue:
44
44
  - `summary` ← GitHub issue title (truncate to 255 chars).
45
- - `description` ← GitHub issue body, prefixed with `h3. From GitHub\n[GH{issueNo}|{url}]\n\n`.
45
+ - `description` ← GitHub issue body run through the markdown → Jira wiki conversion table (`channels/jira.md`) - the body is GitHub Markdown (`###`, `- [ ]`, backticks) and Jira's description field renders wiki markup, so an unconverted body arrives as literal text under a correctly-rendered prefix. Prefix the converted body with the two lines `h3. From GitHub` and `[GH{issueNo}|{url}]`, then a blank line (real newlines in the payload file, never literal `\n`).
46
46
  - `issueType` - infer from labels: `bug` → Bug, `enhancement` → Story, default Task.
47
47
  - `project` - `figmaConfig.jira.projectKey`, or `prefs.global.defaultJiraKey`, or prompt (cached after first use).
48
48
  - `priority` - derive from labels if present (`priority:high` → High, etc.); else leave unset (Jira default).
@@ -66,7 +66,7 @@ Flow:
66
66
 
67
67
  1. Emit `→ posting wiki summary to Jira {jiraId}`.
68
68
  2. Read the first N lines of the primary wiki markdown file (the component's overview page - one of `writtenPaths[0]`).
69
- 3. Render as Jira comment: title line (`h3. Component docs - {componentName}`) + an overview paragraph + a link to the full page (wiki URL, if the adapter returned `pushedRemote`).
69
+ 3. Render as Jira comment: title line (`h3. Component docs - {componentName}`) + an overview paragraph + a link to the full page (wiki URL, if the adapter returned `pushedRemote`). The overview lines come from a Markdown file - convert them via the `channels/jira.md` table before POST, exactly like the title line already is.
70
70
  4. **Run through the humanizer skill** - same policy as Step 4 Confluence: user-facing content must read naturally.
71
71
  5. `POST {jira.baseUrl}/rest/api/2/issue/{jiraId}/comment`.
72
72
  6. Log: `Phase 7: wiki summary posted to Jira {jiraId}`.
@@ -96,7 +96,7 @@ Rationale: autopilot is explicit consent for side-effect creation; muting Jira a
96
96
  Both Claude Code and Copilot CLI implementations MUST:
97
97
 
98
98
  - Resolve the `autoJiraFromGithubIssue` enum the same way.
99
- - Derive Jira fields from GitHub issue metadata identically (title → summary, body → description with `h3. From GitHub` prefix, label → issueType mapping).
99
+ - Derive Jira fields from GitHub issue metadata identically (title → summary, body → description via the markdown → wiki conversion + `h3. From GitHub` prefix, label → issueType mapping).
100
100
  - Patch the GitHub issue body with the `Jira: [KEY](url)` line after create.
101
101
  - Treat failures as non-blocking with the same log shape.
102
102
  - Run the humanizer skill on the wiki-summary Jira comment.
@@ -0,0 +1,67 @@
1
+ ---
2
+ description: "Canonical required-reading list for outward-facing payloads (PR body, Jira comment, closing report) plus the markup dialect per surface. Loaded by every mode that runs Phase 6 or Phase 7."
3
+ ---
4
+
5
+ # Outward-facing payload contracts
6
+
7
+ > Every mode that opens a PR, comments on a tracker, or closes out a run reads this file first. It does not restate the contracts - it names them, so no mode has to carry its own copy and drift from the others.
8
+
9
+ ## Read before Phase 6
10
+
11
+ | Read | Before | Governs |
12
+ |---|---|---|
13
+ | [`channels/pr.md`]($HOME/.claude/multi-agent-refs/channels/pr.md) | assembling the PR body | fixed section set (`summary` → `changes` → `architecture` cond. → `verification` → `dependencies` cond. → `related`), Markdown-only rule, reviewer-preserving Bitbucket PUT payload |
14
+ | [`phases/phase-6-commit.md`]($HOME/.claude/multi-agent-refs/phases/phase-6-commit.md) | committing | commit convention, default-reviewer fetch, draft/ready prompt, push-must-succeed loop |
15
+ | [`rules.md`]($HOME/.claude/multi-agent-refs/rules.md) "External System Outputs" | any REST payload | real newlines, no HTML entities, no hand-rolled JSON, markup dialect per surface |
16
+
17
+ ## Read before Phase 7
18
+
19
+ | Read | Before | Governs |
20
+ |---|---|---|
21
+ | [`channels/jira.md`]($HOME/.claude/multi-agent-refs/channels/jira.md) | posting the Jira comment | fixed section set incl. **Test Scenarios** (Given/When/Then, always present), markdown→wiki conversion table |
22
+ | [`channels/confluence.md`]($HOME/.claude/multi-agent-refs/channels/confluence.md) | writing a Confluence page | storage-format conversion, endpoint flavor |
23
+ | [`phases/phase-7-report.md`]($HOME/.claude/multi-agent-refs/phases/phase-7-report.md) | closing out | Timeline + Agent Activity + Cost Breakdown tables, missing-telemetry disclosure |
24
+ | [`tracker-contract.md`]($HOME/.claude/multi-agent-refs/tracker-contract.md) | every phase boundary | per-phase token narration, completion tile suffix |
25
+
26
+ ## Markup dialect per surface
27
+
28
+ Language is not the only axis an external payload has. Every surface also has a markup dialect, and they are not interchangeable - the same body shipped to the wrong dialect renders as visible garbage, not as a slightly-off style.
29
+
30
+ | Surface | Dialect | Converter | Section set owned by |
31
+ |---|---|---|---|
32
+ | PR description (GitHub / Bitbucket / GitLab) | **Markdown**, no conversion | none - post the assembled markdown verbatim | `channels/pr.md` |
33
+ | GitHub issue body + comment | **Markdown**, no conversion | none | `channels/issue-comment.md` |
34
+ | Jira comment + issue description | **Jira wiki markup** | the table in `channels/jira.md`, applied by the model - no program exists | `channels/jira.md` |
35
+ | Confluence page body | **storage format** (XHTML) | `lib/md2confluence-v3.py` | `channels/confluence.md` |
36
+ | Wiki pages (`.md` files in a git repo) | **Markdown** | none | `channels/wiki.md` |
37
+ | Commit message | plain text | none | `rules/git-conventions.md` |
38
+
39
+ Assemble every body once in Markdown, then convert **only** on the branches that require it. The recurring defect is the reverse: the Jira wiki table is the only text-markup table in the doc set, so it reads like the default and gets applied to a PR body. In a Markdown target, `h2. Title` renders as literal text, `{{identifier}}` keeps its braces, `#` starts an H1 instead of a numbered list, and `*bold*` comes out italic. In a Jira target the mirror-image failure applies: `## Heading` and `**bold**` render literally.
40
+
41
+ A payload that is in the right language but the wrong dialect is a defect of the same severity as one that never posted. Both need a fix, not a follow-up ticket.
42
+
43
+ ## Closing report (required)
44
+
45
+ The run ends with the phase tracker glyph block **and** the numbers behind it - per-phase duration and token spend, plus totals.
46
+
47
+ - Record spend as you go: `phase-tracker.sh tokens <N> <in> <out> [cached]` after **every** LLM call, including each Phase 4 reviewer subagent and each Phase 3 chunk. Counts are additive and nothing reconstructs them after the fact.
48
+ - Tag the model once per phase (`phase-tracker.sh model <N> <name>`) or the cost helper cannot price it and prints `-`.
49
+ - Durations come from the phase timestamps and survive a missed `tokens` call; token spend does not.
50
+ - If any phase has no token data, name those phases and say their cost is unavailable. Never print a report whose cost section is simply absent - the reader cannot tell "cheap run" from "nobody recorded it".
51
+
52
+ ### Missing-telemetry disclosure (Phase 7, required)
53
+
54
+ Before composing the report, compute which phases carry no token data:
55
+
56
+ ```bash
57
+ STATE="$HOME/.claude/logs/multi-agent/$TASK_ID/tracker-state.json"
58
+ UNTRACKED=$(jq -r '[.phases[] | select((.tokens_in // 0) + (.tokens_out // 0) == 0) | .id] | join(", ")' "$STATE")
59
+ ```
60
+
61
+ Non-empty `UNTRACKED` → both the agent-log report and the closing chat summary carry one line in `outputLanguage` naming those phase ids as cost-unavailable. Durations come from the phase timestamps and are reported either way.
62
+
63
+ `smoke-tracker-tokens-invocation.sh` only lints that the phase docs *mention* `phase-tracker.sh tokens`. It cannot observe a run that read the doc and skipped the call, so this run-time disclosure is the only thing between a dropped `tokens` call and an invisible failure.
64
+
65
+ ## Fast modes are not exempt
66
+
67
+ `--dev`, `--dev --local`, and the autopilot variants skip Analysis, Planning, and the interactive test gate. They run Phase 6 and Phase 7 **unchanged**. A short pipeline is not a licence for an improvised payload shape, a missing Test Scenarios section, or a report without numbers.
@@ -105,6 +105,26 @@ No separate task breakdown - the agent handles scope autonomously.
105
105
 
106
106
  **Combinable with autopilot**: `--dev autopilot` = fastest path. Init -> Dev -> Review (auto-fix) -> auto-commit -> auto-PR -> Report (Phase 5 skipped). Zero user interaction (except build failures after 3 retries, and review findings that survive 3 rework cycles).
107
107
 
108
+ ### Intake warnings shared by the whole `--dev` family
109
+
110
+ These apply to `--dev`, `--dev autopilot`, `--dev local`, and `--dev local autopilot` alike. A mode entry doc may not carry its own copy - it points here.
111
+
112
+ **An analysis document was supplied.** Because Phase 1 and Phase 2 are skipped, no phase turns that document into a task breakdown. The doc becomes raw context for one Dev pass, and work comes out ordered by whatever the model read first: the bottom of the dependency chain lands, the screen wiring does not.
113
+
114
+ Say so before starting, once, and offer the choice:
115
+
116
+ ```
117
+ This mode skips Analysis and Planning, so the analysis document will not be turned
118
+ into a task breakdown. For analysis-driven screen work, /multi-agent or
119
+ /multi-agent:local run both phases.
120
+ 1. Continue with --dev (doc as context only)
121
+ 2. Switch to the full pipeline
122
+ ```
123
+
124
+ Autopilot picks 1 and logs the warning rather than asking.
125
+
126
+ **The branch already carries the work.** When the change was developed outside the pipeline, or by hand, the `--dev` family is the wrong entry point: it will try to develop again. `/multi-agent:ship` puts the existing diff through the same review, adds a build+test success gate, opens the PR, and posts the Jira technical-analysis + test-scenario comment - without re-developing. Offer it when the working tree or branch is already ahead of the base with the task's changes.
127
+
108
128
  ---
109
129
 
110
130
  ## Local Mode (`--local`)
@@ -35,7 +35,7 @@ Read preferences: `PREFS_FILE="$HOME/.claude/multi-agent-preferences.json"` (if
35
35
  OUTPUT_LANG=$(jq -r '.global.outputLanguage // "en"' "$PREFS_FILE" 2>/dev/null || echo en)
36
36
  ```
37
37
 
38
- From this point on, every assistant-authored conversational line (status updates, findings, phase headers, summaries, error explanations) renders in `$OUTPUT_LANG`. `AskUserQuestion` payloads stay English. External payloads stay English. See `$HOME/.claude/multi-agent-refs/rules.md` "Language Application" for the full contract. Skipping this step is the failure mode where the user sees an English wall of text in a Turkish session.
38
+ From this point on, everything the user reads renders in `$OUTPUT_LANG`: conversational lines, `AskUserQuestion` `question`/`description`, and external payload bodies (PR/Jira/Confluence). English stays only on `label`/`header`, commit messages, branch names, PR titles, identifiers. Full matrix: `rules.md` "Language Application". Skipping this step is why a Turkish session gets an English wall of text - or an English picker.
39
39
 
40
40
  **Model fallback date gate** (same step, once per run): read `prefs.global.modelFallback`. If `premiumTierUntil` is set and in the past, apply the date-gate trigger from `$HOME/.claude/multi-agent-refs/features/model-fallback.md` - `preferredModel` personas dispatch on `fallbackModel` for this run, with the one-line WARN. Dispatch-error and budget triggers in that contract apply per-dispatch later; nothing else to do here.
41
41
 
@@ -106,6 +106,8 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
106
106
 
107
107
  #### Step 3 - PR Description (technical detail for reviewers)
108
108
 
109
+ **Section set + markup dialect: `channels/pr.md` - read it first.** Phase 7 channels replaces this body with that section set, so build to it. Required reading: `payload-contracts.md`.
110
+
109
111
  Generate a structured PR description based on task type. The PR body targets **code reviewers** - it should be technical: what changed, why, architecture decisions, how to verify.
110
112
 
111
113
  **required**: Run all generated text (PR body, commit message) through the `humanizer` skill before posting. This removes AI-generated patterns (inflated language, filler phrases, repetitive structure) and makes the output sound like a developer wrote it.
@@ -116,49 +118,15 @@ Generate a structured PR description based on task type. The PR body targets **c
116
118
  - Bitbucket: similar patterns
117
119
  - Use `Ref: #N`, `Related: #N`, or `See: {jiraId}` instead
118
120
 
119
- **Default Reviewers**: If `prefs.projects[{project}].defaultReviewers` exists, add `--reviewer` flags. If not, ask on first PR and save to preferences:
120
-
121
- ```
122
- Default reviewers for this project? (comma-separated GitHub usernames, or skip):
123
- ```
124
-
125
- Save to `prefs.projects[{project}].defaultReviewers: ["user1", "user2", "user3", "user4"]`
121
+ **Default Reviewers**: use `prefs.projects[{project}].defaultReviewers` when set; otherwise ask once ("Default reviewers for this project? comma-separated, or skip") and persist the answer there. Per-remote resolution rules are in "Default reviewers (required on every PR)" below - this is the prefs half only.
126
122
 
127
- **Standard PR (bug fix, feature, refactor):**
123
+ **Standard PR (bug fix, feature, refactor)** - invocation shape; body comes from a file so newlines survive:
128
124
 
129
125
  ```bash
130
126
  REVIEWERS=$(jq -r '.projects["'$PROJECT'"].defaultReviewers // [] | map("--reviewer " + .) | join(" ")' "$PREFS_FILE")
131
127
  gh pr create --base {baseBranch} --head {branch} $REVIEWERS \
132
128
  --title "{type}({scope}): {summary} [{jiraId}]" \
133
- --body "$(cat <<'EOF'
134
- ## Summary
135
- {1-3 bullet points: what changed and why}
136
-
137
- ## Jira
138
- {jiraId} - {jira summary or link}
139
-
140
- ## Technical Details
141
- ### Changes
142
- {For each changed file group, explain WHAT changed and WHY}
143
-
144
- ### Architecture
145
- {Any structural decisions: new files, moved code, pattern changes}
146
-
147
- ### Dependencies
148
- {New imports, framework changes, package updates - or "None"}
149
-
150
- ## How to Test
151
- 1. {Step-by-step manual test instructions}
152
- 2. {Verify specific behavior}
153
-
154
- ## Checklist
155
- - [ ] Build passes (0 errors, 0 warnings)
156
- - [ ] Tests pass
157
- - [ ] No hardcoded values (tokens used)
158
- - [ ] Accessibility verified
159
- - [ ] Dark mode verified
160
- EOF
161
- )"
129
+ --body-file /tmp/pr-body-$TASK_ID.md
162
130
  ```
163
131
 
164
132
  **UI Component PR (when SwiftUI component detected):**
@@ -175,9 +143,9 @@ Same `gh pr create` pattern with component-specific body sections:
175
143
 
176
144
  CRITICAL: PR description **MUST** preserve real newlines. Never embed `\n` literally - Bitbucket saves it verbatim. Never use HTML entities. Pattern: heredoc → `jq --rawfile` → `curl --data-binary @file`.
177
145
 
178
- Required payload fields: `title`, `description` (via rawfile), `fromRef` (id + repo slug + project key), `toRef` (same structure), `state: "OPEN"`, `open: true`, `closed: false`, `locked: false`, `reviewers: []`.
146
+ Required payload fields: `title`, `description` (via rawfile), `fromRef` (id + repo slug + project key), `toRef` (same structure), `state: "OPEN"`, `open: true`, `closed: false`, `locked: false`, `reviewers` (the fetched list - `[]` only if that endpoint really returned empty).
179
147
 
180
- To **update** an existing PR: fetch `version`, PUT with same rawfile pattern (version +1). GitHub: prefer `--body-file` over inline `--body`.
148
+ To **update** an existing PR: use the `channels/pr.md` PUT payload - required, not illustrative. It carries `reviewers`/`fromRef`/`toRef`/`draft` (a PUT clears omitted fields) and the GET's `version` unmodified (`version + 1` → 409). Refetch after, assert the reviewer count held. GitHub: `--body-file`.
181
149
 
182
150
  **Default reviewers (required on every PR)**
183
151
 
@@ -307,7 +275,7 @@ update_sibling_links "${PR_URLS[@]}"
307
275
 
308
276
  **Then finalize each repo's worktree**, serially and only now - `update_sibling_links` updates one PR per repo and the loop `cd`s per repo, so removing repo 1's worktree mid-loop breaks 2..N. Run `worktree-finalize.sh` once per repo with that repo's `--worktree` / `--project-root` / `--project`; each skips or removes independently.
309
277
 
310
- `update_sibling_links` does an UPDATE call per PR (Bitbucket: PUT with `version+1`; GitHub: `gh pr edit --body-file`). Idempotent: re-running with the same set is a no-op.
278
+ `update_sibling_links` does an UPDATE call per PR (Bitbucket: the `channels/pr.md` PUT payload, current `version`; GitHub: `gh pr edit --body-file`). Idempotent.
311
279
 
312
280
  ##### GitHub issue body - list all PRs
313
281
 
@@ -153,8 +153,8 @@ Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emi
153
153
 
154
154
  ## Timeline
155
155
 
156
- | # | Phase | Model | Duration | Status | Detail |
157
- | --- | ----- | ----- | -------- | ------ | ------ |
156
+ | # | Phase | Agent | Model | Duration | Tokens (in/out) | Status | Detail |
157
+ | --- | ----- | ----- | ----- | -------- | --------------- | ------ | ------ |
158
158
 
159
159
  ## Agent Activity Report
160
160
 
@@ -200,7 +200,7 @@ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 7 task.completed \
200
200
 
201
201
  **Per-run outcome metrics (evidence corpus):** also emit `run-metrics.mjs --state <agent-state.json>` and append/persist its JSON. It records the numbers that actually answer "did this run go well" - review iterations (rework loops), first-pass-clean, reviewer signal-to-noise (accepted / raw findings), consensus verdict, build outcome. Accumulating these across real runs is the real-world validation that golden tasks + benchmarks only approximate; keep the corpus so the pipeline's quality can be measured, not asserted.
202
202
 
203
- **Cost Breakdown emission (mandatory):** as part of agent-log compose, append the per-task cost block produced by `render-agent-log-cost.sh`. Best-effort - exit 2 (no tracker data) silently skipped:
203
+ **Cost Breakdown emission (mandatory):** as part of agent-log compose, append the per-task cost block produced by `render-agent-log-cost.sh`:
204
204
 
205
205
  ```bash
206
206
  COST_BLOCK=$(bash $HOME/.claude/scripts/render-agent-log-cost.sh "$TASK_ID" 2>/dev/null) && \
@@ -209,6 +209,8 @@ COST_BLOCK=$(bash $HOME/.claude/scripts/render-agent-log-cost.sh "$TASK_ID" 2>/d
209
209
 
210
210
  This is independent of the channels-side `reportContent.costSummary` (which gates the PR-body Cost Summary). agent-log always carries the breakdown when telemetry is present.
211
211
 
212
+ **Missing-telemetry disclosure (required).** Name untracked phase ids as cost-unavailable in the report and closing summary. Mechanic: `payload-contracts.md`.
213
+
212
214
  **Triage memory ingest (mandatory):** after Phase 4 produces a final triage output, persist the accepted/deferred/rejected rows into the per-repo triage corpus so Phase 1 enrichment and Phase 4 prior-art lookup can recall them on future tasks. Idempotent - re-running on the same task writes 0 rows.
213
215
 
214
216
  ```bash
@@ -63,6 +63,12 @@ $HOME/.claude/scripts/phase-tracker.sh update <N> failed # phase failed
63
63
  $HOME/.claude/scripts/phase-tracker.sh update <N> skipped # --dev mode etc.
64
64
  ```
65
65
 
66
+ After every LLM call (counts are additive; skipping this is why runs end with durations but no cost - nothing reconstructs spend afterwards):
67
+
68
+ ```bash
69
+ $HOME/.claude/scripts/phase-tracker.sh tokens <N> <in> <out> [cached]
70
+ ```
71
+
66
72
  For sub-phase progress (e.g. Phase 1's parallel Explore agents, Phase 4's reviewer dispatch + triage + validator gate, Phase 6's commit + push + PR sub-steps):
67
73
 
68
74
  ```bash
@@ -122,6 +122,8 @@ This is the single source of truth. When a contributor or model is unsure where
122
122
 
123
123
  4. **Terminal output, log lines, and `echo`/`printf` calls** also use real newlines - never `\n` literals. For multi-line messages, prefer heredoc.
124
124
 
125
+ 5. **Markup dialect is a second axis beyond language.** Markdown: PR body, GitHub issue, wiki files. Jira: wiki markup. Confluence: storage format. Wrong dialect renders literally. Table: `payload-contracts.md`.
126
+
125
127
  ## Issue Management
126
128
 
127
129
  - **NEVER auto-close** GitHub issues or Jira tickets. Closure requires team review (configurable, typically 4 approvals).
@@ -670,7 +670,7 @@
670
670
  "type": "string",
671
671
  "enum": ["en", "tr"],
672
672
  "default": "en",
673
- "description": "Language used for user-facing INTERACTIVE pipeline copy: pickers (account, project, dev-context, base branch, branch name, maturity ack, channels), Phase 5 test prompt, Phase 6 local-checkout prompt, Phase 6 commit/PR confirmations, TaskCreate card subjects, and phase-banner.sh headers (`PHASE_LANG` resolves from this field via the `auto` sentinel). Canonical label table lives in `pipeline/skills/shared/multi-agent/SKILL.md`. External payloads stay English regardless of this setting: reviewer/triage system prompts, commit messages, PR titles/bodies, Jira comments, wiki content, agent-log.md payloads. Set via `/multi-agent:language prompt <en|tr>` or answered on first run by `/multi-agent:setup`."
673
+ "description": "Locked to 'en': the language of LLM-facing instructions (spec files, agent system prompts, reviewer/triage prompts) and picker structural chrome (AskUserQuestion label/header). What humans read is governed by outputLanguage instead - picker question/description, conversational lines, and external payload bodies (PR description, Jira comment, Confluence/Wiki). Always English regardless of either pref: commit messages, branch names, PR titles, code identifiers, agent-log.md. Canonical matrix: multi-agent-refs/rules.md 'Language Application'. Set via /multi-agent:language or /multi-agent:setup."
674
674
  },
675
675
  "outputLanguage": {
676
676
  "type": "string",
@@ -80,6 +80,16 @@ if [ -s "$AUDIT_FILE" ]; then
80
80
  if [ -f "$ROTATED" ] && command -v gzip >/dev/null 2>&1; then
81
81
  gzip -f "$ROTATED" 2>/dev/null || true
82
82
  fi
83
+
84
+ # gzip preserves the source file's mtime by default, so an archive rotated
85
+ # from log content already older than KEEP_DAYS would carry that same old
86
+ # mtime - and the retention sweep below deletes anything past KEEP_DAYS,
87
+ # so a just-created archive could be purged in this very run before ever
88
+ # being read. Retention has to count from rotation time, not from the age
89
+ # of the content it archived. gzip -f removes the plain file on success, so
90
+ # only touch whichever of the two actually exists.
91
+ [ -f "$ROTATED.gz" ] && touch "$ROTATED.gz"
92
+ [ -f "$ROTATED" ] && touch "$ROTATED"
83
93
  fi
84
94
 
85
95
  # Drop archives older than KEEP_DAYS
@@ -61,6 +61,7 @@ const COMMON_SKILLS = [
61
61
  "agent-introspection-debugging",
62
62
  "skill-creator",
63
63
  "backlog",
64
+ "localization-reuse-map",
64
65
  ];
65
66
 
66
67
  // Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
@@ -134,7 +135,13 @@ function treeFingerprint(dir) {
134
135
  // --- 1. enumerate authoring source ---------------------------------------
135
136
  if (!existsSync(EXTERNAL)) {
136
137
  console.error(`source not found: ${EXTERNAL}`);
137
- process.exit(0);
138
+ // --check-routing exists so CI can gate routing correctness without a
139
+ // checkout of the plugins repo, reading only this authoring source. Exiting
140
+ // 0 here regardless of --check-routing meant a misconfigured/missing
141
+ // source (wrong --pipeline path, an incomplete CI checkout) reported the
142
+ // gate as passing having audited nothing - the exact silent-green failure
143
+ // the gate exists to prevent elsewhere.
144
+ process.exit(args.includes("--check-routing") ? 1 : 0);
138
145
  }
139
146
  const allSkills = listDirs(EXTERNAL);
140
147
 
@@ -38,6 +38,18 @@ const CACHE = process.env.DERIVED_DRIFT_CACHE || join(homedir(), ".claude", "plu
38
38
 
39
39
  const expandHome = (p) => p.replace(/^\$HOME\b|^~/, homedir());
40
40
 
41
+ // Array.prototype.sort() with no comparator sorts these lexicographically
42
+ // ("10.0.0" < "9.0.0" as strings), so a directory listing with both would
43
+ // treat 9.0.0 as newer.
44
+ function semverCompare(a, b) {
45
+ const pa = a.split(".").map(Number);
46
+ const pb = b.split(".").map(Number);
47
+ for (let i = 0; i < 3; i++) {
48
+ if (pa[i] !== pb[i]) return pa[i] - pb[i];
49
+ }
50
+ return 0;
51
+ }
52
+
41
53
  function readJson(path) {
42
54
  try {
43
55
  return JSON.parse(readFileSync(path, "utf-8"));
@@ -116,7 +128,7 @@ function fromCache(entry) {
116
128
  if (!existsSync(dir)) return null;
117
129
  const versions = readdirSync(dir)
118
130
  .filter((d) => /^\d+\.\d+\.\d+$/.test(d))
119
- .sort();
131
+ .sort(semverCompare);
120
132
  const newest = versions[versions.length - 1];
121
133
  return newest
122
134
  ? { version: newest, source: "plugin cache", authoritative: false, behind: null }
@@ -117,6 +117,44 @@ function resolveBase() {
117
117
  die("no base ref found (tried origin/main, main, origin/master, master) - pass --base");
118
118
  }
119
119
 
120
+ // git quotes a `diff --git` path (core.quotePath, on by default) whenever it
121
+ // has a non-ASCII byte or other unusual character - e.g. a Turkish filename
122
+ // renders as `diff --git "a/\303\226deme.swift" "b/\303\226deme.swift"`, not
123
+ // the bare a/...b/... form. The plain regex this replaced never matched that
124
+ // line at all, so every non-ASCII-named file dropped out of the explanation.
125
+ const DIFF_GIT_QUOTED_RE = /^diff --git "a\/((?:[^"\\]|\\.)*)" "b\/((?:[^"\\]|\\.)*)"$/;
126
+ const DIFF_GIT_UNQUOTED_RE = /^diff --git a\/(.+?) b\/(.+)$/;
127
+
128
+ function unquoteGitPath(s) {
129
+ const bytes = [];
130
+ for (let i = 0; i < s.length; i++) {
131
+ if (s[i] === "\\") {
132
+ const octal = s.slice(i + 1, i + 4);
133
+ if (/^[0-7]{3}$/.test(octal)) {
134
+ bytes.push(parseInt(octal, 8));
135
+ i += 3;
136
+ continue;
137
+ }
138
+ const simple = { "\\": 92, '"': 34, t: 9, n: 10, r: 13 };
139
+ const next = s[i + 1];
140
+ if (next in simple) {
141
+ bytes.push(simple[next]);
142
+ i += 1;
143
+ continue;
144
+ }
145
+ }
146
+ bytes.push(s.charCodeAt(i));
147
+ }
148
+ return Buffer.from(bytes).toString("utf-8");
149
+ }
150
+
151
+ function matchDiffGitHeader(line) {
152
+ const q = line.match(DIFF_GIT_QUOTED_RE);
153
+ if (q) return unquoteGitPath(q[2]);
154
+ const u = line.match(DIFF_GIT_UNQUOTED_RE);
155
+ return u ? u[2] : null;
156
+ }
157
+
120
158
  /**
121
159
  * Parse unified diff into a map of {path: [{ oldStart, newStart, lines: [...] }]}
122
160
  * Each hunk's `lines` retains the leading +/- /space prefix.
@@ -127,9 +165,9 @@ function parseDiff(raw) {
127
165
  let currentHunks = null;
128
166
  let currentHunk = null;
129
167
  for (const line of raw.split("\n")) {
130
- const fileMatch = /^diff --git a\/(.+?) b\/(.+)$/.exec(line);
131
- if (fileMatch) {
132
- currentPath = fileMatch[2];
168
+ const fileMatch = matchDiffGitHeader(line);
169
+ if (fileMatch !== null) {
170
+ currentPath = fileMatch;
133
171
  currentHunks = [];
134
172
  files[currentPath] = currentHunks;
135
173
  currentHunk = null;