@mmerterden/multi-agent-pipeline 14.2.2 → 15.1.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 (132) hide show
  1. package/CHANGELOG.md +186 -6
  2. package/README.md +19 -12
  3. package/README.tr.md +19 -12
  4. package/SECURITY.md +43 -0
  5. package/docs/FIGMA_PIPELINE.md +3 -3
  6. package/docs/adr/0006-skills-core-external-split.md +1 -1
  7. package/docs/adr/0007-multi-tool-adapter-framework.md +1 -1
  8. package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
  9. package/docs/adr/README.md +1 -0
  10. package/docs/architecture.md +13 -13
  11. package/docs/ecosystem.md +31 -31
  12. package/docs/features.md +5 -5
  13. package/index.js +6 -1
  14. package/install/_codex-agents.mjs +11 -2
  15. package/install/_common.mjs +109 -3
  16. package/install/_dev-only-files.mjs +0 -1
  17. package/install/_platform-filter.mjs +54 -113
  18. package/install/_plugin-skills.mjs +36 -36
  19. package/install/claude.mjs +251 -61
  20. package/install/codex.mjs +28 -6
  21. package/install/copilot.mjs +69 -9
  22. package/install/index.mjs +9 -3
  23. package/install/templates/codex-instructions.md +1 -1
  24. package/install/templates/copilot-instructions.md +3 -3
  25. package/package.json +2 -3
  26. package/pipeline/commands/multi-agent/SKILL.md +2 -0
  27. package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -3
  28. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
  29. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
  30. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
  31. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
  32. package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
  33. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
  35. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
  36. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  37. package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
  38. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
  39. package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
  40. package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
  41. package/pipeline/commands/multi-agent/refactor/SKILL.md +36 -1
  42. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
  43. package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +8 -8
  44. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
  45. package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
  46. package/pipeline/commands/multi-agent/stack/SKILL.md +62 -40
  47. package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
  48. package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
  49. package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
  50. package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
  51. package/pipeline/commands/multi-agent/update/SKILL.md +4 -4
  52. package/pipeline/lib/issue-fetcher.sh +1 -1
  53. package/pipeline/lib/parse-complaints.sh +316 -0
  54. package/pipeline/multi-agent-refs/channels/wiki.md +3 -3
  55. package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
  56. package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
  57. package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
  58. package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
  59. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
  60. package/pipeline/multi-agent-refs/generate-issue.md +1 -1
  61. package/pipeline/multi-agent-refs/phases/modes.md +1 -1
  62. package/pipeline/multi-agent-refs/phases/operations.md +7 -1
  63. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
  64. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
  65. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
  66. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
  67. package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
  68. package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
  69. package/pipeline/multi-agent-refs/phases/phase-7-report.md +6 -0
  70. package/pipeline/multi-agent-refs/tracker-contract.md +3 -2
  71. package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
  72. package/pipeline/preferences-template.json +18 -5
  73. package/pipeline/rules/figma-pipeline.md +2 -2
  74. package/pipeline/schemas/agent-state.schema.json +1 -1
  75. package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
  76. package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
  77. package/pipeline/schemas/prefs.schema.json +296 -66
  78. package/pipeline/schemas/token-budget.json +2 -2
  79. package/pipeline/scripts/README.md +4 -3
  80. package/pipeline/scripts/_stack-routing.mjs +79 -0
  81. package/pipeline/scripts/audit-log-rotate.sh +4 -1
  82. package/pipeline/scripts/build-skills-index.mjs +11 -0
  83. package/pipeline/scripts/build-stack-plugins.mjs +28 -60
  84. package/pipeline/scripts/check-derived-drift.mjs +55 -28
  85. package/pipeline/scripts/gc-worktrees.sh +4 -1
  86. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  87. package/pipeline/scripts/match-skills.mjs +12 -2
  88. package/pipeline/scripts/migrate-prefs.mjs +33 -21
  89. package/pipeline/scripts/phase-tracker.sh +32 -5
  90. package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
  91. package/pipeline/scripts/run-aggregator.mjs +7 -2
  92. package/pipeline/scripts/scan-agent-config.sh +1 -1
  93. package/pipeline/scripts/skill-conformance.mjs +165 -30
  94. package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
  95. package/pipeline/scripts/test-gap-rules/android.json +25 -0
  96. package/pipeline/scripts/test-gap-rules/ios.json +34 -0
  97. package/pipeline/scripts/test-gap-rules/node.json +29 -0
  98. package/pipeline/scripts/test-gap-rules/python.json +25 -0
  99. package/pipeline/scripts/uninstall.mjs +160 -11
  100. package/pipeline/scripts/usage-report.mjs +426 -0
  101. package/pipeline/scripts/validate-complaint-doc.mjs +250 -0
  102. package/pipeline/scripts/validate-reviewer.mjs +9 -3
  103. package/pipeline/skills/.skill-manifest.json +156 -108
  104. package/pipeline/skills/.skills-index.json +449 -12
  105. package/pipeline/skills/shared/README.md +14 -10
  106. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
  107. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
  108. package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
  109. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
  110. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
  111. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
  112. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
  113. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
  114. package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
  115. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +153 -90
  116. package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +6 -6
  117. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +89 -22
  118. package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
  119. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
  120. package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
  121. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +1 -1
  122. package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
  123. package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
  124. package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
  125. package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
  126. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
  127. package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
  128. package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
  129. package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
  130. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +53 -10
  131. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +4 -3
  132. package/pipeline/skills/skills-index.md +7 -4
