@mmerterden/multi-agent-pipeline 17.6.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +76 -18
  3. package/README.tr.md +55 -16
  4. package/docs/adr/0002-instruction-driven-flag.md +1 -0
  5. package/docs/adr/0005-lazy-phase-docs.md +11 -1
  6. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +1 -0
  7. package/docs/adr/0010-own-code-graph.md +1 -0
  8. package/docs/adr/0011-dormant-ci.md +25 -1
  9. package/docs/adr/0014-six-phase-consolidation.md +134 -0
  10. package/docs/adr/README.md +2 -1
  11. package/docs/architecture.md +37 -38
  12. package/docs/best-practices.md +1 -1
  13. package/docs/ecosystem.md +37 -26
  14. package/docs/engineering.md +1 -1
  15. package/docs/facts.json +45 -0
  16. package/docs/features.md +54 -53
  17. package/docs/performance.md +5 -5
  18. package/docs/recovery-guide.md +9 -9
  19. package/docs/server-readiness.md +188 -0
  20. package/docs/token-budget-history.md +3 -1
  21. package/index.js +18 -3
  22. package/install/_codex-agents.mjs +1 -1
  23. package/install/_common.mjs +42 -17
  24. package/install/_dev-only-files.mjs +8 -0
  25. package/install/_unattended-profile.mjs +113 -0
  26. package/install/index.mjs +48 -0
  27. package/install/templates/claude-hooks.json +1 -1
  28. package/install/templates/codex-instructions.md +1 -1
  29. package/install/templates/copilot-instructions.md +28 -28
  30. package/manifest.json +1065 -0
  31. package/package.json +6 -3
  32. package/pipeline/agents/dev-critic.md +3 -3
  33. package/pipeline/commands/figma-to-swiftui.md +1 -1
  34. package/pipeline/commands/multi-agent/SKILL.md +8 -8
  35. package/pipeline/commands/multi-agent/analysis/SKILL.md +9 -9
  36. package/pipeline/commands/multi-agent/autopilot/SKILL.md +7 -7
  37. package/pipeline/commands/multi-agent/channels/SKILL.md +15 -15
  38. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +6 -6
  39. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
  40. package/pipeline/commands/multi-agent/graph/SKILL.md +1 -1
  41. package/pipeline/commands/multi-agent/help/SKILL.md +62 -62
  42. package/pipeline/commands/multi-agent/language/SKILL.md +2 -2
  43. package/pipeline/commands/multi-agent/local/SKILL.md +11 -11
  44. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +13 -13
  45. package/pipeline/commands/multi-agent/log/SKILL.md +2 -2
  46. package/pipeline/commands/multi-agent/manual-test/SKILL.md +9 -9
  47. package/pipeline/commands/multi-agent/model/SKILL.md +69 -0
  48. package/pipeline/commands/multi-agent/refactor/SKILL.md +3 -3
  49. package/pipeline/commands/multi-agent/resume/SKILL.md +4 -4
  50. package/pipeline/commands/multi-agent/resume-local/SKILL.md +19 -17
  51. package/pipeline/commands/multi-agent/review/SKILL.md +1 -1
  52. package/pipeline/commands/multi-agent/route-off/SKILL.md +36 -0
  53. package/pipeline/commands/multi-agent/route-on/SKILL.md +74 -0
  54. package/pipeline/commands/multi-agent/route-status/SKILL.md +56 -0
  55. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -2
  56. package/pipeline/commands/multi-agent/status/SKILL.md +54 -23
  57. package/pipeline/commands/multi-agent/steer/SKILL.md +2 -2
  58. package/pipeline/commands/multi-agent/sync/SKILL.md +12 -13
  59. package/pipeline/commands/multi-agent/test/SKILL.md +1 -1
  60. package/pipeline/lib/_jira-auth.sh +8 -0
  61. package/pipeline/lib/analysis-jira-write.sh +32 -0
  62. package/pipeline/lib/ask-choice.sh +13 -2
  63. package/pipeline/lib/autopilot-state.sh +8 -0
  64. package/pipeline/lib/credential-inventory.sh +1 -1
  65. package/pipeline/lib/fatal.mjs +129 -0
  66. package/pipeline/lib/fetch-fortify.sh +1 -1
  67. package/pipeline/lib/figma-mcp-refresh.sh +18 -0
  68. package/pipeline/lib/figma-screenshot.sh +18 -0
  69. package/pipeline/lib/invoked-directly.mjs +43 -0
  70. package/pipeline/lib/jira-publish.sh +42 -0
  71. package/pipeline/lib/md2confluence-v3.py +47 -0
  72. package/pipeline/lib/model-rung.sh +142 -0
  73. package/pipeline/lib/outbound-gate.mjs +175 -0
  74. package/pipeline/lib/phase-schema.mjs +88 -0
  75. package/pipeline/lib/plan-todos.sh +32 -11
  76. package/pipeline/lib/post-pr-review.sh +77 -8
  77. package/pipeline/lib/repo-hygiene.sh +8 -3
  78. package/pipeline/lib/require-jq.sh +40 -0
  79. package/pipeline/lib/route-state.sh +161 -0
  80. package/pipeline/lib/run-paths.sh +335 -0
  81. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  82. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  83. package/pipeline/multi-agent-refs/_input-parser.md +1 -1
  84. package/pipeline/multi-agent-refs/analysis/evidence.md +0 -9
  85. package/pipeline/multi-agent-refs/analysis/intake.md +1 -1
  86. package/pipeline/multi-agent-refs/analysis/locked.md +21 -22
  87. package/pipeline/multi-agent-refs/analysis/render.md +1 -1
  88. package/pipeline/multi-agent-refs/analysis/synthesis.md +12 -6
  89. package/pipeline/multi-agent-refs/android-guide.md +1 -1
  90. package/pipeline/multi-agent-refs/audit-guide.md +13 -13
  91. package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
  92. package/pipeline/multi-agent-refs/channels/jira.md +3 -3
  93. package/pipeline/multi-agent-refs/channels/pr.md +4 -4
  94. package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
  95. package/pipeline/multi-agent-refs/component-dispatch.md +3 -3
  96. package/pipeline/multi-agent-refs/cross-cli-contract.md +31 -6
  97. package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +74 -4
  98. package/pipeline/multi-agent-refs/features/code-graph.md +5 -5
  99. package/pipeline/multi-agent-refs/features/cost-analysis.md +93 -0
  100. package/pipeline/multi-agent-refs/features/design-conformance.md +1 -1
  101. package/pipeline/multi-agent-refs/features/dev-critic.md +3 -3
  102. package/pipeline/multi-agent-refs/features/doctor.md +47 -2
  103. package/pipeline/multi-agent-refs/features/external-context-injection.md +3 -3
  104. package/pipeline/multi-agent-refs/features/maturity-followup.md +3 -3
  105. package/pipeline/multi-agent-refs/features/model-fallback.md +5 -5
  106. package/pipeline/multi-agent-refs/features/plan-todos.md +1 -1
  107. package/pipeline/multi-agent-refs/features/repo-map.md +1 -1
  108. package/pipeline/multi-agent-refs/features/review-delta.md +3 -3
  109. package/pipeline/multi-agent-refs/features/review-multi-repo.md +1 -1
  110. package/pipeline/multi-agent-refs/features/scope-check.md +4 -4
  111. package/pipeline/multi-agent-refs/features/skill-conformance.md +2 -2
  112. package/pipeline/multi-agent-refs/features/stack-skill-routing.md +1 -1
  113. package/pipeline/multi-agent-refs/features/verify-by-test.md +4 -4
  114. package/pipeline/multi-agent-refs/features/verify.md +83 -0
  115. package/pipeline/multi-agent-refs/features/visual-evidence.md +19 -19
  116. package/pipeline/multi-agent-refs/features/worktree-finalize.md +6 -6
  117. package/pipeline/multi-agent-refs/issue-jira-triad.md +10 -10
  118. package/pipeline/multi-agent-refs/knowledge.md +11 -11
  119. package/pipeline/multi-agent-refs/multi-repo-integration-build.md +13 -13
  120. package/pipeline/multi-agent-refs/payload-contracts.md +8 -8
  121. package/pipeline/multi-agent-refs/phases/log-format.md +10 -10
  122. package/pipeline/multi-agent-refs/phases/modes.md +30 -30
  123. package/pipeline/multi-agent-refs/phases/operations.md +21 -10
  124. package/pipeline/multi-agent-refs/phases/phase-0-init.md +25 -25
  125. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +599 -0
  126. package/pipeline/multi-agent-refs/phases/{phase-3-dev.md → phase-2-dev.md} +129 -49
  127. package/pipeline/multi-agent-refs/phases/{phase-4-review.md → phase-3-review.md} +225 -107
  128. package/pipeline/multi-agent-refs/phases/{phase-6-commit.md → phase-4-commit.md} +23 -23
  129. package/pipeline/multi-agent-refs/phases/{phase-7-report.md → phase-5-report.md} +29 -29
  130. package/pipeline/multi-agent-refs/phases.md +44 -48
  131. package/pipeline/multi-agent-refs/picker-contract.md +1 -1
  132. package/pipeline/multi-agent-refs/progress-contract.md +6 -6
  133. package/pipeline/multi-agent-refs/readiness-review.md +1 -1
  134. package/pipeline/multi-agent-refs/rules.md +7 -7
  135. package/pipeline/multi-agent-refs/swiftui-guide.md +2 -2
  136. package/pipeline/multi-agent-refs/tracker-contract.md +31 -32
  137. package/pipeline/multi-agent-refs/unattended-contract.md +129 -0
  138. package/pipeline/multi-agent-refs/wiki-capture.md +14 -14
  139. package/pipeline/preferences-template.json +9 -1
  140. package/pipeline/rules/outside-the-pipeline.md +1 -1
  141. package/pipeline/schemas/agent-state.schema.json +50 -50
  142. package/pipeline/schemas/analysis-output.schema.json +2 -2
  143. package/pipeline/schemas/autopilot-config.schema.json +1 -1
  144. package/pipeline/schemas/code-graph.schema.json +1 -1
  145. package/pipeline/schemas/criteria-manifest.schema.json +1 -1
  146. package/pipeline/schemas/dev-critic-output.schema.json +1 -1
  147. package/pipeline/schemas/diff-risk.schema.json +1 -1
  148. package/pipeline/schemas/migrations/prefs-2.4.0-to-2.5.0.mjs +2 -2
  149. package/pipeline/schemas/migrations/prefs-2.6.0-to-2.7.0.mjs +31 -0
  150. package/pipeline/schemas/migrations/state-2.1.0-to-2.2.0.mjs +129 -0
  151. package/pipeline/schemas/phases.json +105 -0
  152. package/pipeline/schemas/plan-todos.schema.json +5 -5
  153. package/pipeline/schemas/planning-output.schema.json +1 -1
  154. package/pipeline/schemas/prefs.schema.json +100 -56
  155. package/pipeline/schemas/reviewer-output.schema.json +3 -3
  156. package/pipeline/schemas/route-config.schema.json +74 -0
  157. package/pipeline/schemas/scope-check.schema.json +1 -1
  158. package/pipeline/schemas/test-gap.schema.json +1 -1
  159. package/pipeline/schemas/token-budget.json +12 -18
  160. package/pipeline/schemas/triage-output.schema.json +6 -6
  161. package/pipeline/scripts/README.md +3 -3
  162. package/pipeline/scripts/_code-graph.mjs +2 -2
  163. package/pipeline/scripts/_run-paths.mjs +372 -0
  164. package/pipeline/scripts/_smoke-root.sh +1 -1
  165. package/pipeline/scripts/aggregate-metrics.mjs +65 -65
  166. package/pipeline/scripts/autopilot-arming.mjs +2 -1
  167. package/pipeline/scripts/autopilot-intake.mjs +2 -1
  168. package/pipeline/scripts/autopilot-runner.mjs +206 -2
  169. package/pipeline/scripts/build-references.mjs +2 -1
  170. package/pipeline/scripts/build-stack-plugins.mjs +10 -2
  171. package/pipeline/scripts/capture-evidence.sh +7 -2
  172. package/pipeline/scripts/capture-flush.sh +8 -8
  173. package/pipeline/scripts/capture-resume.sh +3 -3
  174. package/pipeline/scripts/classify-plan-safety.mjs +3 -2
  175. package/pipeline/scripts/cost-analyze.mjs +600 -0
  176. package/pipeline/scripts/cost-budget-check.mjs +4 -12
  177. package/pipeline/scripts/council-view.mjs +2 -1
  178. package/pipeline/scripts/crush-json.mjs +2 -1
  179. package/pipeline/scripts/diff-explain.mjs +7 -10
  180. package/pipeline/scripts/diff-risk-score.mjs +2 -1
  181. package/pipeline/scripts/doctor.mjs +140 -6
  182. package/pipeline/scripts/evidence-gate.mjs +9 -3
  183. package/pipeline/scripts/feedback-send.mjs +12 -2
  184. package/pipeline/scripts/gc-abandoned.sh +32 -16
  185. package/pipeline/scripts/gc-tmp.sh +1 -1
  186. package/pipeline/scripts/gc-worktrees.sh +12 -5
  187. package/pipeline/scripts/gen-facts.mjs +175 -0
  188. package/pipeline/scripts/gen-mode-dispatch.mjs +32 -37
  189. package/pipeline/scripts/gen-ref-toc.mjs +1 -1
  190. package/pipeline/scripts/github-ssh-setup.sh +64 -7
  191. package/pipeline/scripts/graph-mermaid.mjs +4 -2
  192. package/pipeline/scripts/graph-report.mjs +1 -1
  193. package/pipeline/scripts/jira-attach.sh +1 -1
  194. package/pipeline/scripts/keychain-save.sh +101 -30
  195. package/pipeline/scripts/learn-from-transcripts.mjs +3 -2
  196. package/pipeline/scripts/learning-curve.mjs +36 -31
  197. package/pipeline/scripts/log-metric.sh +17 -4
  198. package/pipeline/scripts/make-manifest.mjs +199 -0
  199. package/pipeline/scripts/memory-save.sh +1 -1
  200. package/pipeline/scripts/migrate-prefs.mjs +24 -6
  201. package/pipeline/scripts/migrate-state.mjs +94 -4
  202. package/pipeline/scripts/phase-banner.sh +26 -22
  203. package/pipeline/scripts/phase-tracker.sh +48 -10
  204. package/pipeline/scripts/plan-coverage-gate.mjs +8 -4
  205. package/pipeline/scripts/pre-commit-check.sh +7 -0
  206. package/pipeline/scripts/pre-push-check.sh +7 -0
  207. package/pipeline/scripts/purge.sh +23 -6
  208. package/pipeline/scripts/render-agent-log-cost.sh +10 -3
  209. package/pipeline/scripts/render-cost-summary.sh +9 -2
  210. package/pipeline/scripts/render-work-summary.sh +14 -7
  211. package/pipeline/scripts/review-file-filter.mjs +5 -3
  212. package/pipeline/scripts/review-scope.mjs +2 -1
  213. package/pipeline/scripts/routine-registry.mjs +2 -1
  214. package/pipeline/scripts/run-aggregator.mjs +26 -20
  215. package/pipeline/scripts/run-metrics.mjs +4 -2
  216. package/pipeline/scripts/runs-index.mjs +353 -0
  217. package/pipeline/scripts/scorecard-snapshot.mjs +178 -0
  218. package/pipeline/scripts/search-logs.sh +18 -0
  219. package/pipeline/scripts/smoke-cross-cli-behavior.sh +6 -6
  220. package/pipeline/scripts/smoke-schema-validation.sh +26 -7
  221. package/pipeline/scripts/test-gap-scan.mjs +2 -1
  222. package/pipeline/scripts/test-integrity-gate.mjs +2 -1
  223. package/pipeline/scripts/token-budget-report.mjs +13 -2
  224. package/pipeline/scripts/triage-memory.mjs +2 -2
  225. package/pipeline/scripts/update-issue-progress.sh +56 -7
  226. package/pipeline/scripts/usage-report.mjs +12 -1
  227. package/pipeline/scripts/validate-analysis-doc.mjs +75 -18
  228. package/pipeline/scripts/validate-code-graph.mjs +6 -3
  229. package/pipeline/scripts/validate-complaint-doc.mjs +2 -1
  230. package/pipeline/scripts/validate-diff-risk.mjs +6 -3
  231. package/pipeline/scripts/validate-planning.mjs +1 -1
  232. package/pipeline/scripts/validate-reviewer.mjs +1 -1
  233. package/pipeline/scripts/validate-state.mjs +45 -5
  234. package/pipeline/scripts/validate-test-gap.mjs +6 -3
  235. package/pipeline/scripts/validate-triage.mjs +6 -4
  236. package/pipeline/scripts/verify-citations.mjs +4 -2
  237. package/pipeline/scripts/verify.mjs +327 -0
  238. package/pipeline/scripts/worktree-finalize.sh +18 -9
  239. package/pipeline/scripts/write-state.mjs +154 -15
  240. package/pipeline/skills/.skill-manifest.json +37 -21
  241. package/pipeline/skills/.skills-index.json +104 -5
  242. package/pipeline/skills/shared/README.md +15 -6
  243. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +2 -2
  244. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +2 -2
  245. package/pipeline/skills/shared/core/multi-agent/SKILL.md +69 -71
  246. package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +3 -3
  247. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +14 -14
  248. package/pipeline/skills/shared/core/multi-agent-diff-explain/SKILL.md +5 -5
  249. package/pipeline/skills/shared/core/multi-agent-graph/SKILL.md +1 -1
  250. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +25 -23
  251. package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +2 -2
  252. package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +2 -2
  253. package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +8 -8
  254. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +6 -6
  255. package/pipeline/skills/shared/core/multi-agent-model/SKILL.md +71 -0
  256. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +3 -3
  257. package/pipeline/skills/shared/core/multi-agent-resume/SKILL.md +1 -1
  258. package/pipeline/skills/shared/core/multi-agent-resume-local/SKILL.md +7 -7
  259. package/pipeline/skills/shared/core/multi-agent-route-off/SKILL.md +39 -0
  260. package/pipeline/skills/shared/core/multi-agent-route-on/SKILL.md +76 -0
  261. package/pipeline/skills/shared/core/multi-agent-route-status/SKILL.md +59 -0
  262. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
  263. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +35 -11
  264. package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +2 -2
  265. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
  266. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/package_app.sh +4 -1
  267. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/setup_dev_signing.sh +4 -1
  268. package/pipeline/skills/shared/external/macos-spm-app-packaging/assets/templates/sign-and-notarize.sh +2 -1
  269. package/pipeline/skills/skills-index.md +13 -4
  270. package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +0 -263
  271. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +0 -344
  272. package/pipeline/multi-agent-refs/phases/phase-5-test.md +0 -182
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * verify.mjs - "is this install the thing that was published".
4
+ *
5
+ * Two questions, and they fail in different directions.
6
+ *
7
+ * 1. PACKAGE. Every file the package shipped, against the SHA-256 recorded in
8
+ * manifest.json at publish time. A file that differs was edited after
9
+ * install or arrived damaged; a file that is missing was never written.
10
+ * 2. INSTALL. ~/.claude holds a COPY of the package's pipeline tree, made by
11
+ * install.js. From the moment it is written the two drift independently,
12
+ * and both directions are real: an edit in ~/.claude is a change with no
13
+ * source, and a file the installer skipped is a script the docs describe
14
+ * and nobody has.
15
+ *
16
+ * Only the Claude tree is compared byte for byte. The Copilot and Codex trees
17
+ * are path-rewritten by the installer on purpose, so a byte difference there is
18
+ * the design rather than the bug, and they are reported as counts.
19
+ *
20
+ * What a green result means, stated plainly because the opposite is easy to
21
+ * imply: it means the bytes match what the publisher recorded. It is not proof
22
+ * of who published them. The manifest, the signature and this verifier all
23
+ * travel inside the same tarball, so anyone able to rewrite one can rewrite the
24
+ * others - provenance is npm's integrity field, not ours. This catches damage,
25
+ * partial installs and post-install edits, which are the failures that actually
26
+ * happen.
27
+ *
28
+ * Usage:
29
+ * verify.mjs both questions, human output
30
+ * verify.mjs --package package integrity only
31
+ * verify.mjs --install install drift only
32
+ * verify.mjs --json
33
+ *
34
+ * Exit codes:
35
+ * 0 everything matches
36
+ * 1 a difference was found
37
+ * 2 nothing to verify (no manifest - a dev checkout, or a pre-manifest version)
38
+ */
39
+
40
+ import { createHash, createPublicKey, verify as cryptoVerify } from "node:crypto";
41
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
42
+ import { homedir } from "node:os";
43
+ import { dirname, join, relative } from "node:path";
44
+ import { fileURLToPath, pathToFileURL } from "node:url";
45
+ import { runMain } from "../lib/fatal.mjs";
46
+
47
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
48
+ const MANIFEST = join(ROOT, "manifest.json");
49
+
50
+ const argv = process.argv.slice(2);
51
+ const has = (f) => argv.includes(f);
52
+ const json = has("--json");
53
+
54
+ /**
55
+ * The ed25519 public key a signature is checked against. It comes from the
56
+ * environment and nothing is pinned in this file yet - so on a stock install a
57
+ * signed manifest reports "signed, no public key to check it against" rather
58
+ * than claiming valid. That is the honest state: a signature nobody can check
59
+ * is not a signature that passed, and hard-coding a key here before there is a
60
+ * release key to hard-code would be the opposite.
61
+ */
62
+ const PUBLIC_KEY = process.env.MULTI_AGENT_SIGNING_PUBKEY || "";
63
+
64
+ const sha256 = (p) => createHash("sha256").update(readFileSync(p)).digest("hex");
65
+
66
+ /**
67
+ * Walk a tree, following symlinks but never twice.
68
+ *
69
+ * Following them is required rather than optional: `install --link` puts a
70
+ * symlink at ~/.claude/scripts, and a walker that skipped symlinks would report
71
+ * a linked install as entirely missing. The cost is that a link pointing at an
72
+ * ancestor turns the walk into a loop, so every directory is recorded by its
73
+ * RESOLVED path and visited once.
74
+ */
75
+ function walk(dir, base = dir, out = [], seen = new Set()) {
76
+ let real;
77
+ try {
78
+ real = realpathSync(dir);
79
+ } catch {
80
+ return out;
81
+ }
82
+ if (seen.has(real)) return out;
83
+ seen.add(real);
84
+ let entries;
85
+ try {
86
+ entries = readdirSync(dir);
87
+ } catch {
88
+ return out;
89
+ }
90
+ for (const name of entries) {
91
+ if (name === ".DS_Store" || name === "__pycache__") continue;
92
+ const abs = join(dir, name);
93
+ let st;
94
+ try {
95
+ st = statSync(abs);
96
+ } catch {
97
+ continue;
98
+ }
99
+ if (st.isDirectory()) walk(abs, base, out, seen);
100
+ else out.push(relative(base, abs));
101
+ }
102
+ return out;
103
+ }
104
+
105
+ // --- 1. package integrity ----------------------------------------------------
106
+
107
+ function verifyPackage() {
108
+ const manifest = JSON.parse(readFileSync(MANIFEST, "utf-8"));
109
+ const changed = [];
110
+ const missing = [];
111
+ for (const [rel, want] of Object.entries(manifest.files)) {
112
+ const abs = join(ROOT, rel);
113
+ if (!existsSync(abs)) {
114
+ missing.push(rel);
115
+ continue;
116
+ }
117
+ if (sha256(abs) !== want) changed.push(rel);
118
+ }
119
+
120
+ let signature = "unsigned";
121
+ const sigPath = join(ROOT, "manifest.sig");
122
+ if (existsSync(sigPath)) {
123
+ if (!PUBLIC_KEY) signature = "signed, no public key to check it against";
124
+ else {
125
+ try {
126
+ const ok = cryptoVerify(
127
+ null,
128
+ Buffer.from(readFileSync(MANIFEST)),
129
+ createPublicKey(PUBLIC_KEY),
130
+ Buffer.from(readFileSync(sigPath, "utf-8").trim(), "base64"),
131
+ );
132
+ signature = ok ? "valid" : "INVALID";
133
+ } catch (err) {
134
+ signature = `unverifiable: ${err?.message ?? err}`;
135
+ }
136
+ }
137
+ }
138
+
139
+ return {
140
+ version: manifest.version,
141
+ commit: manifest.commit,
142
+ fileCount: manifest.fileCount,
143
+ changed,
144
+ missing,
145
+ signature,
146
+ ok: changed.length === 0 && missing.length === 0 && signature !== "INVALID",
147
+ };
148
+ }
149
+
150
+ // --- 2. install drift --------------------------------------------------------
151
+
152
+ /**
153
+ * The trees install.js writes into ~/.claude, and how each one may be compared.
154
+ * Kept as data rather than asked of the installer, because the installer's job
155
+ * is to WRITE them and a verifier that asks the writer what it wrote proves
156
+ * nothing.
157
+ *
158
+ * `bytes` means the copy is verbatim and a difference is drift. `presence` is
159
+ * for commands/, where install.js rewrites each SKILL.md's `description` into
160
+ * the user's outputLanguage - every file differs there BY DESIGN, so comparing
161
+ * bytes would report 57 findings on a healthy machine and teach everyone to
162
+ * ignore the whole report.
163
+ */
164
+ const CLAUDE_TREES = [
165
+ ["scripts", "scripts", "bytes"],
166
+ ["lib", "lib", "bytes"],
167
+ ["multi-agent-refs", "multi-agent-refs", "bytes"],
168
+ ["agents", "agents", "bytes"],
169
+ ["commands/multi-agent", "commands/multi-agent", "presence"],
170
+ ];
171
+
172
+ /**
173
+ * Files the package carries but the installer deliberately does not write:
174
+ * smokes, linters, fixtures and the rest of the maintainer surface. Without
175
+ * this filter every one of them reads as "the installer skipped a file", which
176
+ * is 251 false findings on a correct install.
177
+ */
178
+ async function devOnly() {
179
+ try {
180
+ const mod = await import(new URL("../../install/_dev-only-files.mjs", import.meta.url).href);
181
+ return mod.isDevOnlyScript;
182
+ } catch {
183
+ // No filter available: say so by filtering nothing, and let the counts be
184
+ // wrong loudly rather than silently.
185
+ return null;
186
+ }
187
+ }
188
+
189
+ async function verifyInstall() {
190
+ const home = homedir();
191
+ const claude = join(home, ".claude");
192
+ if (!existsSync(claude)) {
193
+ return { present: false, reason: "no ~/.claude on this machine", ok: true, trees: [] };
194
+ }
195
+
196
+ const isDevOnly = await devOnly();
197
+ const trees = [];
198
+ let differing = 0;
199
+ let absent = 0;
200
+ for (const [srcRel, dstRel, mode] of CLAUDE_TREES) {
201
+ const src = join(ROOT, "pipeline", srcRel);
202
+ const dst = join(claude, dstRel);
203
+ if (!existsSync(src)) continue;
204
+ const srcFiles = walk(src).filter(
205
+ (rel) => !(srcRel === "scripts" && isDevOnly && isDevOnly(rel)),
206
+ );
207
+ const changed = [];
208
+ const missing = [];
209
+ for (const rel of srcFiles) {
210
+ const b = join(dst, rel);
211
+ if (!existsSync(b)) {
212
+ missing.push(rel);
213
+ continue;
214
+ }
215
+ if (mode === "bytes" && sha256(join(src, rel)) !== sha256(b)) changed.push(rel);
216
+ }
217
+ differing += changed.length;
218
+ absent += missing.length;
219
+ trees.push({ tree: dstRel, mode, files: srcFiles.length, changed, missing });
220
+ }
221
+
222
+ const others = [".copilot", ".codex"].filter((h) => existsSync(join(home, h)));
223
+
224
+ return {
225
+ present: true,
226
+ trees,
227
+ differing,
228
+ absent,
229
+ otherHosts: others,
230
+ ok: differing === 0 && absent === 0,
231
+ };
232
+ }
233
+
234
+ // --- output ------------------------------------------------------------------
235
+
236
+ async function main() {
237
+ const wantPackage = has("--package") || !has("--install");
238
+ const wantInstall = has("--install") || !has("--package");
239
+
240
+ if (wantPackage && !existsSync(MANIFEST)) {
241
+ const msg =
242
+ "no manifest.json - this is a source checkout or a version published before manifests existed";
243
+ if (json)
244
+ process.stdout.write(`${JSON.stringify({ kind: "UNVERIFIABLE", reason: msg }, null, 2)}\n`);
245
+ else process.stdout.write(`UNVERIFIABLE: ${msg}\n`);
246
+ process.exitCode = 2;
247
+ return;
248
+ }
249
+
250
+ const pkg = wantPackage ? verifyPackage() : null;
251
+ const inst = wantInstall ? await verifyInstall() : null;
252
+
253
+ if (json) {
254
+ process.stdout.write(`${JSON.stringify({ package: pkg, install: inst }, null, 2)}\n`);
255
+ } else {
256
+ if (pkg) {
257
+ process.stdout.write(
258
+ `paket: ${pkg.version} (${pkg.commit?.slice(0, 12) ?? "commit bilinmiyor"}) - ${pkg.fileCount} dosya, imza: ${pkg.signature}\n`,
259
+ );
260
+ if (!pkg.changed.length && !pkg.missing.length)
261
+ process.stdout.write(" butun dosyalar yayinlandigi gibi\n");
262
+ const SHOWN = 20;
263
+ for (const f of pkg.changed.slice(0, SHOWN)) process.stdout.write(` ! degismis: ${f}\n`);
264
+ for (const f of pkg.missing.slice(0, SHOWN)) process.stdout.write(` ! eksik: ${f}\n`);
265
+ // Counted per list against what was actually PRINTED. Subtracting a flat
266
+ // 40 from the combined total spent a budget the lists had not used: with
267
+ // 25 changed and 0 missing it printed 20, hid 5, and said nothing - a
268
+ // tampered install under-reported by up to twenty files with no sign.
269
+ const hidden =
270
+ Math.max(0, pkg.changed.length - SHOWN) + Math.max(0, pkg.missing.length - SHOWN);
271
+ if (hidden > 0) process.stdout.write(` ... ve ${hidden} dosya daha\n`);
272
+ }
273
+ if (inst) {
274
+ if (!inst.present) process.stdout.write(`kurulum: ${inst.reason}\n`);
275
+ else {
276
+ const total = inst.trees.reduce((a, t) => a + t.files, 0);
277
+ process.stdout.write(
278
+ `kurulum: ~/.claude - ${inst.trees.length} agac, ${total} dosya karsilastirildi\n`,
279
+ );
280
+ for (const t of inst.trees) {
281
+ if (!t.changed.length && !t.missing.length) continue;
282
+ const how =
283
+ t.mode === "presence" ? " (yalnizca varlik: aciklamalar kurulumda cevriliyor)" : "";
284
+ process.stdout.write(
285
+ ` ${t.tree}: ${t.changed.length} farkli, ${t.missing.length} eksik${how}\n`,
286
+ );
287
+ for (const f of [...t.changed.slice(0, 5)]) process.stdout.write(` ! ${f}\n`);
288
+ for (const f of [...t.missing.slice(0, 5)])
289
+ process.stdout.write(` ? ${f} (hic yazilmamis)\n`);
290
+ }
291
+ if (inst.ok) process.stdout.write(" kaynakla birebir ayni\n");
292
+ if (inst.otherHosts.length)
293
+ process.stdout.write(
294
+ ` ${inst.otherHosts.join(", ")} kurulu - yollari kurulumda yeniden yazildigi icin bayt karsilastirmasi yapilmadi\n`,
295
+ );
296
+ }
297
+ }
298
+ }
299
+
300
+ const bad = (pkg && !pkg.ok) || (inst && !inst.ok);
301
+ process.exitCode = bad ? 1 : 0;
302
+ }
303
+
304
+ // Importable AND runnable: index.js calls main() for `multi-agent-pipeline
305
+ // verify`, and the direct-run guard keeps a bare `node verify.mjs` behaving
306
+ // the same way every other script here does.
307
+ export { main };
308
+
309
+ /**
310
+ * realpath, not a string compare against argv[1]. Two ordinary situations break
311
+ * the naive form: macOS hands out /var/folders/... paths that resolve to
312
+ * /private/var/folders/..., and `install --link` puts a SYMLINK at
313
+ * ~/.claude/scripts/verify.mjs - in both cases import.meta.url is the resolved
314
+ * path, argv[1] is not, and the script silently does nothing at all.
315
+ */
316
+ function invokedDirectly() {
317
+ if (!process.argv[1]) return false;
318
+ try {
319
+ return import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href;
320
+ } catch {
321
+ return false;
322
+ }
323
+ }
324
+
325
+ if (invokedDirectly()) {
326
+ runMain("verify", main);
327
+ }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
3
  # worktree-finalize.sh - salvage a finished task's artefacts, then remove its
