@mmerterden/multi-agent-pipeline 12.7.0 → 12.8.0

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 (244) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/install/_common.mjs +48 -0
  3. package/install/_dev-only-files.mjs +125 -5
  4. package/install/claude.mjs +14 -8
  5. package/install/copilot.mjs +5 -8
  6. package/package.json +17 -2
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  8. package/pipeline/lib/credential-store.sh +20 -0
  9. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  10. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  11. package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
  12. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  13. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -0
  14. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  15. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  16. package/pipeline/multi-agent-refs/phases/phase-4-review.md +49 -4
  17. package/pipeline/schemas/prefs.schema.json +6 -0
  18. package/pipeline/scripts/_smoke-root.sh +61 -0
  19. package/pipeline/scripts/audit-log.sh +25 -0
  20. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  21. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  22. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  23. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  27. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  28. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  29. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  33. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  34. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  35. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  39. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  40. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  41. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  45. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  46. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  47. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  51. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  52. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  53. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  57. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  58. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  59. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  63. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  64. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  65. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  69. package/pipeline/eval/golden-tasks/README.md +0 -65
  70. package/pipeline/eval/intent-cases.json +0 -40
  71. package/pipeline/eval/run-metrics-fixture.json +0 -93
  72. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  73. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  74. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  75. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  76. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  77. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  78. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  79. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  80. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  81. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  82. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  83. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  84. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  85. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  86. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  87. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  88. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  89. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  90. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  91. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  92. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  93. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  94. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  95. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  96. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  97. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  98. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  99. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  100. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  101. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  102. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  103. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  104. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  105. package/pipeline/eval/triage/README.md +0 -54
  106. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  107. package/pipeline/scripts/check-md-links.mjs +0 -88
  108. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  109. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  110. package/pipeline/scripts/eval-intent.mjs +0 -107
  111. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  112. package/pipeline/scripts/eval-triage.mjs +0 -171
  113. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  114. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  115. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  116. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  117. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  118. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  119. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  120. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  121. package/pipeline/scripts/lint-skills.mjs +0 -154
  122. package/pipeline/scripts/run-smokes.mjs +0 -130
  123. package/pipeline/scripts/scorecard.mjs +0 -258
  124. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  125. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  126. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  127. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  128. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  129. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  130. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  131. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  132. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  133. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  134. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  135. package/pipeline/scripts/smoke-clarify.sh +0 -148
  136. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  137. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  138. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  139. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  140. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  141. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  142. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  143. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  144. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  145. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  146. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  147. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  148. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  149. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  150. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  151. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  152. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  153. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  154. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  155. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  156. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  157. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  158. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  159. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  160. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  161. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  162. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  163. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  164. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  165. package/pipeline/scripts/smoke-keychain.sh +0 -158
  166. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  167. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  168. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  169. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  170. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  171. package/pipeline/scripts/smoke-md-links.sh +0 -8
  172. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  173. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  174. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  175. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  176. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  177. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  178. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  179. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  180. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  181. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  182. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  183. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  184. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  185. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  186. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  187. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  188. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  189. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  190. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  191. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  192. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  193. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  194. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  195. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  196. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  197. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  198. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  199. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  200. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  201. package/pipeline/scripts/smoke-purge.sh +0 -138
  202. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  203. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  204. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  205. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  206. package/pipeline/scripts/smoke-routines.sh +0 -84
  207. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  208. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  209. package/pipeline/scripts/smoke-search.sh +0 -187
  210. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  211. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  212. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  213. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  214. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  215. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  216. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  217. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  218. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  219. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  220. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  221. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  222. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  223. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  224. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  225. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  226. package/pipeline/scripts/smoke-update-check.sh +0 -135
  227. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  228. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  229. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  230. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  231. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  232. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  233. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  234. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  235. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  236. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  237. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  238. package/pipeline/scripts/smoke-write-state.sh +0 -159
  239. package/pipeline/scripts/sync-parity-check.sh +0 -135
  240. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  241. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  242. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  243. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  244. package/pipeline/scripts/validate-schemas.mjs +0 -88
