@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
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"warn_tokens": 5600
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
-
"total_max_tokens":
|
|
40
|
-
"note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room. Total bumped 52200 -> 52700 at v14.1.0 after two more contracts landed: stack skill routing (Phase 3 pre-flight step 9) and worktree finalize (Phase 6 step 9). Compression came first, as always, and twice: 224 tokens out of Phase 3 by pointing its criteria-ledger and routing steps at their feature files instead of restating them, and 190 out of Phase 6 by moving the finalize contract into refs/features/worktree-finalize.md and leaving the invocation plus the exit-3 semantics. Both new contracts follow the pattern the earlier ones set: the phase doc carries the call and the decision, the feature file carries the reasoning, and the feature files are outside this budget because it loops only the eight phase-N-* keys. Every per-phase max still passes (phase-3 7677/8950, phase-6 5223/6150 and both under warn); only the aggregate needed room. Total bumped 52700 -> 52750 for the Phase 0 Step 3 branch-persistence correction: the step wrote the legacy `projects[].branches` while the TTL filter two sections below read `global.recentBranches`, and both spots named a `{name, lastUsed}` shape the schema rejects (`branch` required, `additionalProperties: false`), so the recent-branch picker option could never populate and a literal implementation would have failed prefs validation. Naming the right target, the right key and the legacy field to avoid costs 41 tokens over the one line it replaces. Compression came first and was applied three times to the replacement text itself, from 120 tokens down to 66, by moving the rationale out of the phase doc entirely: the reasoning now lives where it is enforced, in the migrate-prefs carry-forward comment and the smoke-pref-migration f7 block, leaving the phase doc with only the instruction. 50 was the smallest step that clears it; phase-0-init sits at 10893/12400, far under its own max, so this is purely an aggregate ceiling."
|
|
39
|
+
"total_max_tokens": 53100,
|
|
40
|
+
"note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room. Total bumped 52200 -> 52700 at v14.1.0 after two more contracts landed: stack skill routing (Phase 3 pre-flight step 9) and worktree finalize (Phase 6 step 9). Compression came first, as always, and twice: 224 tokens out of Phase 3 by pointing its criteria-ledger and routing steps at their feature files instead of restating them, and 190 out of Phase 6 by moving the finalize contract into refs/features/worktree-finalize.md and leaving the invocation plus the exit-3 semantics. Both new contracts follow the pattern the earlier ones set: the phase doc carries the call and the decision, the feature file carries the reasoning, and the feature files are outside this budget because it loops only the eight phase-N-* keys. Every per-phase max still passes (phase-3 7677/8950, phase-6 5223/6150 and both under warn); only the aggregate needed room. Total bumped 52700 -> 52750 for the Phase 0 Step 3 branch-persistence correction: the step wrote the legacy `projects[].branches` while the TTL filter two sections below read `global.recentBranches`, and both spots named a `{name, lastUsed}` shape the schema rejects (`branch` required, `additionalProperties: false`), so the recent-branch picker option could never populate and a literal implementation would have failed prefs validation. Naming the right target, the right key and the legacy field to avoid costs 41 tokens over the one line it replaces. Compression came first and was applied three times to the replacement text itself, from 120 tokens down to 66, by moving the rationale out of the phase doc entirely: the reasoning now lives where it is enforced, in the migrate-prefs carry-forward comment and the smoke-pref-migration f7 block, leaving the phase doc with only the instruction. 50 was the smallest step that clears it; phase-0-init sits at 10893/12400, far under its own max, so this is purely an aggregate ceiling. v15.0.0: total 52750 -> 53100, the stack-skill tables in phase-1/2/4 now carry plugin-namespaced names (ai-<stack>-toolkit:<skill>) - functional prefixes, ~170 tokens."
|
|
41
41
|
}
|
|
@@ -82,13 +82,14 @@ Shell scripts invoked during pipeline execution.
|
|
|
82
82
|
- `github-ssh-setup.sh` - first-run SSH key setup helper
|
|
83
83
|
- `sync-parity-check.sh` - manual parity verification helper
|
|
84
84
|
|
|
85
|
-
## Node.js helpers (
|
|
85
|
+
## Node.js helpers (54 `.mjs` files)
|
|
86
86
|
|
|
87
87
|
- `aggregate-metrics.mjs` - collects telemetry aggregates
|
|
88
88
|
- `learning-curve.mjs` - time-bucketed trend over `metrics.jsonl` (first-pass clean rate, review cycles, rework/task, tokens/task, cache ratio); `--bucket`, `--since`, `--json`, `--markdown`
|
|
89
|
-
- `eval-triage.mjs` - runs the
|
|
89
|
+
- `eval-triage.mjs` - runs the 11 triage eval fixtures
|
|
90
|
+
- `eval-mine-corpus.mjs` - manual maintainer CLI: mines `~/.claude/memory/multi-agent/<repo-slug>/triage-corpus.jsonl` from real runs into new triage eval fixtures (dev-only, excluded from the npm package; not part of `npm test`)
|
|
90
91
|
- `gen-skills-index.mjs` - regenerates `pipeline/skills/shared/README.md` skill catalog
|
|
91
|
-
- `migrate-prefs.mjs` - preference migration driver (v2.0.0 through v2.
|
|
92
|
+
- `migrate-prefs.mjs` - preference migration driver (v2.0.0 through v2.6.0)
|
|
92
93
|
- `migrate-state.mjs` - generic migration runner (reads `schemas/migrations/`)
|
|
93
94
|
- `token-budget-report.mjs` - per-phase token usage report
|
|
94
95
|
- `validate-analysis.mjs`, `validate-planning.mjs`, `validate-reviewer.mjs`, `validate-triage.mjs` - per-phase output validators
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// _stack-routing.mjs
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for "which marketplace plugin carries this external
|
|
4
|
+
// skill". Extracted from build-stack-plugins.mjs so the skills-index builder can
|
|
5
|
+
// stamp a `plugin` field per entry without duplicating the routing table - a
|
|
6
|
+
// second copy would drift the first time a pattern changed, and the index would
|
|
7
|
+
// namespace skills into a plugin that no longer carries them.
|
|
8
|
+
//
|
|
9
|
+
// Routing order is load-bearing and mirrors build-stack-plugins step 2:
|
|
10
|
+
// COMMON_SKILLS first (explicit allowlist, wins over any pattern), then
|
|
11
|
+
// APPLE_ONLY (Apple/Xcode-only names no pattern catches), then STACK_PATTERNS.
|
|
12
|
+
// A skill may match several stack patterns; build-stack-plugins ships it to all
|
|
13
|
+
// of them and --check-routing polices accidental multi-routing. routeSkill()
|
|
14
|
+
// returns the full match list so callers can decide.
|
|
15
|
+
|
|
16
|
+
export const COMMON_PLUGIN = "ai-common-toolkit";
|
|
17
|
+
|
|
18
|
+
export const COMMON_SKILLS = [
|
|
19
|
+
"accessibility-compliance-accessibility-audit",
|
|
20
|
+
"firebase",
|
|
21
|
+
"humanizer",
|
|
22
|
+
"council",
|
|
23
|
+
"search-first",
|
|
24
|
+
"agent-introspection-debugging",
|
|
25
|
+
"skill-creator",
|
|
26
|
+
"backlog",
|
|
27
|
+
"localization-reuse-map",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
|
|
31
|
+
export const APPLE_ONLY = [
|
|
32
|
+
"avkit",
|
|
33
|
+
"cryptokit",
|
|
34
|
+
"metrickit-diagnostics",
|
|
35
|
+
"pdfkit",
|
|
36
|
+
"spm-build-analysis",
|
|
37
|
+
"swiftlint",
|
|
38
|
+
"xcode-build-benchmark",
|
|
39
|
+
"xcode-build-fixer",
|
|
40
|
+
"xcode-build-orchestrator",
|
|
41
|
+
"xcode-compilation-analyzer",
|
|
42
|
+
"xcode-project-analyzer",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// stack → matching pattern (per-stack routing patterns (formerly in stack-swap.sh))
|
|
46
|
+
//
|
|
47
|
+
// A skill lands in every plugin whose pattern it matches, so a bare substring
|
|
48
|
+
// alternative that is also a suffix of another stack's skill name silently ships
|
|
49
|
+
// that skill twice. `architecture` did exactly that: it pulled
|
|
50
|
+
// `android-architecture` and `swift-architecture` into ai-backend-toolkit
|
|
51
|
+
// alongside their correct plugins, so a backend project loading the toolkit got
|
|
52
|
+
// Compose and SwiftUI architecture skills. It is anchored to the exact name now.
|
|
53
|
+
// Keep any new single-word alternative anchored unless it genuinely reads as a
|
|
54
|
+
// substring across stacks.
|
|
55
|
+
export const STACK_PATTERNS = {
|
|
56
|
+
"ai-ios-toolkit":
|
|
57
|
+
/(swiftui|swift-|ios-|hig-|apple-|storekit|widgetkit|healthkit|homekit|mapkit|musickit|passkit|pencilkit|realitykit|weatherkit|alarmkit|callkit|cloudkit|coreml|core-|eventkit|energykit|permissionkit|tipkit|shareplay|live-activities|background-processing|app-store|app-clips|app-intents|authentication|contacts-framework|device-integrity|macos-|natural-language|photos-camera|push-notifications|speech-recognition|swiftdata|vision-framework|debugging-instruments|help-skills)/i,
|
|
58
|
+
"ai-android-toolkit":
|
|
59
|
+
/(android|compose-|kotlin-|room-database|retrofit-|gradle-|play-store)/i,
|
|
60
|
+
"ai-backend-toolkit":
|
|
61
|
+
/(fastapi|nodejs|docker|api-pattern|api-security|github-actions|observability|^architecture$|monorepo|clean-code|debugging-strategies|agentflow|closed-loop|context-compression|python-patterns|database-patterns|rest-api-design|testing-backend|ci-cd-pipelines)/i,
|
|
62
|
+
"ai-frontend-toolkit":
|
|
63
|
+
/(react-|nextjs-|typescript-|tailwind-|vue-|web-accessibility|web-performance|css-modern|web-testing|html-semantic)/i,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Which plugins carry this external skill, in routing order.
|
|
68
|
+
* @param {string} name - skill directory name
|
|
69
|
+
* @returns {string[]} plugin names; empty when unrouted
|
|
70
|
+
*/
|
|
71
|
+
export function routeSkill(name) {
|
|
72
|
+
if (COMMON_SKILLS.includes(name)) return [COMMON_PLUGIN];
|
|
73
|
+
if (APPLE_ONLY.includes(name)) return ["ai-ios-toolkit"];
|
|
74
|
+
const hits = [];
|
|
75
|
+
for (const [plugin, re] of Object.entries(STACK_PATTERNS)) {
|
|
76
|
+
if (re.test(name)) hits.push(plugin);
|
|
77
|
+
}
|
|
78
|
+
return hits;
|
|
79
|
+
}
|
|
@@ -57,7 +57,10 @@ if [ -s "$AUDIT_FILE" ]; then
|
|
|
57
57
|
# event appended between the copy and the truncate.
|
|
58
58
|
SPOOL="$AUDIT_DIR/.audit.rotate.$$"
|
|
59
59
|
mv "$AUDIT_FILE" "$SPOOL"
|
|
60
|
-
:
|
|
60
|
+
# touch, not truncate: a writer appending between the mv and this line
|
|
61
|
+
# recreates the live file, and `: >` would destroy that event. touch keeps
|
|
62
|
+
# whatever a racing writer already appended.
|
|
63
|
+
touch "$AUDIT_FILE"
|
|
61
64
|
chmod 600 "$AUDIT_FILE" 2>/dev/null || true
|
|
62
65
|
|
|
63
66
|
# Don't clobber a rotated file from earlier today (multiple invocations).
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { dirname, join, relative, resolve } from "node:path";
|
|
28
28
|
import { fileURLToPath } from "node:url";
|
|
29
|
+
import { routeSkill } from "./_stack-routing.mjs";
|
|
29
30
|
|
|
30
31
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
31
32
|
const repoRoot = resolve(here, "..", "..");
|
|
@@ -92,11 +93,21 @@ for (const full of walkSkills(root)) {
|
|
|
92
93
|
.map((s) => s.trim())
|
|
93
94
|
.filter(Boolean);
|
|
94
95
|
|
|
96
|
+
// External skills reach Claude Code only through the marketplace plugins, so
|
|
97
|
+
// the index carries which plugin owns each one. `invokeAs` is the name a
|
|
98
|
+
// consumer must use: plugin-namespaced on Claude Code, bare elsewhere (the
|
|
99
|
+
// Copilot/Codex copies keep bare directory names). Routing comes from
|
|
100
|
+
// _stack-routing.mjs - the same table build-stack-plugins ships with.
|
|
101
|
+
const plugins = group === "external" ? routeSkill(name) : [];
|
|
102
|
+
const plugin = plugins[0] || null;
|
|
103
|
+
|
|
95
104
|
entries.push({
|
|
96
105
|
name,
|
|
97
106
|
description,
|
|
98
107
|
platform,
|
|
99
108
|
group,
|
|
109
|
+
plugin,
|
|
110
|
+
invokeAs: plugin ? `${plugin}:${name}` : name,
|
|
100
111
|
triggerKeywords: kws,
|
|
101
112
|
triggerPaths: paths,
|
|
102
113
|
relativePath: rel,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* knowledge entries are regenerated.
|
|
15
15
|
*
|
|
16
16
|
* Routing uses the per-stack skill patterns (formerly carried by stack-swap.sh). Genuinely cross-stack
|
|
17
|
-
* skills go to ai-common-
|
|
17
|
+
* skills go to ai-common-toolkit (not into each stack plugin). Apple/
|
|
18
18
|
* Xcode-only vendored skills that match no stack pattern belong to iOS only.
|
|
19
19
|
*
|
|
20
20
|
* Usage:
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "node:fs";
|
|
35
35
|
import { join } from "node:path";
|
|
36
36
|
import { spawnSync } from "node:child_process";
|
|
37
|
+
import { createHash } from "node:crypto";
|
|
37
38
|
|
|
38
39
|
const args = process.argv.slice(2);
|
|
39
40
|
// Accepts both `--key value` and `--key=value`. The `=` form used to fall through to
|
|
@@ -51,54 +52,12 @@ const PIPE_ROOT = getArg("--pipeline", join(HOME, "multi-agent-pipeline"));
|
|
|
51
52
|
const EXTERNAL = join(PIPE_ROOT, "pipeline/skills/shared/external");
|
|
52
53
|
const DRY = args.includes("--dry-run");
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"search-first",
|
|
61
|
-
"agent-introspection-debugging",
|
|
62
|
-
"skill-creator",
|
|
63
|
-
"backlog",
|
|
64
|
-
"localization-reuse-map",
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
|
|
68
|
-
const APPLE_ONLY = [
|
|
69
|
-
"avkit",
|
|
70
|
-
"cryptokit",
|
|
71
|
-
"metrickit-diagnostics",
|
|
72
|
-
"pdfkit",
|
|
73
|
-
"spm-build-analysis",
|
|
74
|
-
"swiftlint",
|
|
75
|
-
"xcode-build-benchmark",
|
|
76
|
-
"xcode-build-fixer",
|
|
77
|
-
"xcode-build-orchestrator",
|
|
78
|
-
"xcode-compilation-analyzer",
|
|
79
|
-
"xcode-project-analyzer",
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
// stack → matching pattern (per-stack routing patterns (formerly in stack-swap.sh))
|
|
83
|
-
//
|
|
84
|
-
// A skill lands in every plugin whose pattern it matches, so a bare substring
|
|
85
|
-
// alternative that is also a suffix of another stack's skill name silently ships
|
|
86
|
-
// that skill twice. `architecture` did exactly that: it pulled
|
|
87
|
-
// `android-architecture` and `swift-architecture` into ai-backend-toolkit
|
|
88
|
-
// alongside their correct plugins, so a backend project loading the toolkit got
|
|
89
|
-
// Compose and SwiftUI architecture skills. It is anchored to the exact name now.
|
|
90
|
-
// Keep any new single-word alternative anchored unless it genuinely reads as a
|
|
91
|
-
// substring across stacks.
|
|
92
|
-
const STACK_PATTERNS = {
|
|
93
|
-
"ai-ios-engineering-toolkit":
|
|
94
|
-
/(swiftui|swift-|ios-|hig-|apple-|storekit|widgetkit|healthkit|homekit|mapkit|musickit|passkit|pencilkit|realitykit|weatherkit|alarmkit|callkit|cloudkit|coreml|core-|eventkit|energykit|permissionkit|tipkit|shareplay|live-activities|background-processing|app-store|app-clips|app-intents|authentication|contacts-framework|device-integrity|macos-|natural-language|photos-camera|push-notifications|speech-recognition|swiftdata|vision-framework|debugging-instruments|help-skills)/i,
|
|
95
|
-
"ai-android-engineering-toolkit":
|
|
96
|
-
/(android|compose-|kotlin-|room-database|retrofit-|gradle-|play-store)/i,
|
|
97
|
-
"ai-backend-toolkit":
|
|
98
|
-
/(fastapi|nodejs|docker|api-pattern|api-security|github-actions|observability|^architecture$|monorepo|clean-code|debugging-strategies|agentflow|closed-loop|context-compression|python-patterns|database-patterns|rest-api-design|testing-backend|ci-cd-pipelines)/i,
|
|
99
|
-
"ai-frontend-engineering-toolkit":
|
|
100
|
-
/(react-|nextjs-|typescript-|tailwind-|vue-|web-accessibility|web-performance|css-modern|web-testing|html-semantic)/i,
|
|
101
|
-
};
|
|
55
|
+
import {
|
|
56
|
+
COMMON_PLUGIN,
|
|
57
|
+
COMMON_SKILLS,
|
|
58
|
+
APPLE_ONLY,
|
|
59
|
+
STACK_PATTERNS,
|
|
60
|
+
} from "./_stack-routing.mjs";
|
|
102
61
|
|
|
103
62
|
const isDir = (p) => {
|
|
104
63
|
try {
|
|
@@ -110,18 +69,23 @@ const isDir = (p) => {
|
|
|
110
69
|
const listDirs = (p) => (existsSync(p) ? readdirSync(p).filter((n) => isDir(join(p, n))) : []);
|
|
111
70
|
|
|
112
71
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
72
|
+
* Content fingerprint for a skill directory: every file's relative path and a
|
|
73
|
+
* hash of its bytes. A size-only fingerprint missed same-length edits, so a
|
|
74
|
+
* content update could ship without a version bump. The whole authoring tree
|
|
75
|
+
* is a few MB of markdown; hashing it per run costs milliseconds and is only
|
|
76
|
+
* used to decide whether a copy + version bump is warranted.
|
|
116
77
|
*/
|
|
117
78
|
function treeFingerprint(dir) {
|
|
118
79
|
const parts = [];
|
|
119
80
|
const walk = (d, rel) => {
|
|
120
|
-
for (const e of readdirSync(d, { withFileTypes: true }).sort((a, b) =>
|
|
81
|
+
for (const e of readdirSync(d, { withFileTypes: true }).sort((a, b) =>
|
|
82
|
+
a.name.localeCompare(b.name),
|
|
83
|
+
)) {
|
|
121
84
|
const full = join(d, e.name);
|
|
122
85
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
123
86
|
if (e.isDirectory()) walk(full, r);
|
|
124
|
-
else if (e.isFile())
|
|
87
|
+
else if (e.isFile())
|
|
88
|
+
parts.push(`${r}:${createHash("sha1").update(readFileSync(full)).digest("hex")}`);
|
|
125
89
|
}
|
|
126
90
|
};
|
|
127
91
|
try {
|
|
@@ -156,7 +120,7 @@ for (const skill of allSkills) {
|
|
|
156
120
|
continue;
|
|
157
121
|
}
|
|
158
122
|
if (APPLE_ONLY.includes(skill)) {
|
|
159
|
-
desired["ai-ios-
|
|
123
|
+
desired["ai-ios-toolkit"].add(skill);
|
|
160
124
|
continue;
|
|
161
125
|
}
|
|
162
126
|
let matched = false;
|
|
@@ -218,7 +182,8 @@ for (const [plugin, want] of Object.entries(desired)) {
|
|
|
218
182
|
const toRemove = [...have].filter((s) => !want.has(s)).sort();
|
|
219
183
|
const setChanged = toAdd.length > 0 || toRemove.length > 0;
|
|
220
184
|
|
|
221
|
-
//
|
|
185
|
+
// Compare EVERY wanted skill against the source, not just the newly added
|
|
186
|
+
// ones, and replace (rm + copy) rather than overlay.
|
|
222
187
|
//
|
|
223
188
|
// This used to copy only on a set change, which quietly broke the single-source
|
|
224
189
|
// claim: editing a skill in shared/external left the plugin serving the old text
|
|
@@ -226,17 +191,20 @@ for (const [plugin, want] of Object.entries(desired)) {
|
|
|
226
191
|
// seeing the plugin copy stay stale while the generator reported "all plugins up
|
|
227
192
|
// to date".
|
|
228
193
|
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
194
|
+
// The rm-before-copy matters too: cpSync overlays and never deletes, so a file
|
|
195
|
+
// REMOVED inside a still-wanted skill survived in every plugin copy forever -
|
|
196
|
+
// and because the overlay kept the orphan on both sides of the fingerprint
|
|
197
|
+
// comparison, the version was not bumped either.
|
|
231
198
|
let contentChanged = false;
|
|
232
199
|
if (!DRY) {
|
|
233
200
|
if (!existsSync(kdir)) mkdirSync(kdir, { recursive: true });
|
|
234
201
|
for (const s of want) {
|
|
235
202
|
const from = join(EXTERNAL, s);
|
|
236
203
|
const to = join(kdir, s);
|
|
237
|
-
|
|
204
|
+
if (treeFingerprint(from) === (existsSync(to) ? treeFingerprint(to) : "")) continue;
|
|
205
|
+
rmSync(to, { recursive: true, force: true });
|
|
238
206
|
cpSync(from, to, { recursive: true });
|
|
239
|
-
|
|
207
|
+
contentChanged = true;
|
|
240
208
|
}
|
|
241
209
|
for (const s of toRemove) rmSync(join(kdir, s), { recursive: true, force: true });
|
|
242
210
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { Buffer } from "node:buffer";
|
|
26
26
|
import { execFileSync } from "node:child_process";
|
|
27
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
27
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { join } from "node:path";
|
|
30
30
|
|
|
@@ -72,29 +72,44 @@ function versionFrom(manifest, pluginName) {
|
|
|
72
72
|
function fromLocalClone(entry) {
|
|
73
73
|
if (!entry.upstreamLocalClone) return null;
|
|
74
74
|
const root = expandHome(entry.upstreamLocalClone);
|
|
75
|
-
const
|
|
75
|
+
const relFile =
|
|
76
76
|
entry.upstreamVersionSource === "plugin.json"
|
|
77
|
-
?
|
|
78
|
-
:
|
|
79
|
-
if (!existsSync(file)) return null;
|
|
80
|
-
const version = versionFrom(readJson(file), entry.upstreamPlugin);
|
|
81
|
-
if (!version) return null;
|
|
77
|
+
? `plugins/${entry.upstreamPlugin}/.claude-plugin/plugin.json`
|
|
78
|
+
: ".claude-plugin/marketplace.json";
|
|
82
79
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
80
|
+
// Read the manifest from origin/main, never the checked-out worktree: a
|
|
81
|
+
// clone parked on an old feature branch reported stale versions as current,
|
|
82
|
+
// flagging correctly-bumped derivations as drifted.
|
|
86
83
|
try {
|
|
87
|
-
const
|
|
84
|
+
const raw = execFileSync("git", ["-C", root, "show", `origin/main:${relFile}`], {
|
|
88
85
|
encoding: "utf-8",
|
|
89
86
|
stdio: ["ignore", "pipe", "ignore"],
|
|
90
|
-
})
|
|
91
|
-
|
|
87
|
+
});
|
|
88
|
+
const version = versionFrom(JSON.parse(raw), entry.upstreamPlugin);
|
|
89
|
+
if (version) {
|
|
90
|
+
// origin/main is only as fresh as the last fetch. Surface fetch age so a
|
|
91
|
+
// months-old ref is not silently trusted.
|
|
92
|
+
let fetchAgeDays = null;
|
|
93
|
+
try {
|
|
94
|
+
const fetchHead = join(root, ".git", "FETCH_HEAD");
|
|
95
|
+
if (existsSync(fetchHead)) {
|
|
96
|
+
fetchAgeDays = Math.floor((Date.now() - statSync(fetchHead).mtimeMs) / 86_400_000);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
fetchAgeDays = null;
|
|
100
|
+
}
|
|
101
|
+
return { version, source: "local clone origin/main", authoritative: true, fetchAgeDays };
|
|
102
|
+
}
|
|
92
103
|
} catch {
|
|
93
|
-
// No
|
|
94
|
-
|
|
104
|
+
// No origin/main ref (not a git checkout, or unfetched): fall through to
|
|
105
|
+
// the worktree file, which is better than nothing but never fresher.
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
const file = join(root, relFile);
|
|
109
|
+
if (!existsSync(file)) return null;
|
|
110
|
+
const version = versionFrom(readJson(file), entry.upstreamPlugin);
|
|
111
|
+
if (!version) return null;
|
|
112
|
+
return { version, source: "local clone worktree", authoritative: true, fetchAgeDays: null };
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
function fromRepoApi(entry) {
|
|
@@ -161,46 +176,58 @@ for (const entry of entries) {
|
|
|
161
176
|
continue;
|
|
162
177
|
}
|
|
163
178
|
const drifted = resolved.version !== entry.derivedFromVersion;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
// An acknowledged drift is a conscious "port pending" decision, pinned to the
|
|
180
|
+
// upstream version it was made against: it stays visible but does not fail
|
|
181
|
+
// the gate, and the moment upstream moves past the pin it is plain drift
|
|
182
|
+
// again. The pin only counts against an AUTHORITATIVE resolution - a cache
|
|
183
|
+
// that happens to hold the pinned version proves nothing about upstream and
|
|
184
|
+
// must not silence the unverified state.
|
|
185
|
+
const acknowledged =
|
|
186
|
+
drifted && resolved.authoritative && entry.driftAcknowledged === resolved.version;
|
|
187
|
+
const status = acknowledged
|
|
188
|
+
? "drift (acknowledged)"
|
|
189
|
+
: drifted
|
|
190
|
+
? "drift"
|
|
191
|
+
: resolved.authoritative
|
|
192
|
+
? "up to date"
|
|
193
|
+
: "unverified (cache only)";
|
|
169
194
|
rows.push({
|
|
170
195
|
label: entry.label,
|
|
171
196
|
plugin: entry.upstreamPlugin,
|
|
172
197
|
from: entry.derivedFromVersion,
|
|
173
198
|
upstream: resolved.version,
|
|
174
199
|
source: resolved.source,
|
|
175
|
-
|
|
200
|
+
fetchAgeDays: resolved.fetchAgeDays ?? null,
|
|
176
201
|
status,
|
|
177
202
|
});
|
|
178
203
|
}
|
|
179
204
|
|
|
180
205
|
const drifted = rows.filter((r) => r.status === "drift").length;
|
|
206
|
+
const acknowledged = rows.filter((r) => r.status === "drift (acknowledged)").length;
|
|
181
207
|
const unverified = rows.filter((r) => r.status.startsWith("unverified")).length;
|
|
182
208
|
const unreachable = rows.filter((r) => r.status === "unreachable").length;
|
|
183
209
|
|
|
184
210
|
if (asJson) {
|
|
185
211
|
console.log(
|
|
186
212
|
JSON.stringify(
|
|
187
|
-
{ configured: rows.length, drifted, unverified, unreachable, entries: rows },
|
|
213
|
+
{ configured: rows.length, drifted, acknowledged, unverified, unreachable, entries: rows },
|
|
188
214
|
null,
|
|
189
215
|
2,
|
|
190
216
|
),
|
|
191
217
|
);
|
|
192
218
|
} else {
|
|
193
219
|
for (const r of rows) {
|
|
194
|
-
const
|
|
220
|
+
const age =
|
|
221
|
+
r.fetchAgeDays != null && r.fetchAgeDays > 7 ? ` [last fetch ${r.fetchAgeDays}d ago]` : "";
|
|
195
222
|
console.log(
|
|
196
|
-
` ${r.status === "up to date" ? "✓" : "✗"} ${r.plugin}: recorded ${r.from}, ` +
|
|
197
|
-
`upstream ${r.upstream ?? "?"} (${r.source})${
|
|
223
|
+
` ${r.status === "up to date" ? "✓" : r.status === "drift (acknowledged)" ? "!" : "✗"} ${r.plugin}: recorded ${r.from}, ` +
|
|
224
|
+
`upstream ${r.upstream ?? "?"} (${r.source})${age} -> ${r.status}`,
|
|
198
225
|
);
|
|
199
226
|
}
|
|
200
227
|
console.log("");
|
|
201
228
|
console.log(
|
|
202
|
-
`══ check-derived-drift: ${rows.length - drifted - unverified - unreachable} passed
|
|
203
|
-
|
|
229
|
+
`══ check-derived-drift: ${rows.length - drifted - unverified - unreachable} passed ` +
|
|
230
|
+
`(${acknowledged} acknowledged), ${drifted + unverified + unreachable} failed ══`,
|
|
204
231
|
);
|
|
205
232
|
}
|
|
206
233
|
|
|
@@ -125,7 +125,10 @@ done
|
|
|
125
125
|
|
|
126
126
|
# 3. Gitlink residue in the index ("Subproject commit" entries under
|
|
127
127
|
# .worktrees/ from a pre-guard `git add -A`).
|
|
128
|
-
|
|
128
|
+
# Path is the tab-separated second field ("mode sha stage\tpath"); splitting on
|
|
129
|
+
# whitespace truncated any path containing a space and the follow-up
|
|
130
|
+
# `git rm --ignore-unmatch` exited 0 on the phantom, reporting a false success.
|
|
131
|
+
gitlinks=$(git -C "$REPO" ls-files -s -- .worktrees 2>/dev/null | awk -F'\t' '$1 ~ /^160000 / { print $2 }')
|
|
129
132
|
if [ -n "$gitlinks" ]; then
|
|
130
133
|
while IFS= read -r path; do
|
|
131
134
|
[ -z "$path" ] && continue
|
|
@@ -82,7 +82,7 @@ md += `**Total:** ${total} skills (${breakdown}). Auto-generated by \`scripts/ge
|
|
|
82
82
|
md += `## Directory layout\n\n`;
|
|
83
83
|
md += `- **\`core/\`** - ${counts.core} \`multi-agent*\` orchestration skills that are pipeline-critical. Edits here are core-code changes.\n`;
|
|
84
84
|
md += `- **\`external/\`** - ${counts.external} iOS / Android / generic skills imported from the upstream skill library. Mirrors of third-party guidance.\n`;
|
|
85
|
-
md += `- Install
|
|
85
|
+
md += `- Install destinations (ADR-0009): Claude Code gets NO local copy of \`external/\` - it loads those skills from the \`multi-agent-plugins\` marketplace, namespaced (\`ai-<stack>-toolkit:<name>\`); only the two compliance catalogs from \`core/\` land in \`~/.claude/skills/\`. Copilot CLI and Codex CLI receive a flat copy filtered to the enabled stacks. \`external/\` remains the single authoring source that \`build-stack-plugins.mjs\` publishes from.\n\n`;
|
|
86
86
|
md += `Source layout is logical grouping only - skill discovery at runtime is unchanged.\n\n`;
|
|
87
87
|
md += `## Categories\n\n`;
|
|
88
88
|
for (const k of order) {
|
|
@@ -52,6 +52,10 @@ function defaultIndexCandidates() {
|
|
|
52
52
|
const repoRoot = resolve(here, "..", ".."); // <repo> when here is <repo>/pipeline/scripts
|
|
53
53
|
return [
|
|
54
54
|
join(installRoot, "skills", ".skills-index.json"),
|
|
55
|
+
// Codex CLI keeps skills under multi-agent-refs/skills (install/codex.mjs
|
|
56
|
+
// writes the index there); without this candidate every dynamic-skill
|
|
57
|
+
// dispatch on Codex exited 1 with "cannot read index".
|
|
58
|
+
join(installRoot, "multi-agent-refs", "skills", ".skills-index.json"),
|
|
55
59
|
join(repoRoot, "pipeline", "skills", ".skills-index.json"),
|
|
56
60
|
];
|
|
57
61
|
}
|
|
@@ -202,13 +206,19 @@ for (const entry of index.entries) {
|
|
|
202
206
|
results.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
203
207
|
const top = results.slice(0, opts.limit);
|
|
204
208
|
|
|
209
|
+
// On Claude Code an external skill resolves only through its marketplace plugin,
|
|
210
|
+
// so surface `invokeAs` (plugin-namespaced) as the primary name. Old indexes
|
|
211
|
+
// without the field fall back to the bare name - same behaviour as before.
|
|
205
212
|
if (opts.json) {
|
|
206
|
-
|
|
213
|
+
const matches = top.map((m) => ({ ...m, invokeAs: m.invokeAs || m.name }));
|
|
214
|
+
process.stdout.write(JSON.stringify({ task: taskText, matches }, null, 2) + "\n");
|
|
207
215
|
} else {
|
|
208
216
|
for (const m of top) {
|
|
209
217
|
const tag = `[${m.group}${m.platform ? "/" + m.platform : ""}]`;
|
|
210
218
|
const desc = (m.description || "").slice(0, 80);
|
|
211
|
-
console.log(
|
|
219
|
+
console.log(
|
|
220
|
+
`${String(m.score).padStart(3)} ${(m.invokeAs || m.name).padEnd(46)} ${tag.padEnd(18)} ${desc}`,
|
|
221
|
+
);
|
|
212
222
|
}
|
|
213
223
|
if (top.length === 0) console.log("(no skills matched)");
|
|
214
224
|
}
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_FILE = path.join(os.homedir(), ".claude", "multi-agent-preferences.json");
|
|
18
19
|
|
|
19
20
|
// Single source of truth for the migration target version. Referenced by the
|
|
20
21
|
// migrate() bump chain AND every user-facing message, so they cannot drift.
|
|
21
|
-
const TARGET_VERSION = "2.
|
|
22
|
+
const TARGET_VERSION = "2.6.0";
|
|
22
23
|
|
|
23
24
|
const USAGE = "Usage: migrate-prefs.mjs [--dry-run] [--file <path>]";
|
|
24
25
|
|
|
@@ -75,7 +76,7 @@ function defaultSettings() {
|
|
|
75
76
|
* @returns {Set<string>}
|
|
76
77
|
*/
|
|
77
78
|
function readMigratableVersions(target) {
|
|
78
|
-
const here = path.dirname(
|
|
79
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
79
80
|
const candidates = [
|
|
80
81
|
path.join(here, "..", "schemas", "prefs.schema.json"),
|
|
81
82
|
path.join(here, "..", "..", "pipeline", "schemas", "prefs.schema.json"),
|
|
@@ -93,7 +94,11 @@ function readMigratableVersions(target) {
|
|
|
93
94
|
}
|
|
94
95
|
// Schema unreadable: refusing to migrate would be worse than migrating from a
|
|
95
96
|
// version this build cannot enumerate, so fall back to the known history.
|
|
96
|
-
|
|
97
|
+
// Every released schemaVersion below TARGET_VERSION belongs here - the
|
|
98
|
+
// 2.4.0->2.5.0 and 2.5.0->2.6.0 bumps both shipped without extending it,
|
|
99
|
+
// which made a schema-less install throw "unknown schemaVersion" on a prefs
|
|
100
|
+
// file one version behind.
|
|
101
|
+
return new Set(["2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0"]);
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
function defaultKeychainMapping() {
|
|
@@ -297,7 +302,9 @@ function migrate(prefs) {
|
|
|
297
302
|
seeded += 1;
|
|
298
303
|
}
|
|
299
304
|
if (seeded > 0) {
|
|
300
|
-
changes.push(
|
|
305
|
+
changes.push(
|
|
306
|
+
`seeded ${seeded} recentBranches entr${seeded === 1 ? "y" : "ies"} for ${projectKey} from legacy projects[].branches`,
|
|
307
|
+
);
|
|
301
308
|
}
|
|
302
309
|
}
|
|
303
310
|
if (!Array.isArray(out.global.recentGroups)) {
|
|
@@ -332,27 +339,32 @@ function migrate(prefs) {
|
|
|
332
339
|
out.global.skillConformance.blockOnCoverageGap = false;
|
|
333
340
|
changes.push("added skillConformance.blockOnCoverageGap=false (report, do not halt)");
|
|
334
341
|
}
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
if (!out.global.
|
|
339
|
-
out.global.
|
|
340
|
-
changes.push("added
|
|
341
|
-
}
|
|
342
|
-
if (typeof out.global.
|
|
343
|
-
const legacyAutoFix =
|
|
344
|
-
|
|
342
|
+
// Rename chain for the pipeline-tail command: /multi-agent:finish (pre-v2.5.0)
|
|
343
|
+
// -> :ship (v2.5.0) -> :resume-local (current). The autoFix value survives every
|
|
344
|
+
// hop - carry whichever legacy block still holds one rather than reset it.
|
|
345
|
+
if (!out.global.resumeLocal || typeof out.global.resumeLocal !== "object") {
|
|
346
|
+
out.global.resumeLocal = {};
|
|
347
|
+
changes.push("added resumeLocal (/multi-agent:ship renamed to :resume-local)");
|
|
348
|
+
}
|
|
349
|
+
if (typeof out.global.resumeLocal.autoFix !== "boolean") {
|
|
350
|
+
const legacyAutoFix =
|
|
351
|
+
typeof out.global.ship?.autoFix === "boolean"
|
|
352
|
+
? out.global.ship.autoFix
|
|
353
|
+
: out.global.finish?.autoFix;
|
|
354
|
+
out.global.resumeLocal.autoFix = typeof legacyAutoFix === "boolean" ? legacyAutoFix : false;
|
|
345
355
|
changes.push(
|
|
346
356
|
typeof legacyAutoFix === "boolean"
|
|
347
|
-
? "moved finish
|
|
348
|
-
: "added
|
|
357
|
+
? "moved ship/finish autoFix -> resumeLocal.autoFix (value preserved)"
|
|
358
|
+
: "added resumeLocal.autoFix=false",
|
|
349
359
|
);
|
|
350
360
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
361
|
+
for (const legacy of ["ship", "finish"]) {
|
|
362
|
+
if (out.global[legacy] && typeof out.global[legacy] === "object") {
|
|
363
|
+
delete out.global[legacy].autoFix;
|
|
364
|
+
if (Object.keys(out.global[legacy]).length === 0) {
|
|
365
|
+
delete out.global[legacy];
|
|
366
|
+
changes.push(`removed obsolete ${legacy} block (renamed to resumeLocal)`);
|
|
367
|
+
}
|
|
356
368
|
}
|
|
357
369
|
}
|
|
358
370
|
if (!out.global.serviceStatus) {
|