package/docs/ecosystem.md CHANGED
@@ -5,7 +5,7 @@ separately, wired together at install time and at run time:
5
5
 
6
6
  | Repo | What it owns | Ships as |
7
7
  |---|---|---|
8
- | **`multi-agent-pipeline`** (this repo) | Orchestration: the 8-phase flow, the 49 slash commands, quality gates, review/triage, cross-CLI parity | npm package (`@mmerterden/multi-agent-pipeline`), installs itself onto Claude Code / Copilot CLI / Codex CLI |
8
+ | **`multi-agent-pipeline`** (this repo) | Orchestration: the 8-phase flow, the 51 slash commands, quality gates, review/triage, cross-CLI parity | npm package (`@mmerterden/multi-agent-pipeline`), installs itself onto Claude Code / Copilot CLI / Codex CLI |
9
9
  | **`multi-agent-plugins`** | Stack knowledge: per-platform component/lifecycle skills (iOS, Android, Frontend, Backend) + shared knowledge | Claude Code marketplace, 5 independently-versioned plugins |
10
10
  | **`dev-toolkit-mcp`** | The pipeline's hands on devices and browsers: 80 MCP tools across 6 categories (simulator/emulator control, accessibility audit, store compliance, web automation, Figma-vs-mock design audit, an agent-DSL batch runner) | npm package, registered as a standard stdio MCP server on every host |
11
11
 
@@ -18,17 +18,17 @@ Either can be swapped or removed without touching the other two's source.
18
18
  graph LR
19
19
  subgraph PIPE ["multi-agent-pipeline (orchestrator)"]
20
20
  direction TB
21
- PHASES["8 phases · 49 commands"]
21
+ PHASES["8 phases · 51 commands"]
22
22
  GATES["deterministic gates + review triage"]
23
23
  end
24
24
 
25
25
  subgraph PLUG ["multi-agent-plugins (stack knowledge)"]
26
26
  direction TB
27
- IOSP["ai-ios-engineering-toolkit"]
28
- ANDP["ai-android-engineering-toolkit"]
29
- FEP["ai-frontend-engineering-toolkit"]
27
+ IOSP["ai-ios-toolkit"]
28
+ ANDP["ai-android-toolkit"]
29
+ FEP["ai-frontend-toolkit"]
30
30
  BEP["ai-backend-toolkit"]
31
- COMP["ai-common-engineering-toolkit"]
31
+ COMP["ai-common-toolkit"]
32
32
  end
33
33
 
34
34
  subgraph DTK ["dev-toolkit-mcp (device/browser hands)"]
@@ -63,8 +63,8 @@ only those:
63
63
  graph TD
64
64
  CC["Claude Code<br/>~/.claude/commands/multi-agent/<br/>(source of truth)"]
65
65
 
66
- CC -->|"Step 2: copy + reformat<br/>49 sub-command skills"| COP["Copilot CLI<br/>~/.copilot/skills/"]
67
- CC -->|"Step 2b: transform<br/>(install.js --codex)"| COD["Codex CLI<br/>1 router skill + 49 refs<br/>+ 8 agent TOML"]
66
+ CC -->|"Step 2: copy + reformat<br/>51 sub-command skills"| COP["Copilot CLI<br/>~/.copilot/skills/"]
67
+ CC -->|"Step 2b: transform<br/>(install.js --codex)"| COD["Codex CLI<br/>1 router skill + 51 refs<br/>+ 8 agent TOML"]
68
68
  CC -->|"Step 3: genericize<br/>(strip personal data)"| REPO["multi-agent-pipeline repo<br/>pipeline/"]
69
69
  CC -->|"Step 4: version + feature sync"| WEB["Website<br/>projects.ts / i18n.tsx"]
70
70
 
@@ -75,28 +75,28 @@ graph TD
75
75
  CC -.->|"sync only SHIPS this,<br/>never authors it"| SYNC3D
76
76
  SYNC3D -.-> DTK2
77
77
 
78
- REPO -->|"npm publish"| NPM["GitHub Packages"]
78
+ REPO -->|"npm publish"| NPM["npm registry (public)"]
79
79
  WEB -->|"git push → auto-deploy"| VERCEL["Vercel"]
80
80
  PLUGREPO -->|"git push"| MKT["Claude Code marketplace"]
81
- DTK2 -->|"npm publish"| NPM2["GitHub Packages<br/>(private)"]
81
+ DTK2 -->|"npm publish"| NPM2["npm registry<br/>(public)"]
82
82
 
83
83
  style CC fill:#f9f,stroke:#333
84
84
  style DTK2 fill:#dff,stroke:#333,stroke-dasharray: 5 5
