@mmerterden/multi-agent-pipeline 13.5.0 → 14.0.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 (119) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +3 -3
  3. package/docs/features.md +1 -1
  4. package/install/_common.mjs +73 -0
  5. package/install/_mcp-register.mjs +70 -31
  6. package/install/_plugin-skills.mjs +73 -14
  7. package/install/claude.mjs +28 -4
  8. package/install/codex.mjs +33 -2
  9. package/install/copilot.mjs +145 -9
  10. package/install/index.mjs +10 -6
  11. package/install/templates/copilot-instructions.md +1 -1
  12. package/package.json +1 -1
  13. package/pipeline/agents/code-reviewer.md +58 -1
  14. package/pipeline/commands/multi-agent/SKILL.md +7 -5
  15. package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -7
  16. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
  17. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +7 -7
  18. package/pipeline/commands/multi-agent/channels/SKILL.md +5 -5
  19. package/pipeline/commands/multi-agent/dev/SKILL.md +23 -18
  20. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +19 -13
  21. package/pipeline/commands/multi-agent/dev-local/SKILL.md +14 -12
  22. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +17 -12
  23. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  24. package/pipeline/commands/multi-agent/help/SKILL.md +4 -4
  25. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  26. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +4 -4
  27. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
  28. package/pipeline/commands/multi-agent/review/SKILL.md +5 -5
  29. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
  30. package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
  31. package/pipeline/commands/multi-agent/setup/SKILL.md +6 -6
  32. package/pipeline/commands/multi-agent/{finish → ship}/SKILL.md +12 -12
  33. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/update/SKILL.md +5 -2
  35. package/pipeline/commands/sim-test.md +2 -2
  36. package/pipeline/lib/credential-store-resolver.sh +16 -0
  37. package/pipeline/lib/credential-store.sh +47 -4
  38. package/pipeline/lib/fetch-figma-annotations.sh +26 -28
  39. package/pipeline/lib/figma-screenshot.sh +28 -39
  40. package/pipeline/lib/figma-token.sh +63 -0
  41. package/pipeline/multi-agent-refs/analysis-template.md +1 -1
  42. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  43. package/pipeline/multi-agent-refs/channels/issue-comment.md +1 -1
  44. package/pipeline/multi-agent-refs/component-dispatch.md +2 -2
  45. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  46. package/pipeline/multi-agent-refs/features/dev-critic.md +2 -2
  47. package/pipeline/multi-agent-refs/features/model-fallback.md +35 -2
  48. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  49. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  50. package/pipeline/multi-agent-refs/features/review-multi-repo.md +3 -3
  51. package/pipeline/multi-agent-refs/features/shadow-git.md +1 -1
  52. package/pipeline/multi-agent-refs/features/skill-conformance.md +116 -0
  53. package/pipeline/multi-agent-refs/features/verify-by-test.md +1 -1
  54. package/pipeline/multi-agent-refs/generate-issue.md +1 -1
  55. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +1 -1
  56. package/pipeline/multi-agent-refs/phases/log-format.md +4 -4
  57. package/pipeline/multi-agent-refs/phases/modes.md +7 -7
  58. package/pipeline/multi-agent-refs/phases/phase-0-init.md +13 -11
  59. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +17 -15
  60. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +7 -7
  61. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +28 -13
  62. package/pipeline/multi-agent-refs/phases/phase-4-review.md +90 -58
  63. package/pipeline/multi-agent-refs/phases/phase-5-test.md +7 -7
  64. package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -8
  65. package/pipeline/multi-agent-refs/phases/phase-7-report.md +8 -8
  66. package/pipeline/multi-agent-refs/phases.md +13 -13
  67. package/pipeline/multi-agent-refs/progress-contract.md +2 -2
  68. package/pipeline/multi-agent-refs/rules.md +7 -5
  69. package/pipeline/multi-agent-refs/swiftui-guide.md +1 -1
  70. package/pipeline/multi-agent-refs/tracker-contract.md +16 -15
  71. package/pipeline/preferences-template.json +7 -1
  72. package/pipeline/rules/figma-pipeline.md +2 -2
  73. package/pipeline/schemas/agent-state.schema.json +333 -79
  74. package/pipeline/schemas/criteria-manifest.schema.json +228 -0
  75. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +64 -0
  76. package/pipeline/schemas/prefs.schema.json +118 -262
  77. package/pipeline/schemas/reviewer-output.schema.json +48 -3
  78. package/pipeline/schemas/token-budget.json +34 -10
  79. package/pipeline/schemas/triage-output.schema.json +112 -27
  80. package/pipeline/scripts/cost-table.json +7 -4
  81. package/pipeline/scripts/gc-worktrees.sh +1 -1
  82. package/pipeline/scripts/gen-mode-dispatch.mjs +6 -6
  83. package/pipeline/scripts/match-skills.mjs +37 -4
  84. package/pipeline/scripts/migrate-prefs.mjs +88 -17
  85. package/pipeline/scripts/phase-tracker.sh +14 -3
  86. package/pipeline/scripts/pre-commit-check.sh +49 -2
  87. package/pipeline/scripts/skill-conformance.mjs +960 -0
  88. package/pipeline/scripts/smoke-schema-validation.sh +17 -4
  89. package/pipeline/scripts/uninstall.mjs +35 -9
  90. package/pipeline/scripts/validate-reviewer.mjs +108 -1
  91. package/pipeline/skills/.skill-manifest.json +1 -1
  92. package/pipeline/skills/.skills-index.json +36 -9
  93. package/pipeline/skills/shared/README.md +15 -12
  94. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +1 -0
  95. package/pipeline/skills/shared/core/apple-archive-compliance/references/rules.yml +167 -0
  96. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -0
  97. package/pipeline/skills/shared/core/google-play-compliance/references/rules.yml +184 -0
  98. package/pipeline/skills/shared/core/multi-agent/SKILL.md +10 -10
  99. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +4 -4
  100. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
  101. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +2 -2
  102. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  103. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +6 -5
  104. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +7 -6
  105. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +4 -3
  106. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +2 -1
  107. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +2 -2
  108. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +1 -1
  109. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -4
  110. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -5
  111. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
  112. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/{multi-agent-finish → multi-agent-ship}/SKILL.md +8 -8
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +44 -5
  115. package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +82 -0
  116. package/pipeline/skills/shared/external/ios-coding-standard/references/STANDARD.md +169 -10
  117. package/pipeline/skills/shared/external/ios-coding-standard/references/lint-local.sh +13 -1
  118. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +335 -16
  119. package/pipeline/skills/skills-index.md +11 -8
