mandrel 1.58.0 → 1.60.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 (319) hide show
  1. package/.agents/README.md +100 -98
  2. package/.agents/docs/SDLC.md +140 -141
  3. package/.agents/docs/configuration.md +16 -16
  4. package/.agents/docs/workflows.md +7 -8
  5. package/.agents/instructions.md +12 -11
  6. package/.agents/personas/architect.md +1 -1
  7. package/.agents/personas/product.md +1 -1
  8. package/.agents/personas/project-manager.md +14 -14
  9. package/.agents/personas/technical-writer.md +1 -1
  10. package/.agents/rules/changelog-style.md +5 -5
  11. package/.agents/rules/git-conventions.md +3 -3
  12. package/.agents/schemas/agentrc.schema.json +3 -3
  13. package/.agents/schemas/audit-rules.json +20 -0
  14. package/.agents/schemas/dispatch-manifest.json +4 -4
  15. package/.agents/schemas/epic-spec.schema.json +15 -45
  16. package/.agents/schemas/lifecycle/README.md +1 -1
  17. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  20. package/.agents/schemas/validation-evidence.schema.json +1 -1
  21. package/.agents/scripts/README.md +1 -1
  22. package/.agents/scripts/acceptance-eval.js +21 -4
  23. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  24. package/.agents/scripts/analyze-execution.js +2 -2
  25. package/.agents/scripts/assert-branch.js +1 -3
  26. package/.agents/scripts/audit-to-stories.js +1 -1
  27. package/.agents/scripts/bootstrap.js +1 -1
  28. package/.agents/scripts/check-arch-cycles.js +360 -0
  29. package/.agents/scripts/check-doc-links.js +2 -3
  30. package/.agents/scripts/coverage-capture.js +24 -3
  31. package/.agents/scripts/diagnose-friction.js +1 -1
  32. package/.agents/scripts/dispatcher.js +2 -2
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/epic-audit-prepare.js +3 -3
  35. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  36. package/.agents/scripts/epic-deliver-preflight.js +11 -9
  37. package/.agents/scripts/epic-deliver-prepare.js +13 -5
  38. package/.agents/scripts/epic-execute-record-wave.js +5 -5
  39. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  40. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  41. package/.agents/scripts/epic-reconcile.js +11 -29
  42. package/.agents/scripts/evidence-gate.js +2 -2
  43. package/.agents/scripts/generate-workflows-doc.js +1 -1
  44. package/.agents/scripts/git-rebase-and-resolve.js +1 -1
  45. package/.agents/scripts/hierarchy-gate.js +40 -24
  46. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  47. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  48. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  49. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  50. package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
  51. package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
  52. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
  53. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
  54. package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
  55. package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
  56. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
  57. package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
  58. package/.agents/scripts/lib/baselines/writer.js +1 -1
  59. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  60. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  61. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  62. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  63. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  64. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  65. package/.agents/scripts/lib/close-validation/commands.js +188 -0
  66. package/.agents/scripts/lib/close-validation/gates.js +235 -0
  67. package/.agents/scripts/lib/close-validation/process.js +101 -0
  68. package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
  69. package/.agents/scripts/lib/close-validation/runner.js +325 -0
  70. package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
  71. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  72. package/.agents/scripts/lib/config/explain.js +1 -1
  73. package/.agents/scripts/lib/config/quality.js +6 -6
  74. package/.agents/scripts/lib/config/runners.js +2 -2
  75. package/.agents/scripts/lib/config/runtime.js +1 -1
  76. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  77. package/.agents/scripts/lib/config-resolver.js +2 -5
  78. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  79. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  80. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  81. package/.agents/scripts/lib/coverage-capture.js +147 -4
  82. package/.agents/scripts/lib/cpu-pool.js +14 -0
  83. package/.agents/scripts/lib/crap-utils.js +6 -11
  84. package/.agents/scripts/lib/duplicate-search.js +1 -1
  85. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  86. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
  87. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  88. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  89. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  90. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  91. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  93. package/.agents/scripts/lib/git-utils.js +24 -22
  94. package/.agents/scripts/lib/label-constants.js +3 -4
  95. package/.agents/scripts/lib/label-taxonomy.js +3 -8
  96. package/.agents/scripts/lib/maintainability-engine.js +1 -1
  97. package/.agents/scripts/lib/maintainability-utils.js +4 -187
  98. package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
  99. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
  100. package/.agents/scripts/lib/orchestration/code-review.js +95 -82
  101. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  102. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  103. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  104. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  105. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
  106. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  107. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
  108. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  109. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  110. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  111. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
  112. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
  113. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
  114. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
  115. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  116. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
  117. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  118. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  119. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  120. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  121. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
  122. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  123. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
  124. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
  125. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  126. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
  127. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
  128. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
  129. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  130. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
  131. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
  132. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  133. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  134. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  135. package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
  136. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  137. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  138. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  139. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
  140. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  141. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
  142. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  143. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  144. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  145. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  146. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  147. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  148. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  149. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  150. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
  151. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  152. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  153. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  154. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
  155. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  156. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
  157. package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
  158. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  159. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  160. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  161. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  162. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  163. package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
  164. package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
  165. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
  166. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
  167. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  170. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
  171. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
  172. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
  173. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  174. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  175. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  176. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
  177. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
  178. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
  179. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
  180. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  181. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
  182. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
  183. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
  184. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
  185. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
  186. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
  187. package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
  188. package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
  189. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  190. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  191. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
  192. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  193. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  194. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
  195. package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
  196. package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
  197. package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
  198. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  199. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
  200. package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
  201. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  202. package/.agents/scripts/lib/preflight-runner.js +1 -1
  203. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  204. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  205. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  206. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  207. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  208. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  209. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  210. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  211. package/.agents/scripts/lib/project-root.js +17 -0
  212. package/.agents/scripts/lib/signals/schema.js +1 -1
  213. package/.agents/scripts/lib/spec/index.js +1 -1
  214. package/.agents/scripts/lib/spec/loader.js +2 -2
  215. package/.agents/scripts/lib/spec/state.js +7 -16
  216. package/.agents/scripts/lib/story-adjacency.js +76 -0
  217. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  218. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  219. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  220. package/.agents/scripts/lib/story-lifecycle.js +9 -9
  221. package/.agents/scripts/lib/story-plan.js +1 -1
  222. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  223. package/.agents/scripts/lib/transpile.js +93 -0
  224. package/.agents/scripts/lib/wave-runner/tick.js +4 -153
  225. package/.agents/scripts/lib/workers/crap-worker.js +1 -1
  226. package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
  228. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
  229. package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
  230. package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
  231. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  232. package/.agents/scripts/lifecycle-emit.js +1 -1
  233. package/.agents/scripts/providers/github/board-add.js +1 -1
  234. package/.agents/scripts/providers/github/errors.js +1 -1
  235. package/.agents/scripts/providers/github/mappers.js +2 -2
  236. package/.agents/scripts/providers/github/tickets.js +114 -10
  237. package/.agents/scripts/resync-status-column.js +1 -1
  238. package/.agents/scripts/retro-run.js +2 -2
  239. package/.agents/scripts/run-lint.js +10 -1
  240. package/.agents/scripts/run-tests.js +24 -4
  241. package/.agents/scripts/single-story-init.js +1 -1
  242. package/.agents/scripts/stories-wave-tick.js +13 -10
  243. package/.agents/scripts/story-close.js +1 -1
  244. package/.agents/scripts/story-init.js +162 -26
  245. package/.agents/scripts/story-phase.js +5 -5
  246. package/.agents/scripts/story-plan.js +3 -3
  247. package/.agents/scripts/sync-branch-from-base.js +2 -2
  248. package/.agents/scripts/validate-docs-freshness.js +1 -1
  249. package/.agents/scripts/wave-tick.js +1 -1
  250. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  251. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  252. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  253. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  254. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  255. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  256. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  257. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  258. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  259. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  260. package/.agents/skills/skills.index.json +7 -7
  261. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
  262. package/.agents/templates/agent-protocol.md +2 -2
  263. package/.agents/workflows/agents-update.md +2 -2
  264. package/.agents/workflows/audit-architecture.md +2 -2
  265. package/.agents/workflows/audit-clean-code.md +2 -2
  266. package/.agents/workflows/audit-dependencies.md +1 -1
  267. package/.agents/workflows/audit-devops.md +1 -1
  268. package/.agents/workflows/audit-documentation.md +226 -0
  269. package/.agents/workflows/audit-lighthouse.md +1 -1
  270. package/.agents/workflows/audit-performance.md +2 -2
  271. package/.agents/workflows/audit-privacy.md +1 -1
  272. package/.agents/workflows/audit-quality.md +2 -2
  273. package/.agents/workflows/audit-security.md +2 -2
  274. package/.agents/workflows/audit-seo.md +1 -1
  275. package/.agents/workflows/audit-sre.md +1 -1
  276. package/.agents/workflows/audit-to-stories.md +10 -10
  277. package/.agents/workflows/audit-ux-ui.md +1 -1
  278. package/.agents/workflows/deliver.md +85 -0
  279. package/.agents/workflows/explain.md +3 -3
  280. package/.agents/workflows/git-merge-pr.md +1 -1
  281. package/.agents/workflows/git-pr-all.md +13 -10
  282. package/.agents/workflows/git-push.md +6 -3
  283. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  284. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  285. package/.agents/workflows/helpers/code-review.md +5 -5
  286. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
  287. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  288. package/.agents/workflows/helpers/diagnose.md +1 -1
  289. package/.agents/workflows/helpers/epic-audit.md +6 -6
  290. package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
  291. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  292. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  293. package/.agents/workflows/helpers/epic-testing.md +3 -3
  294. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  295. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  296. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  297. package/.agents/workflows/helpers/signals.md +1 -1
  298. package/.agents/workflows/helpers/single-story-deliver.md +12 -11
  299. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  300. package/.agents/workflows/onboard.md +21 -20
  301. package/.agents/workflows/plan.md +89 -0
  302. package/.agents/workflows/qa-explore.md +1 -1
  303. package/.agents/workflows/qa-run-harness.md +1 -1
  304. package/README.md +17 -20
  305. package/docs/CHANGELOG.md +1149 -0
  306. package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
  307. package/lib/cli/__tests__/update-reexec.test.js +513 -0
  308. package/lib/cli/init.js +338 -0
  309. package/lib/cli/update.js +413 -52
  310. package/package.json +3 -1
  311. package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
  312. package/.agents/scripts/lib/close-validation.js +0 -897
  313. package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
  314. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
  315. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  316. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
  317. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
  318. package/.agents/scripts/lib/task-utils.js +0 -26
  319. package/.agents/scripts/story-deliver-prepare.js +0 -267