85
85
  ```
86
86
 
87
87
  **Why `dev-toolkit-mcp` is drawn differently.** The other four targets are *derived*
88
- from the Claude Code source sync writes their content. `dev-toolkit-mcp` is not:
88
+ from the Claude Code source - sync writes their content. `dev-toolkit-mcp` is not:
89
89
  it's a separate codebase developed on its own schedule. Sync's Step 3d only
90
90
  *detects* whether it moved (dirty tree, unpushed commits, untagged version), runs
91
- **its own** gate suite, and ships it commit, tag, `npm publish`. If the pipeline
91
+ **its own** gate suite, and ships it - commit, tag, `npm publish`. If the pipeline
92
92
  needs a tool that toolkit doesn't have yet, that's a two-repo change: add the tool
93
93
  in `dev-toolkit-mcp`, ship it, then bump the minimum version pin back in
94
- `cross-cli-contract.md` (see §4).
94
+ `cross-cli-contract.md` (see section 4).
95
95
 
96
96
  **Also not generated: the plugins' own authored skills.** `build-stack-plugins.mjs`
97
97
  only rebuilds each plugin's `knowledge/` folder from `pipeline/skills/shared/external/`.
98
- The plugins' lifecycle skills `create-component`, `evolve-component`,
99
- `figma-utility`, `code-connect`, `branch-and-pr`, `fix-bug`, and the rest are
98
+ The plugins' lifecycle skills - `create-component`, `evolve-component`,
99
+ `figma-utility`, `code-connect`, `branch-and-pr`, `fix-bug`, and the rest - are
100
100
  hand-authored *inside* `multi-agent-plugins` and are never touched by sync.
101
101
 
102
102
  ---
@@ -105,13 +105,13 @@ hand-authored *inside* `multi-agent-plugins` and are never touched by sync.
105
105
 
106
106
  ```mermaid
107
107
  graph TD
108
- EXT["pipeline/skills/shared/external/<br/>150 skills single authoring source<br/>(the pipeline's own phases read these too)"]
108
+ EXT["pipeline/skills/shared/external/<br/>151 skills - single authoring source<br/>(the pipeline's own phases read these too)"]
109
109
 
110
- EXT -->|"cross-stack skills"| COMMONP["ai-common-engineering-toolkit<br/>10 skills · v0.2.3"]
111
- EXT -->|"Apple/Xcode-only"| IOSP["ai-ios-engineering-toolkit<br/>145 skills · v0.6.0"]
112
- EXT -->|"Android/Kotlin-only"| ANDP["ai-android-engineering-toolkit<br/>29 skills · v0.1.3"]
110
+ EXT -->|"cross-stack skills"| COMMONP["ai-common-toolkit<br/>10 skills · v0.2.3"]
111
+ EXT -->|"Apple/Xcode-only"| IOSP["ai-ios-toolkit<br/>145 skills · v0.6.0"]
112
+ EXT -->|"Android/Kotlin-only"| ANDP["ai-android-toolkit<br/>29 skills · v0.1.3"]
113
113
  EXT -->|"backend-only"| BEP["ai-backend-toolkit<br/>32 skills · v0.1.4"]
114
- EXT -->|"web/frontend-only"| FEP["ai-frontend-engineering-toolkit<br/>24 skills · v0.1.3"]
114
+ EXT -->|"web/frontend-only"| FEP["ai-frontend-toolkit<br/>24 skills · v0.1.3"]
115
115
 
116
116
  COMMONP --> BUMP{"skill set<br/>changed?"}
117
117
  IOSP --> BUMP
@@ -128,9 +128,9 @@ graph TD
128
128
 
129
129
  A skill counted in more than one platform plugin (a cross-stack knowledge skill
130
130
  plus, say, an iOS-specific one) is why the plugins' skill counts sum to more than
131
- the 150-skill source: `ai-common` skills are vendored into every stack plugin's
131
+ the 151-skill source: `ai-common` skills are vendored into every stack plugin's
132
132
  `knowledge/`, not deduplicated across them. Versioning is per-plugin and
133
- patch-only from this generator a repo enabling only `ai-ios-engineering-toolkit`
133
+ patch-only from this generator - a repo enabling only `ai-ios-toolkit`
134
134
  never pulls an Android-only change.
135
135
 
136
136
  **Consumption is pull, not push.** A consumer repo enables a stack plugin once
@@ -150,9 +150,9 @@ measurements behind this table):
150
150
 
151
151
  | | Claude Code | Copilot CLI | Codex CLI |
152
152
  |---|---|---|---|