package/CHANGELOG.md CHANGED
@@ -16,6 +16,132 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [12.8.0] - 2026-07-26
20
+
21
+ 12.7.0 fixed nine gates that reported success without checking anything. This
22
+ release is the level above that: gates that check correctly and never run, and a
23
+ shipped surface that could not work where it was shipped to.
24
+
25
+ ### 117 maintainer smokes stopped shipping to users
26
+
27
+ `pipeline/scripts/` was copied wholesale into `~/.claude/scripts/`, so every
28
+ repository CI gate landed on every user's machine. Measured from a real install:
29
+ of 117 installed smoke scripts, **100 failed** and 17 passed only by accident of
30
+ not needing the repo. They resolve the repo root as `dirname($0)/../..`, which in
31
+ an install layout is `$HOME`, and then read `package.json` / `install.js` /
32
+ `node_modules`. What a user saw:
33
+
34
+ ```
35
+ $ bash ~/.claude/scripts/smoke-install-layout.sh
36
+ FAIL: install.js missing
37
+ ```
38
+
39
+ A green repo gate, run in the wrong place, indistinguishable from a broken
40
+ installation. Worse, `/multi-agent:update` actively instructed users to run
41
+ `smoke-cross-cli-behavior.sh`, which reported "3 failed" for the same reason.
42
+
43
+ The default for `smoke-*.sh` is now EXCLUDE, in both the install and the tarball.
44
+ A smoke ships only when a command actually invokes it - `CONSUMER_SMOKES` in
45
+ `install/_dev-only-files.mjs`, currently the two that `/multi-agent:update` runs.
46
+ Both now resolve paths through the new `pipeline/scripts/_smoke-root.sh`, which
47
+ handles the repo and install layouts, so `smoke-cross-cli-behavior.sh` reports
48
+ 20/20 from an install instead of 17/20.
49
+
50
+ Also withheld: `run-smokes.mjs`, `eval-*.mjs`, `pipeline/eval/**`, `scorecard.mjs`,
51
+ the linters, `validate-schemas.mjs`, and the fixture corpora - all of them need
52
+ devDependencies or the repo tree. Install drops from 201 files to 73; the tarball
53
+ from 977 to 753.
54
+
55
+ `smoke-consumer-smoke-surface.sh` holds four invariants: the allowlist equals what
56
+ commands invoke (drift either way fails), every consumer smoke uses the shared
57
+ resolver, no maintainer smoke survives the install filter, and none survives into
58
+ the tarball. `smoke-pack-contents.sh` now evaluates `files[]` the way npm does
59
+ (last matching pattern wins, so a broad negation may be followed by a narrower
60
+ re-inclusion - verified against npm directly) and reports an inert negation that
61
+ guards nothing. `smoke-install-leak-gate.sh` step 4 previously asserted the
62
+ OPPOSITE contract, naming three "essential" smokes that had to install; it now
63
+ derives the expected set from `CONSUMER_SMOKES`.
64
+
65
+ Fixed along the way: both installers computed the excluded-file count by summing
66
+ `countFiles()` over the exclusion array, which returns 0 for every non-literal
67
+ entry, so the count would have under-reported by 100+ files.
68
+
69
+ ### Four gates that were tested, green, and never ran
70
+
71
+ Every gate in this repo checks its own behaviour. Nothing checked that a gate is
72
+ *reachable*. Four were not - each with a passing unit test and a passing smoke,
73
+ and no reference from `pipeline/commands/`, `multi-agent-refs/`, `agents/`,
74
+ `skills/` or `install/`:
75
+
76
+ - **`test-integrity-gate.mjs`** - the anti-reward-hacking control for a suite made
77
+ green by deleting tests. Now Phase 4 Step 1.76, emitting blocking findings that
78
+ merge into the reviewer findings at Step 3.0 (before the no-findings
79
+ short-circuit, which would otherwise skip triage entirely on a diff whose only
80
+ finding is a deleted test). No opt-out: a run that can switch off its own
81
+ anti-reward-hacking control cannot be trusted to report a pass.
82
+ - **`review-scope.mjs`** - the reviewer-count cost gate. Now Phase 4 Step 1.77,
83
+ behind `prefs.global.reviewScopeGate` (default true). Every diff had been paying
84
+ for the full reviewer set.
85
+ - **`audit-log.sh`** - the PAT-lookup audit trail. `.gitignore` carried defensive
86
+ globs for it and `/multi-agent:prune-logs` documented managing it, but
87
+ `credential-store.sh` never called it, so the trail was always empty. Now
88
+ written on every `get`, on both the success and the miss path, through the
89
+ Python delegate and the raw-shell path. Since the trail now actually grows,
90
+ `audit-log.sh` self-rotates past `AUDIT_ROTATE_AT_BYTES` (1 MB) rather than
91
+ depending on a launchd/cron install nothing ever told a user to set up.
92
+ - **`write-state.mjs`** - the atomic `agent-state.json` writer with an advisory
93
+ lock, written for exactly the multi-repo race the phase docs create by sharing
94
+ one state file across worktrees. `operations.md` now documents it as the
95
+ required mechanism, with the exit codes a caller must handle, and
96
+ `phase-0-init.md` points at it where the shared file is introduced.
97
+
98
+ Phase 4 Step 1.75 now scores the diff **once without `--top`**: both new gates
99
+ need every scored file (a shrinking test file ranked 20th; whether any file is
100
+ high-stakes), and the top-5 prompt hint is derived from that report instead of a
101
+ second git walk.
102
+
103
+ `smoke-gate-wiring.sh` sweeps every runtime script for a reference from the
104
+ instruction surface, with maintainer tooling listed explicitly and by reason
105
+ rather than being the silent default for anything unreferenced. It also pins the
106
+ four wirings individually, and asserts the test-integrity gate is fed the full
107
+ report rather than the truncated hint. It found two more scripts on the first run
108
+ (`review-watch.sh`, `audit-log-rotate.sh`), both correctly user-launched.
109
+
110
+ ### Removed: the v9.1.0 legacy-v2 analysis escape hatch
111
+
112
+ Triple-dead and still documented in three files. Its own text said "escape hatch
113
+ removed in v9.2.0" - this is 12.8.0. The `prefs.global.legacyV2AnalysisAllowed` it
114
+ depended on was never added to `prefs.schema.json`, and `global` is
115
+ `additionalProperties: false`, so a user who configured it as documented produced
116
+ a preferences file that fails validation. `template_version < v3` now simply
117
+ aborts. The reclaimed budget paid for the two new phase-4 steps, so the fixed
118
+ per-run context ceiling did not move.
119
+
120
+ ### Gate honesty
121
+
122
+ - **The context ceiling can no longer be bypassed.** It was
123
+ `${CONTEXT_BUDGET_CEILING:-60000}` - the only env-overridable threshold in the
124
+ suite, so `CONTEXT_BUDGET_CEILING=999999` passed while measuring nothing. It is
125
+ a literal now, pinned by `test/context-budget-gate.test.mjs` (the companion to
126
+ `test/coverage-gate.test.mjs`, which already protects the coverage floors from
127
+ the same move). Headroom was 3.5% when this was written - precisely when raising
128
+ the number looks attractive. The test also prints current headroom so it shrinks
129
+ visibly instead of silently.
130
+ - **The coverage claim names its denominator.** "coverage floors held" reads as a
131
+ repo-wide figure; c8 instruments JavaScript, and this repo carries ~30k lines of
132
+ shell and Python that no coverage number covers at all. The metric is now
133
+ "coverage floors held (JavaScript only)" and states the excluded size, matching
134
+ what the scorecard's UNMEASURED section already does for whole categories.
135
+
136
+ ### Also
137
+
138
+ - `smoke-consumer-smoke-surface.sh` and `smoke-gate-wiring.sh` added to `ci-lite`;
139
+ the consumer-surface gate also runs in `release`, since a publish is the only
140
+ moment tarball contents become permanent.
141
+ - `test/dev-only-files.test.mjs` (20 tests) covers the exclusion predicate,
142
+ including that a hypothetical new smoke is withheld by default and that runtime
143
+ scripts the phases invoke are not.
144
+
19
145
  ## [12.7.0] - 2026-07-26
