@mmerterden/multi-agent-pipeline 12.5.0 → 12.7.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 (290) hide show
  1. package/CHANGELOG.md +213 -0
  2. package/README.md +19 -19
  3. package/docs/FIGMA_PIPELINE.md +34 -34
  4. package/docs/adr/0001-three-model-triage.md +12 -12
  5. package/docs/adr/0002-instruction-driven-flag.md +5 -5
  6. package/docs/adr/0003-unified-shared-skills.md +5 -5
  7. package/docs/adr/0004-zero-dependency-philosophy.md +5 -5
  8. package/docs/adr/0005-lazy-phase-docs.md +2 -2
  9. package/docs/adr/0006-skills-core-external-split.md +6 -6
  10. package/docs/adr/0007-multi-tool-adapter-framework.md +19 -19
  11. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +19 -19
  12. package/docs/adr/README.md +1 -1
  13. package/docs/best-practices.md +3 -3
  14. package/docs/features.md +48 -28
  15. package/docs/performance.md +16 -16
  16. package/docs/recovery-guide.md +39 -39
  17. package/index.js +11 -5
  18. package/install/_common.mjs +5 -11
  19. package/install/_copilot-instructions.mjs +2 -2
  20. package/install/_dev-only-files.mjs +2 -1
  21. package/install/_platform-filter.mjs +1 -1
  22. package/install/_telemetry.mjs +1 -1
  23. package/install/claude.mjs +10 -9
  24. package/install/copilot.mjs +10 -19
  25. package/install/index.mjs +7 -15
  26. package/install/templates/copilot-instructions.md +54 -54
  27. package/install.js +1 -1
  28. package/package.json +15 -11
  29. package/pipeline/agents/security-auditor.md +1 -1
  30. package/pipeline/commands/archive-guard.md +5 -5
  31. package/pipeline/commands/multi-agent/SKILL.md +3 -1
  32. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  33. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -1
  34. package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -1
  35. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -1
  36. package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -1
  37. package/pipeline/commands/multi-agent/design-check/SKILL.md +287 -0
  38. package/pipeline/commands/multi-agent/dev/SKILL.md +1 -1
  39. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -1
  40. package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -1
  41. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -1
  42. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -1
  43. package/pipeline/commands/multi-agent/finish/SKILL.md +6 -6
  44. package/pipeline/commands/multi-agent/forget/SKILL.md +1 -1
  45. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  46. package/pipeline/commands/multi-agent/help/SKILL.md +46 -6
  47. package/pipeline/commands/multi-agent/issue/SKILL.md +1 -1
  48. package/pipeline/commands/multi-agent/jira/SKILL.md +1 -1
  49. package/pipeline/commands/multi-agent/kill/SKILL.md +1 -1
  50. package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
  51. package/pipeline/commands/multi-agent/local/SKILL.md +1 -1
  52. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -1
  53. package/pipeline/commands/multi-agent/log/SKILL.md +1 -1
  54. package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -1
  55. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -1
  56. package/pipeline/commands/multi-agent/purge/SKILL.md +1 -1
  57. package/pipeline/commands/multi-agent/refactor/SKILL.md +107 -19
  58. package/pipeline/commands/multi-agent/resume/SKILL.md +2 -2
  59. package/pipeline/commands/multi-agent/review/SKILL.md +2 -2
  60. package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -1
  61. package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -1
  62. package/pipeline/commands/multi-agent/routines/SKILL.md +1 -1
  63. package/pipeline/commands/multi-agent/save/SKILL.md +1 -1
  64. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -1
  65. package/pipeline/commands/multi-agent/search/SKILL.md +1 -1
  66. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  67. package/pipeline/commands/multi-agent/stack/SKILL.md +3 -3
  68. package/pipeline/commands/multi-agent/status/SKILL.md +1 -1
  69. package/pipeline/commands/multi-agent/sync/SKILL.md +123 -16
  70. package/pipeline/commands/multi-agent/test/SKILL.md +2 -2
  71. package/pipeline/commands/multi-agent/uninstall/SKILL.md +1 -1
  72. package/pipeline/commands/multi-agent/update/SKILL.md +3 -3
  73. package/pipeline/commands/sim-test.md +5 -5
  74. package/pipeline/eval/run-metrics-fixture.json +60 -13
  75. package/pipeline/lib/account-resolver.sh +1 -1
  76. package/pipeline/lib/channels-multi-repo.sh +1 -1
  77. package/pipeline/lib/context-link-extractor.sh +1 -1
  78. package/pipeline/lib/credential-store.sh +33 -1
  79. package/pipeline/lib/fetch-confluence.sh +1 -1
  80. package/pipeline/lib/fetch-crashlytics.sh +1 -1
  81. package/pipeline/lib/fetch-fortify.sh +1 -1
  82. package/pipeline/lib/fetch-graylog.sh +1 -1
  83. package/pipeline/lib/fetch-swagger.sh +1 -1
  84. package/pipeline/lib/issue-fetcher.sh +1 -1
  85. package/pipeline/lib/multi-repo-pipeline.sh +1 -1
  86. package/pipeline/lib/repo-cache.sh +1 -1
  87. package/pipeline/lib/submodule-detector.sh +1 -1
  88. package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
  89. package/pipeline/multi-agent-refs/component-generation.md +121 -0
  90. package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
  91. package/pipeline/multi-agent-refs/features/model-fallback.md +2 -2
  92. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +2 -2
  93. package/pipeline/multi-agent-refs/phases/phase-4-review.md +1 -1
  94. package/pipeline/multi-agent-refs/phases/phase-5-test.md +4 -4
  95. package/pipeline/preferences-template.json +23 -12
  96. package/pipeline/schemas/agent-state.schema.json +125 -5
  97. package/pipeline/schemas/analysis-output.schema.json +18 -4
  98. package/pipeline/schemas/analysis-spec.schema.json +120 -32
  99. package/pipeline/schemas/clarify-output.schema.json +15 -5
  100. package/pipeline/schemas/design-check-config.schema.json +183 -0
  101. package/pipeline/schemas/dev-critic-output.schema.json +20 -5
  102. package/pipeline/schemas/figma-project-config.schema.json +42 -10
  103. package/pipeline/schemas/learnings-ledger.schema.json +10 -2
  104. package/pipeline/schemas/migrations/figma-config-1.0.0-to-2.0.0.mjs +1 -4
  105. package/pipeline/schemas/migrations/prefs-2.0.0-to-2.1.0.mjs +24 -7
  106. package/pipeline/schemas/migrations/state-2.0.0-to-2.1.0.mjs +30 -12
  107. package/pipeline/schemas/plan-todos.schema.json +6 -3
  108. package/pipeline/schemas/planning-output.schema.json +5 -1
  109. package/pipeline/schemas/prefs.schema.json +215 -197
  110. package/pipeline/schemas/test-gap.schema.json +5 -5
  111. package/pipeline/schemas/token-budget.json +8 -8
  112. package/pipeline/schemas/triage-corpus.schema.json +1 -1
  113. package/pipeline/scripts/README.md +7 -5
  114. package/pipeline/scripts/aggregate-metrics.mjs +18 -6
  115. package/pipeline/scripts/build-skills-index.mjs +6 -2
  116. package/pipeline/scripts/build-stack-plugins.mjs +142 -39
  117. package/pipeline/scripts/check-derived-drift.mjs +196 -0
  118. package/pipeline/scripts/check-md-links.mjs +6 -2
  119. package/pipeline/scripts/classify-plan-safety.mjs +26 -8
  120. package/pipeline/scripts/cost-budget-check.mjs +11 -6
  121. package/pipeline/scripts/cost-table.json +1 -1
  122. package/pipeline/scripts/diff-explain.mjs +7 -3
  123. package/pipeline/scripts/diff-risk-score.mjs +13 -3
  124. package/pipeline/scripts/eval-golden-tasks-live.mjs +8 -3
  125. package/pipeline/scripts/eval-golden-tasks.mjs +21 -9
  126. package/pipeline/scripts/eval-intent.mjs +8 -4
  127. package/pipeline/scripts/eval-mine-corpus.mjs +14 -4
  128. package/pipeline/scripts/evidence-gate.mjs +7 -2
  129. package/pipeline/scripts/fixtures/install-layout.tsv +7 -7
  130. package/pipeline/scripts/gen-mode-dispatch.mjs +38 -21
  131. package/pipeline/scripts/gen-skills-index.mjs +18 -3
  132. package/pipeline/scripts/learning-curve.mjs +13 -3
  133. package/pipeline/scripts/learnings-ledger.mjs +119 -34
  134. package/pipeline/scripts/lint-mcp-refs.mjs +218 -0
  135. package/pipeline/scripts/lint-skills.mjs +20 -9
  136. package/pipeline/scripts/localize-commands.mjs +6 -1
  137. package/pipeline/scripts/match-skills.mjs +15 -4
  138. package/pipeline/scripts/memory-load.sh +5 -1
  139. package/pipeline/scripts/migrate-prefs.mjs +33 -16
  140. package/pipeline/scripts/phase-tracker.sh +3 -1
  141. package/pipeline/scripts/render-work-summary.sh +4 -1
  142. package/pipeline/scripts/repo-map.mjs +110 -64
  143. package/pipeline/scripts/review-scope.mjs +7 -1
  144. package/pipeline/scripts/routine-registry.mjs +4 -9
  145. package/pipeline/scripts/run-aggregator.mjs +11 -5
  146. package/pipeline/scripts/run-metrics.mjs +13 -8
  147. package/pipeline/scripts/run-smokes.mjs +57 -3
  148. package/pipeline/scripts/scorecard.mjs +258 -0
  149. package/pipeline/scripts/smoke-command-inventory.sh +81 -0
  150. package/pipeline/scripts/smoke-commands-skills-parity.sh +1 -1
  151. package/pipeline/scripts/smoke-compliance-skills.sh +4 -4
  152. package/pipeline/scripts/smoke-context-budget.sh +72 -0
  153. package/pipeline/scripts/smoke-cross-cli-behavior.sh +12 -2
  154. package/pipeline/scripts/smoke-generate-issue.sh +6 -5
  155. package/pipeline/scripts/smoke-model-fallback.sh +1 -1
  156. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +86 -7
  157. package/pipeline/scripts/smoke-own-punctuation.sh +103 -0
  158. package/pipeline/scripts/smoke-per-repo-memory.sh +2 -2
  159. package/pipeline/scripts/smoke-review-readiness.sh +3 -2
  160. package/pipeline/scripts/smoke-schema-validation.sh +19 -5
  161. package/pipeline/scripts/smoke-shadow-git.sh +4 -2
  162. package/pipeline/scripts/smoke-workflow-audit.sh +43 -11
  163. package/pipeline/scripts/smoke-write-state.sh +49 -5
  164. package/pipeline/scripts/test-gap-rules/android.json +11 -11
  165. package/pipeline/scripts/test-gap-rules/ios.json +16 -11
  166. package/pipeline/scripts/test-gap-rules/node.json +19 -7
  167. package/pipeline/scripts/test-gap-rules/python.json +10 -4
  168. package/pipeline/scripts/test-gap-scan.mjs +44 -12
  169. package/pipeline/scripts/test-integrity-gate.mjs +5 -1
  170. package/pipeline/scripts/token-budget-report.mjs +44 -21
  171. package/pipeline/scripts/triage-memory.mjs +142 -32
  172. package/pipeline/scripts/uninstall.mjs +75 -31
  173. package/pipeline/scripts/validate-analysis-doc.mjs +15 -5
  174. package/pipeline/scripts/validate-diff-risk.mjs +32 -18
  175. package/pipeline/scripts/validate-test-gap.mjs +17 -7
  176. package/pipeline/scripts/validate-triage.mjs +17 -5
  177. package/pipeline/scripts/write-state.mjs +32 -9
  178. package/pipeline/skills/.skill-manifest.json +24 -8
  179. package/pipeline/skills/.skills-index.json +125 -89
  180. package/pipeline/skills/shared/README.md +65 -61
  181. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +10 -8
  182. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +1 -1
  183. package/pipeline/skills/shared/core/multi-agent/SKILL.md +26 -279
  184. package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +1 -1
  185. package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
  186. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +1 -1
  187. package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
  188. package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
  189. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +248 -0
  190. package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +1 -1
  191. package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +1 -1
  192. package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +1 -1
  193. package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +1 -1
  194. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +1 -1
  195. package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
  196. package/pipeline/skills/shared/core/multi-agent-forget/SKILL.md +1 -1
  197. package/pipeline/skills/shared/core/multi-agent-garbage-collect/SKILL.md +1 -1
  198. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +23 -1
  199. package/pipeline/skills/shared/core/multi-agent-issue/SKILL.md +1 -1
  200. package/pipeline/skills/shared/core/multi-agent-jira/SKILL.md +1 -1
  201. package/pipeline/skills/shared/core/multi-agent-kill/SKILL.md +1 -1
  202. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
  203. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +1 -1
  204. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +1 -1
  205. package/pipeline/skills/shared/core/multi-agent-log/SKILL.md +1 -1
  206. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +1 -1
  207. package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +1 -1
  208. package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +1 -1
  209. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +82 -18
  210. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  211. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +2 -2
  212. package/pipeline/skills/shared/core/multi-agent-review-issue/SKILL.md +1 -1
  213. package/pipeline/skills/shared/core/multi-agent-review-jira/SKILL.md +1 -1
  214. package/pipeline/skills/shared/core/multi-agent-routines/SKILL.md +1 -1
  215. package/pipeline/skills/shared/core/multi-agent-save/SKILL.md +1 -1
  216. package/pipeline/skills/shared/core/multi-agent-scan/SKILL.md +1 -1
  217. package/pipeline/skills/shared/core/multi-agent-search/SKILL.md +1 -1
  218. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  219. package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +3 -3
  220. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +1 -1
  221. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +51 -9
  222. package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +2 -2
  223. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +1 -1
  224. package/pipeline/skills/shared/core/multi-agent-update/SKILL.md +1 -1
  225. package/pipeline/skills/shared/external/accessibility-compliance-accessibility-audit/SKILL.md +1 -1
  226. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +4 -4
  227. package/pipeline/skills/shared/external/agentflow/SKILL.md +1 -1
  228. package/pipeline/skills/shared/external/android-jetpack-compose-expert/SKILL.md +1 -1
  229. package/pipeline/skills/shared/external/android_ui_verification/SKILL.md +1 -1
  230. package/pipeline/skills/shared/external/api-patterns/SKILL.md +1 -1
  231. package/pipeline/skills/shared/external/api-security-best-practices/SKILL.md +1 -1
  232. package/pipeline/skills/shared/external/app-store-changelog/SKILL.md +1 -1
  233. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  234. package/pipeline/skills/shared/external/backlog/SKILL.md +12 -12
  235. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -1
  236. package/pipeline/skills/shared/external/context-compression/SKILL.md +1 -1
  237. package/pipeline/skills/shared/external/council/SKILL.md +3 -3
  238. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -1
  239. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -1
  240. package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +1 -1
  241. package/pipeline/skills/shared/external/docker-expert/SKILL.md +1 -1
  242. package/pipeline/skills/shared/external/fastapi-pro/SKILL.md +1 -1
  243. package/pipeline/skills/shared/external/firebase/SKILL.md +1 -1
  244. package/pipeline/skills/shared/external/github-actions-templates/SKILL.md +1 -1
  245. package/pipeline/skills/shared/external/help-skills/SKILL.md +1 -1
  246. package/pipeline/skills/shared/external/hig-components-content/SKILL.md +1 -1
  247. package/pipeline/skills/shared/external/hig-components-layout/SKILL.md +1 -1
  248. package/pipeline/skills/shared/external/hig-components-status/SKILL.md +1 -1
  249. package/pipeline/skills/shared/external/hig-components-system/SKILL.md +1 -1
  250. package/pipeline/skills/shared/external/hig-foundations/SKILL.md +1 -1
  251. package/pipeline/skills/shared/external/hig-inputs/SKILL.md +1 -1
  252. package/pipeline/skills/shared/external/hig-patterns/SKILL.md +1 -1
  253. package/pipeline/skills/shared/external/hig-platforms/SKILL.md +1 -1
  254. package/pipeline/skills/shared/external/hig-technologies/SKILL.md +1 -1
  255. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -1
  256. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -1
  257. package/pipeline/skills/shared/external/ios-debugger-agent/SKILL.md +1 -1
  258. package/pipeline/skills/shared/external/ios-developer/SKILL.md +1 -1
  259. package/pipeline/skills/shared/external/kotlin-coroutines-expert/SKILL.md +1 -1
  260. package/pipeline/skills/shared/external/macos-menubar-tuist-app/SKILL.md +1 -1
  261. package/pipeline/skills/shared/external/macos-spm-app-packaging/SKILL.md +1 -1
  262. package/pipeline/skills/shared/external/monorepo-architect/SKILL.md +1 -1
  263. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -1
  264. package/pipeline/skills/shared/external/nodejs-backend-patterns/SKILL.md +1 -1
  265. package/pipeline/skills/shared/external/observability-engineer/SKILL.md +1 -1
  266. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -1
  267. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -1
  268. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -1
  269. package/pipeline/skills/shared/external/search-first/SKILL.md +2 -2
  270. package/pipeline/skills/shared/external/skill-creator/SKILL.md +12 -12
  271. package/pipeline/skills/shared/external/skill-creator/audit.md +21 -21
  272. package/pipeline/skills/shared/external/skill-creator/checklist.md +3 -3
  273. package/pipeline/skills/shared/external/skill-creator/examples.md +10 -10
  274. package/pipeline/skills/shared/external/skill-creator/label-check.md +17 -17
  275. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +86 -50
  276. package/pipeline/skills/shared/external/skill-creator/template.md +9 -9
  277. package/pipeline/skills/shared/external/swift-concurrency-expert/SKILL.md +1 -1
  278. package/pipeline/skills/shared/external/swiftui-performance-audit/SKILL.md +1 -1
  279. package/pipeline/skills/shared/external/swiftui-ui-patterns/SKILL.md +1 -1
  280. package/pipeline/skills/shared/external/swiftui-view-refactor/SKILL.md +1 -1
  281. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -1
  282. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -1
  283. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -1
  284. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -1
  285. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -1
  286. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -1
  287. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -1
  288. package/pipeline/skills/shared/external/xcode-build-benchmark/schemas/build-benchmark.schema.json +9 -49
  289. package/pipeline/skills/skills-index.md +63 -59
  290. package/pipeline/scripts/smoke-plugin-validate.sh +0 -64