153
- | **Pipeline commands** | 49 slash-command skills, native | 49 skills, `multi-agent-{cmd}` naming, copied in | 1 router skill (`multi-agent`) + 49 command specs as reference files Codex silently truncates its skills block past a few dozen entries, so sub-commands are not peer skills here |
154
- | **Stack plugins** | Marketplace plugin, loaded natively, resolved by `.claude/settings.json` enabled-list | Enabled plugin's authored skills copied flat into `~/.copilot/skills/`; `knowledge/` **not** re-copied (already delivered via `shared/external`) | Copied as reference files under `~/.codex/multi-agent-refs/skills/`, plugin-prefixed on name clash (e.g. `architecture` → `ai-ios-engineering-toolkit-architecture`) |
155
- | **Component dispatch (Phase 3)** | Marketplace plugin's `create-component`/`create-screen` skill via the Skill tool | No plugin loader falls back to local frozen `figma-*` skill copies | Not part of the enforced parity axis; classification + state-shape must match, skill *inventory* does not |
153
+ | **Pipeline commands** | 51 slash-command skills, native | 51 skills, `multi-agent-{cmd}` naming, copied in | 1 router skill (`multi-agent`) + 51 command specs as reference files - Codex silently truncates its skills block past a few dozen entries, so sub-commands are not peer skills here |
154
+ | **Stack plugins** | Marketplace plugin, loaded natively, resolved by `.claude/settings.json` enabled-list | Enabled plugin's authored skills copied flat into `~/.copilot/skills/`; `knowledge/` **not** re-copied (already delivered via `shared/external`) | Copied as reference files under `~/.codex/multi-agent-refs/skills/`, plugin-prefixed on name clash (e.g. `architecture` → `ai-ios-toolkit-architecture`) |
155
+ | **Component dispatch (Phase 3)** | Marketplace plugin's `create-component`/`create-screen` skill via the Skill tool | No plugin loader - the enabled stack plugin's authored skills (incl. `create-component`) are copied flat into `~/.copilot/skills/` at install time (the old frozen `figma-*` copies are pruned, they were never a fallback) | Not part of the enforced parity axis; classification + state-shape must match, skill *inventory* does not |
156
156
  | **dev-toolkit-mcp** | `claude mcp add dev-toolkit -- npx -y @mmerterden/dev-toolkit-mcp` | `copilot mcp add dev-toolkit -- npx -y @mmerterden/dev-toolkit-mcp` | `codex mcp add dev-toolkit -- npx -y @mmerterden/dev-toolkit-mcp` (skipped with a warning if `codex` isn't on `PATH`) |
157
157
 
158
158
  `smoke-cross-cli-behavior.sh` and `smoke-codex-install.sh` gate the axes that **do**
@@ -170,7 +170,7 @@ other:
170
170
  graph TD
171
171
  START["Task running: Phase 3 (Dev)"]
172
172
  START -->|"taskType !== component"| TDD["Standard TDD loop<br/>(pipeline's own code)"]
173
- START -->|"taskType === component<br/>+ figmaUrl present"| VALIDATE["ai-ios-engineering-toolkit:figma-validate<br/>(registry, Code Connect, token compliance)"]
173
+ START -->|"taskType === component<br/>+ figmaUrl present"| VALIDATE["ai-ios-toolkit:figma-validate<br/>(registry, Code Connect, token compliance)"]
174
174
  VALIDATE -->|pass| DISPATCH["Skill tool →<br/>create-component / create-screen<br/>/ evolve-component (dual-name fallback)"]
175
175
  VALIDATE -->|fail| HALT1["halt Phase 3, surface why"]
176
176
  DISPATCH --> REPORT1["plugin returns build/test status →<br/>dispatch layer writes state.phases['3'].subphases[]"]
@@ -196,9 +196,9 @@ hard stop, per `component-dispatch.md`).
196
196
  **Phase 5 (and design-check / store-ready) → dev-toolkit** is a long-lived MCP
197
197
  session, not a one-shot call: the same stdio server process answers many tool
198
198
  calls across a phase (boot simulator once, then screenshot/tap/screenshot/tap...).
199
- Several pipeline skills pin a **minimum toolkit version** for a specific tool
199
+ Several pipeline skills pin a **minimum toolkit version** for a specific tool -
200
200
  e.g. `apple-archive-compliance` requires `ios_app_store_audit` from
