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
@@ -0,0 +1,360 @@
1
+ /**
2
+ * CLI: ratchet-down architecture gate for import cycles (Story #3991).
3
+ *
4
+ * Walks every `.js` file under `.agents/scripts/` (excluding
5
+ * `node_modules`), parses relative static-import edges
6
+ * (`from './…/x.js'`), detects directed cycles via DFS, and compares
7
+ * them against the committed allowlist at `baselines/arch-cycles.json`.
8
+ *
9
+ * Ratchet semantics mirror `check-dead-exports.js`:
10
+ * - Any detected cycle NOT in the allowlist → exit 1, cycle path printed.
11
+ * - Allowlisted cycle no longer detected → printed as `-` (removal),
12
+ * warning that the allowlist can shrink. Removals-only exits 0.
13
+ * - Clean diff → exit 0.
14
+ *
15
+ * Cycles are normalized by rotating to the lexicographically-smallest
16
+ * member so the same cycle always serializes identically regardless of
17
+ * the DFS entry point.
18
+ *
19
+ * Flags:
20
+ * --baseline <path> override the allowlist path (default
21
+ * `baselines/arch-cycles.json`, resolved from cwd)
22
+ * --root <path> override the scanned root (default
23
+ * `.agents/scripts`, resolved from cwd)
24
+ * --json write the structured envelope to stdout
25
+ */
26
+
27
+ import fs from 'node:fs';
28
+ import path from 'node:path';
29
+ import process from 'node:process';
30
+ import { runAsCli } from './lib/cli-utils.js';
31
+
32
+ /**
33
+ * Parse argv for `--baseline <path>`, `--root <path>`, and `--json`.
34
+ * Exported so unit tests can pin the parser.
35
+ *
36
+ * @param {string[]} argv
37
+ * @returns {{ baselinePath: string | null, rootPath: string | null, json: boolean }}
38
+ */
39
+ export function parseArgv(argv = []) {
40
+ let baselinePath = null;
41
+ let rootPath = null;
42
+ let json = false;
43
+ for (let i = 0; i < argv.length; i += 1) {
44
+ const a = argv[i];
45
+ if (a === '--baseline') {
46
+ const next = argv[i + 1];
47
+ if (next && !next.startsWith('--')) {
48
+ baselinePath = next;
49
+ i += 1;
50
+ }
51
+ } else if (a === '--root') {
52
+ const next = argv[i + 1];
53
+ if (next && !next.startsWith('--')) {
54
+ rootPath = next;
55
+ i += 1;
56
+ }
57
+ } else if (a === '--json') {
58
+ json = true;
59
+ }
60
+ }
61
+ return { baselinePath, rootPath, json };
62
+ }
63
+
64
+ /**
65
+ * Recursively collect `.js` files under `rootDir`, skipping
66
+ * `node_modules`. Returns absolute paths, sorted for determinism.
67
+ *
68
+ * @param {string} rootDir
69
+ * @returns {string[]}
70
+ */
71
+ export function collectJsFiles(rootDir) {
72
+ const out = [];
73
+ const walk = (dir) => {
74
+ let entries;
75
+ try {
76
+ entries = fs.readdirSync(dir, { withFileTypes: true });
77
+ } catch {
78
+ return;
79
+ }
80
+ for (const entry of entries) {
81
+ if (entry.name === 'node_modules') continue;
82
+ const full = path.join(dir, entry.name);
83
+ if (entry.isDirectory()) {
84
+ walk(full);
85
+ } else if (entry.isFile() && entry.name.endsWith('.js')) {
86
+ out.push(full);
87
+ }
88
+ }
89
+ };
90
+ walk(rootDir);
91
+ return out.sort();
92
+ }
93
+
94
+ const IMPORT_RE = /from\s+['"](\.\.?\/[^'"]+\.js)['"]/g;
95
+
96
+ /**
97
+ * Pure helper: extract relative static-import specifiers from source text.
98
+ *
99
+ * @param {string} source
100
+ * @returns {string[]}
101
+ */
102
+ export function parseRelativeImports(source) {
103
+ const specs = [];
104
+ for (const m of source.matchAll(IMPORT_RE)) {
105
+ specs.push(m[1]);
106
+ }
107
+ return specs;
108
+ }
109
+
110
+ /**
111
+ * Build a directed import graph over the given files. Node identity is the
112
+ * file path relative to `rootDir`, posix-separated, so the graph (and any
113
+ * cycles found in it) serializes identically across platforms. Edges that
114
+ * resolve outside the scanned file set are dropped.
115
+ *
116
+ * @param {string[]} files absolute paths
117
+ * @param {string} rootDir
118
+ * @param {{ readFile?: (p: string) => string }} [opts]
119
+ * @returns {Map<string, string[]>}
120
+ */
121
+ export function buildGraph(files, rootDir, { readFile } = {}) {
122
+ const read = readFile ?? ((p) => fs.readFileSync(p, 'utf-8'));
123
+ const toId = (abs) => path.relative(rootDir, abs).split(path.sep).join('/');
124
+ const idSet = new Set(files.map(toId));
125
+ const graph = new Map();
126
+ for (const file of files) {
127
+ const id = toId(file);
128
+ let source;
129
+ try {
130
+ source = read(file);
131
+ } catch {
132
+ graph.set(id, []);
133
+ continue;
134
+ }
135
+ const edges = [];
136
+ for (const spec of parseRelativeImports(source)) {
137
+ const target = path
138
+ .relative(rootDir, path.resolve(path.dirname(file), spec))
139
+ .split(path.sep)
140
+ .join('/');
141
+ if (idSet.has(target) && target !== id) edges.push(target);
142
+ }
143
+ graph.set(id, [...new Set(edges)].sort());
144
+ }
145
+ return graph;
146
+ }
147
+
148
+ /**
149
+ * Pure helper: rotate a cycle (array of module ids, no repeated terminal
150
+ * element) so it starts at its lexicographically-smallest member. The same
151
+ * cycle therefore always serializes identically regardless of where the
152
+ * DFS entered it.
153
+ *
154
+ * @param {string[]} cycle
155
+ * @returns {string[]}
156
+ */
157
+ export function normalizeCycle(cycle) {
158
+ if (cycle.length === 0) return [];
159
+ let minIdx = 0;
160
+ for (let i = 1; i < cycle.length; i += 1) {
161
+ if (cycle[i] < cycle[minIdx]) minIdx = i;
162
+ }
163
+ return [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
164
+ }
165
+
166
+ /**
167
+ * Detect directed cycles in the graph via iterative-stack DFS (white /
168
+ * gray / black coloring). Each back edge to a gray node yields the cycle
169
+ * slice currently on the DFS path. Cycles are normalized and deduplicated
170
+ * by their serialized form, then sorted for stable output.
171
+ *
172
+ * @param {Map<string, string[]>} graph
173
+ * @returns {string[][]} normalized cycles
174
+ */
175
+ export function findCycles(graph) {
176
+ const WHITE = 0;
177
+ const GRAY = 1;
178
+ const BLACK = 2;
179
+ const color = new Map();
180
+ for (const node of graph.keys()) color.set(node, WHITE);
181
+ const seen = new Map();
182
+
183
+ const pathStack = [];
184
+ const onPath = new Map(); // node -> index in pathStack
185
+
186
+ const visit = (start) => {
187
+ // Iterative DFS frame stack: [node, edge cursor].
188
+ const frames = [[start, 0]];
189
+ color.set(start, GRAY);
190
+ onPath.set(start, pathStack.length);
191
+ pathStack.push(start);
192
+ while (frames.length > 0) {
193
+ const frame = frames[frames.length - 1];
194
+ const [node] = frame;
195
+ const edges = graph.get(node) ?? [];
196
+ if (frame[1] < edges.length) {
197
+ const next = edges[frame[1]];
198
+ frame[1] += 1;
199
+ const c = color.get(next);
200
+ if (c === GRAY) {
201
+ const cycle = normalizeCycle(pathStack.slice(onPath.get(next)));
202
+ seen.set(cycle.join(' -> '), cycle);
203
+ } else if (c === WHITE) {
204
+ color.set(next, GRAY);
205
+ onPath.set(next, pathStack.length);
206
+ pathStack.push(next);
207
+ frames.push([next, 0]);
208
+ }
209
+ } else {
210
+ color.set(node, BLACK);
211
+ onPath.delete(node);
212
+ pathStack.pop();
213
+ frames.pop();
214
+ }
215
+ }
216
+ };
217
+
218
+ for (const node of [...graph.keys()].sort()) {
219
+ if (color.get(node) === WHITE) visit(node);
220
+ }
221
+ return [...seen.values()].sort((a, b) =>
222
+ a.join(' -> ').localeCompare(b.join(' -> ')),
223
+ );
224
+ }
225
+
226
+ /**
227
+ * Pure helper: read the allowlist envelope from disk. Returns the parsed
228
+ * object or `null` when the file is missing or unparseable.
229
+ *
230
+ * @param {string} baselinePath
231
+ * @returns {{ cycles?: string[][] } | null}
232
+ */
233
+ export function loadBaseline(baselinePath) {
234
+ try {
235
+ if (!fs.existsSync(baselinePath)) return null;
236
+ const parsed = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
237
+ if (!parsed || typeof parsed !== 'object') return null;
238
+ return parsed;
239
+ } catch {
240
+ return null;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Pure helper: diff detected cycles against the allowlist. Both sides are
246
+ * normalized before comparison so rotation differences never count as
247
+ * drift. Identity is the ` -> `-joined normalized cycle.
248
+ *
249
+ * @param {string[][]} allowlisted
250
+ * @param {string[][]} detected
251
+ * @returns {{ added: string[][], removed: string[][] }}
252
+ */
253
+ export function diffCycles(allowlisted, detected) {
254
+ const key = (c) => normalizeCycle(c).join(' -> ');
255
+ const baseSet = new Set((allowlisted ?? []).map(key));
256
+ const currentSet = new Set((detected ?? []).map(key));
257
+ const added = (detected ?? []).filter((c) => !baseSet.has(key(c)));
258
+ const removed = (allowlisted ?? []).filter((c) => !currentSet.has(key(c)));
259
+ const sortFn = (a, b) => key(a).localeCompare(key(b));
260
+ return { added: added.sort(sortFn), removed: removed.sort(sortFn) };
261
+ }
262
+
263
+ /**
264
+ * Pure helper: render the human-readable diff. `+` lines are new cycles
265
+ * (gate fail), `-` lines are fixed cycles whose allowlist entry can be
266
+ * removed. A one-line summary always follows.
267
+ *
268
+ * @param {{ added: string[][], removed: string[][] }} diff
269
+ * @returns {string}
270
+ */
271
+ export function renderDiff(diff) {
272
+ const lines = [];
273
+ const fmt = (c) => `${c.join(' -> ')} -> ${c[0]}`;
274
+ for (const c of diff.added) lines.push(`+ ${fmt(c)}`);
275
+ for (const c of diff.removed) lines.push(`- ${fmt(c)}`);
276
+ if (diff.removed.length > 0) {
277
+ lines.push(
278
+ `[arch-cycles] ⚠ ${diff.removed.length} allowlisted cycle(s) no longer detected — shrink baselines/arch-cycles.json`,
279
+ );
280
+ }
281
+ const tag = diff.added.length > 0 ? '(gate fail)' : '(ok)';
282
+ lines.push(
283
+ `[arch-cycles] added=${diff.added.length} removed=${diff.removed.length} ${tag}`,
284
+ );
285
+ return lines.join('\n');
286
+ }
287
+
288
+ /**
289
+ * Top-level CLI entry. Exported so tests can drive the full pipeline
290
+ * against a tmpdir fixture graph.
291
+ *
292
+ * @param {{
293
+ * argv?: string[],
294
+ * cwd?: string,
295
+ * stdout?: { write: (s: string) => void },
296
+ * stderr?: { write: (s: string) => void },
297
+ * }} [opts]
298
+ * @returns {Promise<number>} 0 = clean or removals-only; 1 = new cycle detected
299
+ */
300
+ export async function runCli({
301
+ argv = process.argv.slice(2),
302
+ cwd = process.cwd(),
303
+ stdout = process.stdout,
304
+ stderr = process.stderr,
305
+ } = {}) {
306
+ const { baselinePath, rootPath, json } = parseArgv(argv);
307
+ const resolvedRoot = path.resolve(
308
+ cwd,
309
+ rootPath ?? path.join('.agents', 'scripts'),
310
+ );
311
+ const resolvedBaselinePath = path.resolve(
312
+ cwd,
313
+ baselinePath ?? path.join('baselines', 'arch-cycles.json'),
314
+ );
315
+ if (!fs.existsSync(resolvedRoot)) {
316
+ throw new Error(`[arch-cycles] scan root not found: ${resolvedRoot}`);
317
+ }
318
+ const baseline = loadBaseline(resolvedBaselinePath);
319
+ const allowlisted = Array.isArray(baseline?.cycles) ? baseline.cycles : [];
320
+
321
+ const files = collectJsFiles(resolvedRoot);
322
+ const graph = buildGraph(files, resolvedRoot);
323
+ const detected = findCycles(graph);
324
+ const diff = diffCycles(allowlisted, detected);
325
+ const exitCode = diff.added.length > 0 ? 1 : 0;
326
+
327
+ if (json) {
328
+ const envelope = {
329
+ kind: 'arch-cycles-report',
330
+ root: resolvedRoot,
331
+ baselinePath: resolvedBaselinePath,
332
+ allowlisted: allowlisted.map(normalizeCycle),
333
+ detected,
334
+ added: diff.added,
335
+ removed: diff.removed,
336
+ exitCode,
337
+ };
338
+ stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
339
+ } else {
340
+ if (!baseline) {
341
+ stderr.write(
342
+ `[arch-cycles] ⚠ allowlist not found at ${resolvedBaselinePath} — treating as empty\n`,
343
+ );
344
+ }
345
+ stdout.write(`\n--- arch-cycles preview ---\n`);
346
+ stdout.write(`${renderDiff(diff)}\n`);
347
+ }
348
+
349
+ return exitCode;
350
+ }
351
+
352
+ async function main() {
353
+ return runCli();
354
+ }
355
+
356
+ runAsCli(import.meta.url, main, {
357
+ source: 'arch-cycles',
358
+ propagateExitCode: true,
359
+ errorPrefix: '[arch-cycles] ❌ Fatal error',
360
+ });
@@ -5,7 +5,7 @@
5
5
  // Story #2662 — Internal-link and slash-command resolver for active docs.
6
6
  //
7
7
  // Scans every `*.md` under `docs/` and `.agents/` (excluding
8
- // `docs/CHANGELOG.md` and anything under `docs/archive/**`) and validates:
8
+ // `docs/CHANGELOG.md`) and validates:
9
9
  //
10
10
  // 1. Every Markdown relative-path link `[text](relative/path[#anchor])`
11
11
  // resolves to a real file on disk (anchors are not validated, only
@@ -19,7 +19,7 @@
19
19
  //
20
20
  // 3. No active doc mentions any retired slash command. The retired-command
21
21
  // blocklist is seeded with `agents-bootstrap-github`,
22
- // `single-story-plan` (renamed to `/story-plan`), and `mandrel`
22
+ // `single-story-plan` (renamed to `/plan`), and `mandrel`
23
23
  // (retired in favor of the generated `.agents/docs/workflows.md`
24
24
  // catalog) and takes precedence over the workflow-resolution check —
25
25
  // a retired token is always a non-zero exit even if a stale workflow
@@ -125,7 +125,6 @@ export const SLASH_ALLOWLIST = new Set([
125
125
 
126
126
  function isExcludedRelPath(relPath) {
127
127
  if (relPath === 'docs/CHANGELOG.md') return true;
128
- if (relPath.startsWith('docs/archive/')) return true;
129
128
  return false;
130
129
  }
131
130
 
@@ -9,9 +9,11 @@
9
9
  * 2. With `--skip-when-no-crap-files`: read `git diff --name-only <ref>...HEAD`
10
10
  * (default ref `main`) and exit 0 if no changed file lives under
11
11
  * `crap.targetDirs`.
12
- * 3. Compare the coverage artifact mtime against the newest mtime in
13
- * `crap.targetDirs`. Exit 0 when fresh.
14
- * 4. Otherwise spawn `npm run test:coverage` and propagate its exit code.
12
+ * 3. Test freshness: content digest of `crap.targetDirs` vs. the persisted
13
+ * capture stamp (`coverage/.capture-stamp.json`), falling back to the
14
+ * artifact-mtime heuristic when no stamp exists. Exit 0 when fresh.
15
+ * 4. Otherwise spawn `npm run test:coverage`, write a fresh capture stamp
16
+ * on success, and propagate the exit code.
15
17
  *
16
18
  * Exit codes:
17
19
  * 0 — coverage is fresh (or capture skipped/succeeded).
@@ -24,8 +26,10 @@ import { getChangedFiles } from './lib/changed-files.js';
24
26
  import { getQuality, resolveConfig } from './lib/config-resolver.js';
25
27
  import {
26
28
  anyChangedUnderTargets,
29
+ computeContentDigest,
27
30
  isCoverageFresh,
28
31
  runCapture,
32
+ writeCaptureStamp,
29
33
  } from './lib/coverage-capture.js';
30
34
 
31
35
  import { Logger } from './lib/Logger.js';
@@ -99,6 +103,23 @@ function main() {
99
103
  Logger.error(
100
104
  `[coverage-capture] ✖ npm run test:coverage exited ${code}. Fix failing tests or coverage-threshold breaches before re-running the CRAP gate.`,
101
105
  );
106
+ return code;
107
+ }
108
+
109
+ // Persist the content digest next to the fresh artifact so subsequent
110
+ // freshness checks are content-aware (mtime churn from branch switches no
111
+ // longer invalidates). Best-effort — a missing stamp just means the next
112
+ // check falls back to the mtime heuristic.
113
+ const digest = computeContentDigest(args.cwd, crap.targetDirs);
114
+ if (
115
+ digest &&
116
+ writeCaptureStamp({
117
+ cwd: args.cwd,
118
+ coveragePath: crap.coveragePath,
119
+ digest,
120
+ })
121
+ ) {
122
+ Logger.info('[coverage-capture] Wrote content-digest capture stamp.');
102
123
  }
103
124
  return code;
104
125
  }
@@ -134,7 +134,7 @@ function buildFrictionSignal({
134
134
  timestamp: new Date().toISOString(),
135
135
  epicId: epicId ?? null,
136
136
  storyId: storyId ?? null,
137
- // 3-tier hierarchy (Epic #3163): no Task tier, so friction signals
137
+ // 2-tier hierarchy (Epic #3163): no Task tier, so friction signals
138
138
  // carry no Task id. The field is retained for schema compatibility
139
139
  // and always null.
140
140
  taskId: null,
@@ -72,12 +72,12 @@ import { loadSpec, loadState } from './lib/spec/index.js';
72
72
  * loaded state mapping. The spec-aware renderer (`buildManifestFromSpec`)
73
73
  * looks up each Story's status via `state.mapping[slug].lastObservedAgentState`;
74
74
  * that field is only refreshed by the structural reconciler, so during
75
- * `/epic-deliver` execution it stays `null` and every Story renders as ⬜
75
+ * `/deliver` execution it stays `null` and every Story renders as ⬜
76
76
  * pending even after the Story merges. The wave-runner replaced the
77
77
  * dispatcher-per-wave refresh loop and the local state.json never sees
78
78
  * the progress signal.
79
79
  *
80
- * Under the 3-tier hierarchy (Epic #3163) the runtime manifest's wave
80
+ * Under the 2-tier hierarchy (Epic #3163) the runtime manifest's wave
81
81
  * records carry `stories[]` (each with the live `storyId` + `status` from
82
82
  * `fetchEpicContext`'s GH query), not the retired `tasks[]` shape. The
83
83
  * overlay walks `manifest.waves[].stories[]` and copies each Story's
@@ -8,7 +8,7 @@
8
8
  * any still-stuck entries by enumerating the processes holding handles
9
9
  * inside the worktree path and terminating them.
10
10
  *
11
- * Invoked by `/epic-deliver`, `/epic-plan-spec` / `/epic-plan-decompose`
11
+ * Invoked by `/deliver`, `/epic-plan-spec` / `/epic-plan-decompose`
12
12
  * (via `drainPendingCleanupAtBoot` → `worktree-sweep.js`), and
13
13
  * `story-close` so the pending-cleanup ledger drains automatically
14
14
  * across the sprint lifecycle. Operators can also run it standalone:
@@ -2,7 +2,7 @@
2
2
  /* node:coverage ignore file */
3
3
 
4
4
  /**
5
- * epic-audit-prepare.js — Phase 4 prepare CLI for `/epic-deliver`.
5
+ * epic-audit-prepare.js — Phase 4 prepare CLI for `/deliver`.
6
6
  *
7
7
  * Thin glue around the audit-suite `selectAudits` SDK. Reads the Epic
8
8
  * ticket, runs the change-set selector against the Epic branch diff
@@ -193,7 +193,7 @@ function resolveTaskSizing(config) {
193
193
  * Best-effort and total, mirroring `resolveRiskRoutedLenses`: a
194
194
  * missing/unparseable checkpoint, an absent `planningRisk` field, or a
195
195
  * provider read failure all degrade to `standard` — the neutral default that
196
- * preserves today's behavior — so an Epic that skipped `/epic-plan` (no
196
+ * preserves today's behavior — so an Epic that skipped `/plan` (no
197
197
  * checkpoint) still gets a passing `standard` pass with no new failure mode.
198
198
  * The changed-file count can only escalate a low-risk Epic to `deep` (a wide
199
199
  * diff) and never downgrades a high-risk one; an unknown/absent count is the
@@ -295,7 +295,7 @@ export async function runEpicAuditPrepare(values, deps = {}) {
295
295
  const runner = deps.selectAudits ?? selectAudits;
296
296
 
297
297
  // Pin the change set to the requested Epic's own branch rather than the
298
- // shared checkout's HEAD (Story #3362). Under two concurrent /epic-deliver
298
+ // shared checkout's HEAD (Story #3362). Under two concurrent /deliver
299
299
  // runs sharing one working copy, a HEAD-relative diff silently reports the
300
300
  // *other* Epic's change set; `refs/heads/epic/<id>` is unambiguous.
301
301
  const epicBranch = `epic/${epicId}`;
@@ -3,9 +3,9 @@
3
3
 
4
4
  /**
5
5
  * epic-deliver-note-intervention.js — record a manual-intervention event
6
- * against the active `/epic-deliver` run-state checkpoint.
6
+ * against the active `/deliver` run-state checkpoint.
7
7
  *
8
- * The host LLM driving `/epic-deliver` invokes this CLI whenever it does
8
+ * The host LLM driving `/deliver` invokes this CLI whenever it does
9
9
  * something out-of-band that disqualifies the Epic from auto-merge:
10
10
  *
11
11
  * - `AskUserQuestion` to the operator mid-run
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * epic-deliver-preflight.js — Story #2899 (Epic #2880, F13).
6
6
  *
7
- * Estimates the cost of an upcoming `/epic-deliver` run *before* Story
7
+ * Estimates the cost of an upcoming `/deliver` run *before* Story
8
8
  * fan-out and surfaces the result to the operator on two channels:
9
9
  *
10
10
  * 1. A JSON envelope on stdout (always) with the keys
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * The CLI is intentionally side-effect-light when `--dry-run` is set: no
21
21
  * comment write, no lifecycle emit. The slash-command workflow
22
- * (`/epic-deliver` Phase 1) calls the CLI without `--dry-run` so the
22
+ * (`/deliver` Phase 1) calls the CLI without `--dry-run` so the
23
23
  * comment is upserted; if any threshold is breached, the workflow flips
24
24
  * the Epic to `agent::blocked` and surfaces the envelope in chat for
25
25
  * operator review. The CLI itself does NOT flip labels — the workflow
@@ -70,7 +70,7 @@ const HELP = `Usage: node .agents/scripts/epic-deliver-preflight.js \\
70
70
  [--per-story-claude-tokens <n>]
71
71
 
72
72
  Estimates Story count, install cost, wave count, GitHub API request volume,
73
- and Claude Max quota burn for an Epic *before* /epic-deliver fan-out.
73
+ and Claude Max quota burn for an Epic *before* /deliver fan-out.
74
74
 
75
75
  Flags:
76
76
  --dry-run Compute the estimate and print the JSON
@@ -103,7 +103,7 @@ const DEFAULTS = Object.freeze({
103
103
  perStoryApiRequests: 25,
104
104
  perStoryClaudeTokens: 200_000,
105
105
  // Base GH API budget: snapshot getTicket + getSubTickets + plan/manifest
106
- // reads/writes that happen once per /epic-deliver run regardless of
106
+ // reads/writes that happen once per /deliver run regardless of
107
107
  // Story count. Empirical observation from a 5-Story Epic shows ~30
108
108
  // requests for the non-per-Story floor.
109
109
  baseApiRequests: 30,
@@ -240,7 +240,7 @@ export function renderPreflightBody({
240
240
  );
241
241
  } else {
242
242
  lines.push(
243
- '⛔ **Threshold breaches** — `/epic-deliver` will flip the Epic to `agent::blocked` for operator review:',
243
+ '⛔ **Threshold breaches** — `/deliver` will flip the Epic to `agent::blocked` for operator review:',
244
244
  );
245
245
  for (const b of breaches) {
246
246
  lines.push(`- \`${b.key}\` = ${b.observed} (max ${b.max})`);
@@ -284,7 +284,7 @@ export async function runPreflight({
284
284
  const provider = injectedProvider ?? createProvider(config);
285
285
  const thresholds = getPreflight(config);
286
286
 
287
- // Compose the same two phases /epic-deliver Phase 1 runs so the
287
+ // Compose the same two phases /deliver Phase 1 runs so the
288
288
  // preflight numbers match the actual dispatch plan.
289
289
  const ctx = { epicId, provider };
290
290
  let state = {};
@@ -297,9 +297,11 @@ export async function runPreflight({
297
297
  // Persist the snapshot/DAG envelope so `epic-deliver-prepare.js` can
298
298
  // reuse it instead of re-walking the hierarchy. The cache key is a
299
299
  // deterministic fingerprint of the Epic ticket returned by the same
300
- // `getTicket(epicId)` call that drove `runSnapshotPhase`, so any drift
301
- // (label, body, updatedAt) forces a cache miss in prepare.
302
- const baseSha = computeBaseSha(state.epic);
300
+ // `getTicket(epicId)` call that drove `runSnapshotPhase` **plus** the
301
+ // Story snapshots that drove the wave DAG (Story #4019), so any drift —
302
+ // Epic label/body/updatedAt or a Story-dependency edit — forces a cache
303
+ // miss in prepare.
304
+ const baseSha = computeBaseSha(state.epic, state.stories);
303
305
  let cacheWritten = false;
304
306
  if (!dryRun) {
305
307
  await writePreflightCache({
@@ -2,7 +2,7 @@
2
2
  /* node:coverage ignore file */
3
3
 
4
4
  /**
5
- * epic-deliver-prepare.js — Step 0/1 of the operator-driven `/epic-deliver`.
5
+ * epic-deliver-prepare.js — Step 0/1 of the operator-driven `/deliver`.
6
6
  *
7
7
  * Composes the existing engine phases that the in-process epic-runner used to
8
8
  * call sequentially, but does NOT dispatch any waves. The CLI is the single
@@ -226,16 +226,24 @@ export async function runEpicDeliverPrepare({
226
226
 
227
227
  // Story #3027: try the preflight cache first so we don't re-walk Epic
228
228
  // → Feature → Story when `epic-deliver-preflight.js` already did. The
229
- // cache key is a deterministic fingerprint of the Epic ticket; a
230
- // single getTicket(epicId) round-trip confirms the cache is still
231
- // valid. Cache miss or baseSha mismatch fall back to a fresh pass.
229
+ // cache key is a deterministic fingerprint of the Epic ticket plus the
230
+ // cached Story snapshots (Story #4019): the Epic re-fetch plus one
231
+ // getTicket per cached Story is still far cheaper than the full
232
+ // hierarchy BFS, and a Story-dependency edit now invalidates the cache.
233
+ // Cache miss or baseSha mismatch → fall back to a fresh pass.
232
234
  const ctx = { epicId, provider };
233
235
  let state = {};
234
236
  let cacheStatus = 'miss';
235
237
  const cached = await readPreflightCache({ epicId, cwd });
236
238
  if (cached) {
237
239
  const freshEpic = await provider.getTicket(epicId);
238
- const freshBaseSha = computeBaseSha(freshEpic);
240
+ const cachedStoryIds = cached.stories
241
+ .map((s) => Number(s?.id ?? s?.number))
242
+ .filter((id) => Number.isInteger(id) && id > 0);
243
+ const freshStories = await Promise.all(
244
+ cachedStoryIds.map((id) => provider.getTicket(id)),
245
+ );
246
+ const freshBaseSha = computeBaseSha(freshEpic, freshStories);
239
247
  if (freshBaseSha === cached.baseSha) {
240
248
  state = {
241
249
  epic: cached.epic,
@@ -6,7 +6,7 @@
6
6
  * advance the `epic-run-state` checkpoint, and re-render the unified
7
7
  * `epic-run-progress` rollup on the Epic.
8
8
  *
9
- * The slash-command (`/epic-deliver`) calls this CLI once per wave, after
9
+ * The slash-command (`/deliver`) calls this CLI once per wave, after
10
10
  * its host-level Agent-tool fan-out drains. It is the only writer of the
11
11
  * `epic-run-progress` structured comment for the wave-completion path —
12
12
  * there is no separate `/wave-execute` skill, no `wave-run-progress`
@@ -42,7 +42,7 @@ import { getRunners } from './lib/config/runners.js';
42
42
  import { resolveConfig } from './lib/config-resolver.js';
43
43
  import { Logger } from './lib/Logger.js';
44
44
  import * as epicRunStateStore from './lib/orchestration/epic-run-state-store.js';
45
- import { upsertEpicRunProgress } from './lib/orchestration/epic-runner/progress-reporter.js';
45
+ import { upsertEpicRunProgress } from './lib/orchestration/epic-runner/progress-reporter/composition.js';
46
46
  import {
47
47
  emitStoryDispatchEnd,
48
48
  storyStatusToDispatchOutcome,
@@ -95,7 +95,7 @@ const HELP = `Usage: node .agents/scripts/epic-execute-record-wave.js \\
95
95
 
96
96
  Records the wave's per-Story outcomes, advances the epic-run-state
97
97
  checkpoint, and upserts the unified epic-run-progress rollup on the Epic.
98
- Prints the next action for the /epic-deliver slash command.
98
+ Prints the next action for the /deliver slash command.
99
99
  `;
100
100
 
101
101
  /**
@@ -237,7 +237,7 @@ export async function runEpicExecuteRecordWave({
237
237
  // Closes the start/end pairing the wave-tick reconciler and the
238
238
  // `--check-idle` watchdog use to derive in-flight Stories. Before this
239
239
  // the only producer was `wave-session.js`, which the host-LLM driven
240
- // /epic-deliver path never imports — so every dispatched Story stayed
240
+ // /deliver path never imports — so every dispatched Story stayed
241
241
  // "in-flight" forever and completed Stories tripped the watchdog.
242
242
  // Best-effort: a failed append must not block the wave loop.
243
243
  emitWaveDispatchEnds({ epicId, verified, config });
@@ -257,7 +257,7 @@ export async function runEpicExecuteRecordWave({
257
257
 
258
258
  // 7. Fire the curated webhook events for this wave boundary. Mirrors the
259
259
  // wave-loop emits in `lib/orchestration/epic-runner/phases/iterate-waves.js`
260
- // for the host-LLM driven /epic-deliver path (which does not pass
260
+ // for the host-LLM driven /deliver path (which does not pass
261
261
  // through `runEpic`). Each helper is fire-and-forget — webhook
262
262
  // misconfig or a transient Slack outage must not block the wave loop.
263
263
  await emitWaveBoundaryNotifications({