mandrel 2.59.0 → 2.61.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 (678) hide show
  1. package/.agents/README.md +21 -19
  2. package/.agents/agents/acceptance-critic.md +24 -43
  3. package/.agents/agents/story-worker.md +20 -21
  4. package/.agents/docs/agentrc-reference.json +5 -84
  5. package/.agents/docs/configuration.md +48 -132
  6. package/.agents/docs/workflows.md +2 -2
  7. package/.agents/instructions.md +5 -8
  8. package/.agents/rules/ci-remediation.md +41 -8
  9. package/.agents/rules/gherkin-standards.md +4 -0
  10. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  11. package/.agents/schemas/agentrc.schema.json +16 -496
  12. package/.agents/schemas/story-deliver-terminal.schema.json +13 -3
  13. package/.agents/schemas/validation-evidence.schema.json +2 -1
  14. package/.agents/scripts/README.md +23 -13
  15. package/.agents/scripts/acceptance-eval.js +35 -169
  16. package/.agents/scripts/agents-bootstrap-github.js +27 -129
  17. package/.agents/scripts/apply-quality-bootstrap.js +4 -40
  18. package/.agents/scripts/audit-baselines.js +6 -27
  19. package/.agents/scripts/audit-labels-bootstrap.js +5 -29
  20. package/.agents/scripts/audit-to-stories.js +91 -361
  21. package/.agents/scripts/boot-sweep.js +19 -72
  22. package/.agents/scripts/bootstrap.js +74 -395
  23. package/.agents/scripts/ceremony-derive.js +10 -41
  24. package/.agents/scripts/check-arch-cycles.js +14 -64
  25. package/.agents/scripts/check-baselines.js +8 -48
  26. package/.agents/scripts/check-context-budget.js +105 -311
  27. package/.agents/scripts/check-cyclomatic.js +12 -44
  28. package/.agents/scripts/check-dead-exports.js +13 -64
  29. package/.agents/scripts/check-doc-links.js +38 -186
  30. package/.agents/scripts/check-gherkin-corpus.js +24 -127
  31. package/.agents/scripts/check-test-temp-hygiene.js +28 -169
  32. package/.agents/scripts/coverage-capture.js +34 -98
  33. package/.agents/scripts/deliver-light.js +25 -171
  34. package/.agents/scripts/deliver-recover.js +6 -42
  35. package/.agents/scripts/deliver-run.js +534 -0
  36. package/.agents/scripts/diagnose-friction.js +24 -116
  37. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  38. package/.agents/scripts/evidence-gate.js +23 -85
  39. package/.agents/scripts/file-ci-gap.js +57 -49
  40. package/.agents/scripts/generate-config-docs.js +30 -170
  41. package/.agents/scripts/generate-lens-checklists.js +8 -52
  42. package/.agents/scripts/generate-skills-index.js +14 -112
  43. package/.agents/scripts/generate-workflows-doc.js +10 -70
  44. package/.agents/scripts/git-cleanup.js +2 -34
  45. package/.agents/scripts/lib/Graph.js +24 -81
  46. package/.agents/scripts/lib/ITicketingProvider.js +47 -143
  47. package/.agents/scripts/lib/Logger.js +14 -76
  48. package/.agents/scripts/lib/audit-baselines/engine.js +11 -31
  49. package/.agents/scripts/lib/audit-baselines/gate-surface.js +3 -17
  50. package/.agents/scripts/lib/audit-baselines/headroom.js +4 -20
  51. package/.agents/scripts/lib/audit-baselines/hotspots.js +3 -15
  52. package/.agents/scripts/lib/audit-baselines/kinds.js +23 -89
  53. package/.agents/scripts/lib/audit-baselines/outliers.js +6 -23
  54. package/.agents/scripts/lib/audit-baselines/read.js +3 -16
  55. package/.agents/scripts/lib/audit-baselines/staleness.js +7 -26
  56. package/.agents/scripts/lib/audit-baselines/surface-entry.js +7 -28
  57. package/.agents/scripts/lib/audit-baselines/trend.js +7 -20
  58. package/.agents/scripts/lib/audit-baselines/weights.js +9 -37
  59. package/.agents/scripts/lib/audit-suite/audit-rules-reader.js +3 -18
  60. package/.agents/scripts/lib/audit-suite/checklist-threading.js +26 -124
  61. package/.agents/scripts/lib/audit-suite/dispatch-checklist.js +14 -45
  62. package/.agents/scripts/lib/audit-suite/findings.js +12 -52
  63. package/.agents/scripts/lib/audit-suite/frontmatter.js +5 -29
  64. package/.agents/scripts/lib/audit-suite/index.js +1 -15
  65. package/.agents/scripts/lib/audit-suite/lens-checklist.js +10 -45
  66. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +11 -76
  67. package/.agents/scripts/lib/audit-suite/runner.js +13 -43
  68. package/.agents/scripts/lib/audit-suite/selector.js +49 -349
  69. package/.agents/scripts/lib/audit-suite/substitutions.js +12 -40
  70. package/.agents/scripts/lib/audit-suite/workflow-loader.js +3 -15
  71. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +11 -73
  72. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +8 -37
  73. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +25 -143
  74. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +14 -80
  75. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +4 -18
  76. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +7 -40
  77. package/.agents/scripts/lib/audit-to-stories/group-findings.js +9 -39
  78. package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +10 -66
  79. package/.agents/scripts/lib/audit-to-stories/issue-index.js +6 -30
  80. package/.agents/scripts/lib/audit-to-stories/issues-file.js +6 -36
  81. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +17 -69
  82. package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +17 -85
  83. package/.agents/scripts/lib/audit-to-stories/ledger-record.js +10 -48
  84. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +38 -146
  85. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +7 -42
  86. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +10 -57
  87. package/.agents/scripts/lib/baseline-loader.js +12 -51
  88. package/.agents/scripts/lib/baseline-schema-registry.js +5 -22
  89. package/.agents/scripts/lib/baselines/component-matcher.js +3 -12
  90. package/.agents/scripts/lib/baselines/components.js +10 -61
  91. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +7 -36
  92. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +7 -19
  93. package/.agents/scripts/lib/baselines/crap-preview-scan.js +19 -45
  94. package/.agents/scripts/lib/baselines/crap-updater-cli.js +16 -65
  95. package/.agents/scripts/lib/baselines/diff-scope-cli.js +5 -33
  96. package/.agents/scripts/lib/baselines/duplication-scanner.js +11 -60
  97. package/.agents/scripts/lib/baselines/env-overrides.js +12 -67
  98. package/.agents/scripts/lib/baselines/envelope.js +13 -129
  99. package/.agents/scripts/lib/baselines/exit-codes.js +6 -49
  100. package/.agents/scripts/lib/baselines/git-base.js +23 -142
  101. package/.agents/scripts/lib/baselines/kernel.js +11 -111
  102. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +9 -47
  103. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +8 -51
  104. package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +7 -62
  105. package/.agents/scripts/lib/baselines/kinds/bundle-size.js +8 -29
  106. package/.agents/scripts/lib/baselines/kinds/coverage.js +3 -15
  107. package/.agents/scripts/lib/baselines/kinds/crap.js +94 -415
  108. package/.agents/scripts/lib/baselines/kinds/duplication.js +4 -30
  109. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +7 -53
  110. package/.agents/scripts/lib/baselines/kinds/maintainability.js +15 -80
  111. package/.agents/scripts/lib/baselines/kinds/mutation.js +12 -72
  112. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +4 -11
  113. package/.agents/scripts/lib/baselines/merge-envelopes.js +30 -146
  114. package/.agents/scripts/lib/baselines/path-canon.js +19 -128
  115. package/.agents/scripts/lib/baselines/preview-gates.js +8 -37
  116. package/.agents/scripts/lib/baselines/reader.js +14 -107
  117. package/.agents/scripts/lib/baselines/refresh-service.js +35 -252
  118. package/.agents/scripts/lib/baselines/scope.js +11 -135
  119. package/.agents/scripts/lib/baselines/writer.js +16 -149
  120. package/.agents/scripts/lib/bdd-runner-detect.js +18 -113
  121. package/.agents/scripts/lib/bdd-scenario-budget.js +9 -36
  122. package/.agents/scripts/lib/bdd-scenario-scanner.js +11 -73
  123. package/.agents/scripts/lib/bdd-step-index.js +26 -100
  124. package/.agents/scripts/lib/bootstrap/baseline-merge-driver.js +23 -103
  125. package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -5
  126. package/.agents/scripts/lib/bootstrap/commit-push.js +12 -50
  127. package/.agents/scripts/lib/bootstrap/gh-preflight.js +14 -101
  128. package/.agents/scripts/lib/bootstrap/hitl-confirm.js +5 -30
  129. package/.agents/scripts/lib/bootstrap/install-ledger.js +17 -73
  130. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +17 -121
  131. package/.agents/scripts/lib/bootstrap/manifest.js +15 -86
  132. package/.agents/scripts/lib/bootstrap/merge-methods.js +4 -33
  133. package/.agents/scripts/lib/bootstrap/preflight.js +12 -61
  134. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +34 -235
  135. package/.agents/scripts/lib/bootstrap/prompt.js +21 -129
  136. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +27 -141
  137. package/.agents/scripts/lib/bootstrap/summary.js +1 -8
  138. package/.agents/scripts/lib/bootstrap/workflow-audit.js +11 -84
  139. package/.agents/scripts/lib/branch-name-guard.js +4 -23
  140. package/.agents/scripts/lib/changed-files.js +39 -121
  141. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -24
  142. package/.agents/scripts/lib/checks/index.js +18 -101
  143. package/.agents/scripts/lib/checks/loop-health.js +11 -53
  144. package/.agents/scripts/lib/checks/state.js +16 -91
  145. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +10 -61
  146. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +14 -83
  147. package/.agents/scripts/lib/child-exec.js +39 -108
  148. package/.agents/scripts/lib/cli/standard-args.js +9 -116
  149. package/.agents/scripts/lib/cli-args.js +31 -146
  150. package/.agents/scripts/lib/cli-usage.js +5 -27
  151. package/.agents/scripts/lib/cli-utils.js +2 -12
  152. package/.agents/scripts/lib/close-validation/commands.js +13 -76
  153. package/.agents/scripts/lib/close-validation/gates.js +92 -331
  154. package/.agents/scripts/lib/close-validation/process.js +121 -151
  155. package/.agents/scripts/lib/close-validation/projections/advisories.js +5 -31
  156. package/.agents/scripts/lib/close-validation/projections/crap.js +19 -67
  157. package/.agents/scripts/lib/close-validation/projections/head-sha.js +1 -17
  158. package/.agents/scripts/lib/close-validation/projections/inputs.js +4 -34
  159. package/.agents/scripts/lib/close-validation/projections/maintainability.js +11 -58
  160. package/.agents/scripts/lib/close-validation/runner.js +53 -110
  161. package/.agents/scripts/lib/command-header.js +6 -28
  162. package/.agents/scripts/lib/config/acceptance-eval.js +4 -30
  163. package/.agents/scripts/lib/config/baselines.js +3 -14
  164. package/.agents/scripts/lib/config/ci.js +8 -38
  165. package/.agents/scripts/lib/config/commands.js +1 -15
  166. package/.agents/scripts/lib/config/defaults.js +4 -33
  167. package/.agents/scripts/lib/config/delivery-routing.js +6 -34
  168. package/.agents/scripts/lib/config/explain.js +10 -83
  169. package/.agents/scripts/lib/config/gates/coverage.schema.js +0 -12
  170. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +3 -31
  171. package/.agents/scripts/lib/config/gates/crap.schema.js +1 -34
  172. package/.agents/scripts/lib/config/gates/duplication.schema.js +1 -8
  173. package/.agents/scripts/lib/config/gates/index.js +2 -17
  174. package/.agents/scripts/lib/config/gates/maintainability.schema.js +1 -20
  175. package/.agents/scripts/lib/config/gates/mutation.schema.js +1 -7
  176. package/.agents/scripts/lib/config/gates/shared.js +4 -64
  177. package/.agents/scripts/lib/config/github.js +4 -22
  178. package/.agents/scripts/lib/config/limits.js +4 -57
  179. package/.agents/scripts/lib/config/paths.js +2 -21
  180. package/.agents/scripts/lib/config/qa.js +4 -38
  181. package/.agents/scripts/lib/config/quality.js +76 -432
  182. package/.agents/scripts/lib/config/runners.js +12 -56
  183. package/.agents/scripts/lib/config/runtime.js +13 -57
  184. package/.agents/scripts/lib/config/shared.js +2 -16
  185. package/.agents/scripts/lib/config/sync-agentrc.js +9 -50
  186. package/.agents/scripts/lib/config/temp-paths.js +40 -280
  187. package/.agents/scripts/lib/config/validate-orchestration.js +5 -17
  188. package/.agents/scripts/lib/config/worktree-isolation.js +9 -28
  189. package/.agents/scripts/lib/config-resolver.js +11 -52
  190. package/.agents/scripts/lib/config-schema-shared.js +1 -4
  191. package/.agents/scripts/lib/config-settings-schema-delivery.js +16 -291
  192. package/.agents/scripts/lib/config-settings-schema-quality.js +7 -219
  193. package/.agents/scripts/lib/config-settings-schema.js +37 -274
  194. package/.agents/scripts/lib/coverage-baseline.js +20 -110
  195. package/.agents/scripts/lib/coverage-capture-fullscope.js +18 -35
  196. package/.agents/scripts/lib/coverage-capture-incremental.js +15 -45
  197. package/.agents/scripts/lib/coverage-capture-usage.js +8 -25
  198. package/.agents/scripts/lib/coverage-capture.js +99 -224
  199. package/.agents/scripts/lib/coverage-utils.js +14 -79
  200. package/.agents/scripts/lib/cpu-pool.js +17 -123
  201. package/.agents/scripts/lib/crap-baseline-join.js +16 -88
  202. package/.agents/scripts/lib/crap-coordinates.js +6 -25
  203. package/.agents/scripts/lib/crap-engine.js +40 -153
  204. package/.agents/scripts/lib/crap-method-identity.js +16 -78
  205. package/.agents/scripts/lib/crap-utils.js +31 -155
  206. package/.agents/scripts/lib/cyclomatic-ceiling.js +14 -87
  207. package/.agents/scripts/lib/cyclomatic-scope.js +10 -50
  208. package/.agents/scripts/lib/dead-exports-knip.js +18 -56
  209. package/.agents/scripts/lib/dead-exports-mode.js +5 -24
  210. package/.agents/scripts/lib/degraded-mode.js +3 -26
  211. package/.agents/scripts/lib/dependency-parser.js +8 -40
  212. package/.agents/scripts/lib/dependency-version.js +13 -35
  213. package/.agents/scripts/lib/detect-package-manager.js +6 -36
  214. package/.agents/scripts/lib/doc-tiers.js +22 -124
  215. package/.agents/scripts/lib/duplicate-search.js +8 -72
  216. package/.agents/scripts/lib/env-loader.js +5 -27
  217. package/.agents/scripts/lib/error-redactor.js +6 -31
  218. package/.agents/scripts/lib/errors/index.js +2 -22
  219. package/.agents/scripts/lib/escomplex-ast-compat.js +22 -163
  220. package/.agents/scripts/lib/escomplex-kernel.js +21 -123
  221. package/.agents/scripts/lib/feedback-loop/graduator-core.js +94 -419
  222. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +20 -98
  223. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +27 -149
  224. package/.agents/scripts/lib/findings/audit-ledger.js +23 -97
  225. package/.agents/scripts/lib/findings/classify-finding.js +14 -69
  226. package/.agents/scripts/lib/findings/promote-finding.js +14 -112
  227. package/.agents/scripts/lib/findings/provenance-field.js +11 -52
  228. package/.agents/scripts/lib/findings/route-finding.js +37 -255
  229. package/.agents/scripts/lib/findings/semantic-issue-search.js +14 -59
  230. package/.agents/scripts/lib/findings/severity.js +18 -104
  231. package/.agents/scripts/lib/format-generated-json.js +12 -44
  232. package/.agents/scripts/lib/full-suite-lock.js +160 -429
  233. package/.agents/scripts/lib/full-suite-queue.js +213 -0
  234. package/.agents/scripts/lib/gates/baseline-store.js +5 -9
  235. package/.agents/scripts/lib/gates/friction.js +1 -3
  236. package/.agents/scripts/lib/generated/agentrc-validator.js +2 -2
  237. package/.agents/scripts/lib/gh-exec.js +123 -191
  238. package/.agents/scripts/lib/git/cached-fetch.js +9 -52
  239. package/.agents/scripts/lib/git/sync-from-base.js +15 -107
  240. package/.agents/scripts/lib/git-branch-cleanup.js +14 -61
  241. package/.agents/scripts/lib/git-branch-lifecycle.js +16 -87
  242. package/.agents/scripts/lib/git-utils.js +37 -161
  243. package/.agents/scripts/lib/github/framework-repo.js +8 -71
  244. package/.agents/scripts/lib/github-url.js +2 -17
  245. package/.agents/scripts/lib/import-graph.js +11 -38
  246. package/.agents/scripts/lib/install-cmd-parser.js +4 -13
  247. package/.agents/scripts/lib/json-utils.js +3 -18
  248. package/.agents/scripts/lib/label-constants.js +16 -111
  249. package/.agents/scripts/lib/label-taxonomy.js +4 -30
  250. package/.agents/scripts/lib/maintainability-engine.js +12 -80
  251. package/.agents/scripts/lib/maintainability-unscorable.js +5 -25
  252. package/.agents/scripts/lib/maintainability-utils.js +16 -92
  253. package/.agents/scripts/lib/mandrel-catalog.js +12 -70
  254. package/.agents/scripts/lib/notifications/notifier.js +13 -38
  255. package/.agents/scripts/lib/npm-scripts.js +4 -23
  256. package/.agents/scripts/lib/observability/metrics-ledger.js +16 -76
  257. package/.agents/scripts/lib/observability/runtime-friction.js +44 -286
  258. package/.agents/scripts/lib/observability/signal-validator.js +5 -37
  259. package/.agents/scripts/lib/observability/signals-writer.js +11 -121
  260. package/.agents/scripts/lib/observability/source-classifier.js +22 -203
  261. package/.agents/scripts/lib/observability/terse-result.js +14 -47
  262. package/.agents/scripts/lib/onboard/init-tail.js +13 -74
  263. package/.agents/scripts/lib/onboard/scaffold-docs.js +11 -34
  264. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +25 -139
  265. package/.agents/scripts/lib/orchestration/auto-merge-cwd.js +11 -66
  266. package/.agents/scripts/lib/orchestration/behind-recovery.js +10 -55
  267. package/.agents/scripts/lib/orchestration/ceremony-routing.js +28 -156
  268. package/.agents/scripts/lib/orchestration/change-set.js +7 -36
  269. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +9 -46
  270. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +11 -44
  271. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +5 -24
  272. package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +3 -12
  273. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +5 -38
  274. package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +3 -8
  275. package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +28 -136
  276. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +3 -13
  277. package/.agents/scripts/lib/orchestration/check-state.js +92 -0
  278. package/.agents/scripts/lib/orchestration/ci-gap-intake.js +37 -144
  279. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +121 -157
  280. package/.agents/scripts/lib/orchestration/code-review.js +24 -128
  281. package/.agents/scripts/lib/orchestration/column-sync.js +23 -111
  282. package/.agents/scripts/lib/orchestration/complexity-gate.js +118 -462
  283. package/.agents/scripts/lib/orchestration/deliver-recover.js +36 -168
  284. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +4 -35
  285. package/.agents/scripts/lib/orchestration/dependency-candidates.js +9 -41
  286. package/.agents/scripts/lib/orchestration/diff-magnitude.js +25 -115
  287. package/.agents/scripts/lib/orchestration/doc-reader.js +2 -6
  288. package/.agents/scripts/lib/orchestration/docs-digest.js +13 -51
  289. package/.agents/scripts/lib/orchestration/epic-candidates.js +13 -53
  290. package/.agents/scripts/lib/orchestration/epic-checklist.js +9 -33
  291. package/.agents/scripts/lib/orchestration/epic-container.js +41 -167
  292. package/.agents/scripts/lib/orchestration/epic-expansion.js +10 -40
  293. package/.agents/scripts/lib/orchestration/epic-rollup.js +55 -207
  294. package/.agents/scripts/lib/orchestration/file-assumption-enum.js +2 -22
  295. package/.agents/scripts/lib/orchestration/file-assumptions.js +45 -253
  296. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-detect.js +9 -46
  297. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +7 -57
  298. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +21 -126
  299. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +2 -6
  300. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +1 -4
  301. package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +1 -5
  302. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +9 -34
  303. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +41 -190
  304. package/.agents/scripts/lib/orchestration/git-cleanup/phases/merged-tip.js +9 -45
  305. package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +1 -4
  306. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +19 -85
  307. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +3 -17
  308. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +1 -5
  309. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +16 -100
  310. package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +1 -4
  311. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +16 -67
  312. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +9 -33
  313. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +8 -29
  314. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +15 -68
  315. package/.agents/scripts/lib/orchestration/light-backstop.js +11 -49
  316. package/.agents/scripts/lib/orchestration/light-escalation.js +21 -89
  317. package/.agents/scripts/lib/orchestration/light-suitability.js +37 -307
  318. package/.agents/scripts/lib/orchestration/merge-block-class.js +39 -216
  319. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -377
  320. package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +17 -57
  321. package/.agents/scripts/lib/orchestration/plan-context.js +55 -318
  322. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +22 -79
  323. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +5 -31
  324. package/.agents/scripts/lib/orchestration/plan-metrics.js +29 -108
  325. package/.agents/scripts/lib/orchestration/plan-navigation.js +8 -27
  326. package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +12 -49
  327. package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +18 -73
  328. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +16 -78
  329. package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +6 -30
  330. package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +14 -65
  331. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +21 -76
  332. package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +6 -43
  333. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +14 -68
  334. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +9 -41
  335. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +71 -289
  336. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +2 -12
  337. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +96 -362
  338. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +9 -39
  339. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +60 -186
  340. package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +9 -49
  341. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +7 -28
  342. package/.agents/scripts/lib/orchestration/plan-reachability.js +13 -42
  343. package/.agents/scripts/lib/orchestration/plan-run-labels/reap.js +16 -91
  344. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +16 -54
  345. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +10 -52
  346. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +14 -109
  347. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +11 -83
  348. package/.agents/scripts/lib/orchestration/pr-watch.js +48 -306
  349. package/.agents/scripts/lib/orchestration/project-meta-cache.js +15 -80
  350. package/.agents/scripts/lib/orchestration/project-meta-resolver.js +10 -51
  351. package/.agents/scripts/lib/orchestration/reassert-status-column.js +12 -76
  352. package/.agents/scripts/lib/orchestration/remote-verifier.js +12 -36
  353. package/.agents/scripts/lib/orchestration/resolve-stories.js +33 -164
  354. package/.agents/scripts/lib/orchestration/retro-proposals.js +66 -361
  355. package/.agents/scripts/lib/orchestration/review-base-ref.js +9 -43
  356. package/.agents/scripts/lib/orchestration/review-depth.js +12 -91
  357. package/.agents/scripts/lib/orchestration/review-providers/codex.js +10 -111
  358. package/.agents/scripts/lib/orchestration/review-providers/degraded-gates.js +13 -71
  359. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +7 -51
  360. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +8 -53
  361. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -214
  362. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +7 -52
  363. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +4 -33
  364. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +11 -71
  365. package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +19 -131
  366. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +6 -89
  367. package/.agents/scripts/lib/orchestration/review-providers/types.js +26 -56
  368. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +3 -46
  369. package/.agents/scripts/lib/orchestration/run-epilogue.js +245 -326
  370. package/.agents/scripts/lib/orchestration/run-scoped-config.js +55 -159
  371. package/.agents/scripts/lib/orchestration/single-story-close/close-note.js +7 -34
  372. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +16 -89
  373. package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +18 -109
  374. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +83 -239
  375. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +20 -82
  376. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +103 -97
  377. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -64
  378. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +136 -486
  379. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +30 -134
  380. package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +96 -0
  381. package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +49 -0
  382. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +13 -86
  383. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +20 -95
  384. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +46 -203
  385. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +8 -50
  386. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +12 -83
  387. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -39
  388. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -6
  389. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-outcome.js +4 -28
  390. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +6 -51
  391. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +6 -34
  392. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +23 -121
  393. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +180 -273
  394. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +15 -69
  395. package/.agents/scripts/lib/orchestration/story-body-gate.js +4 -23
  396. package/.agents/scripts/lib/orchestration/story-close/baseline-upward-writeback.js +50 -179
  397. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +11 -41
  398. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +23 -311
  399. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +22 -147
  400. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +16 -67
  401. package/.agents/scripts/lib/orchestration/story-deliver-terminal-schema.js +12 -68
  402. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +42 -200
  403. package/.agents/scripts/lib/orchestration/story-follow-ups.js +167 -252
  404. package/.agents/scripts/lib/orchestration/story-init-envelope.js +52 -0
  405. package/.agents/scripts/lib/orchestration/story-init-remote.js +2 -6
  406. package/.agents/scripts/lib/orchestration/story-reachability.js +3 -23
  407. package/.agents/scripts/lib/orchestration/task-body-validator.js +12 -135
  408. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -131
  409. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +15 -149
  410. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +5 -33
  411. package/.agents/scripts/lib/orchestration/ticket-validator.js +63 -344
  412. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +51 -244
  413. package/.agents/scripts/lib/orchestration/ticketing/reads.js +39 -261
  414. package/.agents/scripts/lib/orchestration/ticketing/state.js +14 -68
  415. package/.agents/scripts/lib/orchestration/ticketing/transition.js +46 -263
  416. package/.agents/scripts/lib/orchestration/ticketing.js +2 -26
  417. package/.agents/scripts/lib/orchestration/verify-credit.js +18 -79
  418. package/.agents/scripts/lib/orchestration/worktree-dirty.js +5 -30
  419. package/.agents/scripts/lib/path-security.js +3 -6
  420. package/.agents/scripts/lib/plan-phase-cleanup.js +9 -49
  421. package/.agents/scripts/lib/preflight-runner.js +13 -69
  422. package/.agents/scripts/lib/process-group.js +143 -0
  423. package/.agents/scripts/lib/project-root.js +2 -8
  424. package/.agents/scripts/lib/provider-factory.js +2 -25
  425. package/.agents/scripts/lib/qa/console-allowlist.js +10 -59
  426. package/.agents/scripts/lib/qa/qa-session.js +15 -69
  427. package/.agents/scripts/lib/qa/redact-evidence.js +18 -129
  428. package/.agents/scripts/lib/qa/resolve-qa-contract.js +26 -135
  429. package/.agents/scripts/lib/qa/resolve-selection.js +16 -84
  430. package/.agents/scripts/lib/reserved-test-ids.js +9 -47
  431. package/.agents/scripts/lib/runtime-deps/dep-resolution.js +13 -53
  432. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +10 -44
  433. package/.agents/scripts/lib/runtime-deps/manifest.js +10 -30
  434. package/.agents/scripts/lib/runtime-deps/parser-major.js +15 -51
  435. package/.agents/scripts/lib/runtime-deps/preflight.js +4 -22
  436. package/.agents/scripts/lib/runtime-deps/scan-imports.js +11 -55
  437. package/.agents/scripts/lib/signals/detectors/common.js +12 -38
  438. package/.agents/scripts/lib/signals/index.js +2 -19
  439. package/.agents/scripts/lib/signals/schema.js +14 -109
  440. package/.agents/scripts/lib/signals/write.js +2 -11
  441. package/.agents/scripts/lib/single-story/confirm-merge.js +31 -72
  442. package/.agents/scripts/lib/single-story/story-merged-notify.js +8 -49
  443. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +4 -26
  444. package/.agents/scripts/lib/single-story-sweep/protection.js +13 -100
  445. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +60 -258
  446. package/.agents/scripts/lib/single-story-sweep.js +14 -97
  447. package/.agents/scripts/lib/skills/parse-skill.js +13 -59
  448. package/.agents/scripts/lib/skills/skills-index.js +8 -29
  449. package/.agents/scripts/lib/skills/walk-skill-files.js +17 -78
  450. package/.agents/scripts/lib/source-extensions.js +11 -42
  451. package/.agents/scripts/lib/source-text/strip-js-comments.js +8 -41
  452. package/.agents/scripts/lib/stdio-flush.js +9 -36
  453. package/.agents/scripts/lib/story-adjacency.js +8 -40
  454. package/.agents/scripts/lib/story-body/body-format-lints.js +51 -66
  455. package/.agents/scripts/lib/story-body/footer-block.js +11 -44
  456. package/.agents/scripts/lib/story-body/story-body.js +123 -344
  457. package/.agents/scripts/lib/temp-retention.js +42 -143
  458. package/.agents/scripts/lib/templates/decomposer-prompts.js +18 -80
  459. package/.agents/scripts/lib/test-env.js +12 -65
  460. package/.agents/scripts/lib/test-run-credit.js +10 -65
  461. package/.agents/scripts/lib/test-runner-contract.js +19 -74
  462. package/.agents/scripts/lib/test-temp.js +38 -184
  463. package/.agents/scripts/lib/test-tiers.js +12 -96
  464. package/.agents/scripts/lib/ticket-body-sections.js +21 -96
  465. package/.agents/scripts/lib/transpile.js +15 -74
  466. package/.agents/scripts/lib/util/concurrent-map.js +6 -25
  467. package/.agents/scripts/lib/util/parse-id-list.js +8 -34
  468. package/.agents/scripts/lib/util/poll-loop.js +8 -28
  469. package/.agents/scripts/lib/util/with-timeout.js +2 -11
  470. package/.agents/scripts/lib/validation-evidence.js +21 -96
  471. package/.agents/scripts/lib/wave-runner/footprint.js +17 -70
  472. package/.agents/scripts/lib/wave-runner/live-probe.js +67 -209
  473. package/.agents/scripts/lib/wave-runner/ready-set.js +72 -301
  474. package/.agents/scripts/lib/workers/crap-worker.js +11 -64
  475. package/.agents/scripts/lib/workers/maintainability-report-worker.js +8 -45
  476. package/.agents/scripts/lib/workers/maintainability-worker.js +5 -21
  477. package/.agents/scripts/lib/workers/serve-worker-messages.js +2 -18
  478. package/.agents/scripts/lib/workflow-closure.js +22 -105
  479. package/.agents/scripts/lib/workspace-provisioner.js +16 -54
  480. package/.agents/scripts/lib/worktree/git-hooks.js +15 -60
  481. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +20 -60
  482. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +10 -62
  483. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +22 -86
  484. package/.agents/scripts/lib/worktree/lifecycle/reap.js +24 -126
  485. package/.agents/scripts/lib/worktree/lifecycle-manager.js +2 -20
  486. package/.agents/scripts/lib/worktree/node-modules-strategy.js +46 -180
  487. package/.agents/scripts/lib/worktree-manager.js +15 -40
  488. package/.agents/scripts/lint-issue-body.js +12 -68
  489. package/.agents/scripts/mandrel-update-preflight.js +10 -70
  490. package/.agents/scripts/merge-baseline.js +30 -105
  491. package/.agents/scripts/nav-registry-diff.js +22 -108
  492. package/.agents/scripts/notify.js +12 -69
  493. package/.agents/scripts/plan-context.js +97 -92
  494. package/.agents/scripts/plan-critics.js +12 -66
  495. package/.agents/scripts/plan-persist.js +71 -137
  496. package/.agents/scripts/plan-run-epilogue.js +20 -43
  497. package/.agents/scripts/pr-watch-with-update.js +79 -263
  498. package/.agents/scripts/providers/github/auth.js +3 -8
  499. package/.agents/scripts/providers/github/blocked-by-add.js +14 -61
  500. package/.agents/scripts/providers/github/board-add.js +5 -21
  501. package/.agents/scripts/providers/github/branch-protection.js +11 -43
  502. package/.agents/scripts/providers/github/cache.js +2 -14
  503. package/.agents/scripts/providers/github/comments.js +7 -40
  504. package/.agents/scripts/providers/github/compose.js +3 -19
  505. package/.agents/scripts/providers/github/errors.js +22 -125
  506. package/.agents/scripts/providers/github/issues.js +31 -123
  507. package/.agents/scripts/providers/github/labels.js +24 -124
  508. package/.agents/scripts/providers/github/mappers.js +5 -27
  509. package/.agents/scripts/providers/github/merge-methods.js +3 -22
  510. package/.agents/scripts/providers/github/project-board.js +2 -17
  511. package/.agents/scripts/providers/github/projects-v2-graphql.js +3 -6
  512. package/.agents/scripts/providers/github/request-helpers.js +5 -32
  513. package/.agents/scripts/providers/github/search-budget.js +9 -39
  514. package/.agents/scripts/providers/github/search-query.js +5 -26
  515. package/.agents/scripts/providers/github/sub-issue-add.js +15 -65
  516. package/.agents/scripts/providers/github/sub-issues.js +7 -36
  517. package/.agents/scripts/providers/github/tickets.js +32 -134
  518. package/.agents/scripts/providers/github.js +18 -59
  519. package/.agents/scripts/prune-plan-run-labels.js +12 -46
  520. package/.agents/scripts/quality-preview.js +35 -220
  521. package/.agents/scripts/resolve-doc-tiers.js +3 -26
  522. package/.agents/scripts/resolve-stories.js +16 -67
  523. package/.agents/scripts/resync-status-column.js +6 -25
  524. package/.agents/scripts/run-tests.js +29 -104
  525. package/.agents/scripts/single-story-close.js +23 -135
  526. package/.agents/scripts/single-story-confirm-merge.js +32 -139
  527. package/.agents/scripts/single-story-init.js +47 -259
  528. package/.agents/scripts/stories-wave-tick.js +179 -415
  529. package/.agents/scripts/sync-agentrc.js +3 -21
  530. package/.agents/scripts/sync-claude-agents.js +9 -55
  531. package/.agents/scripts/sync-claude-commands.js +20 -110
  532. package/.agents/scripts/test-wrapper.js +6 -43
  533. package/.agents/scripts/update-coverage-baseline.js +5 -22
  534. package/.agents/scripts/update-crap-baseline.js +7 -30
  535. package/.agents/scripts/update-duplication-baseline.js +16 -92
  536. package/.agents/scripts/update-maintainability-baseline.js +8 -62
  537. package/.agents/scripts/update-ticket-state.js +1 -7
  538. package/.agents/scripts/validate-skills.js +6 -26
  539. package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
  540. package/.agents/skills/skills.index.json +2 -2
  541. package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
  542. package/.agents/templates/agent-protocol.md +2 -2
  543. package/.agents/templates/docs/audit-sweep-runbook.md +3 -4
  544. package/.agents/workflows/audit-accessibility.md +4 -7
  545. package/.agents/workflows/audit-adrs.md +3 -6
  546. package/.agents/workflows/audit-architecture.md +3 -11
  547. package/.agents/workflows/audit-baselines.md +6 -9
  548. package/.agents/workflows/audit-clean-code.md +8 -12
  549. package/.agents/workflows/audit-data-model.md +3 -7
  550. package/.agents/workflows/audit-dependencies.md +4 -7
  551. package/.agents/workflows/audit-devops.md +4 -8
  552. package/.agents/workflows/audit-documentation.md +1 -4
  553. package/.agents/workflows/audit-mobile.md +4 -7
  554. package/.agents/workflows/audit-navigability.md +3 -6
  555. package/.agents/workflows/audit-performance.md +2 -4
  556. package/.agents/workflows/audit-privacy.md +4 -7
  557. package/.agents/workflows/audit-quality.md +4 -7
  558. package/.agents/workflows/audit-security.md +4 -7
  559. package/.agents/workflows/audit-seo.md +4 -7
  560. package/.agents/workflows/audit-sre.md +4 -7
  561. package/.agents/workflows/audit-to-stories.md +17 -30
  562. package/.agents/workflows/audit-ux-ui.md +4 -7
  563. package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
  564. package/.agents/workflows/helpers/audit-lens-core.md +1 -1
  565. package/.agents/workflows/helpers/code-quality-guardrails.md +12 -15
  566. package/.agents/workflows/helpers/code-review.md +4 -2
  567. package/.agents/workflows/helpers/deliver-digest.md +37 -28
  568. package/.agents/workflows/helpers/deliver-light.md +92 -101
  569. package/.agents/workflows/helpers/deliver-reference.md +162 -220
  570. package/.agents/workflows/helpers/deliver-story-reference.md +197 -607
  571. package/.agents/workflows/helpers/deliver-story.md +17 -18
  572. package/.agents/workflows/helpers/plan-reference.md +125 -167
  573. package/.agents/workflows/helpers/qa-core.md +12 -0
  574. package/.agents/workflows/mandrel-deliver.md +47 -31
  575. package/.agents/workflows/mandrel-plan.md +22 -21
  576. package/.agents/workflows/mandrel-update.md +36 -21
  577. package/.agents/workflows/memory-consolidate.md +1 -1
  578. package/.agents/workflows/qa-assist.md +27 -71
  579. package/.agents/workflows/qa-explore.md +19 -59
  580. package/.agents/workflows/qa-run.md +11 -33
  581. package/README.md +1 -1
  582. package/bin/mandrel.js +5 -46
  583. package/bin/postinstall.js +18 -107
  584. package/docs/CHANGELOG.md +60 -0
  585. package/lib/cli/doctor.js +11 -71
  586. package/lib/cli/init.js +26 -165
  587. package/lib/cli/migrate.js +5 -64
  588. package/lib/cli/registry.js +84 -467
  589. package/lib/cli/sync-agents.js +4 -64
  590. package/lib/cli/sync-commands.js +11 -64
  591. package/lib/cli/sync.js +34 -213
  592. package/lib/cli/uninstall.js +40 -219
  593. package/lib/cli/update.js +326 -523
  594. package/lib/cli/version-check.js +8 -72
  595. package/lib/cli/version-helpers.js +14 -87
  596. package/lib/migrations/helpers/retire-agentrc-key.js +11 -52
  597. package/lib/migrations/index.js +17 -92
  598. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +2 -15
  599. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +1 -13
  600. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +2 -15
  601. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +9 -38
  602. package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +1 -13
  603. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +2 -27
  604. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +2 -23
  605. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +2 -27
  606. package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +21 -0
  607. package/lib/migrations/steps/strip-removed-agentrc-keys.js +333 -0
  608. package/package.json +17 -14
  609. package/.agents/docs/SDLC.md +0 -590
  610. package/.agents/docs/quality-gates.md +0 -1179
  611. package/.agents/rules/known-tooling-behavior.md +0 -150
  612. package/.agents/rules/orchestration-error-handling.md +0 -61
  613. package/.agents/rules/test-seams.md +0 -59
  614. package/.agents/schemas/baselines/lighthouse.schema.json +0 -59
  615. package/.agents/schemas/baselines/lint.schema.json +0 -47
  616. package/.agents/schemas/model-attribution.schema.json +0 -53
  617. package/.agents/scripts/check-action-pinning.js +0 -260
  618. package/.agents/scripts/check-audit-attribution.js +0 -302
  619. package/.agents/scripts/check-baseline-drift.js +0 -211
  620. package/.agents/scripts/check-baseline-scope.js +0 -362
  621. package/.agents/scripts/check-generated-validator.js +0 -202
  622. package/.agents/scripts/check-knip-entries.js +0 -159
  623. package/.agents/scripts/check-lifecycle-lint.js +0 -294
  624. package/.agents/scripts/check-pinned-override-notes.js +0 -102
  625. package/.agents/scripts/check-schema-references.js +0 -366
  626. package/.agents/scripts/check-test-portability.js +0 -512
  627. package/.agents/scripts/check-workflow-citations.js +0 -332
  628. package/.agents/scripts/check-workflow-cli-lint.js +0 -299
  629. package/.agents/scripts/check-workflow-timeouts.js +0 -291
  630. package/.agents/scripts/install-matrix-assert.js +0 -326
  631. package/.agents/scripts/lib/audit-advisories.js +0 -195
  632. package/.agents/scripts/lib/audit-attribution.js +0 -134
  633. package/.agents/scripts/lib/baselines/drift-detector.js +0 -351
  634. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +0 -87
  635. package/.agents/scripts/lib/baselines/kinds/lint.js +0 -184
  636. package/.agents/scripts/lib/baselines/orphan-pruner.js +0 -233
  637. package/.agents/scripts/lib/baselines/scope-assert.js +0 -223
  638. package/.agents/scripts/lib/baselines/scope-inventory.js +0 -314
  639. package/.agents/scripts/lib/c8-cli-path.js +0 -21
  640. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +0 -51
  641. package/.agents/scripts/lib/config/gates/lint.schema.js +0 -18
  642. package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +0 -70
  643. package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +0 -284
  644. package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +0 -80
  645. package/.agents/scripts/lib/dynamic-workflow/degraded-coverage.js +0 -81
  646. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +0 -87
  647. package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +0 -74
  648. package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +0 -90
  649. package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +0 -43
  650. package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +0 -83
  651. package/.agents/scripts/lib/fs-walk.js +0 -52
  652. package/.agents/scripts/lib/knip-config-resolver.js +0 -181
  653. package/.agents/scripts/lib/knip-entry-sync.js +0 -452
  654. package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
  655. package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
  656. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
  657. package/.agents/scripts/lib/pinned-override-notes.js +0 -88
  658. package/.agents/scripts/lib/pinned-override-resolve.js +0 -212
  659. package/.agents/scripts/lib/test-isolate/cli-options.js +0 -93
  660. package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +0 -52
  661. package/.agents/scripts/lib/test-isolate/list-files.js +0 -90
  662. package/.agents/scripts/lib/test-isolate/parse-tap.js +0 -75
  663. package/.agents/scripts/lib/test-isolate/progress-log.js +0 -45
  664. package/.agents/scripts/lib/test-isolate/render-report.js +0 -97
  665. package/.agents/scripts/lib/test-isolate/run-isolate.js +0 -87
  666. package/.agents/scripts/lib/test-isolate/runner.js +0 -483
  667. package/.agents/scripts/lib/test-profile/parse-tap.js +0 -136
  668. package/.agents/scripts/lib/test-profile/render-report.js +0 -45
  669. package/.agents/scripts/lint-label-vocabulary.js +0 -214
  670. package/.agents/scripts/post-structured-comment.js +0 -127
  671. package/.agents/scripts/provision-git-hooks.js +0 -85
  672. package/.agents/scripts/prune-baseline-orphans.js +0 -181
  673. package/.agents/scripts/run-coverage.js +0 -197
  674. package/.agents/scripts/run-lint.js +0 -133
  675. package/.agents/scripts/run-test-profile.js +0 -129
  676. package/.agents/scripts/run-verify.js +0 -118
  677. package/.agents/scripts/test-isolate.js +0 -55
  678. package/.agents/scripts/update-dead-exports-baseline.js +0 -321