package/CHANGELOG.md CHANGED
@@ -16,6 +16,249 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [14.0.0] - 2026-07-29
20
+
21
+ The `--dev` family reviews its own work, and review now checks the code against the
22
+ criteria it was supposed to be built to instead of general good taste.
23
+
24
+ Major because a command was renamed: `/multi-agent:finish` is now `/multi-agent:ship`.
25
+ No alias is kept - `installCommands()` replaces the command tree wholesale, so a clean
26
+ cut leaves nothing half-migrated.
27
+
28
+ ### Added
29
+
30
+ - **Phase 4 Review runs in `dev`, `dev-autopilot`, `dev-local` and `dev-local-autopilot`.**
31
+ Phase sets become `0/3/4/5/6/7` for `dev` and `0/3/4/6/7` for the three variants, in
32
+ `gen-mode-dispatch.mjs` (the generator is the source of truth; the tracker sections are
33
+ regenerated from it, never hand-edited). Analysis and planning are still skipped: they
34
+ shape work that has not happened yet, so a task the user has already scoped can do
35
+ without them, while review judges work that now exists and has no substitute. Accepted
36
+ blocking findings return to Phase 3 under the existing 3-iteration hard kill; the
37
+ autopilot variants auto-fix without prompting and halt on the existing rework-storm
38
+ circuit breaker rather than committing. No new machinery for either - both were already
39
+ built and became reachable the moment Phase 4 entered the phase set.
40
+ - **Phase 4 Step 1.78, criteria resolution** (`skill-conformance.mjs`, zero LLM). Resolves
41
+ which rule registries apply to this diff, scoped to its languages and paths, and writes
42
+ `criteria-manifest.json` BEFORE the reviewers run. That file is the denominator: reviewers
43
+ return one verdict per selected rule ID, so "did it apply this completely" is answerable
44
+ rather than inferred. A reviewer that opened nothing and one that checked everything
45
+ produce identical `findings` arrays, which is why the checklist exists.
46
+ - **Registry discovery is declared, never sniffed by name.** A skill opts in with
47
+ `standards-registry: <path>` in its frontmatter, so the pipeline names no stack-specific
48
+ skill and a future UIKit, Objective-C, Kotlin or backend registry drops in with zero
49
+ pipeline change.
50
+ - **Every registry declares its own `scope`** (`languages`, `paths`, `excludePaths`,
51
+ `notCovered`), and per-rule `scope` narrows it further. Measured before this landed: only
52
+ 2 of the iOS registry's 99 rules carried any applicability field, and the rest wrote their
53
+ scope as English prose inside `mechanism` - so an Objective-C or UIKit diff would have
54
+ collected all 99 SwiftUI-shaped rules, manufacturing findings and burying the real ones.
55
+ - `references/rules.yml` for `apple-archive-compliance` (18 rules) and
56
+ `google-play-compliance` (21), converted from their existing SKILL.md tables with IDs and
57
+ severities preserved. Registry count goes 1 -> 3, so discovery is genuinely
58
+ capability-based rather than one skill with extra steps.
59
+ - **Exception-marker audit**: expired, reason-less, expiry-less, or unknown-ID
60
+ `standard:exception(...)` markers become rule-ID-bearing findings. The marker template is
61
+ read from the registry, never hardcoded, so a registry with different comment syntax works.
62
+ - `state.telemetry.skillCalls[]` - Phase 3 records each skill, plugin skill and guide it
63
+ consulted, with the files it applied them to.
64
+ - `prefs.global.skillConformance.blockOnCoverageGap` (default **false**) and
65
+ `prefs.global.ship.autoFix`, the latter referenced by the tail command's spec since it
66
+ shipped but never actually declared. Prefs schema 2.4.0 -> 2.5.0 with a migration that
67
+ carries any existing `finish.autoFix` value across the rename.
68
+ - Gates: `smoke-dev-mode-review.sh`, `smoke-skill-conformance.sh` (26 checks),
69
+ `smoke-skills-root-resolution.sh`. `smoke-subagent-validators.sh` grew 10 checks for the
70
+ conformance contract; `smoke-mode-dispatch-drift.sh` grew a step that asserts the negative,
71
+ because its per-phase `grep -Fq` could not catch an ABSENT phase.
72
+
73
+ ### Changed
74
+
75
+ - **`/multi-agent:finish` -> `/multi-agent:ship`.** "Finish" never said what it did. The
76
+ command takes work already sitting on a branch through review, a build+test gate, PR and
77
+ report; `ship` says that. With review now inside the dev modes, its remaining job is work
78
+ that had no pipeline run behind it, rather than a patch for a mode that skipped review.
79
+ - **Reviewers cite rule IDs.** `reviewer-output.schema.json` 1.0.0 -> 1.1.0 adds `ruleId`,
80
+ `criteriaSource` and the per-rule `conformance[]` array; `triage-output.schema.json`
81
+ 3.2.0 -> 3.3.0 carries both through triage. `code-reviewer.md` gains a `${CRITERIA}`
82
+ injection slot - the phase doc had claimed for some time that "skills are injected into
83
+ reviewer prompt context" while the agent definition had no slot for them, which is exactly
84
+ why review could not cite a rule.
85
+ - `validate-reviewer.mjs` enforces the checklist with `--criteria`: a selected ID with no
86
+ verdict, a verdict for an ID that was never selected, a `conformant` row with no file
87
+ evidence, and a `violated` row with no matching finding all fail. Without this the field
88
+ would be decoration - the validator is hand-written and does not apply
89
+ `additionalProperties`, so any array at all would have passed.
90
+ - `${CRITERIA}` lives in the shared cacheable prefix, identical for every reviewer.
91
+ Subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the
92
+ largest block in the phase.
93
+ - Phase 4 no longer transcribes SwiftUI interaction and accessibility rules inline; they
94
+ resolve from the registry that declares SwiftUI scope, so criteria and severities live in
95
+ one place. This removed the drift the release exists to close, and reclaimed budget.
96
+ - Phase 3's pre-flight no longer aborts unconditionally on a missing analysis document.
97
+ Steps that read that document are recorded `not-applicable (no Phase 1 in this mode)`,
98
+ which is what the `--dev` family always needed and never had.
99
+ - `migrate-prefs.mjs` derives its migratable-version set instead of enumerating one
100
+ `else if` per version. The old chain had to be extended by hand whenever
101
+ `TARGET_VERSION` moved, and forgetting made the migrator throw `unknown schemaVersion`
102
+ on the exact version it had just been released to migrate from.
103
+ - Token budget 51500 -> 52200, after 820 tokens were compressed first, per the note in
104
+ `token-budget.json`.
105
+
106
+ ### Fixed
107
+
108
+ - **`ios-coding-standard` in the repo was a version behind the installed copy** (95 rules
109
+ v1.0.0 vs 99 rules v1.1.0). Because `installSkills()` copies the repo over the
110
+ destination, the next `/multi-agent:update` would have downgraded a user's registry -
111
+ harmless while nothing read it, a correctness regression the moment Phase 4 blocks on rule
112
+ IDs. Resynced to 99 rules.
113
+ - `modules/*.yml` overlays and `references/EXAMPLES.md` are deliberately NOT shipped: they
114
+ quote real module paths, symbol names and call-site counts from one codebase, so a shipped
115
+ overlay would bind another project's dialect slots to the wrong dialect. `modules/_TEMPLATE.yml`
116
+ carries the shape instead, and the skill states that an unbound slot disables its rules
117
+ rather than defaulting silently.
118
+ - `tracker-contract.md` contradicted itself on skipped phases: one section said to
119
+ `TaskCreate` every phase "even if it will be skipped in this mode", another said omitted
120
+ phases get no `TaskCreate` at all. The mode files and the generator follow the second, so
121
+ the example now matches it.
122
+ - Three docs disagreed on whether `--dev` runs Phase 5. They now agree with the generator.
123
+ - The dispatcher's modular-loading table had no row for `dev` or `dev-autopilot`.
124
+
125
+ ## [13.6.0] - 2026-07-28
126
+
127
+ A review pass across the pipeline, the plugin marketplace and the companion MCP server.
128
+ Every gate was green before it started: each defect below was invisible to the suite,
129
+ and most were invisible because a gate asserted the wrong half of the contract. New
130
+ gates are named per item.
131
+
132
+ ### Fixed
133
+
134
+ - **The Tier 2 Figma PAT could not be found on any migrated install.** `migrate-prefs.mjs`
135
+ consolidated `keychainMapping.figma_pat` into `.figma` and deleted the old key, but the
136
+ setup wizard kept *writing* `figma_pat` (so the next migration deleted the mapping it
137
+ had just created), both Tier 2 fetchers kept *reading* it, and the failure text told the
138
+ user to map the one key guaranteed not to survive. Tier 2 reported `missing-token` while
139
+ a valid PAT sat under the new name. Claude Code's MCP tier masked it; Copilot and Codex,
140
+ which serve no Figma MCP tools at all, fell straight to Tier 3. The lookup now lives once
141
+ in `lib/figma-token.sh` (canonical `figma`, legacy `figma_pat` as a fallback) rather than
142
+ duplicated per fetcher, which is what let the two copies drift from the migration.
143
+ Gate: `smoke-credential-key-alignment.sh`.
144
+ - **Claude Code installs registered no MCP server**, so every skill that reaches for a
145
+ `dev-toolkit` tool - `design-check`, the `ios_*` / `android_*` calls, the archive audits -
146
+ had nothing to call. Codex had registration from day one and Copilot gained it later;
147
+ Claude Code was last and easiest to miss, because a maintainer who registered it by hand
148
+ once sees a working tree forever. `--scope user` is not optional: `claude mcp add`
149
+ defaults to project scope, binding the server to whichever directory the installer ran
150
+ in. Uninstall now deregisters it too. The gate that existed to catch this iterated
151
+ `codex copilot`, so it structurally could not; the host list is now derived from the
152
+ installers on disk.
153
+ - **`@mmerterden/dev-toolkit-mcp` was not on the public npm registry**, while all three
154
+ installers register `npx -y @mmerterden/dev-toolkit-mcp`, which resolves from it. GitHub
155
+ Packages answers 401 to unauthenticated reads even for public packages, so pointing npx
156
+ there is not an alternative. Registration never failed - it only writes host config - so
157
+ the break surfaced later as an E404 on first tool use, and only for users whose `~/.npmrc`
158
+ lacked a scope redirect. Published; `publishConfig` now matches the pipeline's.
159
+ Gate: `smoke-mcp-package-resolvable.sh`.
160
+ - **157 instruction paths resolved only from a repo checkout.** `node pipeline/scripts/x.mjs`
161
+ works when cwd is this repo's root, which is never true during a run: the pipeline works
162
+ inside a worktree of the user's project. The class covered 85 script invocations (bare,
163
+ `node`-prefixed, env-prefixed, `&&`-chained), 16 skill references, 12 rules references,
164
+ and 4 files invoking `"$REPO_ROOT/pipeline/scripts/..."` where `$REPO_ROOT` was never
165
+ assigned anywhere in the pipeline. Gate: `smoke-install-relative-paths.sh`, which
166
+ enumerates the whole class rather than the instances.
167
+ - **`dynamicSkillLoading` had never worked on an installed tree.** Three breaks stacked: a
168
+ full install did not ship `.skills-index.json` (while the schema said it always did),
169
+ `match-skills.mjs` resolved its default index to `$HOME/pipeline/skills/...` from
170
+ `~/.claude/scripts/`, and the runtime contract in the orchestrator invoked a
171
+ repo-relative path. Its own smoke passed `--index` explicitly and ran from the repo, so
172
+ it never exercised either failure. The index now ships on every install for all three
173
+ hosts via one shared helper.
174
+ - **Two compliance skills were unreachable on Codex.** `apple-archive-compliance` and
175
+ `google-play-compliance` live in `skills/shared/core`, and Codex's installer copied only
176
+ `shared/external`. `smoke-compliance-skills.sh` asserted both were wired to four
177
+ consumers - reading the source tree, with no host awareness - so it passed while the host
178
+ had nothing to load. Gate: cross-host reachability parity in `smoke-install-layout.sh`.
179
+ - **A plugin skill whose name a pipeline skill also owns was dropped on the copy hosts.**
180
+ Claude Code reaches both because its loader namespaces plugin skills; Copilot and Codex
181
+ copy flat, so the iOS toolkit's `architecture` (that stack's structural rules) was
182
+ silently unreachable behind the pipeline's generic ADR skill, as was its `backlog` and
183
+ the common toolkit's `index`. Clashes are now plugin-prefixed, the flat analogue of what
184
+ the loader does, so the pipeline keeps the bare name and nothing is lost.
185
+ - **Plugin skills were frozen after their first install.** `skipNames` was a snapshot of
186
+ the destination, so a plugin skill matched its own previous copy and was skipped forever,
187
+ outliving every upstream fix in it. It is now the pipeline-owned set derived from the
188
+ source tree, and each plugin skill dir is wiped before refill so a file dropped upstream
189
+ does not linger.
190
+ - **Copilot skills referenced trees Copilot does not install.** Its skills were copied
191
+ byte-for-byte, so 15 references pointed at `~/.claude/scripts` and `~/.claude/lib` -
192
+ absent on a Copilot-only machine. Codex has had this rewrite since its installer was
193
+ written. Copilot now rewrites the trees it owns, and installs `rules/` as well, closing
194
+ the same silent-nothing fallback Codex's installer already documented.
195
+ - **`phase-tracker.sh` never reclaimed a stale lock on Linux.** It tried `stat -f %m`
196
+ before `stat -c %Y`, and on GNU coreutils `stat -f` is a *valid* flag (`--file-system`,
197
+ where `%m` is the mount point): it succeeds, returns something like `/`, the `||` never
198
+ fires, and the age arithmetic runs on a path. Every tracker call then spun the full ~5s
199
+ bound and fell open with a warning. Every other `stat` call site in the repo already had
200
+ the order right.
201
+ - **The portability gate accepted any file containing `||` anywhere in it**, which is every
202
+ shell script in the repo, so an unguarded BSD/GNU construct shipped green - the
203
+ `phase-tracker` bug above rode through that hole for releases. The guard is now checked
204
+ on the hit's own line, plus a dedicated `stat` ordering rule.
205
+ - **Model IDs were a generation behind.** 21 references dispatched `claude-opus-4-8` and
206
+ `claude-sonnet-4-6` on the opus/sonnet rungs. Both are still valid upstream, so nothing
207
+ errored - Phase 4's reviewer panel, the dev phase and the whole fallback ladder simply ran
208
+ the previous generation. Now `claude-opus-5` and `claude-sonnet-5`, with the rung/ID split
209
+ stated in the fallback contract. Gate: a generation guard in `smoke-model-fallback.sh`.
210
+ - **`rules/pipeline-output-formatting.md` did not exist.** Two shipped skills named it as
211
+ the PR-body contract to follow. The install-layout smoke had been *reporting* paths that
212
+ exist on no host as a note rather than failing on them, which is how it survived; that
213
+ claim is now strict.
214
+ - **A pre-migration preferences file failed validation before the migration could fix it.**
215
+ `firebase_sa`, `firebase_project` and `servicePatMap.figma_pat` are deleted by
216
+ `migrate-prefs.mjs` but were undeclared under `additionalProperties: false`. Declared as
217
+ deprecated tolerance slots, and the deprecated/deleted sets are now checked against each
218
+ other in both halves of the schema.
219
+ - `state.figmaAccess` was a BLOCKING pipeline-wide contract that the state schema never
220
+ declared. Declared, including the new `tier1Unavailable` cause.
221
+ - Four credential-store corrections: the resolver overwrote a pre-set `CRED_STORE` while
222
+ documenting it as an override; `doctor` advertised an env-var fallback that `get` does not
223
+ implement; `resolve_key` read `keychainMapping` with python3 only, so on a Windows box
224
+ without it the mapping was skipped in silence and a present token read as missing (node
225
+ is now the second reader, and node is guaranteed wherever this installs); and macOS
226
+ `list` read the item LABEL rather than the SERVICE attribute `get` looks up.
227
+ - `lint-mcp-refs.mjs` printed one checkmark for two different claims - "the server name is
228
+ recognised" and "its tools were verified to exist" - so Figma and XcodeBuildMCP read as
229
+ verified when nothing had probed them. Verified and declared are now distinguished.
230
+ - 15 orphan `<name>.md` files from a pre-directory install layout sat in a real
231
+ `~/.copilot/skills`, shadowing the proper directory form. Pruned, on both copy hosts.
232
+
233
+ ### Changed
234
+
235
+ - Figma Tier 1 availability is now host-aware. Absent tools (the normal case on Copilot and
236
+ Codex, where the installer registers only `dev-toolkit`) record
237
+ `figmaAccess.tier1Unavailable = "host"` and fall straight to Tier 2 - no probe, no
238
+ re-auth retry, and no "recreate the MCP token" question the user cannot act on. Tools
239
+ present but failing auth is `"auth"`, where the retry does apply. On those two hosts a
240
+ mapped `figma` PAT is the primary path, not a fallback.
241
+ - Phase-doc token budgets recalibrated. The path correction above cost 196 tokens of pure
242
+ correctness; 149 were reclaimed first by compression (Phase 1's Figma tier table now
243
+ points at the Phase 0 probe that already resolved it; Phase 4's Codex constraints point
244
+ at the always-loaded AGENTS.md block). Five warn lines had been permanently amber, which
245
+ makes the amber tier useless as a signal - every warn reset to the documented
246
+ current+10%, the four maxes the new warn would have collided with to current+25%,
247
+ aggregate 51000 -> 51500.
248
+ - The install summary told users to register the Claude Code MCP server by hand. It no
249
+ longer does, because the installer does it.
250
+
251
+ ## [13.5.1] - 2026-07-28
252
+
253
+ ### Fixed
254
+
255
+ - `ios-coding-standard`'s local lint runner defaulted its scratch root to
256
+ `~/.claude/local/...`. On a Copilot- or Codex-only machine that made the script create
257
+ a stray `.claude` directory belonging to a host that is not installed, just to hold
258
+ lint output. It now picks whichever host tree exists and falls back to `$TMPDIR`.
259
+ Found by the Codex sync verification, which flags any `$HOME/.claude/...` reference
260
+ that reaches the Codex tree.
261
+
19
262
  ## [13.5.0] - 2026-07-28