package/lib/cli/update.js CHANGED
@@ -25,11 +25,37 @@
25
25
  * the resolved version so an override can still consume the auto-probed
26
26
  * newest. The registry probe in step 1 always stays on `npm view` (a
27
27
  * PM-agnostic registry query).
28
- * 5. runSync — re-materialize ./.agents/ from the new payload
29
- * 6. runMigrations — apply version-keyed steps for the crossed range
30
- * 7. doctor run the check registry; success all checks pass
28
+ * 5. runSync — re-materialize ./.agents/ **from the newly-installed
29
+ * binary** so the materialized payload is always the target version's.
30
+ * 6. runMigrations apply version-keyed steps for the crossed range,
31
+ * **from the newly-installed binary**.
32
+ * 7. doctor — run the check registry **from the newly-installed
33
+ * binary** so `agents-drift` is never a false-green against stale payload.
31
34
  * 8. surface the changelog for the target version
32
35
  *
36
+ * ## Re-exec of post-install phases (Story #4034)
37
+ *
38
+ * Steps 5–7 execute as **child processes spawned from the newly-installed
39
+ * binary** (`<cwd>/node_modules/.bin/mandrel`) rather than in the running
40
+ * process. Node cannot hot-swap a `require`d module mid-process, so without
41
+ * re-exec, the still-running old binary's `runSync`/`runMigrations`/`runDoctor`
42
+ * code would materialise the old payload even though the package on disk has
43
+ * already been updated. This produced the silent stale-`.agents/`
44
+ * materialization and `doctor` false-green observed in the v1.58.0 → v1.59.0
45
+ * consumer upgrade.
46
+ *
47
+ * The orchestration (progress messages, step tracking, changelog surface, exit
48
+ * code) stays in the parent process; only the version-sensitive phases run from
49
+ * the new bin. The `spawnPhase` seam makes the child-process boundary fully
50
+ * injectable so tests can verify the re-exec path without a real npm install.
51
+ *
52
+ * Backward compatibility: when `runSync`, `runMigrations`, or `runDoctor`
53
+ * are explicitly injected (the historical test-seam pattern) and `spawnPhase`
54
+ * is NOT injected, the in-process seams are used unchanged (old tests stay
55
+ * green). When `spawnPhase` IS injected, it takes priority over the in-process
56
+ * seams for the live phases — so new tests targeting the re-exec boundary can
57
+ * inject `spawnPhase` without touching the old seam interface.
58
+ *
33
59
  * ## No git mutation