4
- # worktree. Called by Phase 6 once the PR is open.
4
+ # worktree. Called by Phase 4 once the PR is open.
5
5
  #
6
6
  # The problem it solves: a task's worktree is dead weight the moment its PR
7
7
  # exists, but people forget to remove it, so `.worktrees/` accumulates full
8
- # checkouts. Removing it at PR-open is safe ONLY if the files Phase 7 and
8
+ # checkouts. Removing it at PR-open is safe ONLY if the files Phase 5 and
9
9
  # `:resume` read are moved somewhere that outlives it - hence salvage first.
10
10
  #
11
11
  # What it deliberately does NOT do:
@@ -28,7 +28,7 @@
28
28
  # 0 removed (or dry-run that would remove)
29
29
  # 3 SKIPPED by a safety precondition - not an error, reason is reported
30
30
  # 1 usage / hard error
31
- set -uo pipefail
31
+ set -euo pipefail
32
32
 
33
33
  WORKTREE=""; PROJECT_ROOT=""; TASK_ID=""; PROJECT=""; BRANCH=""
34
34
  LOGS_ROOT="${HOME}/.claude/logs/multi-agent"
@@ -97,6 +97,10 @@ skip() { REASON="$1"; emit; exit 3; }
97
97
  # preference at all, because turning it off appears to work and does nothing.