@@ -19,14 +19,14 @@
19
19
  "required": ["sourcesScanned", "testsScanned", "gapCount", "gapBySeverity"],
20
20
  "properties": {
21
21
  "sourcesScanned": { "type": "integer", "minimum": 0 },
22
- "testsScanned": { "type": "integer", "minimum": 0 },
23
- "gapCount": { "type": "integer", "minimum": 0 },
22
+ "testsScanned": { "type": "integer", "minimum": 0 },
23
+ "gapCount": { "type": "integer", "minimum": 0 },
24
24
  "gapBySeverity": {
25
25
  "type": "object",
26
26
  "additionalProperties": false,
27
27
  "properties": {
28
- "blocking": { "type": "integer", "minimum": 0 },
29
- "important": { "type": "integer", "minimum": 0 },
28
+ "blocking": { "type": "integer", "minimum": 0 },
29
+ "important": { "type": "integer", "minimum": 0 },
30
30
  "suggestion": { "type": "integer", "minimum": 0 }
31
31
  },
32
32
  "required": ["blocking", "important", "suggestion"]
@@ -41,7 +41,7 @@
41
41
  "required": ["sourcePath", "symbol", "kind", "severity", "expectedTestPaths", "hint"],
42
42
  "properties": {
43
43
  "sourcePath": { "type": "string", "minLength": 1 },
44
- "symbol": { "type": "string", "minLength": 1 },
44
+ "symbol": { "type": "string", "minLength": 1 },
45
45
  "kind": {
46
46
  "type": "string",
47
47
  "description": "Pattern id from the rules file (e.g. public_func, composable_fun, named_export)."
@@ -3,14 +3,14 @@
3
3
  "$id": "https://github.com/mmerterden/multi-agent-pipeline/pipeline/schemas/token-budget.json",
4
4
  "description": "Per-phase token budget for lazy-loaded pipeline docs. Enforced by smoke-token-budget.sh.",
5
5
  "phases": {
6
- "phase-0-init": { "max_tokens": 12400, "warn_tokens": 10900 },
7
- "phase-1-analysis": { "max_tokens": 3750, "warn_tokens": 3300 },
8
- "phase-2-planning": { "max_tokens": 6500, "warn_tokens": 5750 },
9
- "phase-3-dev": { "max_tokens": 7900, "warn_tokens": 6950 },
10
- "phase-4-review": { "max_tokens": 13250, "warn_tokens": 11650 },
11
- "phase-5-test": { "max_tokens": 2550, "warn_tokens": 2250 },
12
- "phase-6-commit": { "max_tokens": 6150, "warn_tokens": 5400 },
13
- "phase-7-report": { "max_tokens": 5600, "warn_tokens": 4950 }
6
+ "phase-0-init": { "max_tokens": 12400, "warn_tokens": 10900 },
7
+ "phase-1-analysis": { "max_tokens": 3750, "warn_tokens": 3300 },
8
+ "phase-2-planning": { "max_tokens": 6500, "warn_tokens": 5750 },
9
+ "phase-3-dev": { "max_tokens": 7900, "warn_tokens": 6950 },
10
+ "phase-4-review": { "max_tokens": 13250, "warn_tokens": 11650 },
11
+ "phase-5-test": { "max_tokens": 2550, "warn_tokens": 2250 },
12
+ "phase-6-commit": { "max_tokens": 6150, "warn_tokens": 5400 },
13
+ "phase-7-report": { "max_tokens": 5600, "warn_tokens": 4950 }
14
14
  },
15
15
  "total_max_tokens": 51000,
16
16
  "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."
@@ -24,7 +24,7 @@
24
24
  "file": { "type": "string" },
25
25
  "line": { "type": ["integer", "null"] },
26
26
  "issue": { "type": "string", "minLength": 1 },
27
- "fix": { "type": ["string", "null"] },
27
+ "fix": { "type": ["string", "null"] },
28
28
  "reason": { "type": ["string", "null"] },
29
29
  "reviewer": { "type": ["string", "null"] },
30
30
  "file_sha": {
@@ -1,10 +1,10 @@
1
1
  # Pipeline Scripts - Category Index
2
2
 
3
- The `pipeline/scripts/` directory holds 59 shell scripts + 12 Node.js `.mjs` scripts in a flat layout. v6.0.0 evaluated moving them into `smoke/`, `hooks/`, `runtime/` subdirs and decided against it: each path change would need to propagate through `package.json` globs, `install.js` deploy logic, npm scripts, sibling script calls, and CI workflow references - a 40+ file touch radius with high regression risk for minor polish value.
3
+ The `pipeline/scripts/` directory holds 148 shell scripts + 45 Node.js `.mjs` scripts in a flat layout. v6.0.0 evaluated moving them into `smoke/`, `hooks/`, `runtime/` subdirs and decided against it: each path change would need to propagate through `package.json` globs, `install.js` deploy logic, npm scripts, sibling script calls, and CI workflow references - a 40+ file touch radius with high regression risk for minor polish value.
4
4
 
5
- Instead, this README is the category index. Use it to navigate.
5
+ Instead, this README is a curated category index of the key scripts, not an exhaustive listing. Use it to navigate.
6
6
 
7
- ## Smoke scripts (42 files - `smoke-*.sh`)
7
+ ## Smoke scripts (118 files - `smoke-*.sh`)
8
8
 
9
9
  Validate contracts. Each emits `══ <name> smoke: N passed, M failed ══` on completion (the `smoke-personal-data.sh` + `smoke-token-budget.sh` suites use `PASS:` / `warnings:` variants). Run via `npm test` (chained) or individually.
10
10
 
@@ -57,6 +57,7 @@ Validate contracts. Each emits `══ <name> smoke: N passed, M failed ══`
57
57
  - `smoke-search.sh` - `/multi-agent:search` log search + smart ranking
58
58
  - `smoke-subagent-validators.sh` - Phase 1/2 validator schemas
59
59
  - `smoke-telemetry.sh` - opt-in telemetry contract
60
+ - `smoke-learning-curve.sh` - `learning-curve.mjs` metrics-trend contract
60
61
  - `smoke-validator-contradiction.sh` - Phase 4 approved/blocking contradiction detection
61
62
  - `smoke-pre-commit.sh` - pre-commit hook integration
62
63
 
@@ -81,12 +82,13 @@ Shell scripts invoked during pipeline execution.
81
82
  - `github-ssh-setup.sh` - first-run SSH key setup helper
82
83
  - `sync-parity-check.sh` - manual parity verification helper
83
84
 
84
- ## Node.js helpers (12 `.mjs` files)
85
+ ## Node.js helpers (45 `.mjs` files)
85
86
 
86
87
  - `aggregate-metrics.mjs` - collects telemetry aggregates
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`
87
89
  - `eval-triage.mjs` - runs the 10 triage eval fixtures
88
90
  - `gen-skills-index.mjs` - regenerates `pipeline/skills/shared/README.md` skill catalog
89
- - `migrate-prefs.mjs` - preference migration driver (v2.0.0 v2.1.0 → v2.2.0)
91
+ - `migrate-prefs.mjs` - preference migration driver (v2.0.0 through v2.4.0)
90
92
  - `migrate-state.mjs` - generic migration runner (reads `schemas/migrations/`)
91
93
  - `token-budget-report.mjs` - per-phase token usage report
92
94
  - `validate-analysis.mjs`, `validate-planning.mjs`, `validate-reviewer.mjs`, `validate-triage.mjs` - per-phase output validators
@@ -230,7 +230,9 @@ if (opts.markdown) {
230
230
  const lines = [];
231
231
  lines.push(`# Pipeline Metrics Summary`);
232
232
  lines.push(``);
233
- lines.push(`_Source: \`${summary.source}\` · Events: ${summary.total_events} (${summary.parse_errors} parse errors) · Unique tasks: ${summary.unique_tasks}_`);
233
+ lines.push(
234
+ `_Source: \`${summary.source}\` · Events: ${summary.total_events} (${summary.parse_errors} parse errors) · Unique tasks: ${summary.unique_tasks}_`,
235
+ );
234
236
  lines.push(``);
235
237
  lines.push(`## Reviews`);
236
238
  lines.push(``);
@@ -245,9 +247,13 @@ if (opts.markdown) {
245
247
  lines.push(`| Category | Count | Rate |`);
246
248
  lines.push(`|----------|-------|------|`);
247
249
  lines.push(`| Raw findings | ${summary.triage_totals.raw} | - |`);
248
- lines.push(`| Accepted | ${summary.triage_totals.accepted} | ${summary.triage_totals.accept_rate ?? " - "} |`);
250
+ lines.push(
251
+ `| Accepted | ${summary.triage_totals.accepted} | ${summary.triage_totals.accept_rate ?? " - "} |`,
252
+ );
249
253
  lines.push(`| Deferred | ${summary.triage_totals.deferred} | - |`);
250
- lines.push(`| Rejected | ${summary.triage_totals.rejected} | ${summary.triage_totals.reject_rate ?? " - "} |`);
254
+ lines.push(
255
+ `| Rejected | ${summary.triage_totals.rejected} | ${summary.triage_totals.reject_rate ?? " - "} |`,
256
+ );
251
257
 
252
258
  if (Object.keys(summary.edge_cases).length) {
253
259
  lines.push(``);
@@ -266,14 +272,20 @@ if (opts.markdown) {
266
272
  lines.push(``);
267
273
  lines.push(`| Model | Calls | Duration (ms) | Tokens In | Tokens Out | Cached | Cache % |`);
268
274
  lines.push(`|-------|-------|---------------|-----------|------------|--------|---------|`);
269
- for (const [m, t] of Object.entries(summary.cost_per_model).sort((a, b) => b[1].calls - a[1].calls)) {
275
+ for (const [m, t] of Object.entries(summary.cost_per_model).sort(
276
+ (a, b) => b[1].calls - a[1].calls,
277
+ )) {
270
278
  const pct = t.cache_ratio === null ? " - " : `${Math.round(t.cache_ratio * 100)}%`;
271
- lines.push(`| \`${m}\` | ${t.calls} | ${t.duration_ms} | ${t.tokens_in} | ${t.tokens_out} | ${t.tokens_cached} | ${pct} |`);
279
+ lines.push(
280
+ `| \`${m}\` | ${t.calls} | ${t.duration_ms} | ${t.tokens_in} | ${t.tokens_out} | ${t.tokens_cached} | ${pct} |`,
281
+ );
272
282
  }
273
283
  const oc = summary.cache_reuse;
274
284
  const ocPct = oc.cache_ratio === null ? " - " : `${Math.round(oc.cache_ratio * 100)}%`;
275
285
  lines.push(``);
276
- lines.push(`_Overall prompt-cache reuse: ${oc.tokens_cached.toLocaleString()} cached / ${(oc.tokens_in + oc.tokens_cached).toLocaleString()} total input tokens = **${ocPct}**._`);
286
+ lines.push(
287
+ `_Overall prompt-cache reuse: ${oc.tokens_cached.toLocaleString()} cached / ${(oc.tokens_in + oc.tokens_cached).toLocaleString()} total input tokens = **${ocPct}**._`,
288
+ );
277
289
  }
278
290
  console.log(lines.join("\n"));
279
291
  process.exit(0);
@@ -40,7 +40,11 @@ function* walkSkills(dir) {
40
40
  for (const entry of readdirSync(dir)) {
41
41
  const full = join(dir, entry);
42
42
  let st;
43
- try { st = statSync(full); } catch { continue; }
43
+ try {
44
+ st = statSync(full);
45
+ } catch {
46
+ continue;
47
+ }
44
48
  if (st.isDirectory()) yield* walkSkills(full);
45
49
  else if (entry === "SKILL.md") yield full;
46
50
  }
@@ -56,7 +60,7 @@ function parseFrontmatter(raw) {
56
60
  const m = line.match(/^([\w-]+):\s*(.*)$/);
57
61
  if (!m) continue;
58
62
  let value = m[2].trim();
59
- if (value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
63
+ if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
60
64
  out[m[1]] = value;
61
65
  }
62
66
  return out;
@@ -10,7 +10,7 @@
10
10
  * publish artifact.
11
11
  *
12
12
  * The authored lifecycle skills (index / reference / workflow / tools) live in the
13
- * plugin repo and are NEVER touched here only the `knowledge/` layer + plugin.json
13
+ * plugin repo and are NEVER touched here - only the `knowledge/` layer + plugin.json
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
@@ -23,37 +23,83 @@
23
23
  * Exit code 0 always (report-only); prints a JSON-ish summary + per-plugin diff.
24
24
  */
25
25
  import {
26
- readdirSync, existsSync, rmSync, cpSync, readFileSync, writeFileSync, mkdirSync, statSync,
27
- } from 'node:fs';
28
- import { join } from 'node:path';
26
+ readdirSync,
27
+ existsSync,
28
+ rmSync,
29
+ cpSync,
30
+ readFileSync,
31
+ writeFileSync,
32
+ mkdirSync,
33
+ statSync,
34
+ } from "node:fs";
35
+ import { join } from "node:path";
29
36
 
30
37
  const args = process.argv.slice(2);
31
- const getArg = (k, d) => { const i = args.indexOf(k); return i >= 0 ? args[i + 1] : d; };
38
+ const getArg = (k, d) => {
39
+ const i = args.indexOf(k);
40
+ return i >= 0 ? args[i + 1] : d;
41
+ };
32
42
  const HOME = process.env.HOME;
33
- const PLUGINS_REPO = getArg('--plugins-repo', join(HOME, 'multi-agent-plugins'));
34
- const PIPE_ROOT = getArg('--pipeline', join(HOME, 'multi-agent-pipeline'));
35
- const EXTERNAL = join(PIPE_ROOT, 'pipeline/skills/shared/external');
36
- const DRY = args.includes('--dry-run');
43
+ const PLUGINS_REPO = getArg("--plugins-repo", join(HOME, "multi-agent-plugins"));
44
+ const PIPE_ROOT = getArg("--pipeline", join(HOME, "multi-agent-pipeline"));
45
+ const EXTERNAL = join(PIPE_ROOT, "pipeline/skills/shared/external");
46
+ const DRY = args.includes("--dry-run");
37
47
 
38
- const COMMON_PLUGIN = 'ai-common-engineering-toolkit';
39
- const COMMON_SKILLS = ['accessibility-compliance-accessibility-audit', 'firebase', 'humanizer', 'council', 'search-first', 'agent-introspection-debugging', 'skill-creator', 'backlog'];
48
+ const COMMON_PLUGIN = "ai-common-engineering-toolkit";
49
+ const COMMON_SKILLS = [
50
+ "accessibility-compliance-accessibility-audit",
51
+ "firebase",
52
+ "humanizer",
53
+ "council",
54
+ "search-first",
55
+ "agent-introspection-debugging",
56
+ "skill-creator",
57
+ "backlog",
58
+ ];
40
59
 
41
60
  // Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
42
61
  const APPLE_ONLY = [
43
- 'avkit', 'cryptokit', 'metrickit-diagnostics', 'pdfkit', 'spm-build-analysis',
44
- 'swiftlint', 'xcode-build-benchmark', 'xcode-build-fixer', 'xcode-build-orchestrator',
45
- 'xcode-compilation-analyzer', 'xcode-project-analyzer',
62
+ "avkit",
63
+ "cryptokit",
64
+ "metrickit-diagnostics",
65
+ "pdfkit",
66
+ "spm-build-analysis",
67
+ "swiftlint",
68
+ "xcode-build-benchmark",
69
+ "xcode-build-fixer",
70
+ "xcode-build-orchestrator",
71
+ "xcode-compilation-analyzer",
72
+ "xcode-project-analyzer",
46
73
  ];
47
74
 
48
75
  // stack → matching pattern (per-stack routing patterns (formerly in stack-swap.sh))
76
+ //
77
+ // A skill lands in every plugin whose pattern it matches, so a bare substring
78
+ // alternative that is also a suffix of another stack's skill name silently ships
79
+ // that skill twice. `architecture` did exactly that: it pulled
80
+ // `android-architecture` and `swift-architecture` into ai-backend-toolkit
81
+ // alongside their correct plugins, so a backend project loading the toolkit got
82
+ // Compose and SwiftUI architecture skills. It is anchored to the exact name now.
83
+ // Keep any new single-word alternative anchored unless it genuinely reads as a
84
+ // substring across stacks.
49
85
  const STACK_PATTERNS = {
50
- 'ai-ios-engineering-toolkit': /(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,
51
- 'ai-android-engineering-toolkit': /(android|compose-|kotlin-|room-database|retrofit-|gradle-|play-store)/i,
52
- 'ai-backend-toolkit': /(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,
53
- 'ai-frontend-engineering-toolkit': /(react-|nextjs-|typescript-|tailwind-|vue-|web-accessibility|web-performance|css-modern|web-testing|html-semantic)/i,
86
+ "ai-ios-engineering-toolkit":
87
+ /(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,
88
+ "ai-android-engineering-toolkit":
89
+ /(android|compose-|kotlin-|room-database|retrofit-|gradle-|play-store)/i,
90
+ "ai-backend-toolkit":
91
+ /(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,
92
+ "ai-frontend-engineering-toolkit":
93
+ /(react-|nextjs-|typescript-|tailwind-|vue-|web-accessibility|web-performance|css-modern|web-testing|html-semantic)/i,
54
94
  };
55
95
 
56
- const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
96
+ const isDir = (p) => {
97
+ try {
98
+ return statSync(p).isDirectory();
99
+ } catch {
100
+ return false;
101
+ }
102
+ };
57
103
  const listDirs = (p) => (existsSync(p) ? readdirSync(p).filter((n) => isDir(join(p, n))) : []);
58
104
 
59
105
  // --- 1. enumerate authoring source ---------------------------------------
@@ -69,22 +115,67 @@ for (const p of Object.keys(STACK_PATTERNS)) desired[p] = new Set();
69
115
 
70
116
  const unrouted = [];
71
117
  for (const skill of allSkills) {
72
- if (COMMON_SKILLS.includes(skill)) { desired[COMMON_PLUGIN].add(skill); continue; }
73
- if (APPLE_ONLY.includes(skill)) { desired['ai-ios-engineering-toolkit'].add(skill); continue; }
118
+ if (COMMON_SKILLS.includes(skill)) {
119
+ desired[COMMON_PLUGIN].add(skill);
120
+ continue;
121
+ }
122
+ if (APPLE_ONLY.includes(skill)) {
123
+ desired["ai-ios-engineering-toolkit"].add(skill);
124
+ continue;
125
+ }
74
126
  let matched = false;
75
127
  for (const [plugin, re] of Object.entries(STACK_PATTERNS)) {
76
- if (re.test(skill)) { desired[plugin].add(skill); matched = true; }
128
+ if (re.test(skill)) {
129
+ desired[plugin].add(skill);
130
+ matched = true;
131
+ }
77
132
  }
78
133
  if (!matched) unrouted.push(skill);
79
134
  }
80
135
 
136
+ // --- 2b. routing audit ---------------------------------------------------
137
+ // The loop above adds a skill to EVERY plugin whose pattern matches, so a bare
138
+ // substring alternative that also reads as a suffix of another stack's skill
139
+ // name ships that skill into two plugins. `architecture` did that to
140
+ // android-architecture and swift-architecture, which is how ai-backend-toolkit
141
+ // came to carry Compose and SwiftUI guidance.
142
+ //
143
+ // --check-routing runs this audit alone. It reads nothing but the authoring
144
+ // source in this repo, so CI can gate it without a checkout of the plugins repo.
145
+ const multiRouted = [];
146
+ for (const skill of allSkills) {
147
+ if (COMMON_SKILLS.includes(skill) || APPLE_ONLY.includes(skill)) continue;
148
+ const owners = Object.entries(STACK_PATTERNS)
149
+ .filter(([, re]) => re.test(skill))
150
+ .map(([p]) => p);
151
+ if (owners.length > 1) multiRouted.push(`${skill} -> ${owners.join(" + ")}`);
152
+ }
153
+
154
+ if (args.includes("--check-routing")) {
155
+ if (multiRouted.length) {
156
+ console.error(
157
+ `check-routing: ${multiRouted.length} skill(s) routed to more than one stack plugin`,
158
+ );
159
+ for (const m of multiRouted) console.error(` FAIL ${m}`);
160
+ console.error("fix: anchor the offending STACK_PATTERNS alternative to the exact skill name");
161
+ process.exit(1);
162
+ }
163
+ console.log(
164
+ `check-routing: clean (${allSkills.length} skills, ${unrouted.length} unrouted, 0 multi-routed)`,
165
+ );
166
+ process.exit(0);
167
+ }
168
+
81
169
  // --- 3. sync each plugin's knowledge/ + regenerate plugin.json -----------
82
170
  const report = [];
83
171
  for (const [plugin, want] of Object.entries(desired)) {
84
- const pdir = join(PLUGINS_REPO, 'plugins', plugin);
85
- const kdir = join(pdir, 'skills', 'knowledge');
86
- const pjPath = join(pdir, '.claude-plugin', 'plugin.json');
87
- if (!existsSync(pjPath)) { report.push({ plugin, error: 'plugin.json missing' }); continue; }
172
+ const pdir = join(PLUGINS_REPO, "plugins", plugin);
173
+ const kdir = join(pdir, "skills", "knowledge");
174
+ const pjPath = join(pdir, ".claude-plugin", "plugin.json");
175
+ if (!existsSync(pjPath)) {
176
+ report.push({ plugin, error: "plugin.json missing" });
177
+ continue;
178
+ }
88
179
 
89
180
  const have = new Set(listDirs(kdir));
90
181
  const toAdd = [...want].filter((s) => !have.has(s)).sort();
@@ -98,16 +189,16 @@ for (const [plugin, want] of Object.entries(desired)) {
98
189
  }
99
190
 
100
191
  // regenerate plugin.json skills[]: keep non-knowledge entries, rebuild knowledge set
101
- const pj = JSON.parse(readFileSync(pjPath, 'utf8'));
102
- const nonKnowledge = (pj.skills || []).filter((s) => !s.startsWith('./skills/knowledge/'));
192
+ const pj = JSON.parse(readFileSync(pjPath, "utf8"));
193
+ const nonKnowledge = (pj.skills || []).filter((s) => !s.startsWith("./skills/knowledge/"));
103
194
  const finalKnowledge = (DRY ? [...want] : listDirs(kdir)).map((s) => `./skills/knowledge/${s}`);
104
195
  const newSkills = [...new Set([...nonKnowledge, ...finalKnowledge])].sort();
105
196
  const skillsChanged = JSON.stringify(pj.skills) !== JSON.stringify(newSkills);
106
197
 
107
- const oldV = pj.version || '0.1.0';
198
+ const oldV = pj.version || "0.1.0";
108
199
  let newV = oldV;
109
200
  if (changed || skillsChanged) {
110
- const [maj, min, patch] = oldV.split('.').map((n) => parseInt(n, 10) || 0);
201
+ const [maj, min, patch] = oldV.split(".").map((n) => parseInt(n, 10) || 0);
111
202
  newV = `${maj}.${min}.${patch + 1}`;
112
203
  }
113
204
  if (!DRY && (changed || skillsChanged)) {
@@ -115,23 +206,35 @@ for (const [plugin, want] of Object.entries(desired)) {
115
206
  pj.version = newV;
116
207
  writeFileSync(pjPath, `${JSON.stringify(pj, null, 2)}\n`);
117
208
  }
118
- report.push({ plugin, added: toAdd, removed: toRemove, skillCount: newSkills.length, version: oldV === newV ? oldV : `${oldV} → ${newV}`, bumped: oldV !== newV });
209
+ report.push({
210
+ plugin,
211
+ added: toAdd,
212
+ removed: toRemove,
213
+ skillCount: newSkills.length,
214
+ version: oldV === newV ? oldV : `${oldV} → ${newV}`,
215
+ bumped: oldV !== newV,
216
+ });
119
217
  }
120
218
 
121
219
  // --- 4. report ------------------------------------------------------------
122
- console.log(`build-stack-plugins ${DRY ? '(dry-run)' : ''}`);
220
+ console.log(`build-stack-plugins ${DRY ? "(dry-run)" : ""}`);
123
221
  console.log(`source: ${EXTERNAL} (${allSkills.length} skills)`);
124
222
  console.log(`target: ${PLUGINS_REPO}`);
125
223
  for (const r of report) {
126
- if (r.error) { console.log(` ✗ ${r.plugin}: ${r.error}`); continue; }
127
- const flag = r.bumped ? 'BUMPED' : 'unchanged';
224
+ if (r.error) {
225
+ console.log(` ${r.plugin}: ${r.error}`);
226
+ continue;
227
+ }
228
+ const flag = r.bumped ? "BUMPED" : "unchanged";
128
229
  console.log(` ${r.plugin}: skills=${r.skillCount} version=${r.version} [${flag}]`);
129
- if (r.added.length) console.log(` + ${r.added.join(', ')}`);
130
- if (r.removed.length) console.log(` - ${r.removed.join(', ')}`);
230
+ if (r.added.length) console.log(` + ${r.added.join(", ")}`);
231
+ if (r.removed.length) console.log(` - ${r.removed.join(", ")}`);
131
232
  }
132
233
  if (unrouted.length) {
133
- console.log(` ⚠ unrouted (matched no stack, not common/apple-only) NOT copied anywhere:`);
134
- console.log(` ${unrouted.join(', ')}`);
234
+ console.log(` ⚠ unrouted (matched no stack, not common/apple-only) - NOT copied anywhere:`);
235
+ console.log(` ${unrouted.join(", ")}`);
135
236
  }
136
237
  const anyBump = report.some((r) => r.bumped);
137
- console.log(anyBump ? 'result: changes detected (versions bumped)' : 'result: all plugins up to date');
238
+ console.log(
239
+ anyBump ? "result: changes detected (versions bumped)" : "result: all plugins up to date",
240
+ );
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ // check-derived-drift.mjs - resolve the upstream version for every entry in
3
+ // prefs `global.derivedSkillSources[]` and report drift.
4
+ //
5
+ // This exists because the resolution order is the whole correctness question,
6
+ // and prose in a skill file cannot be tested. The step used to read the
7
+ // installed plugin cache and call the answer authoritative. A cache is only as
8
+ // fresh as the last `claude marketplace update`, so it reported "up to date"
9
+ // for a derivation that was four releases behind, and the version it had been
10
+ // recorded from was the wrong manifest besides.
11
+ //
12
+ // Resolution order, first source that answers wins:
13
+ // 1. upstreamLocalClone a working copy of the upstream repo
14
+ // 2. upstreamRepoUrl the same manifest over `gh api`
15
+ // 3. the plugin cache a mirror; can only ever yield "unverified"
16
+ //
17
+ // Which manifest carries the version is `upstreamVersionSource`, default
18
+ // marketplace.json, because that is what a marketplace consumer resolves.
19
+ //
20
+ // Exit codes: 0 = no drift (or not configured), 1 = drift or unverified,
21
+ // 2 = a configured entry could not be resolved at all.
22
+ //
23
+ // Flags: --json machine-readable output for the scorecard gate.
24
+
25
+ import { Buffer } from "node:buffer";
26
+ import { execFileSync } from "node:child_process";
27
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
28
+ import { homedir } from "node:os";
29
+ import { join } from "node:path";
30
+
31
+ const asJson = process.argv.includes("--json");
32
+ // Overridable so the gate can be driven against fixtures. Real runs read the
33
+ // user's prefs; a test that had to write into $HOME to exercise this would be
34
+ // a test nobody runs.
35
+ const PREFS =
36
+ process.env.DERIVED_DRIFT_PREFS || join(homedir(), ".claude", "multi-agent-preferences.json");
37
+ const CACHE = process.env.DERIVED_DRIFT_CACHE || join(homedir(), ".claude", "plugins", "cache");
38
+
39
+ const expandHome = (p) => p.replace(/^\$HOME\b|^~/, homedir());
40
+
41
+ function readJson(path) {
42
+ try {
43
+ return JSON.parse(readFileSync(path, "utf-8"));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ // A manifest names its version either directly (plugin.json) or inside a
50
+ // plugins[] entry keyed by name (marketplace.json).
51
+ function versionFrom(manifest, pluginName) {
52
+ if (!manifest) return null;
53
+ if (Array.isArray(manifest.plugins)) {
54
+ const hit = manifest.plugins.find((p) => p.name === pluginName);
55
+ return hit?.version ?? null;
56
+ }
57
+ return manifest.version ?? null;
58
+ }
59
+
60
+ function fromLocalClone(entry) {
61
+ if (!entry.upstreamLocalClone) return null;
62
+ const root = expandHome(entry.upstreamLocalClone);
63
+ const file =
64
+ entry.upstreamVersionSource === "plugin.json"
65
+ ? join(root, "plugins", entry.upstreamPlugin, ".claude-plugin", "plugin.json")
66
+ : join(root, ".claude-plugin", "marketplace.json");
67
+ if (!existsSync(file)) return null;
68
+ const version = versionFrom(readJson(file), entry.upstreamPlugin);
69
+ if (!version) return null;
70
+
71
+ // A clone behind its own remote is still a stale answer, just a less stale
72
+ // one than the cache. Say so rather than presenting it as current.
73
+ let behind;
74
+ try {
75
+ const out = execFileSync("git", ["-C", root, "rev-list", "--count", "HEAD..@{u}"], {
76
+ encoding: "utf-8",
77
+ stdio: ["ignore", "pipe", "ignore"],
78
+ }).trim();
79
+ behind = Number(out) || 0;
80
+ } catch {
81
+ // No upstream branch configured, or not a git checkout at all.
82
+ behind = null;
83
+ }
84
+
85
+ return { version, source: "local clone", authoritative: true, behind };
86
+ }
87
+
88
+ function fromRepoApi(entry) {
89
+ if (!entry.upstreamRepoUrl) return null;
90
+ const m = entry.upstreamRepoUrl.match(/github\.com\/([^/]+)\/([^/.]+)/);
91
+ if (!m) return null;
92
+ const path =
93
+ entry.upstreamVersionSource === "plugin.json"
94
+ ? `plugins/${entry.upstreamPlugin}/.claude-plugin/plugin.json`
95
+ : ".claude-plugin/marketplace.json";
96
+ try {
97
+ const raw = execFileSync(
98
+ "gh",
99
+ ["api", `repos/${m[1]}/${m[2]}/contents/${path}`, "--jq", ".content"],
100
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] },
101
+ ).trim();
102
+ const version = versionFrom(
103
+ JSON.parse(Buffer.from(raw, "base64").toString("utf-8")),
104
+ entry.upstreamPlugin,
105
+ );
106
+ return version ? { version, source: "repo api", authoritative: true, behind: 0 } : null;
107
+ } catch {
108
+ // 404 here means unreachable for the active account, not "no drift".
109
+ return null;
110
+ }
111
+ }
112
+
113
+ function fromCache(entry) {
114
+ if (!entry.upstreamMarketplace) return null;
115
+ const dir = join(CACHE, entry.upstreamMarketplace, entry.upstreamPlugin);
116
+ if (!existsSync(dir)) return null;
117
+ const versions = readdirSync(dir)
118
+ .filter((d) => /^\d+\.\d+\.\d+$/.test(d))
119
+ .sort();
120
+ const newest = versions[versions.length - 1];
121
+ return newest
122
+ ? { version: newest, source: "plugin cache", authoritative: false, behind: null }
123
+ : null;
124
+ }
125
+
126
+ const prefs = readJson(PREFS);
127
+ const entries = prefs?.global?.derivedSkillSources ?? [];
128
+
129
+ if (entries.length === 0) {
130
+ const msg = "check-derived-drift: no derived-skill sources configured";
131
+ console.log(
132
+ asJson ? JSON.stringify({ configured: 0, drifted: 0, unverified: 0, entries: [] }) : msg,
133
+ );
134
+ process.exit(0);
135
+ }
136
+
137
+ const rows = [];
138
+ for (const entry of entries) {
139
+ const resolved = fromLocalClone(entry) || fromRepoApi(entry) || fromCache(entry);
140
+ if (!resolved) {
141
+ rows.push({
142
+ label: entry.label,
143
+ plugin: entry.upstreamPlugin,
144
+ from: entry.derivedFromVersion,
145
+ upstream: null,
146
+ source: "unreachable",
147
+ status: "unreachable",
148
+ });
149
+ continue;
150
+ }
151
+ const drifted = resolved.version !== entry.derivedFromVersion;
152
+ const status = drifted
153
+ ? "drift"
154
+ : resolved.authoritative
155
+ ? "up to date"
156
+ : "unverified (cache only)";
157
+ rows.push({
158
+ label: entry.label,
159
+ plugin: entry.upstreamPlugin,
160
+ from: entry.derivedFromVersion,
161
+ upstream: resolved.version,
162
+ source: resolved.source,
163
+ cloneBehind: resolved.behind,
164
+ status,
165
+ });
166
+ }
167
+
168
+ const drifted = rows.filter((r) => r.status === "drift").length;
169
+ const unverified = rows.filter((r) => r.status.startsWith("unverified")).length;
170
+ const unreachable = rows.filter((r) => r.status === "unreachable").length;
171
+
172
+ if (asJson) {
173
+ console.log(
174
+ JSON.stringify(
175
+ { configured: rows.length, drifted, unverified, unreachable, entries: rows },
176
+ null,
177
+ 2,
178
+ ),
179
+ );
180
+ } else {
181
+ for (const r of rows) {
182
+ const behind = r.cloneBehind ? ` [clone ${r.cloneBehind} behind its remote]` : "";
183
+ console.log(
184
+ ` ${r.status === "up to date" ? "✓" : "✗"} ${r.plugin}: recorded ${r.from}, ` +
185
+ `upstream ${r.upstream ?? "?"} (${r.source})${behind} -> ${r.status}`,
186
+ );
187
+ }
188
+ console.log("");
189
+ console.log(
190
+ `══ check-derived-drift: ${rows.length - drifted - unverified - unreachable} passed, ` +
191
+ `${drifted + unverified + unreachable} failed ══`,
192
+ );
193
+ }
194
+
195
+ if (unreachable > 0) process.exit(2);
196
+ process.exit(drifted + unverified > 0 ? 1 : 0);
@@ -33,8 +33,12 @@ function mdFiles(dir, acc = []) {
33
33
  }
34
34
 
35
35
  const targets = [
36
- ...readdirSync(repoRoot).filter((f) => f.endsWith(".md")).map((f) => join(repoRoot, f)),
37
- ...["docs", "examples", "pipeline"].filter((d) => existsSync(join(repoRoot, d))).flatMap((d) => mdFiles(join(repoRoot, d))),
36
+ ...readdirSync(repoRoot)
37
+ .filter((f) => f.endsWith(".md"))
38
+ .map((f) => join(repoRoot, f)),
39
+ ...["docs", "examples", "pipeline"]
40
+ .filter((d) => existsSync(join(repoRoot, d)))
41
+ .flatMap((d) => mdFiles(join(repoRoot, d))),
38
42
  // CHANGELOG-archive.md is frozen history - it legitimately references docs
39
43
  // that were deleted in later releases.
40
44
  ]