20
146
 
21
147
  A refactor pass whose theme turned out to be one recurring defect: gates that
@@ -239,6 +239,54 @@ export function pruneLegacyMultiAgentSkills(skillsDir) {
239
239
  return removed;
240
240
  }
241
241
 
242
+ /**
243
+ * Trees an older installer created and no current one manages.
244
+ *
245
+ * A tree that stops being copied does NOT stop existing: the wipe-before-copy
246
+ * pattern only runs for trees the installer still writes, so dropping one from
247
+ * the installer leaves whatever it last wrote on every existing machine forever.
248
+ *
249
+ * `eval/` is the case that prompted this. An older version copied the eval
250
+ * corpora into `~/.claude/eval/`; the harnesses that read them are maintainer CI
251
+ * tooling and no longer ship, so neither installer references `eval` any more and
252
+ * neither did uninstall - 31 stale files sat there with nothing left that could
253
+ * ever read or remove them.
254
+ *
255
+ * @type {ReadonlyArray<{dir: string, reason: string}>}
256
+ */
257
+ export const ABANDONED_TREES = Object.freeze([
258
+ {
259
+ dir: "eval",
260
+ reason: "eval corpora; the harnesses that read them are maintainer-only and no longer ship",
261
+ },
262
+ ]);
263
+
264
+ /**
265
+ * Remove trees an older install left behind under `root`.
266
+ *
267
+ * @param {string} root e.g. `$HOME/.claude`
268
+ * @returns {number} directories removed
269
+ */
270
+ export function pruneAbandonedTrees(root) {
271
+ let removed = 0;
272
+ for (const { dir, reason } of ABANDONED_TREES) {
273
+ const target = join(root, dir);
274
+ if (!existsSync(target)) continue;
275
+ if (dryRun) {
276
+ console.log(` [dry-run] would remove abandoned tree ${target} (${reason})`);
277
+ removed++;
278
+ continue;
279
+ }
280
+ try {
281
+ rmSync(target, { recursive: true, force: true });
282
+ removed++;
283
+ } catch {
284
+ /* non-fatal - a leftover tree must never fail an install */
285
+ }
286
+ }
287
+ return removed;
288
+ }
289
+
242
290
  /**
243
291
  * Copy a single file, honouring dry-run mode.
244
292
  * @param {string} src
@@ -1,18 +1,138 @@
1
1
  /**
2
- * Dev-only files excluded from end-user installs.
2
+ * Dev-only files excluded from end-user installs and from the published tarball.
3
3
  *
4
- * These reference the maintainer's private upstream and contain personal data
5
- * (org names, hostnames, emails) on the `from:` side of substitution rules.
6
- * End users never run them, so we keep them out of `~/.claude/scripts/` and
7
- * `~/.copilot/scripts/`.
4
+ * Two separate reasons live here, and they must not be conflated:
5
+ *
6
+ * 1. LEAK: the file references the maintainer's private upstream or carries
7
+ * personal data (org names, hostnames, emails) on the `from:` side of a
8
+ * substitution rule. Shipping it is a disclosure, not just noise.
9
+ *
10
+ * 2. MAINTAINER CI: the file is a repository gate. It resolves the repo root
11
+ * as `dirname($0)/../..` and reads `package.json` / `install.js` /
12
+ * `node_modules` / the git tree. In an install layout that path is `$HOME`,
13
+ * so the script reports things like `FAIL: install.js missing` - a green
14
+ * repo check that reads as a broken installation to whoever ran it.
15
+ * Measured before this list existed: of 117 installed smoke scripts, 100
16
+ * failed that way and 17 passed only by accident of not needing the repo.
17
+ *
18
+ * Reason 2 is why the default for `smoke-*.sh` is EXCLUDE. A smoke ships only
19
+ * when a command actually tells a user to run it - that set is CONSUMER_SMOKES
20
+ * below, and `smoke-consumer-smoke-surface.sh` fails if the two drift apart.
8
21
  *
9
22
  * @module install/_dev-only-files
10
23
  */
11
24
 