98
98
  PREFS="${PREFS_FILE:-$HOME/.claude/multi-agent-preferences.json}"
99
99
  if [ -f "$PREFS" ] && command -v node >/dev/null 2>&1; then
100
+ # `|| echo true` on the far side already covers a node that throws: an
101
+ # unreadable preference file reads as "not set", which is the default. Noted
102
+ # here because it is the one assignment in this file that was ALREADY safe
103
+ # under `set -e`, and the next reader should not have to re-derive that.
100
104
  enabled="$(node -e '
101
105
  const fs=require("fs");
102
106
  try {
@@ -121,7 +125,7 @@ rp_pr="$(cd "$PROJECT_ROOT" 2>/dev/null && pwd -P || echo "")"
121
125
  [ "$rp_wt" != "$rp_pr" ] || skip "worktree equals project root (--local mode) - nothing to remove"
122
126
 
123
127
  # Refuse if the caller is standing inside the tree being removed. A shell left on
124
- # a deleted inode is worse than a leftover directory, and Phase 6 legitimately
128
+ # a deleted inode is worse than a leftover directory, and Phase 4 legitimately
125
129
  # cd's into the worktree earlier, so this is a real case rather than a theoretical
126
130
  # one. The caller must cd out first.
127
131
  cwd_rp="$(pwd -P)"
@@ -173,6 +177,9 @@ fi
173
177
  # alternatives are end-anchored - unanchored, `.build.log.old` was forgiven here
174
178
  # but not deletable below, which made every removal refuse forever.
175
179
  ARTIFACTS_RE='^(agent-state\.json|phase-tracker\.json|triage-output\.json|\.review-diff\.txt|\.build\.log|\.test\.log)$|^\.pipeline/'
180
+ # Forgiven at the end of the pipeline (see below): a worktree whose git dir is
181
+ # already gone reports nothing, and "nothing dirty" is the correct reading of
182
+ # that - aborting would strand the removal this function exists to perform.
176
183
  dirty="$(git -C "$rp_wt" status --porcelain 2>/dev/null | awk '
177
184
  {
178
185
  st = substr($0, 1, 2); path = substr($0, 4);
@@ -183,7 +190,7 @@ dirty="$(git -C "$rp_wt" status --porcelain 2>/dev/null | awk '
183
190
  continue # our own untracked artefact
184
191
  fi
185
192
  printf '%s\n' "$path"
186
- done)"
193
+ done || true)"
187
194
  if [ -n "$dirty" ]; then