34
60
  *
35
61
  * The npm dependency bump rewrites `package.json` / `package-lock.json` in the
@@ -55,6 +81,20 @@
55
81
  * - **with `--major`**: apply the major target and print the runbook inline.
56
82
  * Routine minor/patch bumps within the 1.x line are never gated.
57
83
  *
84
+ * ## Changelog surface
85
+ *
86
+ * `defaultSurfaceChangelog` prints the `docs/CHANGELOG.md` section(s) for the
87
+ * applied range `(current, target]`. It resolves the file against the target
88
+ * version's install directory (the freshly bumped `node_modules/mandrel/`),
89
+ * where the changelog is now included in the published tarball
90
+ * (`docs/CHANGELOG.md` in the `files` allowlist — Story #4035).
91
+ *
92
+ * When the packaged file is absent (e.g. an older installed version predating
93
+ * Story #4035), the seam attempts a one-shot HTTP GET of the raw file from
94
+ * GitHub via the injectable `fetchChangelog` seam. If that fetch also fails,
95
+ * the seam degrades gracefully — never throwing — and emits an actionable
96
+ * message directing the operator to the GitHub Releases page.
97
+ *
58
98
  * ## Injectable seams (used by lib/cli/__tests__/update*.test.js)
59
99
  *
60
100
  * - `argv` — subcommand args (after `mandrel update`)
@@ -62,12 +102,24 @@
62
102
  * - `resolveTargetVersion`— async, returns the newest published version
63
103
  * - `npmUpdate` — async, performs the dependency bump (no git);
64
104
  * receives `(target, { installCmd })`
65
- * - `runSync` re-materializes ./.agents/ (lib/cli/sync.js)
66
- * - `runMigrations` — version-keyed migration runner (lib/migrations)
67
- * - `runDoctor` — async, returns { ok, results } from the registry
105
+ * - `spawnPhase` async, spawns a post-install phase from the new
106
+ * binary; receives `(phase, args, { binPath, cwd })`
107
+ * and returns `{ ok, stdout, stderr }`. When
108
+ * injected, it takes priority over `runSync`,
109
+ * `runMigrations`, and `runDoctor` for the live
110
+ * phases. See § Re-exec of post-install phases.
111
+ * - `runSync` — re-materializes ./.agents/ (lib/cli/sync.js).
112
+ * Used when `spawnPhase` is NOT injected (backward
113
+ * compat for tests that pre-date Story #4034).
114
+ * - `runMigrations` — version-keyed migration runner (lib/migrations).
115
+ * Used when `spawnPhase` is NOT injected.
116
+ * - `runDoctor` — async, returns { ok, results } from the registry.
117
+ * Used when `spawnPhase` is NOT injected.
68
118
  * - `surfaceChangelog` — emits the target changelog section
69
119
  * - `write` / `writeErr` — stdout / stderr sinks
70
120
  * - `exit` — process.exit replacement
121
+ * - `cwd` — process.cwd() replacement (used to resolve the
122
+ * new binary path when `spawnPhase` is absent)
71
123
  *
72
124
  * Security (security-baseline § 5 — Data Leakage & Logging): logs only version
73
125
  * strings, step names, and the runbook path. No tokens, credentials, or env
@@ -86,10 +138,17 @@
86
138
  * the install argv is a tokenized list whose only variable segment is a
87
139
  * resolved semver string — see `lib/install-cmd-parser.js` for the shared
88
140
  * tokenize-and-spawn rationale this module reuses (no duplicated workaround).
141
+ *
142
+ * The `spawnPhase` default (Story #4034) similarly uses `shell: true` only on
143
+ * Windows: the new binary resolves from `node_modules/.bin/mandrel` (a fixed,
144
+ * non-operator-supplied path) and the per-phase argv vector is a constant
145
+ * fixed list (e.g. `['sync']`, `['migrate', '--from', v, '--to', v]`,
146
+ * `['doctor']`) with no injection risk regardless of the shell flag.
89
147
  */
90
148
 
91
149
  import { spawnSync } from 'node:child_process';
92
150
  import nodeFs from 'node:fs';
151
+ import nodeHttps from 'node:https';
93
152
  import path from 'node:path';
94
153
  import { fileURLToPath } from 'node:url';
95
154
 
@@ -105,6 +164,19 @@ const RUNBOOK_PATH = 'docs/upgrade-major.md';
105
164
  /** The published package whose newest version `mandrel update` advances to. */
106
165
  const PACKAGE_NAME = 'mandrel';
107
166
 
167
+ /**
168
+ * GitHub raw-file base URL for fetching `docs/CHANGELOG.md` when the packaged
169
+ * file is absent (Story #4035 — GitHub fallback). Resolves to the tagged
170
+ * release, e.g. `.../mandrel-v1.59.0/docs/CHANGELOG.md`.
171
+ */
172
+ const GITHUB_RAW_BASE = 'https://raw.githubusercontent.com/dsj1984/mandrel/';
173
+
174
+ /**
175
+ * Human-readable GitHub Releases page — surfaced in the actionable fallback
176
+ * message when neither the packaged file nor the GitHub fetch succeeds.
177
+ */
178
+ const GITHUB_RELEASES_URL = 'https://github.com/dsj1984/mandrel/releases';
179
+
108
180
  /** Default freshness-cache filename — mirrors version-check.js. */
109
181
  const DEFAULT_CACHE_FILENAME = 'version-check.json';
110
182
 
@@ -408,6 +480,72 @@ export function defaultNpmUpdate(
408
480
  }
409
481
  }
410
482
 
483
+ /**
484
+ * Fetch `docs/CHANGELOG.md` for a specific mandrel tag from GitHub's raw
485
+ * content endpoint. This is the fallback when the packaged file is absent
486
+ * (e.g. an older install predating Story #4035 which added the file to the
487
+ * npm `files` allowlist).
488
+ *
489
+ * Injectable via the `fetchChangelog` seam so tests can verify the fallback
490
+ * path without issuing real network calls.
491
+ *
492
+ * The tag shape follows the `mandrel-vX.Y.Z` namespace (namespaced at
493
+ * `mandrel-v1.44.0`; bare `vX.Y.Z` for earlier releases). This function
494
+ * tries the namespaced tag first, then the bare-tag form, so it covers both
495
+ * tag series without forcing callers to know the boundary.
496
+ *
497
+ * Security (security-baseline § Transport & Headers): the URL is constructed
498
+ * from a constant base and a semver string — no user input, no shell
499
+ * interpolation. The GET is a read-only fetch with no credentials.
500
+ *
501
+ * @param {string} version - The target semver string (e.g. `"1.59.0"`).
502
+ * @param {{
503
+ * https?: typeof nodeHttps,
504
+ * }} [deps]
505
+ * @returns {Promise<string>} The raw changelog text.
506
+ * @throws {Error} When both tag forms return a non-2xx response or the request
507
+ * errors out — the caller handles this gracefully.
508
+ */
509
+ export async function fetchChangelogFromGitHub(
510
+ version,
511
+ { https: httpsImpl = nodeHttps } = {},
512
+ ) {
513
+ const tags = [`mandrel-v${version}`, `v${version}`];
514
+
515
+ /**
516
+ * @param {string} url
517
+ * @returns {Promise<{ status: number, body: string }>}
518
+ */
519
+ const httpGet = (url) =>
520
+ new Promise((resolve, reject) => {
521
+ httpsImpl
522
+ .get(url, (res) => {
523
+ const chunks = [];
524
+ res.on('data', (d) => chunks.push(d));
525
+ res.on('end', () =>
526
+ resolve({
527
+ status: res.statusCode ?? 0,
528
+ body: Buffer.concat(chunks).toString('utf8'),
529
+ }),
530
+ );
531
+ })
532
+ .on('error', reject);
533
+ });
534
+
535
+ for (const tag of tags) {
536
+ const url = `${GITHUB_RAW_BASE}${tag}/docs/CHANGELOG.md`;
537
+ // eslint-disable-next-line no-await-in-loop
538
+ const { status, body } = await httpGet(url);
539
+ if (status >= 200 && status < 300) {
540
+ return body;
541
+ }
542
+ }
543
+
544
+ throw new Error(
545
+ `mandrel update: GitHub fetch for mandrel v${version} docs/CHANGELOG.md returned non-2xx for all tag forms (tried: ${tags.join(', ')})`,
546
+ );
547
+ }
548
+
411
549
  /**
412
550
  * Default `surfaceChangelog` seam: print the `docs/CHANGELOG.md` section(s)
413
551
  * covering the applied version range `(current, target]`. The changelog is
@@ -415,38 +553,61 @@ export function defaultNpmUpdate(
415
553
  * prints every section whose version is newer than `current` and no newer than
416
554
  * `target`.
417
555
  *
418
- * Degrades gracefully (warns, never throws) when the file is absent or no
419
- * matching section is found — surfacing the changelog is best-effort and must
420
- * never fail an otherwise-successful upgrade.
556
+ * Resolution order (Story #4035):
557
+ * 1. Read `docs/CHANGELOG.md` from the target version's install directory
558
+ * (`node_modules/mandrel/docs/CHANGELOG.md` now in the published
559
+ * tarball since `package.json` lists `docs/CHANGELOG.md` in `files`).
560
+ * 2. When the packaged file is absent (older install), fetch it from GitHub
561
+ * via the injectable `fetchChangelog` seam.
562
+ * 3. When both sources fail, emit an actionable warning with a link to the
563
+ * GitHub Releases page — never a bare "not found … skipping".
564
+ *
565
+ * Degrades gracefully (warns, never throws) — surfacing the changelog is
566
+ * best-effort and must never fail an otherwise-successful upgrade.
421
567
  *
422
568
  * @param {string} target - The applied target version.
423
569
  * @param {{
424
570
  * current?: string,
425
571
  * changelogPath?: string,
426
572
  * fs?: typeof nodeFs,
573
+ * fetchChangelog?: (version: string) => Promise<string>,
427
574
  * write?: (s: string) => void,
428
575
  * writeErr?: (s: string) => void,
429
576
  * }} [opts]
430
- * @returns {void}
577
+ * @returns {Promise<void>}
431
578
  */
432
- function defaultSurfaceChangelog(
579
+ async function defaultSurfaceChangelog(
433
580
  target,
434
581
  {
435
582
  current,
436
583
  changelogPath = path.join(resolveProjectRoot(), 'docs', 'CHANGELOG.md'),
437
584
  fs = nodeFs,
585
+ fetchChangelog = fetchChangelogFromGitHub,
438
586
  write = (s) => process.stdout.write(s),
439
587
  writeErr = (s) => process.stderr.write(s),
440
588
  } = {},
441
589
  ) {
442
590
  let raw;
591
+
592
+ // 1. Try the packaged file (present in installs since Story #4035).
443
593
  try {
444
594
  raw = fs.readFileSync(changelogPath, 'utf8');
445
595
  } catch {
446
- writeErr(
447
- `mandrel update: changelog not found at ${changelogPath} — skipping changelog surface.\n`,
448
- );
449
- return;
596
+ // File absent — fall through to GitHub fetch.
597
+ }
598
+
599
+ // 2. Packaged file absent: attempt a GitHub fetch for the target tag.
600
+ if (raw === undefined) {
601
+ try {
602
+ raw = await fetchChangelog(target);
603
+ } catch {
604
+ // Both sources unavailable — emit an actionable message and return.
605
+ writeErr(
606
+ `mandrel update: changelog not available for v${target} — ` +
607
+ `view the release notes at ${GITHUB_RELEASES_URL}\n`,
608
+ );
609
+ return;
610
+ }
450
611
  }
451
612
 
452
613
  const sections = parseChangelogSections(raw);
@@ -458,7 +619,8 @@ function defaultSurfaceChangelog(
458
619
 
459
620
  if (relevant.length === 0) {
460
621
  writeErr(
461
- `mandrel update: no CHANGELOG section found for v${target} — skipping changelog surface.\n`,
622
+ `mandrel update: no CHANGELOG section found for v${target} — ` +
623
+ `view the release notes at ${GITHUB_RELEASES_URL}\n`,
462
624
  );
463
625
  return;
464
626
  }
@@ -509,6 +671,8 @@ function parseChangelogSections(raw) {
509
671
  * report whether all passed. Mirrors lib/cli/doctor.js's pass accounting
510
672
  * without the formatted report (the orchestrator owns its own output).
511
673
  *
674
+ * This is used only when `spawnPhase` is NOT injected (backward-compat path).
675
+ *
512
676
  * @param {{ checks?: typeof registry }} [opts]
513
677
  * @returns {Promise<{ ok: boolean, results: Array<{ name: string, ok: boolean }> }>}
514
678
  */
@@ -521,6 +685,69 @@ async function defaultRunDoctor({ checks = registry } = {}) {
521
685
  return { ok: results.every((r) => r.ok), results };
522
686
  }
523
687
 
688
+ /**
689
+ * Resolve the path to the `mandrel` binary inside `node_modules/.bin/` for the
690
+ * given project root. On Windows the binary is a `.cmd` shim; on POSIX it is a
691
+ * plain executable. The resolved path is used as the target for the post-install
692
+ * phase re-exec (Story #4034).
693
+ *
694
+ * @param {string} projectRoot - Absolute path to the consumer project.
695
+ * @returns {string} Absolute path to the new binary.
696
+ */
697
+ export function resolveNewBinPath(projectRoot) {
698
+ const binName = process.platform === 'win32' ? 'mandrel.cmd' : 'mandrel';
699
+ return path.join(projectRoot, 'node_modules', '.bin', binName);
700
+ }
701
+
702
+ /**
703
+ * Default `spawnPhase` seam (Story #4034): spawn a post-install phase from the
704
+ * newly-installed `mandrel` binary and stream its stdout/stderr through the
705
+ * parent's write sinks. Each phase runs as an isolated child process so the
706
+ * newly-installed module code (not the currently-loaded old module) executes.
707
+ *
708
+ * The spawn uses `shell: true` only on Windows where the binary is a `.cmd`
709
+ * shim (CVE-2024-27980 parity). The argv vector is a fixed constant list
710
+ * per phase — no operator-supplied data enters the vector, so the shell flag
711
+ * carries no injection risk (security-baseline § Output & Rendering).
712
+ *
713
+ * Throws when the child exits non-zero so the orchestrator can surface the
714
+ * failure to the operator.
715
+ *
716
+ * @param {string} phase - The mandrel sub-command to run (e.g. `'sync'`).
717
+ * @param {string[]} args - Additional arguments for the sub-command.
718
+ * @param {{
719
+ * binPath: string,
720
+ * cwd: string,
721
+ * write: (s: string) => void,
722
+ * writeErr: (s: string) => void,
723
+ * spawnFn?: typeof spawnSync,
724
+ * }} opts
725
+ * @returns {{ ok: boolean, stdout: string, stderr: string }}
726
+ */
727
+ export function defaultSpawnPhase(
728
+ phase,
729
+ args,
730
+ { binPath, cwd, write, writeErr, spawnFn = spawnSync },
731
+ ) {
732
+ const argv = [phase, ...args];
733
+ const r = spawnFn(binPath, argv, {
734
+ cwd,
735
+ encoding: 'utf8',
736
+ shell: process.platform === 'win32',
737
+ });
738
+ const stdout = typeof r.stdout === 'string' ? r.stdout : '';
739
+ const stderr = typeof r.stderr === 'string' ? r.stderr : '';
740
+ if (stdout) write(stdout);
741
+ if (stderr) writeErr(stderr);
742
+ if (r.error) {
743
+ throw new Error(
744
+ `mandrel update: failed to spawn \`mandrel ${phase}\` from new binary: ${r.error.message}`,
745
+ );
746
+ }
747
+ const ok = r.status === 0;
748
+ return { ok, stdout, stderr };
749
+ }
750
+
524
751
  /**
525
752
  * The ordered step names the orchestrator drives on a non-major bump. Shared
526
753
  * by the live path and the `--dry-run` plan printout so the two never drift.
@@ -576,6 +803,7 @@ function emitMajorRefusal(target, writeErr) {
576
803
  * currentVersion?: string | (() => string),
577
804
  * resolveTargetVersion?: () => (string | Promise<string>),
578
805
  * npmUpdate?: (version: string, opts: { installCmd?: string }) => unknown | Promise<unknown>,
806
+ * 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 },
579
807
  * runSync?: typeof defaultRunSync,
580
808
  * runMigrations?: typeof defaultRunMigrations,
581
809
  * runDoctor?: typeof defaultRunDoctor,
@@ -583,6 +811,7 @@ function emitMajorRefusal(target, writeErr) {
583
811
  * write?: (s: string) => void,
584
812
  * writeErr?: (s: string) => void,
585
813
  * exit?: (code: number) => void,
814
+ * cwd?: () => string,
586
815
  * }} [opts]
587
816
  * @returns {Promise<{
588
817
  * ok: boolean,
@@ -599,6 +828,7 @@ export async function runUpdate({
599
828
  currentVersion,
600
829
  resolveTargetVersion,
601
830
  npmUpdate,
831
+ spawnPhase,
602
832
  runSync = defaultRunSync,
603
833
  runMigrations = defaultRunMigrations,
604
834
  runDoctor = defaultRunDoctor,
@@ -606,6 +836,7 @@ export async function runUpdate({
606
836
  write = (s) => process.stdout.write(s),
607
837
  writeErr = (s) => process.stderr.write(s),
608
838
  exit = (code) => process.exit(code),
839
+ cwd = () => process.cwd(),
609
840
  } = {}) {
610
841
  const dryRun = argv.includes('--dry-run');
611
842
  const allowMajor = argv.includes('--major');
@@ -703,40 +934,136 @@ export async function runUpdate({
703
934
  await npmUpdate(target, { installCmd });
704
935
  stepsRun.push('npm-update');
705
936
 
706
- // 2. runSync re-materialize ./.agents/ from the freshly installed payload.
707
- runSync({ argv: [] });
708
- stepsRun.push('runSync');
937
+ // Decide whether to use the re-exec path (spawnPhase) or the in-process
938
+ // backward-compat seams (runSync / runMigrations / runDoctor).
939
+ //
940
+ // spawnPhase injected → re-exec path: all post-install phases run from the
941
+ // newly-installed binary. This is the production path and is what fixes
942
+ // the stale-materialization bug (Story #4034).
943
+ //
944
+ // spawnPhase NOT injected → in-process path: the original pre-Story-#4034
945
+ // behaviour. Tests that pre-date this change inject runSync/runMigrations/
946
+ // runDoctor and rely on the in-process path; they stay green without any
947
+ // modification.
948
+ const useReExec = typeof spawnPhase === 'function';
709
949
 
710
- // 3. runMigrations — apply version-keyed steps for the crossed range.
711
- runMigrations({ fromVersion: current, toVersion: target, ctx: {} });
712
- stepsRun.push('runMigrations');
950
+ if (useReExec) {
951
+ // Re-exec path: post-install phases run from the new binary.
952
+ const projectRoot = cwd();
953
+ const binPath = resolveNewBinPath(projectRoot);
713
954
 
714
- // 4. doctorverify the resulting install.
715
- const doctor = await runDoctor();
716
- stepsRun.push('doctor');
717
-
718
- // 5. surface the target changelog (best-effort; optional seam).
719
- if (typeof surfaceChangelog === 'function') {
720
- await surfaceChangelog(target);
721
- }
955
+ // 2. runSync from new bin re-materialize ./.agents/ from the freshly
956
+ // installed payload. Running from the new bin ensures the copied files
957
+ // come from the new package's .agents/ tree, not the old loaded module.
958
+ const syncResult = await spawnPhase('sync', [], {
959
+ binPath,
960
+ cwd: projectRoot,
961
+ write,
962
+ writeErr,
963
+ });
964
+ if (!syncResult.ok) {
965
+ throw new Error(
966
+ `mandrel update: \`mandrel sync\` from new binary exited non-zero — ` +
967
+ 'the .agents/ materialization may be incomplete. ' +
968
+ 'Run `mandrel sync` manually to restore.',
969
+ );
970
+ }
971
+ stepsRun.push('runSync');
722
972
 
723
- if (!doctor.ok) {
724
- const failed = doctor.results.filter((r) => !r.ok).map((r) => r.name);
725
- writeErr(
726
- `mandrel update: upgraded to v${target} but doctor reported failures: ` +
727
- `${failed.join(', ')}\n` +
728
- ' Run `mandrel doctor` for remedies.\n',
973
+ // 3. runMigrations from new bin — apply version-keyed steps for the
974
+ // crossed range. The new binary's migration registry contains any steps
975
+ // added in the target version; the old process's registry does not.
976
+ const migrateResult = await spawnPhase(
977
+ 'migrate',
978
+ ['--from', current, '--to', target],
979
+ { binPath, cwd: projectRoot, write, writeErr },
729
980
  );
730
- exit(1);
731
- return {
732
- ok: false,
733
- action: 'doctor-failed',
734
- currentVersion: current,
735
- targetVersion: target,
736
- major,
737
- stepsRun,
738
- dryRun: false,
739
- };
981
+ if (!migrateResult.ok) {
982
+ throw new Error(
983
+ `mandrel update: \`mandrel migrate\` from new binary exited non-zero — ` +
984
+ `some migrations for v${current} → v${target} may not have applied. ` +
985
+ `Run \`mandrel migrate --from ${current} --to ${target}\` manually to retry.`,
986
+ );
987
+ }
988
+ stepsRun.push('runMigrations');
989
+
990
+ // 4. doctor from new bin — verify the resulting install. Running from the
991
+ // new bin is critical: the agents-drift check compares the materialized
992
+ // .agents/ against the installed package payload. When the old process
993
+ // runs this check, it resolves the package root to its own (old) install
994
+ // dir, so drift against the new payload is invisible. The new binary
995
+ // resolves the package root to the now-installed new version, producing
996
+ // an accurate result.
997
+ const doctorResult = await spawnPhase('doctor', [], {
998
+ binPath,
999
+ cwd: projectRoot,
1000
+ write,
1001
+ writeErr,
1002
+ });
1003
+ stepsRun.push('doctor');
1004
+
1005
+ // 5. surface the target changelog (best-effort; optional seam).
1006
+ if (typeof surfaceChangelog === 'function') {
1007
+ await surfaceChangelog(target);
1008
+ }
1009
+
1010
+ if (!doctorResult.ok) {
1011
+ writeErr(
1012
+ `mandrel update: upgraded to v${target} but doctor reported failures.\n` +
1013
+ ' → Run `mandrel doctor` for remedies.\n',
1014
+ );
1015
+ exit(1);
1016
+ return {
1017
+ ok: false,
1018
+ action: 'doctor-failed',
1019
+ currentVersion: current,
1020
+ targetVersion: target,
1021
+ major,
1022
+ stepsRun,
1023
+ dryRun: false,
1024
+ };
1025
+ }
1026
+ } else {
1027
+ // In-process backward-compat path (pre-Story-#4034 behaviour).
1028
+ // Used when no `spawnPhase` seam is injected — preserves full backward
1029
+ // compatibility with existing tests that inject runSync/runMigrations/
1030
+ // runDoctor directly.
1031
+
1032
+ // 2. runSync — re-materialize ./.agents/ from the new payload.
1033
+ runSync({ argv: [] });
1034
+ stepsRun.push('runSync');
1035
+
1036
+ // 3. runMigrations — apply version-keyed steps for the crossed range.
1037
+ runMigrations({ fromVersion: current, toVersion: target, ctx: {} });
1038
+ stepsRun.push('runMigrations');
1039
+
1040
+ // 4. doctor — verify the resulting install.
1041
+ const doctor = await runDoctor();
1042
+ stepsRun.push('doctor');
1043
+
1044
+ // 5. surface the target changelog (best-effort; optional seam).
1045
+ if (typeof surfaceChangelog === 'function') {
1046
+ await surfaceChangelog(target);
1047
+ }
1048
+
1049
+ if (!doctor.ok) {
1050
+ const failed = doctor.results.filter((r) => !r.ok).map((r) => r.name);
1051
+ writeErr(
1052
+ `mandrel update: upgraded to v${target} but doctor reported failures: ` +
1053
+ `${failed.join(', ')}\n` +
1054
+ ' → Run `mandrel doctor` for remedies.\n',
1055
+ );
1056
+ exit(1);
1057
+ return {
1058
+ ok: false,
1059
+ action: 'doctor-failed',
1060
+ currentVersion: current,
1061
+ targetVersion: target,
1062
+ major,
1063
+ stepsRun,
1064
+ dryRun: false,
1065
+ };
1066
+ }
740
1067
  }
741
1068
 
742
1069
  write(`✅ Updated to v${target}. The lockfile bump is staged for review.\n`);
@@ -763,8 +1090,17 @@ export async function runUpdate({
763
1090
  * lockfile (`pnpm`/`yarn`/`npm`), or the `--install-cmd` override —
764
1091
  * through the shared `runInstallCommand` helper — no git mutation;
765
1092
  * lockfile left staged.
1093
+ * - `spawnPhase` is wired to `defaultSpawnPhase`, which spawns each
1094
+ * post-install phase (sync, migrate, doctor) from the newly-installed
1095
+ * binary (`node_modules/.bin/mandrel`). This is the Story #4034 fix:
1096
+ * the new bin loads the new package's module code and resolves paths
1097
+ * against the new install dir, so sync/migrate/doctor can never observe
1098
+ * the old payload.
766
1099
  * - `surfaceChangelog` prints the relevant `docs/CHANGELOG.md` section(s)
767
- * for the applied range, degrading gracefully when the file is absent.
1100
+ * for the applied range. Reads from the packaged file first; falls back to
1101
+ * a GitHub raw-content fetch via the injectable `fetchChangelog` seam when
1102
+ * the packaged file is absent; emits an actionable link to the GitHub
1103
+ * Releases page when both sources fail (Story #4035).
768
1104
  *
769
1105
  * Every seam stays injectable on `runUpdate`; these are merely the
770
1106
  * no-seam-provided fallbacks, so the existing seam-driven tests stay green.
@@ -773,8 +1109,9 @@ export async function runUpdate({
773
1109
  *
774
1110
  * The second `deps` argument exposes the **process boundaries** the production
775
1111
  * defaults shell out across (`versionRunner` = `npm view`, `runInstall` =
776
- * the install spawn) plus `fs` / `cachePath` / `now`, so the entrypoint can be
777
- * driven end-to-end with the network/npm boundary stubbed and no real I/O.
1112
+ * the install spawn, `spawnFn` = the phase-spawn boundary) plus `fs` /
1113
+ * `cachePath` / `now`, so the entrypoint can be driven end-to-end with the
1114
+ * network/npm boundary stubbed and no real I/O.
778
1115
  * `bin/mandrel.js` calls `run(argv)` with no `deps`, getting the production
779
1116
  * wiring; tests pass fakes. The `deps` surface is NOT part of the public
780
1117
  * subcommand contract — `bin/mandrel.js` only ever supplies `argv`.
@@ -787,7 +1124,9 @@ export async function runUpdate({
787
1124
  * now?: Date,
788
1125
  * versionRunner?: () => string,
789
1126
  * runInstall?: (installCmd: string, cwd: string) => { status: number, stderr: string },
1127
+ * spawnFn?: typeof spawnSync,
790
1128
  * changelogPath?: string,
1129
+ * fetchChangelog?: (version: string) => Promise<string>,
791
1130
  * runUpdate?: typeof runUpdate,
792
1131
  * runSync?: typeof defaultRunSync,
793
1132
  * runMigrations?: typeof defaultRunMigrations,
@@ -806,7 +1145,9 @@ export default async function run(argv = [], deps = {}) {
806
1145
  now,
807
1146
  versionRunner,
808
1147
  runInstall,
1148
+ spawnFn,
809
1149
  changelogPath,
1150
+ fetchChangelog,
810
1151
  runUpdate: runUpdateImpl = runUpdate,
811
1152
  runSync,
812
1153
  runMigrations,
@@ -819,6 +1160,16 @@ export default async function run(argv = [], deps = {}) {
819
1160
 
820
1161
  const current = deps.currentVersion ?? defaultCurrentVersion(fs);
821
1162
 
1163
+ // The production spawnPhase default: spawn each post-install phase from
1164
+ // node_modules/.bin/mandrel (the newly-installed binary). spawnFn is
1165
+ // injectable so tests can stub the spawn boundary without running a real
1166
+ // child process.
1167
+ const productionSpawnPhase = (phase, args, opts) =>
1168
+ defaultSpawnPhase(phase, args, {
1169
+ ...opts,
1170
+ ...(spawnFn ? { spawnFn } : {}),
1171
+ });
1172
+
822
1173
  await runUpdateImpl({
823
1174
  argv,
824
1175
  currentVersion: current,
@@ -836,17 +1187,27 @@ export default async function run(argv = [], deps = {}) {
836
1187
  ...(runInstall ? { runInstall } : {}),
837
1188
  fs,
838
1189
  }),
1190
+ // In the production default export, always wire spawnPhase unless the
1191
+ // caller injects the old-style in-process seams (runSync/runMigrations/
1192
+ // runDoctor). When any old-style seam is present, fall back to in-process
1193
+ // behavior to preserve the full backward-compat surface that the
1194
+ // entrypoint test (update-entrypoint.test.js) relies on.
1195
+ ...(runSync || runMigrations || runDoctor
1196
+ ? {
1197
+ ...(runSync ? { runSync } : {}),
1198
+ ...(runMigrations ? { runMigrations } : {}),
1199
+ ...(runDoctor ? { runDoctor } : {}),
1200
+ }
1201
+ : { spawnPhase: productionSpawnPhase }),
839
1202
  surfaceChangelog: (target) =>
840
1203
  defaultSurfaceChangelog(target, {
841
1204
  current,
842
1205
  fs,
843
1206
  ...(changelogPath ? { changelogPath } : {}),
1207
+ ...(fetchChangelog ? { fetchChangelog } : {}),
844
1208
  ...(write ? { write } : {}),
845
1209
  ...(writeErr ? { writeErr } : {}),
846
1210
  }),
847
- ...(runSync ? { runSync } : {}),
848
- ...(runMigrations ? { runMigrations } : {}),
849
- ...(runDoctor ? { runDoctor } : {}),
850
1211
  ...(write ? { write } : {}),
851
1212
  ...(writeErr ? { writeErr } : {}),
852
1213
  ...(exit ? { exit } : {}),
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.58.0",
3
+ "version": "1.60.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, personas, skills, and SDLC workflows that govern AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  ".agents/",
8
8
  "bin/",
9
+ "docs/CHANGELOG.md",
9
10
  "lib/"
10
11
  ],
11
12
  "publishConfig": {
@@ -36,6 +37,7 @@
36
37
  "format:check": "biome ci .",
37
38
  "maintainability:check": "node .agents/scripts/check-baselines.js --gate maintainability",
38
39
  "maintainability:update": "node .agents/scripts/update-maintainability-baseline.js",
40
+ "check:arch": "node .agents/scripts/check-arch-cycles.js",
39
41
  "crap:check": "node .agents/scripts/check-baselines.js --gate crap",
40
42
  "crap:update": "node .agents/scripts/update-crap-baseline.js",
41
43
  "duplication:check": "node .agents/scripts/check-baselines.js --gate duplication",