package/lib/cli/update.js CHANGED
@@ -1,133 +1,12 @@
1
1
  // lib/cli/update.js
2
2
  /**
3
- * `mandrel update` subcommand — the auto-update orchestrator (f-update-command,
4
- * Story #3503, Epic #3437 — Auto-Update & Version Lifecycle).
5
- *
6
- * Advances `mandrel` to the newest published version, re-materializes
7
- * `.agents/`, runs applicable version-keyed migrations, surfaces the target
8
- * changelog, and verifies the result via the doctor registry. Major crossings
9
- * are applied like any other bump (hard-cutover doctrine —
10
- * `.agents/rules/git-conventions.md` § Contract Cutovers).
11
- *
12
- * ## Ordered cycle (happy path)
13
- *
14
- * 1. resolve target version (newest published) and the current version
15
- * 2. drift-aware short-circuit — version check gates on installed version AND
16
- * materialized `.agents/` state (Story #4065). When already on newest AND
17
- * no drift: nothing to do (true no-op). When already on newest BUT drift
18
- * detected: skip npm-update/migrations, run sync + sync-commands to heal.
19
- * 3. install — bump the dependency (lockfile bump left STAGED).
20
- * The package manager is auto-detected from the lockfile in the project
21
- * root: `pnpm-lock.yaml` ⇒ `pnpm add -D …` (with `-w` at a
22
- * `pnpm-workspace.yaml` root), `yarn.lock` ⇒ `yarn add -D …`, otherwise
23
- * `npm install …`. An explicit `--install-cmd "<pm> <args>"` overrides
24
- * detection; a `{target}` placeholder in the override is substituted with
25
- * the resolved version so an override can still consume the auto-probed
26
- * newest. The registry probe in step 1 always stays on `npm view` (a
27
- * PM-agnostic registry query).
28
- * 4. runSync — re-materialize ./.agents/ **from the newly-installed
29
- * binary** so the materialized payload is always the target version's.
30
- * 5. runMigrations — apply version-keyed steps for the crossed range,
31
- * **from the newly-installed binary**.
32
- * 6. doctor — run the check registry **from the newly-installed
33
- * binary** so `agents-drift` is never a false-green against stale payload.
34
- * 7. surface the changelog for the target version
35
- *
36
- * ## Re-exec of post-install phases (Story #4034)
37
- *
38
- * Steps 4–6 execute as **child processes spawned from the newly-installed
39
- * bin script** (`node <cwd>/node_modules/mandrel/bin/mandrel.js`; Story #4613
40
- * resolves the script layout-agnostically rather than via the `.bin` shim)
41
- * rather than in the running
42
- * process. Node cannot hot-swap a `require`d module mid-process, so without
43
- * re-exec, the still-running old binary's `runSync`/`runMigrations`/`runDoctor`
44
- * code would materialise the old payload even though the package on disk has
45
- * already been updated. This produced the silent stale-`.agents/`
46
- * materialization and `doctor` false-green observed in the v1.58.0 → v1.59.0
47
- * consumer upgrade.
48
- *
49
- * The orchestration (progress messages, step tracking, changelog surface, exit
50
- * code) stays in the parent process; only the version-sensitive phases run from
51
- * the new bin. The `spawnPhase` seam makes the child-process boundary fully
52
- * injectable so tests can verify the re-exec path without a real npm install.
53
- * It is the **only** post-install execution path — tests stub the spawn
54
- * boundary rather than swapping in an in-process implementation (No-Shim:
55
- * `.agents/rules/git-conventions.md` § Contract Cutovers).
56
- *
57
- * ## No git mutation
58
- *
59
- * The npm dependency bump rewrites `package.json` / `package-lock.json` in the
60
- * working tree but the orchestrator performs **no** `git add` / `git commit`:
61
- * the lockfile bump is left staged-on-disk for the operator to review and
62
- * commit. This module never shells out to git.
63
- *
64
- * ## `--dry-run`
65
- *
66
- * Prints the resolved target version and the ordered step plan, then returns
67
- * without invoking any effectful seam (no npm update, no sync, no migrations,
68
- * no doctor) and writing nothing.
69
- *
70
- * ## Changelog surface
71
- *
72
- * `defaultSurfaceChangelog` prints the `docs/CHANGELOG.md` section(s) for the
73
- * applied range `(current, target]`. It resolves the file against the target
74
- * version's install directory (the freshly bumped `node_modules/mandrel/`),
75
- * where the changelog is now included in the published tarball
76
- * (`docs/CHANGELOG.md` in the `files` allowlist — Story #4035).
77
- *
78
- * When the packaged file is absent (e.g. an older installed version predating
79
- * Story #4035), the seam attempts a one-shot HTTP GET of the raw file from
80
- * GitHub via the injectable `fetchChangelog` seam. If that fetch also fails,
81
- * the seam degrades gracefully — never throwing — and emits an actionable
82
- * message directing the operator to the GitHub Releases page.
83
- *
84
- * ## Injectable seams (used by lib/cli/__tests__/update*.test.js)
85
- *
86
- * - `argv` — subcommand args (after `mandrel update`)
87
- * - `currentVersion` — the installed `mandrel` version string
88
- * - `resolveTargetVersion`— async, returns the newest published version
89
- * - `checkDrift` — sync or async, returns `true` when `.agents/`
90
- * differs from the installed payload. Used by the
91
- * drift-aware no-op short-circuit (Story #4065).
92
- * Defaults to `() => !runAgentsDrift().ok`, which
93
- * reuses the same `agents-drift` doctor signal.
94
- * - `npmUpdate` — async, performs the dependency bump (no git);
95
- * receives `(target, { installCmd })`
96
- * - `spawnPhase` — async, spawns a post-install phase from the new
97
- * binary; receives `(phase, args, { binPath, cwd })`
98
- * and returns `{ ok, stdout, stderr }`. This is the
99
- * sole post-install execution path. See § Re-exec of
100
- * post-install phases.
101
- * - `surfaceChangelog` — emits the target changelog section
102
- * - `write` / `writeErr` — stdout / stderr sinks
103
- * - `exit` — process.exit replacement
104
- * - `cwd` — process.cwd() replacement (used to resolve the
105
- * new binary path the post-install phases spawn from)
106
- *
107
- * Security (security-baseline § 5 — Data Leakage & Logging): logs only version
108
- * strings and step names. No tokens, credentials, or env
109
- * values are read or logged; no shell-string interpolation occurs here (the
110
- * npm bump is delegated to the injected `npmUpdate` seam, which owns transport).
111
- *
112
- * ## Windows spawn (CVE-2024-27980)
113
- *
114
- * Both child-process boundaries — the `npm view` registry probe and the
115
- * install — route through helpers that pass `shell: process.platform ===
116
- * 'win32'`. On Windows `npm`/`pnpm`/`yarn` resolve to `.cmd` shims, and
117
- * Node 18.20+/20.12+/22+/24 refuses to spawn `.cmd`/`.bat` with `shell:false`
118
- * (the CVE-2024-27980 hardening), throwing `spawnSync npm ENOENT`. The win32
119
- * shell flag is the documented fix. It is injection-safe because every argv
120
- * here is a **fixed vector**: the probe argv is the constant package name, and
121
- * the install argv is a tokenized list whose only variable segment is a
122
- * resolved semver string — see `lib/install-cmd-parser.js` for the shared
123
- * tokenize-and-spawn rationale this module reuses (no duplicated workaround).
124
- *
125
- * The `spawnPhase` default (Story #4034) does **not** use the win32 shell flag:
126
- * it spawns `process.execPath` (node) against the resolved `bin/mandrel.js`
127
- * script (Story #4613), so it never touches a `.cmd` shim and needs no
128
- * shell on any platform. The per-phase argv vector is a constant fixed list
129
- * (e.g. `['sync']`, `['migrate', '--from', v, '--to', v]`, `['doctor']`) with
130
- * no operator-supplied data.
3
+ * `mandrel update`: install the newest published version, then re-materialize
4
+ * `.agents/`, run migrations and doctor from the NEW binary (Node cannot
5
+ * hot-swap loaded modules, so in-process phases would materialize the old
6
+ * payload), and surface the changelog. Already-newest with `.agents/` drift
7
+ * runs only the sync phases. Never mutates git: it reports the real index state.
8
+ * Windows: `npm`/`pnpm`/`yarn` are `.cmd` shims that need `shell: true` under
9
+ * CVE-2024-27980; every argv here is a fixed vector, so that stays safe.
131
10
  */