188
195
  first="$(printf '%s\n' "$dirty" | head -1)"
189
196
  skip "worktree has uncommitted changes (e.g. $first) - refusing to remove"
@@ -206,7 +213,7 @@ fi
206
213
  [ "$head_sha" = "$upstream_sha" ] || skip "HEAD ($(echo "$head_sha" | cut -c1-8)) differs from remote ($(echo "$upstream_sha" | cut -c1-8)) - push before finalizing"
207
214
 
208
215
  # --- salvage ---------------------------------------------------------------
209
- # Everything Phase 7, :resume, :status and :log read out of the worktree. Each is
216
+ # Everything Phase 5, :resume, :status and :log read out of the worktree. Each is
210
217
  # copied only if present; a task that never reached Phase 4 has no triage output
211
218
  # and that is not an error.
212
219
  DEST="$LOGS_ROOT/${PROJECT:+$PROJECT/}$TASK_ID/artifacts"
@@ -219,7 +226,9 @@ copy_one() {
219
226
  SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
220
227
  return 0
221
228
  fi
222
- mkdir -p "$DEST/$(dirname "$1")" 2>/dev/null
229
+ # Forgiven: this function already tolerates a copy that does not happen, and
230
+ # aborting here would skip the worktree removal that follows.
231
+ mkdir -p "$DEST/$(dirname "$1")" 2>/dev/null || true
223
232
  [ -d "$DEST/$1" ] && rm -rf "${DEST:?}/$1"
224
233
  cp -R "$src" "$DEST/$1" 2>/dev/null && SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
225
234
  }