20
263
 
21
264
  ### Fixed
package/README.md CHANGED
@@ -60,9 +60,9 @@ The discipline behind all of this - bounded loops, evidence gates, token-budgete
60
60
  |---|---|---|
61
61
  | Full | `/multi-agent "task"` | All 8 phases, interactive |
62
62
  | Autopilot | `/multi-agent:autopilot "task"` | All 8 phases, no confirmations |
63
- | Dev | `/multi-agent:dev "task"` | Init → Dev → Commit → Report |
63
+ | Dev | `/multi-agent:dev "task"` | Init → Dev → Review → Test → Commit → Report |
64
64
  | Local | `/multi-agent:local "task"` | Full pipeline, current branch (no worktree) |
65
- | Finish | `/multi-agent:finish` | Run the review→test→commit→report tail over local work |
65
+ | Finish | `/multi-agent:ship` | Run the review→test→commit→report tail over local work |
66
66
  | Audit | `/multi-agent:design-check` | Mock-mode vs Figma conformance, local-only |
67
67
  | Audit | `/multi-agent:testflight-validation` | Pre-submission gates for a TestFlight build: static archive audit → Apple's `altool --validate-app` → Review-Guidelines check. Validates only, never uploads |
68
68
 
@@ -131,7 +131,7 @@ Runs on **macOS**, **Linux**, and **Windows** (Git Bash / WSL). Shell and creden
131
131
  | Repo | What it is |