201
- `dev-toolkit-mcp ≥ v2.9.0` enforced in `cross-cli-contract.md` and checked by
201
+ `dev-toolkit-mcp ≥ v2.9.0` - enforced in `cross-cli-contract.md` and checked by
202
202
  `/multi-agent:sync` Step 3d before any dev-toolkit release ships (a version bump
203
203
  that drops or renames a tool a pipeline skill depends on is a **major** bump, by
204
204
  that step's own contract).
@@ -221,8 +221,8 @@ that step's own contract).
221
221
  - **Pipeline ↔ plugins boundary = Skill tool, one direction.** The pipeline
222
222
  classifies (`taskType`, `componentScope`) and tracks state; it never reads or
223
223
  writes plugin-internal files. This is why a corporate marketplace can ship a
224
- same-named plugin (`ai-ios-engineering-toolkit:create-ui-component` vs the public
225
- `create-component`) and dispatch still resolves correctly the dual-name
224
+ same-named plugin (`ai-ios-toolkit:create-ui-component` vs the public
225
+ `create-component`) and dispatch still resolves correctly - the dual-name
226
226
  fallback lives in the pipeline, the implementation stays entirely in whichever
227
227
  plugin is enabled.
228
228
  - **Pipeline ↔ dev-toolkit boundary = MCP protocol, versioned contract.** The
@@ -233,5 +233,5 @@ that step's own contract).
233
233
  contract.
234
234
  - **Neither boundary is symmetric.** The pipeline depends on both other repos being
235
235
  present *for specific task types* (component work, UI testing) but functions
236
- without either a non-component bugfix task never touches the plugin marketplace,
236
+ without either - a non-component bugfix task never touches the plugin marketplace,
237
237
  and a task with no UI-testing step never opens the MCP connection.
package/docs/features.md CHANGED
@@ -45,14 +45,14 @@ Build commands, test runners, lint tools, and review focus areas all adapt to th
45
45
 
46
46
  ### Stack Selection (marketplace plugins)
47
47
 
48
- Stack skill sets ship as versioned plugins in the `multi-agent-plugins` marketplace. Selecting a stack enables the matching plugin(s) in the current repo's `.claude/settings.json` `enabledPlugins` - no skill copying, no session restart tricks, no directory shuffling. The `ai-common-engineering-toolkit` (accessibility audit, humanizer, Firebase) is always enabled alongside the stack plugin.
48
+ Stack skill sets ship as versioned plugins in the `multi-agent-plugins` marketplace. Selecting a stack enables the matching plugin(s) in the current repo's `.claude/settings.json` `enabledPlugins` - no skill copying, no session restart tricks, no directory shuffling. The `ai-common-toolkit` (accessibility audit, humanizer, Firebase) is always enabled alongside the stack plugin.
49
49
 
50
50
  ```bash
51
- /multi-agent:stack ios # ai-ios-engineering-toolkit (SwiftUI, Xcode, HIG)
52
- /multi-agent:stack android # ai-android-engineering-toolkit (Compose, Gradle, Hilt)
51
+ /multi-agent:stack ios # ai-ios-toolkit (SwiftUI, Xcode, HIG)
52
+ /multi-agent:stack android # ai-android-toolkit (Compose, Gradle, Hilt)
53
53
  /multi-agent:stack mobile # iOS + Android combined
54
54
  /multi-agent:stack backend # ai-backend-toolkit (spec-driven APIs)
55
- /multi-agent:stack frontend # ai-frontend-engineering-toolkit (React/TSX)
55
+ /multi-agent:stack frontend # ai-frontend-toolkit (React/TSX)
56
56
  /multi-agent:stack fullstack # backend + frontend
57
57
  /multi-agent:stack all # every stack plugin
58
58
  ```
@@ -273,7 +273,7 @@ Turn a recurring, project-specific job into a first-class `/multi-agent:<name>`
273
273
 
274
274
  ### Figma / Component Generation (dispatched to marketplace plugins)
275
275
 
276
- Component + Figma-to-code work is no longer bundled in this repo. When Phase 0 classifies a task as `component`, Phase 3 dispatches it to the per-stack marketplace plugins (`ai-ios-engineering-toolkit` / `ai-android-engineering-toolkit` in the `multi-agent-plugins` marketplace) via the Skill tool. The plugin's component skill generates `{Name}Configuration.swift`, `{Name}View.swift`, `{Name}+Modifiers.swift`, `{Name}.figma.swift`, and `FIGMA.md` with a variant matrix, then runs a 14-item pre-commit checklist covering design tokens, accessibility, tests, and Code Connect.
276
+ Component + Figma-to-code work is no longer bundled in this repo. When Phase 0 classifies a task as `component`, Phase 3 dispatches it to the per-stack marketplace plugins (`ai-ios-toolkit` / `ai-android-toolkit` in the `multi-agent-plugins` marketplace) via the Skill tool. The plugin's component skill generates `{Name}Configuration.swift`, `{Name}View.swift`, `{Name}+Modifiers.swift`, `{Name}.figma.swift`, and `FIGMA.md` with a variant matrix, then runs a 14-item pre-commit checklist covering design tokens, accessibility, tests, and Code Connect.
277
277
 
278
278
  The plugin's cross-cutting integration skills feed component detection + implementation when the design triggers them (content: form / price / ui-patterns; interaction: navigation / overlays / bottom-sheets). Each is native-SwiftUI-first and reads project specifics (token namespaces, component paths, UI systems) from `figma-config`, including the optional `ui.navigationSystem` / `ui.overlaySystem` / `ui.sheetSystem` hooks (absent -> stock SwiftUI), so the same capabilities work on any SwiftUI codebase. The plugin's evolve-component skill reconciles an existing component against current Figma (drift-heal) and additively extends it, behind a human gate.
279
279
 
package/index.js CHANGED
@@ -53,13 +53,18 @@ if (command === "--version" || command === "-v" || command === "version") {
53
53
  npx @mmerterden/multi-agent-pipeline install --codex Install for Codex CLI
54
54
  npx @mmerterden/multi-agent-pipeline install --all Claude + Copilot + Codex
55
55
  npx @mmerterden/multi-agent-pipeline install --link Use symlinks (saves tokens, dev mode)
56
+ npx @mmerterden/multi-agent-pipeline install --dry-run Preview what would be written, write nothing
57
+ npx @mmerterden/multi-agent-pipeline install --index-only Ship only the skills index (dynamic skill loading)
58
+ npx @mmerterden/multi-agent-pipeline install --prune-external Also remove pre-v15 catalog copies that differ from the shipped catalog
56
59
 
57
60
  Uninstall (token-preserving - Keychain/Credential Manager untouched):
58
61
  npx @mmerterden/multi-agent-pipeline uninstall Interactive: remove from all installed targets
59
62
  npx @mmerterden/multi-agent-pipeline uninstall --yes Skip prompt
60
63
  npx @mmerterden/multi-agent-pipeline uninstall --dry-run Report what would be removed
61
64
  npx @mmerterden/multi-agent-pipeline uninstall --claude Only Claude Code
65
+ npx @mmerterden/multi-agent-pipeline uninstall --copilot Only Copilot CLI
62
66
  npx @mmerterden/multi-agent-pipeline uninstall --codex Only Codex CLI
67
+ npx @mmerterden/multi-agent-pipeline uninstall --all-data ALSO remove pipeline settings + logs/state/metrics
63
68
  npx @mmerterden/multi-agent-pipeline uninstall --cursor Legacy pre-v10.7 adapter-file cleanup (also --copilot-chat / --antigravity; --target=<path> overrides cwd)
64
69
 
65
70
  Help:
@@ -68,7 +73,7 @@ if (command === "--version" || command === "-v" || command === "version") {
68
73
 
69
74
  Options:
70
75
  --no-color Disable colored output
71
- --target=<path> Target dir for legacy adapter cleanup on uninstall (defaults to cwd)
76
+ --target=<path> Target dir override (install and legacy-adapter cleanup on uninstall; defaults to cwd)
72
77
  --platform=ios|android|all Filter external skills by platform (default: all)
73
78
 
74
79
  After installation:
@@ -24,8 +24,17 @@ import { ensureDir, ensureRealDir, isDryRun, writeFile } from "./_common.mjs";
24
24
  * The pipeline's ladder is `fable -> opus -> sonnet -> haiku`. Codex offers no
25
25
  * Anthropic models, so each tier maps onto an OpenAI model plus an effort
26
26
  * setting: effort carries the depth distinction that the model id carries on
27
- * Claude Code. Keep this table in sync with the Codex column of the Phase 4
28
- * reviewer matrix and with `pipeline/scripts/cost-table.json`.
27
+ * Claude Code.
28
+ *
29
+ * This table resolves a persona's DEFAULT tier, which is not the same thing as
30
+ * a per-slot override. Phase 4 dispatches Reviewer 3 with an explicit
31
+ * `gpt-5.6` @ `medium` (see `phases/phase-4-review.md`, `claude-md-template.md`
32
+ * and `reviewer-output.schema.json`, which all state that value) even though
33
+ * the persona's own tier is `sonnet` and resolves here to `gpt-5.4`. That is
34
+ * deliberate: the Codex panel buys its diversity from effort, so two slots
35
+ * share a model at different efforts while a third changes model. Do not
36
+ * "reconcile" the two by editing either side without deciding which behavior
37
+ * you want, and keep this table in sync with `pipeline/scripts/cost-table.json`.
29
38
  */
30
39
  export const CODEX_TIER_MAP = Object.freeze({
31
40
  fable: { model: "gpt-5.6", reasoning_effort: "xhigh" },
@@ -10,11 +10,15 @@
10
10
  */
11
11
 
12
12
  import {
13
+ chmodSync,
13
14
  cpSync,
14
15
  existsSync,
15
16
  lstatSync,
16
17
  mkdirSync,
18
+ readFileSync,
17
19
  readdirSync,
20
+ realpathSync,
21
+ renameSync,
18
22
  rmSync,
19
23
  statSync,
20
24
  symlinkSync,
@@ -87,7 +91,7 @@ export function ensureRealDir(dir) {
87
91
  * @param {string} agentsSrc - pipeline/agents source directory
88
92
  * @returns {Set<string>}
89
93
  */
90
- export function pipelineAgentFileNames(agentsSrc) {
94
+ function pipelineAgentFileNames(agentsSrc) {
91
95
  const names = new Set();
92
96
  if (existsSync(agentsSrc)) {
93
97
  for (const entry of readdirSync(agentsSrc, { withFileTypes: true })) {
@@ -266,7 +270,7 @@ export function pruneLegacyMultiAgentSkills(skillsDir) {
266
270
  *
267
271
  * @type {ReadonlyArray<{dir: string, root?: "claude"|"home", reason: string}>}
268
272
  */
269
- export const ABANDONED_TREES = Object.freeze([
273
+ const ABANDONED_TREES = Object.freeze([
270
274
  {
271
275
  dir: "eval",
272
276
  reason: "eval corpora; the harnesses that read them are maintainer-only and no longer ship",
@@ -279,6 +283,24 @@ export const ABANDONED_TREES = Object.freeze([
279
283
  },
280
284
  ]);
281
285
 
286
+ /**
287
+ * Registry of command renames. Command names are an interface: users type
288
+ * them, docs and saved routines reference them, and Copilot installs derive
289
+ * `multi-agent-<name>` skill dirs from them. A shipped name may only ever
290
+ * disappear by being recorded here as `oldName: newName` - the
291
+ * install-lifecycle test compares the shipped tree against its committed
292
+ * snapshot and fails on any removal that has no rename entry, so a command
293
+ * cannot vanish between versions by accident.
294
+ *
295
+ * @type {Readonly<Record<string, string>>}
296
+ */
297
+ export const COMMAND_RENAMES = Object.freeze({
298
+ delete: "uninstall",
299
+ // v15.0.0: "continue local work through the pipeline tail" reads as a resume
300
+ // variant, not a shipping action (the command opens the PR but does not merge).
301
+ ship: "resume-local",
302
+ });
303
+
282
304
  /**
283
305
  * Remove trees an older install left behind.
284
306
  *
@@ -404,7 +426,50 @@ export function writeFile(path, content) {
404
426
  console.log(` [dry-run] would write ${path}`);
405
427
  return;
406
428
  }
407
- writeFileSync(path, content);
429
+ atomicWrite(path, content);
430
+ }
431
+
432
+ /**
433
+ * tmp + rename so a crash mid-write can never leave a truncated file behind -
434
+ * several call sites rewrite host config the CLI needs in order to start
435
+ * (`~/.claude/settings.json`, `copilot-instructions.md`).
436
+ *
437
+ * Two properties a naive rename would silently destroy on co-owned files:
438
+ *
439
+ * - **symlinks**: users keep these files in a dotfiles repo and symlink them
440
+ * in. Renaming over the link replaces it with a regular file, so the
441
+ * dotfiles copy goes stale and the next dotfiles apply reverts our edits.
442
+ * Resolve the link first and write through it.
443
+ * - **mode**: a rename installs the tmp file's default 0644 over whatever the
444
+ * user set (0600 on a settings file holding tokens is a real case). Carry
445
+ * the existing mode across.
446
+ *
447
+ * @param {string} path
448
+ * @param {string} content
449
+ */
450
+ export function atomicWrite(path, content) {
451
+ let target = path;
452
+ try {
453
+ if (lstatSync(path).isSymbolicLink()) target = realpathSync(path);
454
+ } catch {
455
+ // Missing file: nothing to preserve, write the path as given.
456
+ }
457
+ let mode;
458
+ try {
459
+ mode = statSync(target).mode & 0o777;
460
+ } catch {
461
+ mode = undefined;
462
+ }
463
+ const tmp = `${target}.tmp-${process.pid}`;
464
+ writeFileSync(tmp, content);
465
+ if (mode !== undefined) {
466
+ try {
467
+ chmodSync(tmp, mode);
468
+ } catch {
469
+ // Best effort: a failed chmod must not lose the write.
470
+ }
471
+ }
472
+ renameSync(tmp, target);
408
473
  }
409
474
 
410
475
  /**
@@ -457,3 +522,44 @@ export function wipeDir(dir) {
457
522
  /* non-fatal */
458
523
  }
459
524
  }
525
+
526
+
527
+ /**
528
+ * @param {string} a
529
+ * @param {string} b
530
+ * @returns {boolean} true when both trees have identical entries and file bytes
531
+ */
532
+ export function dirsIdentical(a, b) {
533
+ const namesA = readdirSync(a).sort();
534
+ const namesB = readdirSync(b).sort();
535
+ if (namesA.length !== namesB.length || namesA.some((n, i) => n !== namesB[i])) return false;
536
+ for (const name of namesA) {
537
+ const pa = join(a, name);
538
+ const pb = join(b, name);
539
+ const sa = statSync(pa);
540
+ const sb = statSync(pb);
541
+ if (sa.isDirectory() !== sb.isDirectory()) return false;
542
+ if (sa.isDirectory()) {
543
+ if (!dirsIdentical(pa, pb)) return false;
544
+ } else if (!readFileSync(pa).equals(readFileSync(pb))) {
545
+ return false;
546
+ }
547
+ }
548
+ return true;
549
+ }
550
+
551
+ /**
552
+ * Same `local-only: true` frontmatter convention the command wrappers use:
553
+ * a dir carrying it is the user's and no prune path may take it.
554
+ *
555
+ * @param {string} dir
556
+ * @returns {boolean}
557
+ */
558
+ export function isLocalOnlySkill(dir) {
559
+ try {
560
+ const skill = join(dir, "SKILL.md");
561
+ return existsSync(skill) && /^local-only:\s*true\s*$/m.test(readFileSync(skill, "utf-8"));
562
+ } catch {
563
+ return true;
564
+ }
565
+ }
@@ -64,7 +64,6 @@ const DEV_ONLY_TOOLING = Object.freeze([
64
64
  "validate-schemas.mjs", // validates the repo's own schema files, needs ajv
65
65
  "sync-parity-check.sh",
66
66
  "benchmark-phase-0.sh",
67
- "test-gap-rules", // rule corpus for the repo's own test-gap gate
68
67
  ]);
69
68
 
70
69
  /** Eval harnesses (`eval-*.mjs`) read `pipeline/eval/**`, which never ships. */
@@ -1,132 +1,73 @@
1
1
  /**
2
- * Platform classification + filtered copy for the external skill catalog.
2
+ * Stack partition + delivery manifest for the external skill catalog.
3
3
  *
4
- * The `--platform=ios|android|all` flag narrows `pipeline/skills/shared/external/`
5
- * to the requested platform. Generic skills always copy regardless of filter.
4
+ * The pre-v15 prefix classifier and its filtered-copy path
5
+ * (`classifyExternalSkill` / `copyExternalSkillsFiltered`) were removed once the
6
+ * routing table (`_stack-routing.mjs`) became the single source of stack
7
+ * affinity - `partitionExternalSkillsByPlugins` is what every installer uses.
6
8
  *
7
9
  * @module install/_platform-filter
8
10
  */
9
11
 
10
- import { readdirSync } from "fs";
12
+ import { readdirSync, writeFileSync } from "fs";
11
13
  import { join } from "path";
12
- import { copyDir, countFiles, ensureDir } from "./_common.mjs";
14
+ import { isDryRun } from "./_common.mjs";
15
+ import { routeSkill } from "../pipeline/scripts/_stack-routing.mjs";
13
16
 
14
- /**
15
- * Heuristic prefix lists - intentionally loose so new skills added to
16
- * `shared/external/` are classified by name (no manifest required). A skill
17
- * that doesn't match either list is treated as "generic" and copies for every
18
- * platform.
19
- */
20
- const IOS_PREFIXES = [
21
- "swift",
22
- "swiftui",
23
- "ios",
24
- "apple",
25
- "xcode",
26
- "uikit",
27
- "watchkit",
28
- "passkit",
29
- "storekit",
30
- "healthkit",
31
- "activitykit",
32
- "weatherkit",
33
- "pencilkit",
34
- "callkit",
35
- "eventkit",
36
- "homekit",
37
- "mapkit",
38
- "musickit",
39
- "permissionkit",
40
- "speech",
41
- "metrickit",
42
- "widgetkit",
43
- "vision",
44
- "app-store",
45
- "app-clips",
46
- "app-intents",
47
- "app-tracking",
48
- "hig-",
49
- "coreml",
50
- "core-nfc",
51
- "core-bluetooth",
52
- "core-motion",
53
- "cloudkit",
54
- "live-activities",
55
- "realitykit",
56
- "tipkit",
57
- "alarmkit",
58
- "energykit",
59
- "natural-language",
60
- "authentication",
61
- "background-processing",
62
- "contacts",
63
- "device-integrity",
64
- "debugging-instruments",
65
- "macos-",
66
- "ios-",
67
- "photos-camera",
68
- "push-notifications",
69
- "shareplay",
70
- "apple-on-device",
71
- "swiftdata",
72
- "swift-",
73
- ];
74
-
75
- const ANDROID_PREFIXES = [
76
- "android",
77
- "kotlin",
78
- "jetpack",
79
- "compose",
80
- "room",
81
- "retrofit",
82
- "gradle",
83
- "material",
84
- "play-store",
85
- ];
17
+ /** Uninstall reads this to know exactly which external skill dirs are ours to remove. */
18
+ export const EXTERNAL_SKILLS_MANIFEST = ".external-skills-manifest.json";
86
19
 
87
20
  /**
88
- * Classify an external skill directory name into its platform affinity.
21
+ * Enabled-stack partition of the external skill catalog.
22
+ *
23
+ * The prefix classifier above only knows ios/android/generic, so it cannot
24
+ * express "backend toolkit not enabled". Routing can: `routeSkill` is the same
25
+ * table `build-stack-plugins.mjs` ships plugins with, so filtering by it keeps
26
+ * a host's local copy byte-aligned with what the enabled plugins would serve on
27
+ * Claude Code. Unrouted skills are kept - dropping a skill no table claims
28
+ * would make it unreachable everywhere.
89
29
  *
90
- * @param {string} skillName - directory name under `shared/external/`
91
- * @returns {"ios" | "android" | "generic"}
30
+ * @param {string} externalSrc - absolute path to `pipeline/skills/shared/external/`
31
+ * @param {string[]} enabledPluginNames - e.g. ["ai-ios-toolkit", "ai-common-toolkit"]
32
+ * @returns {{ keep: string[], skipped: string[] }}
92
33
  */
93
- export function classifyExternalSkill(skillName) {
94
- const lower = skillName.toLowerCase();
95
- if (IOS_PREFIXES.some((p) => lower.startsWith(p))) return "ios";
96
- if (ANDROID_PREFIXES.some((p) => lower.startsWith(p))) return "android";
97
- return "generic";
34
+ export function partitionExternalSkillsByPlugins(externalSrc, enabledPluginNames) {
35
+ const keep = [];
36
+ const skipped = [];
37
+ for (const e of readdirSync(externalSrc, { withFileTypes: true })) {
38
+ if (!e.isDirectory()) continue;
39
+ const plugins = routeSkill(e.name);
40
+ if (plugins.length === 0 || plugins.some((p) => enabledPluginNames.includes(p))) {
41
+ keep.push(e.name);
42
+ } else {
43
+ skipped.push(e.name);
44
+ }
45
+ }
46
+ return { keep, skipped };
98
47
  }
99
48
 
100
49
  /**
101
- * Platform-filtered copy of `shared/external/` skills.
50
+ * Record exactly which external skill dirs THIS install delivered.
102
51
  *
103
- * @param {string} externalSrc - absolute path to `pipeline/skills/shared/external/`
104
- * @param {string} dest - absolute path to target install dir
105
- * @param {{ platformFlag: "ios"|"android"|"all", useSymlinks?: boolean }} opts
106
- * @returns {{ copied: number, skipped: number }}
52
+ * Uninstall needs to remove the distributed catalog without touching
53
+ * user-authored skill dirs that happen to share a name. The shipped
54
+ * `.skills-index.json` cannot answer that: it is the full catalog, so it lists
55
+ * platform-filtered skills that were never installed, and a newer package's
56
+ * tree lists skills the installed version never shipped. Both cases would make
57
+ * uninstall delete a user's own directory. Same contract as
58
+ * `.plugin-skills-manifest.json` for plugin-delivered skills.
59
+ *
60
+ * @param {string} dest
61
+ * @param {string[]} names
107
62
  */
108
- export function copyExternalSkillsFiltered(externalSrc, dest, opts) {
109
- const { platformFlag, useSymlinks = false } = opts;
110
- if (platformFlag === "all") {
111
- copyDir(externalSrc, dest, { useSymlinks });
112
- return { copied: countFiles(externalSrc), skipped: 0 };
113
- }
114
-
115
- ensureDir(dest);
116
- let copied = 0;
117
- let skipped = 0;
118
- for (const entry of readdirSync(externalSrc, { withFileTypes: true })) {
119
- if (!entry.isDirectory()) continue;
120
- const classification = classifyExternalSkill(entry.name);
121
- const shouldCopy = classification === "generic" || classification === platformFlag;
122
- const src = join(externalSrc, entry.name);
123
- const dst = join(dest, entry.name);
124
- if (shouldCopy) {
125
- copyDir(src, dst, { useSymlinks });
126
- copied += countFiles(src);
127
- } else {
128
- skipped += 1;
129
- }
63
+ export function writeExternalSkillsManifest(dest, names) {
64
+ if (isDryRun()) return;
65
+ try {
66
+ writeFileSync(
67
+ join(dest, EXTERNAL_SKILLS_MANIFEST),
68
+ JSON.stringify([...names].sort(), null, 2) + "\n",
69
+ );
70
+ } catch {
71
+ /* best-effort - a missing manifest just means uninstall falls back */
130
72
  }
131
- return { copied, skipped };
132
73
  }