@@ -240,7 +249,7 @@ copy_from_logs() {
240
249
  SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
241
250
  return 0
242
251
  fi
243
- mkdir -p "$DEST/$(dirname "$1")" 2>/dev/null
252
+ mkdir -p "$DEST/$(dirname "$1")" 2>/dev/null || true
244
253
  cp -R "$src" "$DEST/$1" 2>/dev/null && SALVAGED="${SALVAGED:+$SALVAGED,}\"$1\""
245
254
  }
246
255
 
@@ -300,7 +309,7 @@ if git -C "$rp_pr" worktree remove "$rp_wt" 2>/dev/null; then
300
309
  # Stamp the outcome into the SALVAGED agent-state.json here, rather than leaving
301
310
  # the caller to write it. In single-repo worktree mode the caller's $STATE_FILE
302
311
  # is the copy that just went away with the worktree, so a write there fails and
303
- # `worktreeRemovedAt` / `artifactsPath` land nowhere - which then makes Phase 7
312
+ # `worktreeRemovedAt` / `artifactsPath` land nowhere - which then makes Phase 5
304
313
  # read a dead path, fall back to the removed worktree, and silently skip the
305
314
  # triage-memory ingest. Exactly the [ -f ]-guarded degradation this step exists
306
315
  # to prevent.