25
+ import { readdirSync } from "node:fs";
26
+ import { join } from "node:path";
27
+
28
+ /**
29
+ * Smoke suites an end user is instructed to run, and which therefore must work
30
+ * from an installed layout (`~/.claude/scripts/`) as well as from the repo.
31
+ *
32
+ * `/multi-agent:update` runs both as its post-update verification. Anything
33
+ * added here MUST resolve its paths through `_smoke-root.sh`, never through a
34
+ * bare `dirname($0)/../..`.
35
+ */
36
+ export const CONSUMER_SMOKES = Object.freeze([
37
+ "smoke-schema-validation.sh",
38
+ "smoke-cross-cli-behavior.sh",
39
+ ]);
40
+
41
+ /**
42
+ * Every `smoke-*.sh` that is NOT in CONSUMER_SMOKES. Expressed as one regex so a
43
+ * newly added smoke is excluded by default rather than shipping until someone
44
+ * notices - the safe direction for reason 2 above.
45
+ */
46
+ export const MAINTAINER_SMOKES = new RegExp(
47
+ `^smoke-(?!${CONSUMER_SMOKES.map((n) => n.replace(/^smoke-/, "").replace(/\./g, "\\.")).join(
48
+ "|",
49
+ )}$)`,
50
+ );
51
+
52
+ /**
53
+ * Repository tooling that is not a smoke: the smoke runner itself, the eval
54
+ * harnesses, the scorecard, and the linters. All of them need devDependencies
55
+ * (`ajv`, `c8`, `eslint`) or the repo tree, neither of which exists in an
56
+ * install.
57
+ */
58
+ const DEV_ONLY_TOOLING = Object.freeze([
59
+ "run-smokes.mjs",
60
+ "scorecard.mjs",
61
+ "lint-skills.mjs",
62
+ "lint-mcp-refs.mjs",
63
+ "check-md-links.mjs",
64
+ "validate-schemas.mjs", // validates the repo's own schema files, needs ajv
65
+ "sync-parity-check.sh",
66
+ "benchmark-phase-0.sh",
67
+ "test-gap-rules", // rule corpus for the repo's own test-gap gate
68
+ ]);
69
+
70
+ /** Eval harnesses (`eval-*.mjs`) read `pipeline/eval/**`, which never ships. */
71
+ const EVAL_HARNESSES = /^eval-.*\.mjs$/;
72
+
12
73
  export const DEV_ONLY_SCRIPTS = Object.freeze([
74
+ // --- reason 1: leak ---
13
75
  "smoke-figma-config-schema.sh",
14
76
  "smoke-personal-data.sh", // scanner contains the patterns it detects
15
77
  "smoke-install-leak-gate.sh", // same - scanner shadows the install pipeline
16
78
  "validate-prefs.mjs", // needs ajv (devDependency); end users have no node_modules here
17
79
  "fixtures", // smoke test fixtures live in the repo only, never ship to users
80
+ // --- reason 2: maintainer CI ---
81
+ ...DEV_ONLY_TOOLING,
82
+ EVAL_HARNESSES,
83
+ MAINTAINER_SMOKES,
18
84
  ]);
85
+
86
+ /**
87
+ * True when `name` (a path relative to `pipeline/scripts/`) must not be
88
+ * installed. Shared by both installers' excluded-file counters and by
89
+ * `smoke-consumer-smoke-surface.sh`, so the count and the gate can never
90
+ * disagree with what `copyDir` actually filtered.
91
+ *
92
+ * @param {string} name
93
+ * @returns {boolean}
94
+ */
95
+ export function isDevOnlyScript(name) {
96
+ const rel = String(name).split("\\").join("/").replace(/^\//, "");
97
+ return DEV_ONLY_SCRIPTS.some((pat) =>
98
+ typeof pat === "string" ? rel === pat || rel.startsWith(pat + "/") : pat.test(rel),
99
+ );
100
+ }
101
+
102
+ /**
103
+ * How many files under `scriptsDir` `copyDir` will filter out.
104
+ *
105
+ * Walks the tree and applies `isDevOnlyScript` to the same relative paths
106
+ * `copyDir`'s filter sees, so the number printed by an install is the number
107
+ * actually withheld. The previous form summed `countFiles()` over the exclusion
108
+ * array, which silently returned 0 for every non-literal (regex) entry.
109
+ *
110
+ * @param {string} scriptsDir absolute path to `pipeline/scripts`
111
+ * @returns {number}
112
+ */
113
+ export function countDevOnlyFiles(scriptsDir) {
114
+ let excluded = 0;
115
+ const walk = (abs, rel) => {
116
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
117
+ const childRel = rel ? `${rel}/${entry.name}` : entry.name;
118
+ const childAbs = join(abs, entry.name);
119
+ if (entry.isDirectory()) {
120
+ // An excluded directory takes its whole subtree with it, exactly as
121
+ // cpSync's filter does when it returns false for the directory itself.
122
+ if (isDevOnlyScript(childRel)) excluded += countTree(childAbs);
123
+ else walk(childAbs, childRel);
124
+ } else if (isDevOnlyScript(childRel)) {
125
+ excluded++;
126
+ }
127
+ }
128
+ };
129
+ const countTree = (abs) => {
130
+ let n = 0;
131
+ for (const entry of readdirSync(abs, { withFileTypes: true })) {
132
+ n += entry.isDirectory() ? countTree(join(abs, entry.name)) : 1;
133
+ }
134
+ return n;
135
+ };
136
+ walk(scriptsDir, "");
137
+ return excluded;
138
+ }
@@ -27,13 +27,14 @@ import {
27
27
  ensureDir,
28
28
  ensureRealDir,
29
29
  isDryRun,
30
+ pruneAbandonedTrees,
30
31
  pruneLegacyMultiAgentSkills,
31
32
  removePipelineAgentFiles,
32
33
  wipeDir,
33
34
  writeFile,
34
35
  } from "./_common.mjs";
35
36
  import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
36
- import { DEV_ONLY_SCRIPTS } from "./_dev-only-files.mjs";
37
+ import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
37
38
 