132
11
 
133
12
  import { spawnSync } from 'node:child_process';
@@ -146,38 +25,18 @@ import {
146
25
  resolveConsumerPinVersion,
147
26
  } from './version-helpers.js';
148
27
 
149
- /** The published package whose newest version `mandrel update` advances to. */
150
28
  const PACKAGE_NAME = 'mandrel';
151
29
 
152
- /**
153
- * GitHub raw-file base URL for fetching `docs/CHANGELOG.md` when the packaged
154
- * file is absent (Story #4035 — GitHub fallback). Resolves to the tagged
155
- * release, e.g. `.../mandrel-v1.59.0/docs/CHANGELOG.md`.
156
- */
157
30
  const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/dsj1984/mandrel/';
158
31
 
159
- /**
160
- * Human-readable GitHub Releases page — surfaced in the actionable fallback
161
- * message when neither the packaged file nor the GitHub fetch succeeds.
162
- */
163
32
  const GITHUB_RELEASES_URL = 'https://github.com/dsj1984/mandrel/releases';
164
33
 
165
- /** Default freshness-cache filename — mirrors version-check.js. */
166
34
  const DEFAULT_CACHE_FILENAME = 'version-check.json';
167
35
 
168
36
  /**
169
- * Resolve the installed `mandrel` version from this package's own
170
- * `package.json`. The module lives at `<root>/lib/cli/update.js`, so the
171
- * manifest is two directories up.
172
- *
173
- * Pre-Story-#4525 this was the update decision's `current` — the exact
174
- * self-referential confusion #4525 filed: "the version of the mandrel that
175
- * is executing" is tautologically `>= target` whenever the installed
176
- * package is newest, which made the `npm-update` step unreachable whenever
177
- * a consumer's declared pin had fallen behind what happened to be resolved
178
- * in `node_modules`. It survives here only as the last-resort fallback
179
- * inside {@link resolveCurrentVersionForUpdate}, for the case where even
180
- * `node_modules` resolution from the consumer root fails.
37
+ * The version of the EXECUTING package — last-resort fallback only: as the
38
+ * update decision's `current` it is tautologically newest and would hide a
39
+ * lagging consumer pin.
181
40
  *
182
41
  * @param {typeof nodeFs} [fs]
183
42
  * @returns {string}
@@ -190,27 +49,14 @@ function defaultCurrentVersion(fs = nodeFs) {
190
49
  }
191
50
 
192
51
  /**
193
- * Resolve the "current" version for the `mandrel update` decision
194
- * (Story #4525 / #4530): the consumer's declared `mandrel` dependency pin
195
- * when it resolves to a plain semver, falling back — in order — to the
196
- * version actually resolvable in the consumer's `node_modules` (anchored at
197
- * `consumerRoot` via the same resolution `mandrel sync` uses, unlike the
198
- * pre-#4525 self-referential `defaultCurrentVersion`), and finally to
199
- * `defaultCurrentVersion` itself when neither resolves (a corrupted or
200
- * highly unusual install — keeps `mandrel update` from throwing outright
201
- * rather than silently misreporting "already current").
202
- *
203
- * The declared pin is preferred because it is exactly what the `npm-update`
204
- * step moves: a consumer whose `package.json` pin lags an inflated
205
- * `node_modules` resolution (e.g. an out-of-band symlink or manual
206
- * `npm install mandrel@latest --no-save`) must still see `planUpdate`
207
- * choose `updated`, not `resynced` — the bug #4525 reported.
52
+ * The update decision's `current`: the consumer's declared pin (it is what
53
+ * `npm-update` moves, so a pin lagging an inflated `node_modules` still
54
+ * updates), else the consumer's resolved `node_modules` version, else the
55
+ * executing package's own.
208
56
  *
209
57
  * @param {string} consumerRoot
210
58
  * @param {typeof nodeFs} [fs]
211
- * @param {{ resolvePackageRoot?: (fromDir: string) => string }} [opts] - test
212
- * seam for the `node_modules` resolution tier; defaults to the real
213
- * `defaultResolvePackageRoot` from `sync.js`.
59
+ * @param {{ resolvePackageRoot?: (fromDir: string) => string }} [opts]
214
60
  * @returns {string}
215
61
  */