132
132
  |---|---|
133
133
  | [`mmerterden/multi-agent-plugins`](https://github.com/mmerterden/multi-agent-plugins) | Marketplace of per-stack skill toolkits (iOS / Android / Frontend / Backend + common). `/multi-agent:stack` enables the matching plugin. |
134
- | [`mmerterden/dev-toolkit-mcp`](https://github.com/mmerterden/dev-toolkit-mcp) | MCP server for UI testing / simulator capture / xcodebuild - powers the Phase 5 UI Bug Hunter. `npx @mmerterden/dev-toolkit-mcp` |
134
+ | [`mmerterden/dev-toolkit-mcp`](https://github.com/mmerterden/dev-toolkit-mcp) | MCP server for UI testing / simulator capture / xcodebuild - powers the Phase 5 UI Bug Hunter. Published on the public npm registry as [`@mmerterden/dev-toolkit-mcp`](https://www.npmjs.com/package/@mmerterden/dev-toolkit-mcp); the installer registers it with each CLI for you, so `npx` resolves it with no extra configuration. |
135
135
 
136
136
  ## License
137
137
 
package/docs/features.md CHANGED
@@ -26,7 +26,7 @@ Each phase reads its own spec file under `pipeline/multi-agent-refs/phases/phase
26
26
  | Flag | Effect |
27
27
  | ----------- | ----------------------------------------------------------------------------------- |
28
28
  | `autopilot` | Skip all confirmation prompts; still fails safe on review blockers + build retries. |
29
- | `--dev` | Strip to Init → Dev(Opus self-contained) → Commit → Report. No Phase 1/2/4/5. |
29
+ | `--dev` | Strip to Init → Dev(Opus self-contained) → Review → Test → Commit → Report. No Phase 1/2. |
30
30
  | `--local` | No worktree - works directly in `$PROJECT_ROOT` on a local branch. |
31
31
 
32
32
  Compose freely: `--dev --local autopilot` = shortest, least-friction path.
@@ -321,6 +321,79 @@ export function copyFile(src, dest) {
321
321
  cpSync(src, dest, { force: true });
322
322
  }
323
323
 
324
+ /**
325
+ * Remove `<name>.md` files left at the root of a skills tree by the
326
+ * pre-directory install layout.
327
+ *
328
+ * Skills have been `<name>/SKILL.md` for a long time, but an older layout wrote
329
+ * `<name>.md` flat. Nothing ever pruned those, so 15 of them sat in
330
+ * `~/.copilot/skills` indefinitely: dead duplicates of skills that also existed in
331
+ * their proper directory form, inflating the skill count and offering the host a
332
+ * second, stale copy of the same guidance.
333
+ *
334
+ * The prune condition is deliberately narrow: a file is removed ONLY when a
335
+ * directory of the same name sits beside it. That is unambiguous evidence of the old
336
+ * layout, and it cannot touch a note a user dropped in the tree - a loose `.md` is
337
+ * not a discoverable skill on any host, so there is no valid file this rule can hit.
338
+ *
339
+ * @param {string} skillsDir
340
+ * @returns {number} files removed
341
+ */
342
+ export function pruneOrphanSkillFiles(skillsDir) {
343
+ if (!existsSync(skillsDir)) return 0;
344
+ let entries;
345
+ try {
346
+ entries = readdirSync(skillsDir, { withFileTypes: true });
347
+ } catch {
348
+ return 0;
349
+ }
350
+ const dirNames = new Set(entries.filter((e) => e.isDirectory()).map((e) => e.name));
351
+ let removed = 0;
352
+ for (const entry of entries) {
353
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
354
+ const base = entry.name.slice(0, -3);
355
+ if (!dirNames.has(base)) continue;
356
+ if (dryRun) {
357
+ console.log(` [dry-run] would prune stale flat skill file ${join(skillsDir, entry.name)}`);
358
+ removed++;
359
+ continue;
360
+ }
361
+ try {
362
+ rmSync(join(skillsDir, entry.name), { force: true });
363
+ removed++;
364
+ } catch {
365
+ /* non-fatal */
366
+ }
367
+ }
368
+ return removed;
369
+ }
370
+
371
+ /**
372
+ * Ship the skills index into a host's skills tree.
373
+ *
374
+ * `.skills-index.json` is what `match-skills.mjs` reads when
375
+ * `prefs.global.dynamicSkillLoading` is on; `skills-index.md` is the human-readable
376
+ * twin. Both used to be copied ONLY under `--index-only`, while the preferences
377
+ * schema stated that a full install always ships the index. It did not, so turning
378
+ * the pref on after a normal install produced `cannot read index` and a silent
379
+ * fall back to eager loading. One helper, called from every target, so the two
380
+ * install modes cannot disagree about it again.
381
+ *
382
+ * @param {string} pipelineSrc - the repo's `pipeline/` directory
383
+ * @param {string} skillsDest - the host's skills directory
384
+ * @returns {number} files copied
385
+ */
386
+ export function copySkillsIndex(pipelineSrc, skillsDest) {
387
+ let copied = 0;
388
+ for (const name of [".skills-index.json", "skills-index.md"]) {
389
+ const src = join(pipelineSrc, "skills", name);
390
+ if (!existsSync(src)) continue;
391
+ copyFile(src, join(skillsDest, name));
392
+ copied++;
393
+ }
394
+ return copied;
395
+ }
396
+
324
397
  /**
325
398
  * Write a file, honouring dry-run mode.
326
399
  * @param {string} path
@@ -3,15 +3,20 @@
3
3
  *
4
4
  * WHY THIS IS SHARED
5
5
  *
6
- * Codex got MCP registration when its installer was written; Copilot never did. The
7
- * result was a host that carried all 257 pipeline skills and could not call a single
8
- * one of the 80 dev-toolkit tools those skills depend on - `design-check`, every
9
- * `ios_*` / `android_*` simulator call, the archive audits. Nothing failed at install
10
- * time, and the skills only fail when a run actually reaches for a tool.
6
+ * Codex got MCP registration when its installer was written; Copilot never did, and
7
+ * neither did Claude Code. The result was a host that carried the full pipeline skill
8
+ * set and could not call a single one of the 80 dev-toolkit tools those skills depend
9
+ * on - `design-check`, every `ios_*` / `android_*` simulator call, the archive audits.
10
+ * Nothing failed at install time, and the skills only fail when a run actually reaches
11
+ * for a tool.
11
12
  *
12
- * Both CLIs take the same shape (`<cli> mcp add <name> -- <command> [args...]`) and
13
- * both write their own user-level config, so the difference was purely that one
14
- * installer called it and the other did not. One implementation, two callers.
13
+ * Claude Code was the longest-lived instance of that bug and the easiest to miss,
14
+ * because a maintainer who registered the server by hand once sees a working tree
15
+ * forever while every fresh install ships without it.
16
+ *
17
+ * All three CLIs take the same shape (`<cli> mcp add [scope] <name> -- <command>
18
+ * [args...]`), so the difference was purely which installers called it. One
19
+ * implementation, three callers.
15
20
  *
16
21
  * @module install/_mcp-register
17
22
  */
@@ -27,17 +32,35 @@ export const MCP_SERVER_NAME = "dev-toolkit";
27
32
  export const MCP_SERVER_PACKAGE = "@mmerterden/dev-toolkit-mcp";
28
33
 
29
34
  /**
30
- * Where each host's CLI might live.
35
+ * Per-host CLI invocation details.
31
36
  *
32
- * A bare name is resolved through PATH by execFileSync. The explicit paths matter for
33
- * Copilot: `copilot` is frequently a *shell alias* for `gh copilot`, and an alias is
34
- * invisible to execFileSync - so a PATH-only lookup reports "not on PATH" on a machine
35
- * where the user runs `copilot` every day. `gh` installs the real binary under
37
+ * `candidates`: where the CLI might live. A bare name is resolved through PATH by
38
+ * execFileSync. The explicit paths matter for Copilot: `copilot` is frequently a
39
+ * *shell alias* for `gh copilot`, and an alias is invisible to execFileSync - so a
40
+ * PATH-only lookup reports "not on PATH" on a machine where the user runs
41
+ * `copilot` every day. `gh` installs the real binary under
36
42
  * ~/.local/share/gh/copilot/.
43
+ *
44
+ * `scopeArgs`: options inserted before the server name. Only Claude Code needs
45
+ * them, and it needs them badly: `claude mcp add` defaults to `--scope local`,
46
+ * which binds the server to whichever directory the installer happened to run
47
+ * in. Every other project would then see no dev-toolkit server at all. Codex
48
+ * writes `~/.codex/config.toml` and Copilot writes `~/.copilot/mcp-config.json`,
49
+ * both already user-global, so both take no scope flag - passing one errors.
37
50
  */
38
- const CLI_CANDIDATES = Object.freeze({
39
- codex: ["codex"],
40
- copilot: ["copilot", join(homedir(), ".local", "share", "gh", "copilot", "copilot")],
51
+ const HOSTS = Object.freeze({
52
+ claude: {
53
+ candidates: ["claude", join(homedir(), ".local", "bin", "claude")],
54
+ scopeArgs: ["--scope", "user"],
55
+ },
56
+ codex: {
57
+ candidates: ["codex"],
58
+ scopeArgs: [],
59
+ },
60
+ copilot: {
61
+ candidates: ["copilot", join(homedir(), ".local", "share", "gh", "copilot", "copilot")],
62
+ scopeArgs: [],
63
+ },
41
64
  });
42
65
 
43
66
  /**
@@ -45,28 +68,36 @@ const CLI_CANDIDATES = Object.freeze({
45
68
  * try. Returning a bare name rather than null keeps the "not on PATH" diagnosis in one
46
69
  * place: the execFileSync catch.
47
70
  *
48
- * @param {"codex"|"copilot"} host
71
+ * @param {"claude"|"codex"|"copilot"} host
49
72
  * @returns {string}
50
73
  */
51
74
  function resolveCli(host) {
52
- const candidates = CLI_CANDIDATES[host] || [host];
75
+ const candidates = HOSTS[host]?.candidates || [host];
53
76
  for (const c of candidates) {
54
77
  if (c.includes("/") && existsSync(c)) return c;
55
78
  }
56
79
  return candidates[0];
57
80
  }
58
81
 
82
+ /** Does this output say the server was already registered? */
83
+ function saysAlreadyExists(text) {
84
+ return /already exists/i.test(text);
85
+ }
86
+
59
87
  /**
60
- * Register the MCP server. Idempotent from the caller's point of view, though the two
61
- * CLIs get there differently:
88
+ * Register the MCP server. Idempotent from the caller's point of view, though the
89
+ * three CLIs get there by three different routes:
62
90
  *
63
91
  * - `codex mcp add` replaces an existing entry and exits 0.
64
92
  * - `copilot mcp add` refuses, printing `Server "<name>" already exists` and exiting 1.
93
+ * - `claude mcp add` refuses, printing `MCP server <name> already exists in <scope>
94
+ * config` and exiting **0**.
65
95
  *
66
- * So a non-zero exit is not automatically a failure. An already-registered server is
67
- * the desired end state, and reporting it as "skipped MCP registration (Command
68
- * failed)" would send the user to fix something that is already correct - which is
69
- * exactly what the first version of this did.
96
+ * So neither the exit code nor the stream tells you on its own. A non-zero exit is not
97
+ * automatically a failure, and a zero exit is not automatically a fresh write - which
98
+ * is why the "already exists" test runs on both paths. Reporting an existing server as
99
+ * "skipped MCP registration (Command failed)" would send the user to fix something that
100
+ * is already correct, and that is exactly what the first version of this did.
70
101
  *
71
102
  * The existing entry is left alone rather than removed and re-added: a user may have
72
103
  * added env vars or switched the command to a local checkout, and an installer has no
@@ -75,13 +106,14 @@ function resolveCli(host) {
75
106
  * Never fatal: a missing CLI or a failed write must not fail the install, because the
76
107
  * rest of the tree is still correct and useful. It prints the manual command instead.
77
108
  *
78
- * @param {"codex"|"copilot"} host
109
+ * @param {"claude"|"codex"|"copilot"} host
79
110
  * @param {string} label - host label for log lines
80
111
  * @returns {{registered: boolean, alreadyPresent?: boolean, reason?: string}}
81
112
  */
82
113
  export function registerMcpServer(host, label) {
83
114
  const cli = resolveCli(host);
84
- const manual = `${host} mcp add ${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`;
115
+ const scopeArgs = HOSTS[host]?.scopeArgs || [];
116
+ const manual = `${host} mcp add ${scopeArgs.join(" ")}${scopeArgs.length ? " " : ""}${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`;
85
117
 
86
118
  if (isDryRun()) {
87
119
  console.log(` [dry-run] would run: ${manual}`);
@@ -100,17 +132,24 @@ export function registerMcpServer(host, label) {
100
132
  try {
101
133
  // Bounded: an installer must never hang on a child process. This is a local config
102
134
  // write and returns in milliseconds.
103
- execFileSync(cli, ["mcp", "add", MCP_SERVER_NAME, "--", "npx", "-y", MCP_SERVER_PACKAGE], {
104
- stdio: "pipe",
105
- timeout: 20_000,
106
- });
135
+ const stdout = execFileSync(
136
+ cli,
137
+ ["mcp", "add", ...scopeArgs, MCP_SERVER_NAME, "--", "npx", "-y", MCP_SERVER_PACKAGE],
138
+ { stdio: "pipe", timeout: 20_000 },
139
+ );
140
+ // Claude Code refuses a duplicate on the SUCCESS path (exit 0), so the check
141
+ // cannot live in the catch alone.
142
+ if (saysAlreadyExists(stdout?.toString() ?? "")) {
143
+ console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
144
+ return { registered: true, alreadyPresent: true };
145
+ }
107
146
  console.log(` -> ${label}: registered the ${MCP_SERVER_NAME} MCP server`);
108
147
  return { registered: true };
109
148
  } catch (e) {
110
149
  // The CLI writes its diagnosis to stderr, which execFileSync parks on the error
111
150
  // rather than in `message`. Read both, or "already exists" is invisible.
112
151
  const out = `${e?.stderr?.toString() ?? ""}\n${e?.stdout?.toString() ?? ""}\n${e?.message ?? ""}`;
113
- if (/already exists/i.test(out)) {
152
+ if (saysAlreadyExists(out)) {
114
153
  console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
115
154
  return { registered: true, alreadyPresent: true };
116
155
  }