38
39
  /**
39
40
  * Dead matcher written by pre-v11.4.1 installs. Hook matchers only ever see
@@ -64,6 +65,14 @@ export function installClaude(ctx) {
64
65
  const CLAUDE_LIB = join(home, ".claude", "lib");
65
66
  const CLAUDE_MA_REFS = join(home, ".claude", "multi-agent-refs");
66
67
 
68
+ // Before laying anything down: drop trees an older install created that no
69
+ // current installer manages. Wipe-before-copy only protects trees still being
70
+ // written, so an abandoned one persists on every existing machine forever.
71
+ const prunedTrees = pruneAbandonedTrees(join(home, ".claude"));
72
+ if (prunedTrees > 0) {
73
+ console.log(` [Claude Code] Removed ${prunedTrees} abandoned tree(s) from an older install`);
74
+ }
75
+
67
76
  installCommands(pipelineSrc, CLAUDE_COMMANDS, useSymlinks);
68
77
  installMultiAgentRefs(pipelineSrc, CLAUDE_MA_REFS, useSymlinks);
69
78
  installScripts(pipelineSrc, CLAUDE_SCRIPTS, useSymlinks);
@@ -201,13 +210,10 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
201
210
  if (!useSymlinks) ensureRealDir(dest);
202
211
  wipeDir(dest);
203
212
  copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
204
- // Count files actually excluded, not the array length: DEV_ONLY_SCRIPTS mixes
205
- // individual .sh files with the `fixtures` directory (many files), so the
206
- // array length under-counts the real exclusion.
207
- const excludedCount = DEV_ONLY_SCRIPTS.reduce(
208
- (n, name) => n + countFiles(join(scriptsSrc, name)),
209
- 0,
210
- );
213
+ // Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
214
+ // literal names, whole directories, and regexes (the maintainer-smoke rule),
215
+ // so summing countFiles() over the array reported 0 for every regex entry.
216
+ const excludedCount = countDevOnlyFiles(scriptsSrc);
211
217
  const scriptCount = countFiles(scriptsSrc) - excludedCount;
212
218
  console.log(` -> ${scriptCount} files copied to ${dest} (${excludedCount} dev-only excluded)`);
213
219
  }
@@ -23,7 +23,7 @@ import {
23
23
  writeFile,
24
24
  } from "./_common.mjs";
25
25
  import { copyExternalSkillsFiltered } from "./_platform-filter.mjs";
26
- import { DEV_ONLY_SCRIPTS } from "./_dev-only-files.mjs";
26
+ import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
27
27
  import { generateCopilotInstructions } from "./_copilot-instructions.mjs";
28
28
 
29
29
  /**
@@ -166,13 +166,10 @@ function installScripts(pipelineSrc, dest, useSymlinks) {
166
166
  if (!useSymlinks) ensureRealDir(dest);
167
167
  wipeDir(dest);
168
168
  copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
169
- // Count files actually excluded, not the array length: DEV_ONLY_SCRIPTS mixes
170
- // individual .sh files with the `fixtures` directory (many files), so the
171
- // array length under-counts the real exclusion.
172
- const excludedCount = DEV_ONLY_SCRIPTS.reduce(
173
- (n, name) => n + countFiles(join(scriptsSrc, name)),
174
- 0,
175
- );
169
+ // Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
170
+ // literal names, whole directories, and regexes (the maintainer-smoke rule),
171
+ // so summing countFiles() over the array reported 0 for every regex entry.
172
+ const excludedCount = countDevOnlyFiles(scriptsSrc);
176
173
  const scriptCount = countFiles(scriptsSrc) - excludedCount;
177
174
  console.log(` -> ${scriptCount} files copied to ${dest} (${excludedCount} dev-only excluded)`);
178
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "12.7.0",
3
+ "version": "12.8.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -75,7 +75,22 @@
75
75
  "!pipeline/scripts/smoke-personal-data.sh",
76
76
  "!pipeline/scripts/validate-prefs.mjs",
77
77
  "!pipeline/scripts/smoke-install-leak-gate.sh",
78
- "!docs/internal/**"
78
+ "!docs/internal/**",
79
+ "!pipeline/scripts/smoke-*.sh",
80
+ "!pipeline/scripts/run-smokes.mjs",
81
+ "!pipeline/scripts/eval-*.mjs",
82
+ "!pipeline/scripts/scorecard.mjs",
83
+ "!pipeline/scripts/lint-skills.mjs",
84
+ "!pipeline/scripts/lint-mcp-refs.mjs",
85
+ "!pipeline/scripts/check-md-links.mjs",
86
+ "!pipeline/scripts/validate-schemas.mjs",
87
+ "!pipeline/scripts/sync-parity-check.sh",
88
+ "!pipeline/scripts/benchmark-phase-0.sh",
89
+ "!pipeline/scripts/fixtures/**",
90
+ "!pipeline/scripts/test-gap-rules/**",
91
+ "!pipeline/eval/**",
92
+ "pipeline/scripts/smoke-schema-validation.sh",
93
+ "pipeline/scripts/smoke-cross-cli-behavior.sh"
79
94
  ],
80
95
  "devDependencies": {
81
96
  "@eslint/js": "^10.0.1",
@@ -65,7 +65,7 @@ When citing a Locked decision in code or docs, prefer `Locked <n> (<short label>
65
65
  27. **Evidence digest caches Phase 1b and 1c.** `evidence_digest = sha256(featureName || sorted(platforms) || repoEvidence.summary || conventions.summary)`. When the same feature name is invoked again against the same set of repos and the digest matches, Phase 1b and 1c are skipped and the cached `evidence.repoEvidence` / `evidence.conventions` is reused. Cache TTL is 24 hours; manual invalidation via `--no-cache` flag.
66
66
  28. **SwiftUI Preview block mandatory (iOS projection, SwiftUI only).** When the iOS file is produced AND the affected view is a SwiftUI view (detected via `import SwiftUI` + `: View` protocol conformance in `evidence.repoEvidence[<repo>].buckets.uiComponents`), Section 13.6 renders a Preview block table covering at minimum: canonical default (LTR Light), Dark, RTL, Dynamic Type accessibilityLarge, and one error variant. Loading state and edge-case variants are added when distinct from canonical. UIKit-only features (no SwiftUI view artefact) drop Section 13.6 with note `(N/A: UIKit-only feature)`. Preview macro convention (`#Preview` for Swift 5.9+ vs legacy `PreviewProvider`) is read from `evidence.conventions[<repo>].previewMacro`. Each Preview variant listed in Section 13.6 must have a matching row in Section 15.2 Snapshot Tests; a Preview without a snapshot row triggers a Section 20 Risk.
67
67
  29. **Variant usage explicit and bounded.** Section 6 inventory rows list which variants this feature consumes per component (concrete enum case + bool value). New Section 6.X (Variant Usage Matrix) catalogues the full variant axis vs. used subset with a rationale per excluded variant. Sections 13.6 (Preview) and 15.2 (Snapshot) cover only the used subset; expanding the variant set requires updating Section 6.X first.
68
- 30. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide, v9.0.0; legacy soft-skip v9.1.0).** The analysis document is the sole design source for every downstream phase. After Phase 1 of `/multi-agent:analysis` produces `analysis/<feature>-<platform>.md`, Phase 2 Planning, Phase 3 Dev, Phase 4 Review, Phase 5 Test, Phase 6 Commit, and Phase 7 Report consume only the analysis document plus repo Code Connect mappings (`*.figma.swift` / `*.figma.kt`). Calling `mcp__claude_ai_Figma__*`, hitting `api.figma.com`, or fetching a `figma.com/design/...` URL during Phase 2+ is a violation. Applies to every mode that runs Phase 2+: `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, `/multi-agent:local-autopilot`, `/multi-agent:dev`, `/multi-agent:dev-autopilot`, `/multi-agent:dev-local`, `/multi-agent:dev-local-autopilot`. Hard requirement (v9.0.0): Phase 2 Pre-item and Phase 3 Pre-item (BLOCKING) abort the run when the analysis document is missing. **Legacy soft-skip (v9.1.0+):** existing v2 analyses (`template_version: v2`) emit `WARN: legacy v2 analysis doc detected; v3 expected. Re-run /multi-agent:analysis to regenerate. Continuing with reduced binding strength.` and the run continues when `prefs.global.legacyV2AnalysisAllowed == true` OR env `MULTI_AGENT_LEGACY_V2_ANALYSIS=allow` is set. The soft-skip degrades Pass B footnote enforcement to warning level and skips Phase 1c convention extraction (no `conventions[]` data in v2 docs). The escape hatch is removed in v9.2.0; users must regenerate v3 docs by then. Smoke gate `pipeline/scripts/smoke-no-mcp-in-dev-phases.sh` reads `state.telemetry.mcpCalls[]` and fails on any entry with `phase >= 2`. Halt condition: if a downstream phase needs design information not present in the analysis document, the run halts and the user re-runs `/multi-agent:analysis` to regenerate. Memory: `[[mcp-only-in-analysis]]`. Generic rule rationale and access matrix: see `pipeline/rules/figma-pipeline.md` "MUST: No MCP outside analysis phase".
68
+ 30. **Analysis as self-contained design bridge - no MCP outside analysis phase (BLOCKING, pipeline-wide).** The analysis document is the sole design source for every downstream phase. After Phase 1 of `/multi-agent:analysis` produces `analysis/<feature>-<platform>.md`, Phase 2 Planning, Phase 3 Dev, Phase 4 Review, Phase 5 Test, Phase 6 Commit, and Phase 7 Report consume only the analysis document plus repo Code Connect mappings (`*.figma.swift` / `*.figma.kt`). Calling `mcp__claude_ai_Figma__*`, hitting `api.figma.com`, or fetching a `figma.com/design/...` URL during Phase 2+ is a violation. Applies to every mode that runs Phase 2+: `/multi-agent`, `/multi-agent:autopilot`, `/multi-agent:local`, `/multi-agent:local-autopilot`, `/multi-agent:dev`, `/multi-agent:dev-autopilot`, `/multi-agent:dev-local`, `/multi-agent:dev-local-autopilot`. Hard requirement (v9.0.0): Phase 2 Pre-item and Phase 3 Pre-item (BLOCKING) abort the run when the analysis document is missing. Memory: `[[mcp-only-in-analysis]]`. Generic rule rationale and access matrix: see `pipeline/rules/figma-pipeline.md` "MUST: No MCP outside analysis phase".
69
69
  31. **Business-rule to acceptance-criterion to test traceability (AI + human spine).** The analysis is a development handoff that both an AI implementer and a human reviewer must act on, so it is bound by one shared-ID vocabulary. Every business rule carries a stable id `BR-<slug>-NN` (Section 4.4). Each rule maps to at least one acceptance criterion written Given / When / Then (binary - two readers must not be able to disagree on pass/fail). Each acceptance criterion maps to unit-test scenarios in Section 15.1, one row per case across happy / boundary / error / empty-nil (enumerate at least the failure modes; agents hallucinate error handling when it is omitted). The same ids thread onward: Section 15.6 UI-test flows reference the `BR-` ids and use stable selectors (accessibilityIdentifier / testTag), Section 16 accessibility items reuse those identifiers, Section 11 analytics events cite their triggering rule or story, and Section 5/7 layout cells carry token + Figma node refs. Never invent copy or values (blank beats a guess; a missing source becomes a Section 20 Open Question). **Mode-aware gate:** in Full mode a business rule with no acceptance criterion, or an acceptance criterion with no Section 15.1 scenario, fails the dispatch gate. In **Lite mode Section 15 is not rendered**, so the rule-to-test half does not apply - Section 4.4 still lists each rule with its Given/When/Then acceptance criterion (the acceptance criterion is itself the testable statement), and the 15.1 mapping is deferred to whenever the feature is later analyzed in Full or implemented via `/multi-agent:dev`. The rule-to-acceptance-criterion half always holds, in both modes.
70
70
 
71
71
  ## Input
@@ -113,18 +113,36 @@ if isinstance(name, str) and name.strip():
113
113
  }
114
114
 
115
115
  # --- Subcommands ------------------------------------------------------------
116
+ # Append one PAT-lookup audit event. Every `get` goes through here, which is the
117
+ # point of the control: the audit trail described in the .gitignore hardening and
118
+ # managed by `/multi-agent:prune-logs` was written by audit-log.sh, but nothing
119
+ # ever called it, so the trail was always empty and the control was inert.
120
+ #
121
+ # Never fails the caller and never echoes a secret: audit-log.sh hashes the repo
122
+ # URL, logs the logical key (not the value), and is silent on error by design.
123
+ audit_lookup() {
124
+ local logical="$1" success="$2"
125
+ local audit="$SCRIPT_DIR/../scripts/audit-log.sh"
126
+ [ -f "$audit" ] || return 0
127
+ bash "$audit" pat_lookup "$logical" "${USER:-unknown}" "${MULTI_AGENT_REPO_URL:-}" "$success" \
128
+ >/dev/null 2>&1 || true
129
+ }
130
+
116
131
  do_get() {
117
132
  local key="${1:-}"
118
133
  [ -z "$key" ] && { echo "usage: $0 get <key>" >&2; exit 3; }
134
+ local logical="$key"
119
135
  key=$(resolve_key "$key")
120
136
  if delegate_to_python; then
121
137
  local val rc
122
138
  val=$(python3 "$KEYCHAIN_PY" get "$key" 2>/dev/null) || rc=$?
123
139
  rc=${rc:-0}
124
140
  if [ "$rc" -eq 0 ] && [ -n "$val" ]; then
141
+ audit_lookup "$logical" true
125
142
  printf '%s' "$val"
126
143
  return 0
127
144
  fi
145
+ audit_lookup "$logical" false
128
146
  return 1
129
147
  fi
130
148
  local val=""
@@ -149,8 +167,10 @@ if (\$c) { \$c.GetNetworkCredential().Password }
149
167
  echo "ERR: unsupported platform" >&2; exit 2 ;;
150
168
  esac
151
169
  if [ -z "$val" ]; then
170
+ audit_lookup "$logical" false
152
171
  return 1
153
172
  fi
173
+ audit_lookup "$logical" true
154
174
  printf '%s' "$val"
155
175
  }
156
176
 
@@ -14,7 +14,7 @@ First step of every `multi-agent` flow **that touches a remote provider**.
14
14
  > repos, account-picker is bypassed and the state file's `accountId` is left
15
15
  > `null` with `tokens={}`. Phases 6/7 read these as "local-only" signals.
16
16
 
17
- > **Language**: Per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix - `AskUserQuestion.label` and `header` stay English (UI button + chip contract); `question` and `description` follow `outputLanguage`. Commit/PR/Jira payloads: subject + branch names + code identifiers stay English; body text follows `outputLanguage`.
17
+ > **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
18
18
 
19
19
  ## Steps
20
20
 
@@ -6,7 +6,7 @@ description: "Internal - dev context (extra repos) picker for multi-agent."
6
6
 
7
7
  Selects extra repos the pipeline may touch beyond the primary repo(s) - typically submodules (e.g. SDKs vendored inside an app repo) or sibling libraries. Each candidate is enriched with a `canPush` flag so the picker can pre-select repos that the active account is allowed to edit, and surface read-only ones as advisory context the pipeline will not modify.
8
8
 
9
- > **Language**: Per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix - `AskUserQuestion.label` and `header` stay English (UI button + chip contract); `question` and `description` follow `outputLanguage`. Commit/PR/Jira/Wiki payloads: subject + branch names + code identifiers stay English; body text follows `outputLanguage`.
9
+ > **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
10
10
 
11
11
  ## Steps
12
12
 
@@ -6,7 +6,7 @@ description: "Internal - multi-select repo picker for multi-agent."
6
6
 
7
7
  Used by `picker-issue`, `picker-jira`, `gh-num`, and `freetext` flows.
8
8
 
9
- > **Language**: Per `$HOME/.claude/multi-agent-refs/rules.md` Language Application matrix - `AskUserQuestion.label` and `header` stay English (UI button + chip contract); `question` and `description` follow `outputLanguage`. Commit/PR/Jira/Wiki payloads: subject + branch names + code identifiers stay English; body text follows `outputLanguage`.
9
+ > **Language**: see `picker-contract.md` + `rules.md` Language Application matrix.
10
10
 
11
11
  ## Steps
12
12
 
@@ -67,6 +67,34 @@ Run phases sequentially. Log every step to `agent-log.md`.
67
67
  If any phase fails, mark task as `paused` and stop - user can `resume` later.
68
68
  Update `agent-state.json` at EVERY phase transition.
69
69
 
70
+ ### Writing `agent-state.json` (required mechanism)
71
+
72
+ Every state update goes through `write-state.mjs`. Never write the file with a
73
+ plain read-modify-write (`jq ... > tmp && mv`, an editor tool, `cat >`):
74
+
75
+ ```bash
76
+ # Merge a patch into the current state (the normal case).
77
+ echo '{"currentPhase":4,"phases":{"4":{"status":"running"}}}' \
78
+ | node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
79
+
80
+ # Full replacement, only when rewriting the document wholesale.
81
+ printf '%s' "$NEW_STATE" | node $HOME/.claude/scripts/write-state.mjs --replace "$STATE_FILE"
82
+ ```
83
+
84
+ Why the indirection is required: multi-repo mode and concurrent worktrees share
85
+ one `agent-state.json` per task id, so two writers using read-then-write lose one
86
+ of the two updates - the second write is computed from a snapshot taken before
87
+ the first landed. `write-state.mjs` does tmpfile + rename (atomic on POSIX) under
88
+ an advisory `.lock`, reclaims a lock whose holder PID is dead, and releases the
89
+ lock on every error path.
90
+
91
+ Exit codes the caller must handle: `0` written, `1` invalid JSON on stdin, `2`
92
+ lock timeout (another writer held it past the acquire window - retry once, then
93
+ halt per the halt-visibility rule), `3` I/O error.
94
+
95
+ Reads need no wrapper; the rename makes any read see either the old or the new
96
+ document, never a truncated one.
97
+
70
98
  **Halt visibility (required, autopilot included).** A halt is never silent. Whenever a phase halts on a hard error (validator failed twice, no subagent returned, dispatch error past fallback, lock irrecoverable), in addition to the `agent-log.md` line: (a) write `state.status = "paused"` and `state.haltReason = "<phase>:<cause>"`; (b) record the cause on the tracker via `phase-tracker.sh meta <phase> halt "<cause>"` and `phase-tracker.sh update <phase> failed`; (c) emit one `>&2` alert line `HALT phase <N>: <cause> - resume with /multi-agent:resume #<id>`. Autopilot suppresses *confirmations*, not *halts* - the user must always be able to see why an unattended run stopped without reading the log.
71
99
 
72
100
  ### Pipeline Best Practices
@@ -487,6 +487,7 @@ done
487
487
 
488
488
  State file in multi-repo mode:
489
489
  - Single shared `agent-state.json` lives at `$HOME/.claude/logs/multi-agent/{first-project}/{task-id}/agent-state.json` (anchored on the first repo for back-compat with `multi-agent log`/`status` commands)
490
+ - **One shared file + per-repo writers is exactly the race `write-state.mjs` exists for.** Every update to it (here and in every later phase) goes through `node $HOME/.claude/scripts/write-state.mjs` per the required mechanism in `operations.md` "Writing `agent-state.json`". A read-modify-write from two repos in the same loop drops one repo's `projects[]` entry.
490
491
  - `state.projects[]` holds per-repo `{name, root, worktreePath, branch, baseBranch, identity, platform, baseFetchStatus, commit, pr, pushAttempts, buildStatus}` - see `agent-state.schema.json`
491
492
  - Scalar fields (`project`, `projectRoot`, `worktreePath`, `branch`, `baseBranch`, `identity`) mirror `projects[0]` so legacy phases that read scalars keep working
492
493
  - Atomicity: if any repo's worktree creation fails (collision aborted, fetch aborted, disk full), roll back already-created worktrees: `git -C $proj worktree remove --force $WT_PATH; git -C $proj branch -D $BRANCH`. Never leave a partial multi-repo state.
@@ -13,7 +13,7 @@ Phase 2 Planning consumes the analysis document. MCP forbidden.
13
13
  - Abort with `ERR: analysis doc missing. Run /multi-agent:analysis "<feature>" first.`
14
14
 
15
15
  2. **Parse YAML front-matter**: `feature`, `platform`, `language`, `mode`, `evidence_digest`, `template_version`. Persist into `state.analysis.frontMatter`.
16
- - **Soft-skip (v9.1.0+, Locked 30)**: when `template_version == v2` AND (`prefs.global.legacyV2AnalysisAllowed == true` OR env `MULTI_AGENT_LEGACY_V2_ANALYSIS=allow`), emit `WARN: legacy v2 analysis doc detected; v3 expected. Continuing in degraded mode.` Step 3 section coverage check skips v3-specific sections (10 Localization, 11 Analytics, 12 Deeplink/Push, 13.6 SwiftUI Preview, 21 References). Phase 4 reviewer flags missing v3 sections rather than blocking. Escape hatch removed in v9.2.0; users must regenerate v3 docs by then.
16
+ - **`template_version` < `v3` aborts**: `ERR: analysis doc is template v<n>; v3 required. Re-run /multi-agent:analysis to regenerate.` No degraded mode.
17
17
 
18
18
  3. **Section coverage check**: verify these sections are non-empty (template v3 required sections per Locked 2):
19
19
  - Section 1 Summary, Section 2 Goals + Non-Goals, Section 4 User Stories, Section 9 API Contracts, Section 13 Architecture Plan, Section 14 Files to Add, Section 20 Risks, Section 21 References
@@ -16,8 +16,7 @@ Pre-flight steps (run in order, abort on failure):
16
16
  2. **Parse YAML front-matter** (the doc has a `template_version: v3` header):
17
17
  - `feature`, `platform`, `language`, `mode (full | lite)`, `evidence_digest`
18
18
  - Persist into `state.analysis.frontMatter`
19
- - **Abort condition (v9.0.x strict)**: `template_version` < `v3` -> exit with `ERR: analysis doc is template v<n>; v9 requires v3. Re-run analysis to regenerate.`
20
- - **Soft-skip (v9.1.0+, Locked 30)**: when `prefs.global.legacyV2AnalysisAllowed == true` OR env `MULTI_AGENT_LEGACY_V2_ANALYSIS=allow` is set AND `template_version == v2`, emit `WARN: legacy v2 analysis doc detected; v3 expected. Continuing with reduced binding strength.` and persist `state.dev.legacyAnalysisMode = true`. Step 4 (Code Connect lookup) and Step 6 (conventions handoff) still run, but Step 5 (standards binding) and Pass B footnote enforcement degrade to warning level (Phase 4 reviewer flags missing citations rather than blocking). Escape hatch removed in v9.2.0.
19
+ - **Abort condition**: `template_version` < `v3` -> exit with `ERR: analysis doc is template v<n>; v3 required. Re-run /multi-agent:analysis to regenerate.` No degraded mode.
21
20
 
22
21
  3. **Evidence digest match (cache validation)**: if `state.run.lastAnalysisDigest` is present AND matches `frontMatter.evidence_digest`, mark as fresh; otherwise WARN that analysis is stale (continue but flag).
23
22