216
62
  export function resolveCurrentVersionForUpdate(
@@ -231,42 +77,15 @@ export function resolveCurrentVersionForUpdate(
231
77
  }
232
78
  }
233
79
 
234
- /**
235
- * Resolve the project root — the directory two levels up from this module
236
- * (`<root>/lib/cli/update.js`). Mirrors `lib/cli/registry.js#resolveProjectRoot`.
237
- *
238
- * @returns {string}
239
- */
80
+ /** @returns {string} */
240
81
  function resolveProjectRoot() {
241
82
  const here = path.dirname(fileURLToPath(import.meta.url));
242
83
  return path.resolve(here, '..', '..');
243
84
  }
244
85
 
245
86
  /**
246
- * Default `resolveTargetVersion` seam: determine the newest published
247
- * `mandrel` version via the daily freshness cache (`version-check.js`).
248
- *
249
- * When `bypassCache` is `true` (the default for an explicit `mandrel update`
250
- * call — Story #4046 A1b), `isStale` is asked to skip its freshness read via
251
- * `forceRefresh`, so any existing cache is ignored and exactly one network
252
- * probe is issued. The cache is still written so the post-update
253
- * `version-current` advisory has a fresh baseline.
254
- *
255
- * The bypass deliberately does **not** shift `now`: the same clock is stamped
256
- * into the refreshed `checkedAt`, so a shifted clock would persist a
257
- * ~48h-future timestamp and defeat the 24h window for every later passive
258
- * check until real time caught up (Story #4878).
259
- *
260
- * When `bypassCache` is `false` (passive staleness checks only), the normal
261
- * 24h-cache semantics apply: a fresh cache returns the cached version with
262
- * zero network I/O.
263
- *
264
- * The network probe shells `npm view` through `spawnSync` with a fixed argument
265
- * vector (no shell-string interpolation; the package name is a constant). On
266
- * Windows the spawn sets `shell: true` so the `npm.cmd` shim resolves under the
267
- * CVE-2024-27980 hardening (mirrors `lib/install-cmd-parser.js`); the fixed
268
- * argv carries no injection risk even with the shell flag set
269
- * (security-baseline § Output & Rendering).
87
+ * Newest published version via the daily freshness cache. `bypassCache` forces
88
+ * one probe but still writes the cache.
270
89
  *
271
90
  * @param {{
272
91
  * cachePath?: string,
@@ -286,9 +105,8 @@ async function defaultResolveTargetVersion({
286
105
  bypassCache = false,
287
106
  log = () => {},
288
107
  } = {}) {
289
- // Bypass the cache by telling isStale to skip its freshness read — never by
290
- // shifting `now`, which is also the clock persisted as `checkedAt`
291
- // (Story #4046 A1b forcing the probe; Story #4878 keeping the stamp honest).
108
+ // Bypass via forceRefresh, never by shifting `now`: `now` is persisted as
109
+ // `checkedAt`, and a shifted stamp would defeat the 24h window.
292
110
  const result = await isStale({
293
111
  cachePath,
294
112
  now,
@@ -301,15 +119,10 @@ async function defaultResolveTargetVersion({
301
119
  }
302
120
 
303
121
  /**
304
- * Default network `runner` for the freshness probe: shells
305
- * `npm view mandrel version` synchronously and returns the trimmed
306
- * stdout. Fixed argv (the package name is a constant), and `shell:true` only on
307
- * Windows so the `npm.cmd` shim resolves under CVE-2024-27980 — the fixed
308
- * vector keeps it injection-safe with or without the shell flag.
122
+ * `npm view mandrel version`, trimmed.
309
123
  *
310
- * @param {{ spawnSync?: typeof spawnSync }} [deps] — test seam for the spawn
311
- * boundary; defaults to the real `node:child_process` spawnSync.
312
- * @returns {string} The newest published version string.
124
+ * @param {{ spawnSync?: typeof spawnSync }} [deps]
125
+ * @returns {string}
313
126
  */
