@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.
- package/CHANGELOG.md +186 -6
- package/README.md +19 -12
- package/README.tr.md +19 -12
- package/SECURITY.md +43 -0
- package/docs/FIGMA_PIPELINE.md +3 -3
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0007-multi-tool-adapter-framework.md +1 -1
- package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +13 -13
- package/docs/ecosystem.md +31 -31
- package/docs/features.md +5 -5
- package/index.js +6 -1
- package/install/_codex-agents.mjs +11 -2
- package/install/_common.mjs +109 -3
- package/install/_dev-only-files.mjs +0 -1
- package/install/_platform-filter.mjs +54 -113
- package/install/_plugin-skills.mjs +36 -36
- package/install/claude.mjs +251 -61
- package/install/codex.mjs +28 -6
- package/install/copilot.mjs +69 -9
- package/install/index.mjs +9 -3
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +3 -3
- package/package.json +2 -3
- package/pipeline/commands/multi-agent/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
- package/pipeline/commands/multi-agent/refactor/SKILL.md +36 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +8 -8
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/stack/SKILL.md +62 -40
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +4 -4
- package/pipeline/lib/issue-fetcher.sh +1 -1
- package/pipeline/lib/parse-complaints.sh +316 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +3 -3
- package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
- package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
- package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
- package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/phases/modes.md +1 -1
- package/pipeline/multi-agent-refs/phases/operations.md +7 -1
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +6 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +3 -2
- package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
- package/pipeline/preferences-template.json +18 -5
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +1 -1
- package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
- package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
- package/pipeline/schemas/prefs.schema.json +296 -66
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/README.md +4 -3
- package/pipeline/scripts/_stack-routing.mjs +79 -0
- package/pipeline/scripts/audit-log-rotate.sh +4 -1
- package/pipeline/scripts/build-skills-index.mjs +11 -0
- package/pipeline/scripts/build-stack-plugins.mjs +28 -60
- package/pipeline/scripts/check-derived-drift.mjs +55 -28
- package/pipeline/scripts/gc-worktrees.sh +4 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/scripts/match-skills.mjs +12 -2
- package/pipeline/scripts/migrate-prefs.mjs +33 -21
- package/pipeline/scripts/phase-tracker.sh +32 -5
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
- package/pipeline/scripts/run-aggregator.mjs +7 -2
- package/pipeline/scripts/scan-agent-config.sh +1 -1
- package/pipeline/scripts/skill-conformance.mjs +165 -30
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/test-gap-rules/android.json +25 -0
- package/pipeline/scripts/test-gap-rules/ios.json +34 -0
- package/pipeline/scripts/test-gap-rules/node.json +29 -0
- package/pipeline/scripts/test-gap-rules/python.json +25 -0
- package/pipeline/scripts/uninstall.mjs +160 -11
- package/pipeline/scripts/usage-report.mjs +426 -0
- package/pipeline/scripts/validate-complaint-doc.mjs +250 -0
- package/pipeline/scripts/validate-reviewer.mjs +9 -3
- package/pipeline/skills/.skill-manifest.json +156 -108
- package/pipeline/skills/.skills-index.json +449 -12
- package/pipeline/skills/shared/README.md +14 -10
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +153 -90
- package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +6 -6
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +89 -22
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +1 -1
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
- package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
- package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
- package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
- package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +53 -10
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +4 -3
- 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
|
|
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 ·
|
|
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-
|
|
28
|
-
ANDP["ai-android-
|
|
29
|
-
FEP["ai-frontend-
|
|
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-
|
|
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/>
|
|
67
|
-
CC -->|"Step 2b: transform<br/>(install.js --codex)"| COD["Codex CLI<br/>1 router skill +
|
|
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["
|
|
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["
|
|
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
|
|
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
|
|
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
|
|
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
|
|
99
|
-
`figma-utility`, `code-connect`, `branch-and-pr`, `fix-bug`, and the rest
|
|
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/>
|
|
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-
|
|
111
|
-
EXT -->|"Apple/Xcode-only"| IOSP["ai-ios-
|
|
112
|
-
EXT -->|"Android/Kotlin-only"| ANDP["ai-android-
|
|
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-
|
|
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
|
|
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
|
|
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** |
|
|
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-
|
|
155
|
-
| **Component dispatch (Phase 3)** | Marketplace plugin's `create-component`/`create-screen` skill via the Skill tool | No plugin loader
|
|
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-
|
|
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`
|
|
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-
|
|
225
|
-
`create-component`) and dispatch still resolves correctly
|
|
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
|
|
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-
|
|
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-
|
|
52
|
-
/multi-agent:stack android # ai-android-
|
|
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-
|
|
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-
|
|
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
|
|
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.
|
|
28
|
-
*
|
|
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" },
|
package/install/_common.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2
|
+
* Stack partition + delivery manifest for the external skill catalog.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
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 {
|
|
14
|
+
import { isDryRun } from "./_common.mjs";
|
|
15
|
+
import { routeSkill } from "../pipeline/scripts/_stack-routing.mjs";
|
|
13
16
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
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
|
-
*
|
|
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}
|
|
91
|
-
* @
|
|
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
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
*
|
|
50
|
+
* Record exactly which external skill dirs THIS install delivered.
|
|
102
51
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
}
|