314
127
  export function defaultVersionRunner({ spawnSync: spawn = spawnSync } = {}) {
315
128
  const r = spawn('npm', ['view', PACKAGE_NAME, 'version'], {
@@ -337,9 +150,7 @@ export function defaultVersionRunner({ spawnSync: spawn = spawnSync } = {}) {
337
150
  }
338
151
 
339
152
  /**
340
- * Map a detected package manager to the command that re-runs a full install.
341
- * Surfaced in the repair hint when an install fails so the operator can restore
342
- * `node_modules` to a consistent state (Story #3575 AC-4).
153
+ * The full-install command named in the failed-install repair hint.
343
154
  *
344
155
  * @param {'pnpm' | 'yarn' | 'npm'} packageManager
345
156
  * @returns {string}
@@ -351,29 +162,30 @@ function repairInstallCommand(packageManager) {
351
162
  }
352
163
 
353
164
  /**
354
- * Detect the project's package manager by probing for a lockfile in `cwd`.
355
- * Precedence mirrors the ecosystem norm: a `pnpm-lock.yaml` wins over a
356
- * `yarn.lock`, which wins over the npm default. `workspaceRoot` is true only
357
- * for pnpm when a `pnpm-workspace.yaml` sits alongside the lockfile — the
358
- * signal that `pnpm add` must carry `-w` to target the workspace-root manifest.
359
- *
360
- * Running the wrong package manager (e.g. `npm install` in a pnpm workspace) is
361
- * the root cause this resolves (Story #3575): npm chokes on the pnpm-managed
362
- * tree, exits non-zero, and can flip `node_modules` to a stale store entry.
363
- * Detecting the lockfile keeps the bump on the operator's real package manager
364
- * so the change lands in the matching lockfile.
165
+ * The lockfile-detected package manager, so the bump runs under the tool that
166
+ * owns the tree (npm in a pnpm workspace fails and can corrupt `node_modules`).
167
+ * `workspaceRoot` means `pnpm add` needs `-w`. `bun` coerces to `npm`: the
168
+ * install builder has no bun command.
365
169
  *
366
- * Delegates to the shared `detectPackageManagerWithWorkspace` helper
367
- * (Story #4048 B3 — one implementation per concept). The `fs` seam is adapted
368
- * to the shared module's `exists` contract; the shared module's `bun` return
369
- * value coerces to `npm` here because `bun add` is not yet a first-class update
370
- * path for this orchestrator.
371
- *
372
- * @param {string} [cwd] - Project root to probe (default `process.cwd()`).
170
+ * @param {string} [cwd]
373
171
  * @param {typeof nodeFs} [fs]
374
172
  * @returns {{ packageManager: 'pnpm' | 'yarn' | 'npm', workspaceRoot: boolean }}
375
173
  */
376
174
  export function detectPackageManager(cwd = process.cwd(), fs = nodeFs) {
175
+ const result = probePackageManager(cwd, fs);
176
+ const packageManager =
177
+ result.packageManager === 'bun' ? 'npm' : result.packageManager;
178
+ return { packageManager, workspaceRoot: result.workspaceRoot };
179
+ }
180
+
181
+ /**
182
+ * The uncoerced probe; the staging report must name bun's real lockfile.
183
+ *
184
+ * @param {string} cwd
185
+ * @param {typeof nodeFs} [fs]
186
+ * @returns {{ packageManager: 'pnpm'|'yarn'|'bun'|'npm', workspaceRoot: boolean }}
187
+ */
188
+ function probePackageManager(cwd, fs = nodeFs) {
377
189
  const exists = (p) => {
378
190
  try {
379
191
  return fs.existsSync(p);
@@ -381,33 +193,15 @@ export function detectPackageManager(cwd = process.cwd(), fs = nodeFs) {
381
193
  return false;
382
194
  }
383
195
  };
384
- const result = detectPackageManagerWithWorkspace(cwd, exists);
385
- // Coerce `bun` → `npm` because this orchestrator's install-command builder
386
- // only handles pnpm / yarn / npm today.
387
- const packageManager =
388
- result.packageManager === 'bun' ? 'npm' : result.packageManager;
389
- return { packageManager, workspaceRoot: result.workspaceRoot };
196
+ return detectPackageManagerWithWorkspace(cwd, exists);
390
197
  }
391
198
 
392
199
  /**
393
- * Resolve the install command string `defaultNpmUpdate` runs.
394
- *
395
- * With no override the command is built from the detected package manager:
396
- * - `pnpm` ⇒ `pnpm add -D mandrel@<target>` (plus ` -w` at a pnpm
397
- * workspace root)
398
- * - `yarn` ⇒ `yarn add -D mandrel@<target>`
399
- * - `npm` ⇒ `npm install mandrel@<target>` (the unchanged default)
400
- *
401
- * An explicit `--install-cmd` override is used verbatim, except that a
402
- * `{target}` placeholder is substituted with the resolved semver so an override
403
- * can still consume the auto-probed newest version (Story #3575 AC-3).
404
- *
405
- * This function is pure: package-manager detection happens in
406
- * `detectPackageManager` (the only filesystem seam) and is passed in as
407
- * `detected`, keeping the command-string assembly trivially unit-testable.
200
+ * The install command. An `--install-cmd` override is used verbatim except
201
+ * that `{target}` is replaced with the resolved semver.
408
202
  *
409
- * @param {string} target - The resolved semver to install.
410
- * @param {string} [override] - Operator-supplied `--install-cmd` value.
203
+ * @param {string} target
204
+ * @param {string} [override]
411
205
  * @param {{
412
206
  * packageManager?: 'pnpm' | 'yarn' | 'npm',
413
207
  * workspaceRoot?: boolean,
@@ -434,27 +228,221 @@ export function resolveInstallCmd(
434
228
  return `npm install ${PACKAGE_NAME}@${target}`;
435
229
  }
436
230
 
231
+ const LOCKFILE_BY_PACKAGE_MANAGER = {
232
+ pnpm: 'pnpm-lock.yaml',
233
+ yarn: 'yarn.lock',
234
+ bun: 'bun.lockb',
235
+ npm: 'package-lock.json',
236
+ };
237
+
238
+ const MANIFEST_FILENAME = 'package.json';
239
+
240
+ const AGENTS_DIR = '.agents';
241
+
242
+ /**
243
+ * @param {string} cwd
244
+ * @param {typeof nodeFs} [fs]
245
+ * @returns {string}
246
+ */
247
+ export function defaultDetectLockfile(cwd, fs = nodeFs) {
248
+ const { packageManager } = probePackageManager(cwd, fs);
249
+ return LOCKFILE_BY_PACKAGE_MANAGER[packageManager] ?? 'package-lock.json';
250
+ }
251
+
252
+ const NEUTRAL_STAGING_LINE =
253
+ 'Review the working tree and commit the bump (git not available to report staging state).';
254
+
255
+ const DEGRADED_GIT_STATE = Object.freeze({
256
+ ok: false,
257
+ stagedManifest: false,
258
+ stagedLockfile: false,
259
+ stagedPayload: false,
260
+ tracksAgents: false,
261
+ });
262
+
263
+ const spawnOk = (result) =>
264
+ Boolean(result) && !result.error && result.status === 0;
265
+
266
+ /**
267
+ * The path a porcelain v1 record reports as staged, else `null`. Staged means
268
+ * index differs from HEAD AND the worktree agrees (`Y` blank), so a pair staged
269
+ * before the install and rewritten by it (`MM`) is not staged. Renames yield
270
+ * the new path; porcelain quoting is stripped.
271
+ *
272
+ * @param {string} record
273
+ * @returns {string | null}
274
+ */
275
+ function stagedPathFromPorcelain(record) {
276
+ if (record.length < 4 || record[1] !== ' ') return null;
277
+ if (record[0] === ' ' || record[0] === '?') return null;
278
+ const raw = record.slice(3).trim();
279
+ const arrow = raw.lastIndexOf(' -> ');
280
+ const pathPart = arrow === -1 ? raw : raw.slice(arrow + 4);
281
+ const path =
282
+ pathPart.startsWith('"') && pathPart.endsWith('"')
283
+ ? pathPart.slice(1, -1)
284
+ : pathPart;
285
+ return path.length > 0 ? path : null;
286
+ }
287
+
288
+ /**
289
+ * The probe-state slot a staged path fills. Payload is tested first (a
290
+ * `package.json` inside `.agents/` is payload). The manifest/lockfile tests
291
+ * accept a leading prefix because porcelain paths are repo-root-relative;
292
+ * that is safe only because the caller's pathspec anchors the query at `cwd`.
293
+ *
294
+ * @param {string} path
295
+ * @param {string} lockfile
296
+ * @returns {'stagedPayload' | 'stagedManifest' | 'stagedLockfile' | null}
297
+ */
298
+ function stagedSlotFor(path, lockfile) {
299
+ if (path === AGENTS_DIR || /(^|\/)\.agents\//.test(path))
300
+ return 'stagedPayload';
301
+ const isRootFile = (name) => path === name || path.endsWith(`/${name}`);
302
+ if (isRootFile(MANIFEST_FILENAME)) return 'stagedManifest';
303
+ return isRootFile(lockfile) ? 'stagedLockfile' : null;
304
+ }
305
+
437
306
  /**
438
- * Default `npmUpdate` seam: install the resolved target version. The install
439
- * rewrites `package.json` / the lockfile on disk (left staged for the
440
- * operator); this performs no git mutation.
307
+ * Read-only index probe: a `cwd`-anchored `git status --porcelain` over the
308
+ * manifest, lockfile and `.agents`, plus `git ls-files .agents` (is the
309
+ * payload tracked?). Never throws; any failure degrades to `{ ok: false }`.
441
310
  *
442
- * The package manager is auto-detected from `cwd`'s lockfile (Story #3575) so
443
- * the bump lands in the operator's real lockfile rather than running
444
- * `npm install` against a pnpm/yarn-managed tree. The install routes through
445
- * the shared `runInstallCommand` helper from `lib/install-cmd-parser.js`, which
446
- * tokenizes the command and spawns with `shell: process.platform === 'win32'`
447
- * so the Windows `.cmd` shim resolves under CVE-2024-27980 — the win32 shell
448
- * handling and tokenization are reused, not re-implemented here. The resolved
449
- * argv is a fixed vector; an `--install-cmd` override is tokenized and escaped
450
- * per-arg by the parser even when the win32 shell flag is required.
311
+ * @param {{
312
+ * cwd?: string,
313
+ * lockfile?: string,
314
+ * spawnSync?: typeof spawnSync,
315
+ * }} [opts]
316
+ * @returns {{
317
+ * ok: boolean,
318
+ * stagedManifest: boolean,
319
+ * stagedLockfile: boolean,
320
+ * stagedPayload: boolean,
321
+ * tracksAgents: boolean,
322
+ * }}
323
+ */
324
+ export function defaultGitStatus({
325
+ cwd = process.cwd(),
326
+ lockfile = LOCKFILE_BY_PACKAGE_MANAGER.npm,
327
+ spawnSync: spawn = spawnSync,
328
+ } = {}) {
329
+ const run = (args) => spawn('git', args, { cwd, encoding: 'utf8' });
330
+ try {
331
+ const status = run([
332
+ 'status',
333
+ '--porcelain',
334
+ '--',
335
+ MANIFEST_FILENAME,
336
+ lockfile,
337
+ AGENTS_DIR,
338
+ ]);
339
+ if (!spawnOk(status)) return DEGRADED_GIT_STATE;
340
+ const agents = run(['ls-files', AGENTS_DIR]);
341
+ const state = {
342
+ ...DEGRADED_GIT_STATE,
343
+ ok: true,
344
+ tracksAgents: spawnOk(agents) && String(agents.stdout).trim().length > 0,
345
+ };
346
+ for (const record of String(status.stdout).split('\n')) {
347
+ const path = stagedPathFromPorcelain(record);
348
+ const slot = path && stagedSlotFor(path, lockfile);
349
+ if (slot) state[slot] = true;
350
+ }
351
+ return state;
352
+ } catch {
353
+ return DEGRADED_GIT_STATE;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * The staging line a successful run closes with. `bump` reports the manifest +
359
+ * lockfile pair, and a not-staged hint also names a tracked `.agents/` (else
360
+ * that diff is silently dropped). `payload` (drift heal) reports only
361
+ * `.agents/`, or `''` when it is untracked.
451
362
  *
452
- * On any install failure the thrown error names the detected package manager's
453
- * own `install` command so the operator can restore `node_modules` to a
454
- * consistent state — `mandrel update` never silently leaves a half-mutated
455
- * tree (Story #3575 AC-4).
363
+ * @param {{
364
+ * ok?: boolean,
365
+ * stagedManifest?: boolean,
366
+ * stagedLockfile?: boolean,
367
+ * stagedPayload?: boolean,
368
+ * tracksAgents?: boolean,
369
+ * }} gitState
370
+ * @param {string} lockfile
371
+ * @param {{ scope?: 'bump' | 'payload' }} [opts]
372
+ * @returns {string}
373
+ */
374
+ export function formatStagingReport(
375
+ gitState,
376
+ lockfile,
377
+ { scope = 'bump' } = {},
378
+ ) {
379
+ const {
380
+ ok = false,
381
+ stagedManifest = false,
382
+ stagedLockfile = false,
383
+ stagedPayload = false,
384
+ tracksAgents = false,
385
+ } = gitState ?? {};
386
+
387
+ if (!ok) return NEUTRAL_STAGING_LINE;
388
+
389
+ if (scope === 'payload') {
390
+ if (!tracksAgents) return '';
391
+ return stagedPayload
392
+ ? `The re-materialized ${AGENTS_DIR}/ payload is staged for review.`
393
+ : `The re-materialized ${AGENTS_DIR}/ payload is NOT staged. Review and stage it: git add ${AGENTS_DIR}/`;
394
+ }
395
+
396
+ if (stagedManifest && stagedLockfile) {
397
+ return `The dependency bump is staged for review (${MANIFEST_FILENAME}, ${lockfile}).`;
398
+ }
399
+
400
+ const targets = [MANIFEST_FILENAME, lockfile];
401
+ let note = '';
402
+ if (tracksAgents) {
403
+ targets.push(`${AGENTS_DIR}/`);
404
+ note = ` ${AGENTS_DIR}/ is tracked here, so stage the re-materialized payload too.`;
405
+ }
406
+ return `The dependency bump is NOT staged.${note} Review and stage it: git add ${targets.join(' ')}`;
407
+ }
408
+
409
+ /**
410
+ * The staging line, degrading any seam failure to the neutral line so a
411
+ * courtesy report never crashes a completed update.
456
412
  *
457
- * @param {string} target - The version to install.
413
+ * @param {{
414
+ * gitStatus: (opts: { cwd: string, lockfile: string }) => object,
415
+ * detectLockfile: (cwd: string) => string,
416
+ * projectRoot: string,
417
+ * scope?: 'bump' | 'payload',
418
+ * }} deps
419
+ * @returns {string}
420
+ */
421
+ function resolveStagingReport({
422
+ gitStatus,
423
+ detectLockfile,
424
+ projectRoot,
425
+ scope = 'bump',
426
+ }) {
427
+ try {
428
+ const lockfile = detectLockfile(projectRoot);
429
+ return formatStagingReport(
430
+ gitStatus({ cwd: projectRoot, lockfile }),
431
+ lockfile,
432
+ { scope },
433
+ );
434
+ } catch {
435
+ return NEUTRAL_STAGING_LINE;
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Install `target` with the detected package manager (no git mutation). The
441
+ * shared `runInstallCommand` tokenizes and escapes per-arg under the win32
442
+ * shell. A failure names the repair install so `node_modules` is never left
443
+ * silently half-mutated.
444
+ *
445
+ * @param {string} target
458
446
  * @param {{
459
447
  * installCmd?: string,
460
448
  * runInstall?: typeof runInstallCommand,
@@ -494,30 +482,16 @@ export function defaultNpmUpdate(
494
482
  }
495
483
 
496
484
  /**
497
- * Fetch `docs/CHANGELOG.md` for a specific mandrel tag from GitHub's raw
498
- * content endpoint. This is the fallback when the packaged file is absent
499
- * (e.g. an older install predating Story #4035 which added the file to the
500
- * npm `files` allowlist).
501
- *
502
- * Injectable via the `fetchChangelog` seam so tests can verify the fallback
503
- * path without issuing real network calls.
485
+ * `docs/CHANGELOG.md` from GitHub raw, for installs whose package lacks it.
486
+ * Tags are `mandrel-vX.Y.Z` from 1.44.0 and bare `vX.Y.Z` before, so both
487
+ * are tried.
504
488
  *
505
- * The tag shape follows the `mandrel-vX.Y.Z` namespace (namespaced at
506
- * `mandrel-v1.44.0`; bare `vX.Y.Z` for earlier releases). This function
507
- * tries the namespaced tag first, then the bare-tag form, so it covers both
508
- * tag series without forcing callers to know the boundary.
509
- *
510
- * Security (security-baseline § Transport & Headers): the URL is constructed
511
- * from a constant base and a semver string — no user input, no shell
512
- * interpolation. The GET is a read-only fetch with no credentials.
513
- *
514
- * @param {string} version - The target semver string (e.g. `"1.59.0"`).
489
+ * @param {string} version
515
490
  * @param {{
516
491
  * https?: typeof nodeHttps,
517
492
  * }} [deps]
518
- * @returns {Promise<string>} The raw changelog text.
519
- * @throws {Error} When both tag forms return a non-2xx response or the request
520
- * errors out — the caller handles this gracefully.
493
+ * @returns {Promise<string>}
494
+ * @throws {Error} When every tag form is non-2xx or the request errors.
521
495
  */
522
496
  export async function fetchChangelogFromGitHub(
523
497
  version,
@@ -560,25 +534,10 @@ export async function fetchChangelogFromGitHub(
560
534
  }
561
535
 
562
536
  /**
563
- * Default `surfaceChangelog` seam: print the `docs/CHANGELOG.md` section(s)
564
- * covering the applied version range `(current, target]`. The changelog is
565
- * authored by release-please with `## [<version>](…)` section headers; this
566
- * prints every section whose version is newer than `current` and no newer than
567
- * `target`.
568
- *
569
- * Resolution order (Story #4035):
570
- * 1. Read `docs/CHANGELOG.md` from the target version's install directory
571
- * (`node_modules/mandrel/docs/CHANGELOG.md` — now in the published
572
- * tarball since `package.json` lists `docs/CHANGELOG.md` in `files`).
573
- * 2. When the packaged file is absent (older install), fetch it from GitHub
574
- * via the injectable `fetchChangelog` seam.
575
- * 3. When both sources fail, emit an actionable warning with a link to the
576
- * GitHub Releases page — never a bare "not found … skipping".
537
+ * Print the changelog sections in `(current, target]` — packaged file first,
538
+ * then GitHub, then a Releases link. Best-effort: warns, never throws.
577
539
  *
578
- * Degrades gracefully (warns, never throws) — surfacing the changelog is
579
- * best-effort and must never fail an otherwise-successful upgrade.
580
- *
581
- * @param {string} target - The applied target version.
540
+ * @param {string} target
582
541
  * @param {{
583
542
  * current?: string,
584
543
  * changelogPath?: string,
@@ -602,19 +561,16 @@ async function defaultSurfaceChangelog(
602
561
  ) {
603
562
  let raw;
604
563
 
605
- // 1. Try the packaged file (present in installs since Story #4035).
606
564
  try {
607
565
  raw = fs.readFileSync(changelogPath, 'utf8');
608
566
  } catch {
609
- // File absent — fall through to GitHub fetch.
567
+ // Absent: fall through to the GitHub fetch.
610
568
  }
611
569
 
612
- // 2. Packaged file absent: attempt a GitHub fetch for the target tag.
613
570
  if (raw === undefined) {
614
571
  try {
615
572
  raw = await fetchChangelog(target);
616
573
  } catch {
617
- // Both sources unavailable — emit an actionable message and return.
618
574
  writeErr(
619
575
  `mandrel update: changelog not available for v${target} — ` +
620
576
  `view the release notes at ${GITHUB_RELEASES_URL}\n`,
@@ -645,9 +601,8 @@ async function defaultSurfaceChangelog(
645
601
  }
646
602
 
647
603
  /**
648
- * Split a release-please `CHANGELOG.md` into `{ version, body }` sections keyed
649
- * by the `## [<version>]…` headers. Each `body` includes the header line and
650
- * everything up to (but not including) the next version header.
604
+ * Split a release-please changelog on `## [<version>]` headers; each body
605
+ * includes its header line.
651
606
  *
652
607
  * @param {string} raw
653
608
  * @returns {Array<{ version: string, body: string }>}
@@ -680,32 +635,13 @@ function parseChangelogSections(raw) {
680
635
  }
681
636
 
682
637
  /**
683
- * Resolve the newly-installed `mandrel` bin **script**
684
- * (`<packageRoot>/bin/mandrel.js`) from the consumer project root. This is the
685
- * target for the post-install phase re-exec (Story #4034), spawned via
686
- * `process.execPath` (node) rather than executed directly — see
687
- * {@link defaultSpawnPhase}.
638
+ * The consumer's installed `bin/mandrel.js`, NOT the `.bin` shim: the script
639
+ * ships non-executable and pnpm symlinks the shim straight at it, so spawning
640
+ * the shim fails with EACCES. Node runs the script directly instead.
688
641
  *
689
- * It deliberately does **not** return the `node_modules/.bin/mandrel` shim.
690
- * That shim only works because npm chmods the bin target `+x` at install time:
691
- * `bin/mandrel.js` ships non-executable in the published tarball, and pnpm
692
- * symlinks `.bin/mandrel` straight at it, so spawning the shim directly fails
693
- * with `EACCES` under pnpm (Story #4613). Spawning node against the resolved
694
- * `.js` script removes the dependency on the exec bit, the shebang, and the
695
- * Windows `.cmd` shim entirely.
696
- *
697
- * Resolution reuses the same consumer-anchored resolver
698
- * (`defaultResolvePackageRoot`) that {@link resolveCurrentVersionForUpdate}
699
- * uses, so it points at the consumer's install rather than a copy hoisted next
700
- * to this CLI module. The `mandrel` package directory is version-invariant
701
- * (`node_modules/mandrel/`), so resolving it before the in-place `npm-update`
702
- * step still yields the directory whose `bin/mandrel.js` the install overwrites.
703
- *
704
- * @param {string} projectRoot - Absolute path to the consumer project.
705
- * @param {{ resolvePackageRoot?: (fromDir: string) => string }} [opts] - test
706
- * seam for the `node_modules` resolution; defaults to the real
707
- * `defaultResolvePackageRoot` from `sync.js`.
708
- * @returns {string} Absolute path to the new bin script.
642
+ * @param {string} projectRoot
643
+ * @param {{ resolvePackageRoot?: (fromDir: string) => string }} [opts]
644
+ * @returns {string}
709
645
  */
710
646
  export function resolveNewBinScriptPath(
711
647
  projectRoot,
@@ -716,33 +652,19 @@ export function resolveNewBinScriptPath(
716
652
  }
717
653
 
718
654
  /**
719
- * Default `spawnPhase` seam (Story #4034): spawn a post-install phase from the
720
- * newly-installed `mandrel` bin script and stream its stdout/stderr through the
721
- * parent's write sinks. Each phase runs as an isolated child process so the
722
- * newly-installed module code (not the currently-loaded old module) executes.
723
- *
724
- * The child is spawned as `process.execPath <binScript> <phase> …` — node run
725
- * against the resolved `bin/mandrel.js` (see {@link resolveNewBinScriptPath}).
726
- * Spawning node against a plain `.js` file removes any dependency on the bin's
727
- * exec bit, its shebang, or a Windows `.cmd` shim, so **no** `shell` flag is
728
- * needed on any platform (this is the pnpm/layout-agnostic fix, Story #4613,
729
- * that retired the former win32-only `shell: true` branch). The argv vector is
730
- * a fixed constant list per phase — no operator-supplied data enters it
731
- * (security-baseline § Output & Rendering).
655
+ * Run one post-install phase as `node <binScript> <phase> …` so the new
656
+ * package's code executes; no shell is needed on any platform. Throws only on
657
+ * a spawn error.
732
658
  *
733
- * Throws when the child exits non-zero so the orchestrator can surface the
734
- * failure to the operator.
735
- *
736
- * @param {string} phase - The mandrel sub-command to run (e.g. `'sync'`).
737
- * @param {string[]} args - Additional arguments for the sub-command.
659
+ * @param {string} phase
660
+ * @param {string[]} args
738
661
  * @param {{
739
662
  * binPath: string,
740
663
  * cwd: string,
741
664
  * write: (s: string) => void,
742
665
  * writeErr: (s: string) => void,
743
666
  * spawnFn?: typeof spawnSync,
744
- * }} opts - `binPath` is the resolved bin **script** path (not the
745
- * `node_modules/.bin` shim); it becomes node's first argv entry.
667
+ * }} opts
746
668
  * @returns {{ ok: boolean, stdout: string, stderr: string }}
747
669
  */
748
670
  export function defaultSpawnPhase(
@@ -768,19 +690,7 @@ export function defaultSpawnPhase(
768
690
  return { ok, stdout, stderr };
769
691
  }
770
692
 
771
- /**
772
- * The ordered step names the orchestrator drives on an update. Shared
773
- * by the live path and the `--dry-run` plan printout so the two never drift.
774
- *
775
- * Step ordering (Story #4046 A1c; sync-agents added by Story #4528/#4530):
776
- * 1. npm-update — install the new version
777
- * 2. runSync — re-materialize .agents/ from the new payload
778
- * 3. sync-commands — regenerate .claude/commands/ from the new payload
779
- * 4. sync-agents — regenerate .claude/agents/ from the new payload
780
- * 5. runMigrations — apply version-keyed migrations
781
- * 6. doctor — validate the post-upgrade state
782
- * 7. surface changelog — print the changelog (always last, best-effort)
783
- */
693
+ /** The `--dry-run` printout of the full-upgrade step order. */
784
694
  const STEP_PLAN = [
785
695
  'npm-update',
786
696
  'runSync',
@@ -792,19 +702,9 @@ const STEP_PLAN = [
792
702
  ];
793
703
 
794
704
  /**
795
- * The ordered post-install phase descriptors for a full upgrade. Each entry is
796
- * a plain value (no I/O) describing one step the executor drives:
797
- *
798
- * - `kind: 'npm-update'` — bump the dependency via the `npmUpdate` seam.
799
- * - `kind: 'spawn'` — spawn `phase`/`args` from the new binary; a
800
- * non-zero exit is fatal and throws `failMessage`.
801
- * - `kind: 'doctor'` — spawn `doctor` from the new binary; a non-zero
802
- * exit is *soft* (maps to `action: 'doctor-failed'`
803
- * + exit 1), so it carries no `failMessage`.
804
- *
805
- * `label` is the name pushed into the run's `stepsRun[]` (the external return
806
- * contract). `migrate` is the only phase whose argv depends on the version
807
- * range, so its descriptor is built per-plan in `planUpdate`.
705
+ * Full-upgrade phases. A `spawn` non-zero exit is fatal (throws
706
+ * `failMessage`); a `doctor` failure is soft (`doctor-failed`, exit 1).
707
+ * `label` is what `stepsRun[]` reports.
808
708
  *
809
709
  * @param {string} current
810
710
  * @param {string} target
@@ -834,10 +734,7 @@ function fullUpgradeSteps(current, target) {
834
734
  'Run `npm run sync:commands` manually to restore.',
835
735
  },
836
736
  {
837
- // Story #4528/#4530: the CLI update path previously never projected the
838
- // role-agent tree at all — only the bootstrap path did. Added alongside
839
- // sync-commands so `.claude/agents/` materializes here too, which is
840
- // what makes the tightened `agents-in-sync` doctor check satisfiable.
737
+ // Required for the `agents-in-sync` doctor check to be satisfiable.
841
738
  kind: 'spawn',
842
739
  phase: 'sync-agents',
843
740
  args: [],
@@ -862,10 +759,7 @@ function fullUpgradeSteps(current, target) {
862
759
  }
863
760
 
864
761
  /**
865
- * The ordered phase descriptors for a drift-heal (version already current, but
866
- * `.agents/` is stale). No npm-update, no migrations, no doctor — only the
867
- * three sync phases re-materialize the payload from the already-installed
868
- * binary.
762
+ * Drift-heal phases: the sync phases only, from the installed binary.
869
763
  *
870
764
  * @returns {Array<{ kind: 'spawn', phase: string, args: string[], label: string, failMessage: string }>}
871
765
  */
@@ -892,7 +786,6 @@ function driftHealSteps() {
892
786
  'Run `npm run sync:commands` manually to restore.',
893
787
  },
894
788
  {
895
- // Story #4528/#4530: see the matching entry in fullUpgradeSteps().
896
789
  kind: 'spawn',
897
790
  phase: 'sync-agents',
898
791
  args: [],
@@ -906,25 +799,7 @@ function driftHealSteps() {
906
799
  }
907
800
 
908
801
  /**
909
- * Pure decision function for `mandrel update`: given the resolved version
910
- * inputs and the two flags, decide which of the four actions to take and the
911
- * ordered phase plan for that action. **No I/O** — no filesystem, child
912
- * process, network, `write`, or `exit`. This isolates the scheduler-style
913
- * branch-selection and step-sequencing logic (the surface under review in
914
- * Story #4182 / audit::architecture) so it can be exercised as a table over
915
- * plain inputs rather than by running the whole async orchestration with every
916
- * seam stubbed.
917
- *
918
- * The four actions:
919
- *
920
- * - `up-to-date` — version is current and no drift. True no-op; `steps: []`.
921
- * - `dry-run` — `dryRun` is set. `steps: []` (nothing is executed); the
922
- * `variant` distinguishes the drift-heal preview from the
923
- * full-upgrade preview so the executor prints the right plan.
924
- * - `resynced` — version is current but drift detected. Heal via the two
925
- * sync phases (`driftHealSteps()`).
926
- * - `updated` — a newer version is available. Full upgrade
927
- * (`fullUpgradeSteps(current, target)`).
802
+ * Pure (no I/O) choice of action and phase plan.
928
803
  *
929
804
  * @param {{ current: string, target: string, dryRun: boolean, hasDrift: boolean }} input
930
805
  * @returns {{
@@ -937,7 +812,6 @@ export function planUpdate({ current, target, dryRun, hasDrift }) {
937
812
  const versionCurrent = compareVersions(target, current) <= 0;
938
813
 
939
814
  if (versionCurrent) {
940
- // Version is already newest. The only remaining question is drift.
941
815
  if (!hasDrift) {
942
816
  return { action: 'up-to-date', steps: [] };
943
817
  }
@@ -947,8 +821,7 @@ export function planUpdate({ current, target, dryRun, hasDrift }) {
947
821
  return { action: 'resynced', steps: driftHealSteps() };
948
822
  }
949
823
 
950
- // A newer version is available — full upgrade (drift is irrelevant here; the
951
- // post-upgrade doctor phase re-checks materialization).
824
+ // Drift is irrelevant here: the post-upgrade doctor re-checks it.
952
825
  if (dryRun) {
953
826
  return { action: 'dry-run', steps: [], variant: 'full-upgrade' };
954
827
  }
@@ -956,14 +829,8 @@ export function planUpdate({ current, target, dryRun, hasDrift }) {
956
829
  }
957
830
 
958
831
  /**
959
- * Extract the `--install-cmd "<cmd>"` value from the subcommand argv. Accepts
960
- * both the space form (`--install-cmd npm install …`, captured as the single
961
- * following token group) and the `=` form (`--install-cmd="<cmd>"`). Returns
962
- * `undefined` when the flag is absent so the default package manager is used.
963
- *
964
- * The argv tokenizer hands us a pre-split array; with the space form the shell
965
- * has already collapsed a quoted value into one element, so the immediate next
966
- * token is the full command string.
832
+ * `--install-cmd <cmd>` or `--install-cmd=<cmd>`; the shell has already joined
833
+ * a quoted value into one token.
967
834
  *
968
835
  * @param {string[]} argv
969
836
  * @returns {string | undefined}
@@ -982,12 +849,6 @@ function parseInstallCmdFlag(argv) {
982
849
  }
983
850
 
984
851
  /**
985
- * Resolve the drift signal for the no-op short-circuit. Prefers the injected
986
- * `checkDrift` seam (unit-test friendly); falls back to the production
987
- * `runAgentsDrift` helper. Only consulted when the installed version is already
988
- * the newest (Story #4065) — a real version bump skips drift entirely (the
989
- * post-upgrade doctor phase re-checks materialization).
990
- *
991
852
  * @param {(() => boolean | Promise<boolean>) | undefined} checkDrift
992
853
  * @returns {Promise<boolean>}
993
854
  */
@@ -998,12 +859,8 @@ async function resolveDrift(checkDrift) {
998
859
  }
999
860
 
1000
861
  /**
1001
- * Execute the ordered phase plan returned by `planUpdate` for the `updated` /
1002
- * `resynced` actions. This is the thin side-effecting shell: it owns the
1003
- * `npmUpdate` seam call, the `spawnPhase` re-exec boundary, the per-step
1004
- * `stepsRun` accounting, and the soft doctor-fail (`exit(1)` +
1005
- * `action: 'doctor-failed'`). The branch-selection logic that produced `steps`
1006
- * lives in the pure `planUpdate`.
862
+ * Drive `planUpdate`'s steps. A doctor failure still surfaces the changelog,
863
+ * then exits 1.
1007
864
  *
1008
865
  * @param {{
1009
866
  * steps: Array<{ kind: 'npm-update' | 'spawn' | 'doctor', label: string, phase?: string, args?: string[], failMessage?: string }>,
@@ -1036,11 +893,8 @@ async function executePlan({
1036
893
  const stepsRun = [];
1037
894
  let doctorOk = true;
1038
895
 
1039
- // Resolve the new bin script lazily and once, on the first spawn phase.
1040
- // Deferring it past the `npm-update` step means (a) a missing `npmUpdate`
1041
- // seam surfaces its own clear error first, and (b) resolution reflects the
1042
- // just-installed package. The `mandrel` package directory is version-stable,
1043
- // so resolving after the in-place bump yields the same directory either way.
896
+ // Resolved lazily after `npm-update`, so a missing npmUpdate seam reports
897
+ // its own error first.
1044
898
  let binPath;
1045
899
  const binScript = () => {
1046
900
  if (binPath === undefined) binPath = resolveBinScript(projectRoot);
@@ -1049,8 +903,6 @@ async function executePlan({
1049
903
 
1050
904
  for (const step of steps) {
1051
905
  if (step.kind === 'npm-update') {
1052
- // Bump the dependency. The lockfile change is left STAGED on disk; this
1053
- // module never commits.
1054
906
  if (typeof npmUpdate !== 'function') {
1055
907
  throw new Error(
1056
908
  'mandrel update: npmUpdate seam is required to bump the dependency',
@@ -1062,9 +914,6 @@ async function executePlan({
1062
914
  continue;
1063
915
  }
1064
916
 
1065
- // Both 'spawn' and 'doctor' kinds run a post-install phase from the
1066
- // newly-installed binary (the Story #4034 re-exec boundary), so the new
1067
- // package's module code — not the old loaded module — executes.
1068
917
  // eslint-disable-next-line no-await-in-loop
1069
918
  const result = await spawnPhase(step.phase, step.args, {
1070
919
  binPath: binScript(),
@@ -1075,18 +924,12 @@ async function executePlan({
1075
924
  stepsRun.push(step.label);
1076
925
 
1077
926
  if (step.kind === 'doctor') {
1078
- // Doctor failure is SOFT: record it, keep going to surface the changelog,
1079
- // then map to exit(1) + doctor-failed by the caller.
1080
927
  doctorOk = result.ok;
1081
928
  } else if (!result.ok) {
1082
- // sync / sync-commands / migrate failures are FATAL.
1083
929
  throw new Error(step.failMessage);
1084
930
  }
1085
931
  }
1086
932
 
1087
- // Surface the target changelog (best-effort; optional seam). Runs even when
1088
- // doctor failed, so the operator still sees the changelog for the version
1089
- // that landed on disk.
1090
933
  if (typeof surfaceChangelog === 'function') {
1091
934
  await surfaceChangelog(target);
1092
935
  }
@@ -1103,14 +946,6 @@ async function executePlan({
1103
946
  }
1104
947
 
1105
948
  /**
1106
- * Run the `mandrel update` orchestration cycle.
1107
- *
1108
- * The cycle is split into a pure decision (`planUpdate`) and a side-effecting
1109
- * shell (this function + `executePlan`). `runUpdate` resolves the inputs
1110
- * (current / target / drift) through the injectable seams, calls `planUpdate`
1111
- * to select the action and its ordered phase plan, then drives the plan through
1112
- * the `spawnPhase` / `write` / `exit` shell.
1113
- *
1114
949
  * @param {{
1115
950
  * argv?: string[],
1116
951
  * currentVersion?: string | (() => string),
@@ -1119,6 +954,8 @@ async function executePlan({
1119
954
  * checkDrift?: () => (boolean | Promise<boolean>),
1120
955
  * spawnPhase?: (phase: string, args: string[], opts: { binPath: string, cwd: string, write: (s: string) => void, writeErr: (s: string) => void }) => Promise<{ ok: boolean, stdout: string, stderr: string }> | { ok: boolean, stdout: string, stderr: string },
1121
956
  * surfaceChangelog?: (version: string) => unknown | Promise<unknown>,
957
+ * gitStatus?: (opts: { cwd: string }) => { ok: boolean, staged?: string[], unstaged?: string[], tracksAgents?: boolean },
958
+ * detectLockfile?: (cwd: string) => string,
1122
959
  * write?: (s: string) => void,
1123
960
  * writeErr?: (s: string) => void,
1124
961
  * exit?: (code: number) => void,
@@ -1142,6 +979,8 @@ export async function runUpdate({
1142
979
  checkDrift,
1143
980
  spawnPhase,
1144
981
  surfaceChangelog,
982
+ gitStatus = defaultGitStatus,
983
+ detectLockfile = defaultDetectLockfile,
1145
984
  write = (s) => process.stdout.write(s),
1146
985
  writeErr = (s) => process.stderr.write(s),
1147
986
  exit = (code) => process.exit(code),
@@ -1163,9 +1002,7 @@ export async function runUpdate({
1163
1002
  }
1164
1003
  const target = String(await resolveTargetVersion());
1165
1004
 
1166
- // Drift only matters when the installed version is already the newest. Probe
1167
- // it solely on that branch so a real version bump never calls the drift seam
1168
- // (Story #4065).
1005
+ // Probe drift only when already newest; a real bump never calls the seam.
1169
1006
  const hasDrift =
1170
1007
  compareVersions(target, current) <= 0
1171
1008
  ? await resolveDrift(checkDrift)
@@ -1173,7 +1010,6 @@ export async function runUpdate({
1173
1010
 
1174
1011
  const plan = planUpdate({ current, target, dryRun, hasDrift });
1175
1012
 
1176
- // --- up-to-date: true no-op ----------------------------------------------
1177
1013
  if (plan.action === 'up-to-date') {
1178
1014
  write(`✅ Already up to date (v${current} is the newest version).\n`);
1179
1015
  return {
@@ -1186,7 +1022,6 @@ export async function runUpdate({
1186
1022
  };
1187
1023
  }
1188
1024
 
1189
- // --- dry-run: print the plan, execute nothing -----------------------------
1190
1025
  if (plan.action === 'dry-run') {
1191
1026
  if (plan.variant === 'drift-heal') {
1192
1027
  write(
@@ -1219,7 +1054,6 @@ export async function runUpdate({
1219
1054
  };
1220
1055
  }
1221
1056
 
1222
- // --- resynced / updated: execute the phase plan ---------------------------
1223
1057
  const projectRoot = cwd();
1224
1058
 
1225
1059
  if (plan.action === 'resynced') {
@@ -1245,8 +1079,16 @@ export async function runUpdate({
1245
1079
  });
1246
1080
 
1247
1081
  if (plan.action === 'resynced') {
1082
+ // No dependency is bumped here, so the re-materialized payload IS the diff.
1083
+ const payloadLine = resolveStagingReport({
1084
+ gitStatus,
1085
+ detectLockfile,
1086
+ projectRoot,
1087
+ scope: 'payload',
1088
+ });
1248
1089
  write(
1249
- `✅ Healed .agents/ drift (v${current}). The materialized payload is now current.\n`,
1090
+ `✅ Healed .agents/ drift (v${current}). The materialized payload is now current.` +
1091
+ `${payloadLine ? ` ${payloadLine}` : ''}\n`,
1250
1092
  );
1251
1093
  return {
1252
1094
  ok: true,
@@ -1258,7 +1100,6 @@ export async function runUpdate({
1258
1100
  };
1259
1101
  }
1260
1102
 
1261
- // plan.action === 'updated'
1262
1103
  if (!doctorOk) {
1263
1104
  return {
1264
1105
  ok: false,
@@ -1270,7 +1111,9 @@ export async function runUpdate({
1270
1111
  };
1271
1112
  }
1272
1113
 
1273
- write(`✅ Updated to v${target}. The lockfile bump is staged for review.\n`);
1114
+ write(
1115
+ `✅ Updated to v${target}. ${resolveStagingReport({ gitStatus, detectLockfile, projectRoot })}\n`,
1116
+ );
1274
1117
  return {
1275
1118
  ok: true,
1276
1119
  action: 'updated',
@@ -1282,46 +1125,10 @@ export async function runUpdate({
1282
1125
  }
1283
1126
 
1284
1127
  /**
1285
- * Default export consumed by `bin/mandrel.js`.
1128
+ * Entry point for `bin/mandrel.js`: wires the production seams. `deps` exposes
1129
+ * the process boundaries for tests only; it is not public contract.
1286
1130
  *
1287
- * Wires the production-default seams that `runUpdate` leaves injectable:
1288
- * - `resolveTargetVersion` always probes the registry via `isStale` with
1289
- * `bypassCache: true` — the 24h cache is overridden for explicit update
1290
- * calls so the resolved version is always fresh (Story #4046 A1b). The
1291
- * cache is still written so the `version-current` doctor advisory reads
1292
- * a current baseline after the upgrade.
1293
- * - `npmUpdate` runs the install command — auto-detected from the project
1294
- * lockfile (`pnpm`/`yarn`/`npm`), or the `--install-cmd` override —
1295
- * through the shared `runInstallCommand` helper — no git mutation;
1296
- * lockfile left staged.
1297
- * - `spawnPhase` is wired to `defaultSpawnPhase`, which spawns each
1298
- * post-install phase (sync, sync-commands, migrate, doctor) as
1299
- * `node <packageRoot>/bin/mandrel.js …` (Story #4613 — the resolved bin
1300
- * script, not the `node_modules/.bin` shim). This is the
1301
- * Story #4034 fix: the new bin loads the new package's module code and
1302
- * resolves paths against the new install dir, so these phases can never
1303
- * observe the old payload.
1304
- * - `surfaceChangelog` prints the relevant `docs/CHANGELOG.md` section(s)
1305
- * for the applied range. Reads from the packaged file first; falls back to
1306
- * a GitHub raw-content fetch via the injectable `fetchChangelog` seam when
1307
- * the packaged file is absent; emits an actionable link to the GitHub
1308
- * Releases page when both sources fail (Story #4035).
1309
- *
1310
- * Every seam stays injectable on `runUpdate`; these are merely the
1311
- * no-seam-provided fallbacks, so the existing seam-driven tests stay green.
1312
- * `--dry-run` / `--install-cmd` are parsed from `argv` by
1313
- * `runUpdate` itself.
1314
- *
1315
- * The second `deps` argument exposes the **process boundaries** the production
1316
- * defaults shell out across (`versionRunner` = `npm view`, `runInstall` =
1317
- * the install spawn, `spawnFn` = the phase-spawn boundary) plus `fs` /
1318
- * `cachePath` / `now`, so the entrypoint can be driven end-to-end with the
1319
- * network/npm boundary stubbed and no real I/O.
1320
- * `bin/mandrel.js` calls `run(argv)` with no `deps`, getting the production
1321
- * wiring; tests pass fakes. The `deps` surface is NOT part of the public
1322
- * subcommand contract — `bin/mandrel.js` only ever supplies `argv`.
1323
- *
1324
- * @param {string[]} argv - Subcommand arguments (after `mandrel update`).
1131
+ * @param {string[]} argv
1325
1132
  * @param {{
1326
1133
  * currentVersion?: string,
1327
1134
  * cachePath?: string,
@@ -1336,6 +1143,8 @@ export async function runUpdate({
1336
1143
  * cwd?: () => string,
1337
1144
  * resolveBinScript?: (projectRoot: string) => string,
1338
1145
  * checkDrift?: () => (boolean | Promise<boolean>),
1146
+ * gitStatus?: (opts: { cwd: string }) => object,
1147
+ * detectLockfile?: (cwd: string) => string,
1339
1148
  * write?: (s: string) => void,
1340
1149
  * writeErr?: (s: string) => void,
1341
1150
  * exit?: (code: number) => void,
@@ -1361,22 +1170,15 @@ export default async function run(argv = [], deps = {}) {
1361
1170
  cwd,
1362
1171
  resolveBinScript,
1363
1172
  checkDrift,
1173
+ gitStatus,
1174
+ detectLockfile,
1364
1175
  } = deps;
1365
1176
 
1366
1177
  const cwdFn = typeof cwd === 'function' ? cwd : () => process.cwd();
1367
1178
 
1368
- // Story #4525/#4530: prefer the consumer's declared dependency pin over
1369
- // the pre-#4525 self-referential read — see resolveCurrentVersionForUpdate.
1370
1179
  const current =
1371
1180
  deps.currentVersion ?? resolveCurrentVersionForUpdate(cwdFn(), fs);
1372
1181
 
1373
- // The production spawnPhase: spawn each post-install phase as
1374
- // `node <packageRoot>/bin/mandrel.js …` (the newly-installed bin script,
1375
- // resolved layout-agnostically per Story #4613 — not the node_modules/.bin
1376
- // shim). This is the sole post-install execution path (No-Shim — Story #4182
1377
- // retired the in-process runSync/runMigrations/runDoctor seam set). spawnFn
1378
- // is injectable so tests can stub the spawn boundary without running a real
1379
- // child process.
1380
1182
  const productionSpawnPhase = (phase, args, opts) =>
1381
1183
  defaultSpawnPhase(phase, args, {
1382
1184
  ...opts,
@@ -1386,8 +1188,7 @@ export default async function run(argv = [], deps = {}) {
1386
1188
  await runUpdateImpl({
1387
1189
  argv,
1388
1190
  currentVersion: current,
1389
- // Always bypass the 24h cache on an explicit `mandrel update` so the
1390
- // resolved target is fresh from the registry (Story #4046 A1b).
1191
+ // An explicit update always bypasses the 24h cache.
1391
1192
  resolveTargetVersion: () =>
1392
1193
  defaultResolveTargetVersion({
1393
1194
  cachePath:
@@ -1405,6 +1206,10 @@ export default async function run(argv = [], deps = {}) {
1405
1206
  fs,
1406
1207
  }),
1407
1208
  ...(checkDrift ? { checkDrift } : {}),
1209
+ ...(gitStatus ? { gitStatus } : {}),
1210
+ ...(detectLockfile
1211
+ ? { detectLockfile }
1212
+ : { detectLockfile: (dir) => defaultDetectLockfile(dir, fs) }),
1408
1213
  spawnPhase: productionSpawnPhase,
1409
1214
  surfaceChangelog: (target) =>
1410
1215
  defaultSurfaceChangelog(target, {
@@ -1419,8 +1224,6 @@ export default async function run(argv = [], deps = {}) {
1419
1224
  writeErr,
1420
1225
  exit,
1421
1226
  cwd: cwdFn,
1422
- // Pass through undefined in production so runUpdate applies its default
1423
- // resolver (resolveNewBinScriptPath); tests inject a stub for a fake root.
1424
1227
  resolveBinScript,
1425
1228
  });
1426
1229
  }