mandrel 1.94.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/.agents/README.md +59 -73
  2. package/.agents/agents/story-worker.md +12 -13
  3. package/.agents/docs/SDLC.md +489 -1285
  4. package/.agents/docs/agentrc-reference.json +177 -67
  5. package/.agents/docs/configuration.md +104 -138
  6. package/.agents/docs/execution-reference.md +17 -20
  7. package/.agents/docs/quality-gates.md +13 -19
  8. package/.agents/docs/workflows.md +3 -3
  9. package/.agents/instructions.md +70 -81
  10. package/.agents/rules/ci-remediation.md +8 -12
  11. package/.agents/rules/git-conventions-reference.md +6 -7
  12. package/.agents/rules/git-conventions.md +16 -22
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +54 -214
  15. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  16. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  17. package/.agents/schemas/risk-verdict.schema.json +0 -13
  18. package/.agents/scripts/analyze-execution.js +1 -1
  19. package/.agents/scripts/audit-to-stories.js +7 -7
  20. package/.agents/scripts/boot-sweep.js +1 -1
  21. package/.agents/scripts/check-lifecycle-lint.js +6 -9
  22. package/.agents/scripts/check-prepush-recovery.js +1 -1
  23. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  24. package/.agents/scripts/lib/Logger.js +6 -10
  25. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  26. package/.agents/scripts/lib/audit-suite/selector.js +5 -5
  27. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  28. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  29. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
  30. package/.agents/scripts/lib/baselines/reader.js +1 -6
  31. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  32. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  33. package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
  34. package/.agents/scripts/lib/checks/index.js +2 -1
  35. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
  36. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  37. package/.agents/scripts/lib/cli-args.js +16 -7
  38. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  39. package/.agents/scripts/lib/config/ci.js +6 -31
  40. package/.agents/scripts/lib/config/delivery-routing.js +45 -29
  41. package/.agents/scripts/lib/config/explain.js +55 -36
  42. package/.agents/scripts/lib/config/limits.js +17 -58
  43. package/.agents/scripts/lib/config/paths.js +0 -2
  44. package/.agents/scripts/lib/config/quality.js +1 -1
  45. package/.agents/scripts/lib/config/runners.js +17 -50
  46. package/.agents/scripts/lib/config/temp-paths.js +19 -14
  47. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  48. package/.agents/scripts/lib/config-resolver.js +2 -7
  49. package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
  50. package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
  51. package/.agents/scripts/lib/config-settings-schema.js +52 -38
  52. package/.agents/scripts/lib/dependency-parser.js +3 -2
  53. package/.agents/scripts/lib/doc-tiers.js +2 -2
  54. package/.agents/scripts/lib/duplicate-search.js +211 -41
  55. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  56. package/.agents/scripts/lib/findings/promote-finding.js +5 -5
  57. package/.agents/scripts/lib/framework-version.js +2 -3
  58. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  59. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
  60. package/.agents/scripts/lib/git-utils.js +32 -6
  61. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  62. package/.agents/scripts/lib/label-constants.js +10 -23
  63. package/.agents/scripts/lib/label-taxonomy.js +9 -43
  64. package/.agents/scripts/lib/observability/active-story-env.js +1 -1
  65. package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
  66. package/.agents/scripts/lib/observability/source-classifier.js +3 -3
  67. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  68. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
  69. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
  70. package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
  71. package/.agents/scripts/lib/orchestration/code-review.js +20 -268
  72. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  73. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
  74. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  75. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  76. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  77. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  78. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
  79. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
  80. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  81. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
  82. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  83. package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
  84. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  85. package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
  86. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
  87. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  88. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
  89. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
  90. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
  91. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
  92. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
  93. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  94. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  95. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
  96. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  97. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
  98. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
  99. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
  100. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
  101. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  102. package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
  103. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  104. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
  105. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  106. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
  107. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  108. package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
  109. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  110. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  111. package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
  112. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
  117. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  118. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  119. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  121. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  122. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
  123. package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
  124. package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
  125. package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
  126. package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
  127. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  128. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
  129. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
  130. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
  131. package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
  132. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
  133. package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
  134. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
  135. package/.agents/scripts/lib/planning-corpus.js +16 -11
  136. package/.agents/scripts/lib/preflight-runner.js +2 -2
  137. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  138. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
  139. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  140. package/.agents/scripts/lib/story-adjacency.js +11 -14
  141. package/.agents/scripts/lib/story-body/story-body.js +124 -70
  142. package/.agents/scripts/lib/story-plan.js +2 -4
  143. package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
  144. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  145. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
  146. package/.agents/scripts/lib/validation-evidence.js +1 -1
  147. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
  148. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  149. package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
  150. package/.agents/scripts/lint-issue-body.js +71 -40
  151. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  152. package/.agents/scripts/notify.js +4 -3
  153. package/.agents/scripts/plan-context.js +64 -74
  154. package/.agents/scripts/plan-persist.js +121 -280
  155. package/.agents/scripts/plan-run-epilogue.js +97 -0
  156. package/.agents/scripts/providers/github/issues.js +17 -33
  157. package/.agents/scripts/providers/github/mappers.js +0 -12
  158. package/.agents/scripts/providers/github/tickets.js +2 -5
  159. package/.agents/scripts/resolve-plan-run.js +117 -0
  160. package/.agents/scripts/signals-view.js +24 -19
  161. package/.agents/scripts/single-story-close.js +11 -14
  162. package/.agents/scripts/single-story-confirm-merge.js +39 -23
  163. package/.agents/scripts/single-story-init.js +29 -20
  164. package/.agents/scripts/stories-wave-tick.js +6 -6
  165. package/.agents/scripts/story-plan.js +26 -47
  166. package/.agents/scripts/update-ticket-state.js +6 -15
  167. package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
  168. package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
  169. package/.agents/skills/core/scope-triage/SKILL.md +28 -172
  170. package/.agents/skills/skills.index.json +3 -43
  171. package/.agents/starter-agentrc.json +0 -5
  172. package/.agents/templates/agent-protocol.md +9 -10
  173. package/.agents/workflows/audit-architecture.md +3 -3
  174. package/.agents/workflows/audit-clean-code.md +3 -3
  175. package/.agents/workflows/audit-dependencies.md +3 -3
  176. package/.agents/workflows/audit-devops.md +3 -3
  177. package/.agents/workflows/audit-documentation.md +5 -5
  178. package/.agents/workflows/audit-lighthouse.md +3 -3
  179. package/.agents/workflows/audit-navigability.md +3 -2
  180. package/.agents/workflows/audit-performance.md +3 -3
  181. package/.agents/workflows/audit-privacy.md +3 -3
  182. package/.agents/workflows/audit-quality.md +3 -3
  183. package/.agents/workflows/audit-security.md +3 -3
  184. package/.agents/workflows/audit-seo.md +3 -3
  185. package/.agents/workflows/audit-sre.md +3 -3
  186. package/.agents/workflows/audit-to-stories.md +20 -20
  187. package/.agents/workflows/audit-ux-ui.md +3 -3
  188. package/.agents/workflows/deliver.md +122 -174
  189. package/.agents/workflows/git-cleanup.md +3 -4
  190. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  191. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
  192. package/.agents/workflows/helpers/code-review.md +70 -193
  193. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
  194. package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
  195. package/.agents/workflows/helpers/diagnose.md +10 -10
  196. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  197. package/.agents/workflows/helpers/signals.md +16 -16
  198. package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
  199. package/.agents/workflows/mandrel-update.md +2 -1
  200. package/.agents/workflows/plan.md +112 -145
  201. package/.agents/workflows/qa-assist.md +24 -30
  202. package/.agents/workflows/qa-explore.md +29 -38
  203. package/.agents/workflows/qa-run.md +2 -2
  204. package/README.md +9 -8
  205. package/docs/CHANGELOG.md +30 -0
  206. package/lib/migrations/index.js +6 -5
  207. package/package.json +2 -2
  208. package/.agents/agents/retro.md +0 -42
  209. package/.agents/personas/architect.md +0 -113
  210. package/.agents/personas/devops-engineer.md +0 -38
  211. package/.agents/personas/engineer.md +0 -33
  212. package/.agents/personas/project-manager.md +0 -114
  213. package/.agents/personas/qa-engineer.md +0 -95
  214. package/.agents/personas/security-engineer.md +0 -111
  215. package/.agents/personas/technical-writer.md +0 -101
  216. package/.agents/schemas/dispatch-manifest.json +0 -232
  217. package/.agents/schemas/epic-spec.schema.json +0 -153
  218. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  219. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  220. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  221. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  222. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  223. package/.agents/scripts/dispatcher.js +0 -295
  224. package/.agents/scripts/epic-audit-prepare.js +0 -497
  225. package/.agents/scripts/epic-audit-recheck.js +0 -274
  226. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  227. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  228. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  229. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  230. package/.agents/scripts/epic-plan-clarity.js +0 -211
  231. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  232. package/.agents/scripts/epic-reconcile.js +0 -625
  233. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  234. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  235. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  236. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  237. package/.agents/scripts/lib/config/preflight.js +0 -58
  238. package/.agents/scripts/lib/config/retro.js +0 -77
  239. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  240. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  241. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  242. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  243. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  244. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  245. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  246. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  247. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  248. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  249. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  250. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  251. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  252. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  253. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  254. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  255. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  256. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  257. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  258. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  259. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  260. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  261. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  262. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  263. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  264. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  265. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  266. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  267. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  268. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  269. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  270. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  271. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  272. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  273. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  274. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  275. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  276. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  277. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  278. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  279. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  280. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  281. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  282. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  283. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  284. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  285. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  286. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  287. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  288. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  289. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  290. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  291. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  292. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  293. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  295. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  296. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  297. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  298. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  299. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  300. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  301. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  302. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  303. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  304. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  305. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  306. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  307. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  308. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  309. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  310. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  311. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  312. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  313. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  314. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  315. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  316. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  317. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  318. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  319. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  320. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  321. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  322. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  323. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  324. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  325. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  326. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  327. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  328. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  329. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  330. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  331. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  332. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  333. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  334. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  335. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  336. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  337. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  338. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  339. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  340. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  341. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  342. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  343. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  344. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  345. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  346. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  347. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  348. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  349. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  350. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  351. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  352. package/.agents/scripts/lib/spec/index.js +0 -36
  353. package/.agents/scripts/lib/spec/loader.js +0 -425
  354. package/.agents/scripts/lib/spec/state.js +0 -208
  355. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  356. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  357. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  358. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  359. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  360. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  361. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  362. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  363. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  364. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  365. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  366. package/.agents/scripts/lifecycle-emit.js +0 -510
  367. package/.agents/scripts/retro-run.js +0 -218
  368. package/.agents/scripts/slice-phase.js +0 -361
  369. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  370. package/.agents/scripts/story-close.js +0 -294
  371. package/.agents/scripts/story-init.js +0 -599
  372. package/.agents/scripts/story-phase.js +0 -369
  373. package/.agents/scripts/wave-tick.js +0 -464
  374. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  375. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  376. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  377. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  378. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  379. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  380. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  381. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  382. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  383. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  384. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  385. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  386. package/.agents/workflows/helpers/epic-audit.md +0 -189
  387. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  388. package/.agents/workflows/helpers/epic-testing.md +0 -125
  389. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  390. package/.agents/workflows/helpers/plan-epic.md +0 -353
  391. package/.agents/workflows/helpers/plan-story.md +0 -251
  392. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  393. /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
@@ -1,264 +0,0 @@
1
- // .agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js
2
- /**
3
- * BranchCleaner — lifecycle listener that owns post-merge **branch** reap
4
- * for `/deliver`. Story #2398 (companion to Cleaner, which owns the
5
- * temp-tree archival half of end-of-Epic cleanup).
6
- *
7
- * Subscribes to:
8
- * - `epic.cleanup.start` → and ONLY this event.
9
- *
10
- * Why `epic.cleanup.start` and not `epic.merge.armed`:
11
- * Cleaner subscribes to `epic.merge.armed` and is the source of the
12
- * `epic.cleanup.start → epic.cleanup.end → epic.complete` terminal
13
- * sequence. The Bus runs listeners awaited and in registration order,
14
- * so a BranchCleaner registered on `epic.cleanup.start` BEFORE Cleaner
15
- * registers on `epic.merge.armed` will run synchronously inside
16
- * Cleaner's emit chain, between the start and end events, and crucially
17
- * BEFORE Cleaner's archive rename moves `temp/epic-<id>/` to
18
- * `temp/archive/epic-<id>-<ts>/`. The state checkpoint that we read to
19
- * enumerate Story branches lives on the Epic Issue (as the
20
- * `epic-run-state` structured comment), not under `temp/`, so the
21
- * ordering above is for symmetry with the existing Cleaner contract
22
- * — both halves of cleanup observe the same temp tree as it exists at
23
- * `epic.cleanup.start`.
24
- *
25
- * Side effects executed inside `handle()`:
26
- * 1. Read the `epic-run-state` checkpoint via the injected
27
- * `checkpointer`. Skip with classification `no-state` when the
28
- * checkpoint is missing or carries no Stories (a brand-new run
29
- * that armed without ever finalising a wave should never reach
30
- * `epic.cleanup.start`, but the guard keeps this listener
31
- * idempotent under replay).
32
- * 2. Call `reapEpicBranches()` from `lib/orchestration/epic-cleanup.js`,
33
- * which:
34
- * - switches the main checkout off `epic/<id>` to the baseBranch
35
- * when needed (so `git branch -D epic/<id>` isn't refused);
36
- * - removes attached worktrees with the Windows file-lock fallback
37
- * (worktree-remove → worktree-remove --force → fs.rm + prune);
38
- * - deletes every `story-<id>` branch listed in the checkpoint plus
39
- * the `epic/<id>` branch itself;
40
- * - runs `git remote prune` to drop stale `<remote>/...` tracking
41
- * refs left behind by `gh pr merge --delete-branch`;
42
- * - deletes the `wt-branch` scratch ref left by `story-close.js`'s
43
- * internal merge worktree when it is no longer checked out;
44
- * - fast-forwards the base branch to `<remote>/<baseBranch>` after the
45
- * confirmed merge so the local checkout converges to origin with no
46
- * manual `git pull` (skipped when the epic branch is kept for an
47
- * open PR).
48
- * 3. Record one classification entry per invocation (`reaped`,
49
- * `no-state`, `failed`, or `skipped-duplicate`) so failures surface
50
- * in the lifecycle ledger alongside Cleaner's archival outcome.
51
- *
52
- * Idempotency contract: per-instance `Set<string>` of `${event}:${seqId}`
53
- * keys — repeat `(event, seqId)` invocation short-circuits and classifies
54
- * `skipped-duplicate`. Mirrors Cleaner's bus-level replay defence.
55
- *
56
- * Side-effect firewall: the listener mutates **local git state** (branch
57
- * refs, worktree paths, remote tracking refs) and nothing else. It does
58
- * NOT touch the temp tree, the ticketing provider (the checkpointer
59
- * `read()` is the only ticketing call and is read-only), or label state.
60
- * Label transitions stay with LabelTransitioner on `epic.complete`.
61
- *
62
- * Production failure mode: `reapEpicBranches()` aggregates per-branch
63
- * failures into the result envelope rather than throwing — a single
64
- * branch that won't reap (e.g. because the operator stashed unmerged
65
- * work onto it) must not block the rest of cleanup. The listener
66
- * therefore records `failed` as a classification with the failure
67
- * summary; orchestration callers that want hard-stop semantics can
68
- * inspect the classifications. Programmer errors in the listener's own
69
- * inputs (missing bus / non-numeric epicId / missing checkpointer)
70
- * throw at construction time per
71
- * `.agents/rules/orchestration-error-handling.md`.
72
- */
73
-
74
- import { rmSync as defaultRmSync } from 'node:fs';
75
-
76
- import { gitSpawn as defaultGitSpawn } from '../../../git-utils.js';
77
- import { reapEpicBranches } from '../../epic-cleanup.js';
78
-
79
- /**
80
- * BranchCleaner listener.
81
- */
82
- export class BranchCleaner {
83
- /**
84
- * @param {object} opts
85
- * @param {object} opts.bus
86
- * @param {number} opts.epicId
87
- * @param {{ read: () => Promise<object|null> }} opts.checkpointer
88
- * Object exposing `read()` returning the `epic-run-state` checkpoint
89
- * shape (epicId + waves[].stories[].id). Production passes the
90
- * stateless `epic-run-state-store` module bound to `{provider, epicId}`;
91
- * tests pass a fake.
92
- * @param {string} opts.cwd - Absolute path to the **main** checkout
93
- * (not a worktree). `reapEpicBranches()` runs `git checkout`,
94
- * `git branch -D`, and `git remote prune` against this directory.
95
- * @param {string} [opts.baseBranch='main'] - Branch to switch the main
96
- * checkout to before reaping `epic/<id>`.
97
- * @param {string} [opts.remote='origin'] - Remote to prune.
98
- * @param {Function} [opts.gitSpawn] - Injectable git spawner for tests.
99
- * @param {Function} [opts.rmSyncFn] - Injectable filesystem remove for
100
- * the worktree Windows-lock fallback.
101
- * @param {Function} [opts.spawnFn] - Injectable process spawner forwarded
102
- * to `reapEpicBranches` for the Story #3367 open-PR guard probe
103
- * (`gh pr list --head epic/<id> --state open`). Defaults to the real
104
- * `spawnSync` inside `epic-cleanup.js`; tests inject a stub.
105
- * @param {{ info?: Function, warn?: Function, debug?: Function }} [opts.logger]
106
- */
107
- constructor(opts = {}) {
108
- if (
109
- !opts.bus ||
110
- typeof opts.bus.on !== 'function' ||
111
- typeof opts.bus.emit !== 'function'
112
- ) {
113
- throw new TypeError('BranchCleaner requires a bus with on() and emit()');
114
- }
115
- if (!Number.isInteger(opts.epicId) || opts.epicId < 1) {
116
- throw new TypeError('BranchCleaner requires a numeric epicId');
117
- }
118
- if (!opts.checkpointer || typeof opts.checkpointer.read !== 'function') {
119
- throw new TypeError(
120
- 'BranchCleaner requires a checkpointer exposing read()',
121
- );
122
- }
123
- if (typeof opts.cwd !== 'string' || opts.cwd.length === 0) {
124
- throw new TypeError('BranchCleaner requires a non-empty cwd string');
125
- }
126
- this.bus = opts.bus;
127
- this.epicId = opts.epicId;
128
- this.checkpointer = opts.checkpointer;
129
- this.cwd = opts.cwd;
130
- this.baseBranch = opts.baseBranch ?? 'main';
131
- this.remote = opts.remote ?? 'origin';
132
- this.gitSpawn = opts.gitSpawn ?? defaultGitSpawn;
133
- this.rmSyncFn = opts.rmSyncFn ?? defaultRmSync;
134
- // Story #3367 — forwarded to reapEpicBranches' open-PR guard probe.
135
- // `undefined` lets epic-cleanup.js fall back to its real `spawnSync`.
136
- this.spawnFn = opts.spawnFn;
137
- this.logger = opts.logger ?? console;
138
- /** @type {Set<string>} `${event}:${seqId}` idempotency cache. */
139
- this._seen = new Set();
140
- /**
141
- * Classification log — every `epic.cleanup.start` observed lands
142
- * here with the outcome (`reaped`, `no-state`, `failed`,
143
- * `skipped-duplicate`). Mirrors Cleaner's "no silent skip" surface.
144
- */
145
- this.classifications = [];
146
- // Frozen tuple — BranchCleaner subscribes to EXACTLY one event.
147
- this.events = Object.freeze(['epic.cleanup.start']);
148
- }
149
-
150
- register() {
151
- return this.events.map((event) =>
152
- this.bus.on(event, async (ctx) => this.handle(ctx)),
153
- );
154
- }
155
-
156
- async handle({ event, seqId }) {
157
- const key = `${event}:${seqId}`;
158
- if (this._seen.has(key)) {
159
- this.classifications.push({
160
- event,
161
- seqId,
162
- outcome: 'skipped-duplicate',
163
- });
164
- this.logger.debug?.(`[BranchCleaner] skip duplicate ${key} (idempotent)`);
165
- return;
166
- }
167
- this._seen.add(key);
168
-
169
- let state;
170
- try {
171
- state = await this.checkpointer.read();
172
- } catch (err) {
173
- this.classifications.push({
174
- event,
175
- seqId,
176
- outcome: 'failed',
177
- reason: `checkpoint-read-failed:${err?.message ?? err}`,
178
- });
179
- this.logger.warn?.(
180
- `[BranchCleaner] checkpoint read failed: ${err?.message ?? err}`,
181
- );
182
- return;
183
- }
184
-
185
- if (!state || !Number.isInteger(state.epicId) || state.epicId < 1) {
186
- this.classifications.push({
187
- event,
188
- seqId,
189
- outcome: 'no-state',
190
- reason: 'checkpoint-absent-or-empty',
191
- });
192
- this.logger.info?.(
193
- '[BranchCleaner] no epic-run-state checkpoint; nothing to reap.',
194
- );
195
- return;
196
- }
197
-
198
- const result = reapEpicBranches({
199
- state,
200
- cwd: this.cwd,
201
- gitSpawn: this.gitSpawn,
202
- rmSyncFn: this.rmSyncFn,
203
- baseBranch: this.baseBranch,
204
- remote: this.remote,
205
- spawnFn: this.spawnFn,
206
- logger: this.logger,
207
- });
208
-
209
- const summary = summarizeReap(result);
210
- if (result.ok) {
211
- this.classifications.push({
212
- event,
213
- seqId,
214
- outcome: 'reaped',
215
- epicId: result.epicId,
216
- ...summary,
217
- });
218
- this.logger.info?.(
219
- `[BranchCleaner] reaped ${summary.branchesDeleted} branch(es) + ${summary.worktreesRemoved} worktree(s) for epic/${result.epicId}`,
220
- );
221
- } else {
222
- this.classifications.push({
223
- event,
224
- seqId,
225
- outcome: 'failed',
226
- reason: 'reap-failures',
227
- epicId: result.epicId,
228
- ...summary,
229
- failures: result.failures.map((f) => ({
230
- branch: f.branch,
231
- stderr: f.stderr ?? null,
232
- })),
233
- });
234
- this.logger.warn?.(
235
- `[BranchCleaner] ${result.failures.length} branch(es) failed to reap for epic/${result.epicId}`,
236
- );
237
- }
238
- }
239
-
240
- reset() {
241
- this._seen.clear();
242
- this.classifications = [];
243
- }
244
- }
245
-
246
- /**
247
- * Pure: condense a `reapEpicBranches()` result into the counts that the
248
- * classification log carries. Exported for tests.
249
- *
250
- * @param {{ reaped: Array<object>, pruned: { pruned: string[] }|null, wtBranch: { deleted: boolean }|null, fastForward: { applied: boolean }|null }} result
251
- * @returns {{ branchesDeleted: number, worktreesRemoved: number, tracksPruned: number, wtBranchDeleted: boolean, fastForwarded: boolean }}
252
- */
253
- export function summarizeReap(result) {
254
- const reaped = Array.isArray(result?.reaped) ? result.reaped : [];
255
- return {
256
- branchesDeleted: reaped.filter((r) => r.branchDeleted).length,
257
- worktreesRemoved: reaped.filter(
258
- (r) => r.worktreeReaped && r.method !== 'no-worktree',
259
- ).length,
260
- tracksPruned: result?.pruned?.pruned?.length ?? 0,
261
- wtBranchDeleted: result?.wtBranch?.deleted === true,
262
- fastForwarded: result?.fastForward?.applied === true,
263
- };
264
- }
@@ -1,284 +0,0 @@
1
- // .agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js
2
- /**
3
- * CheckpointPointerWriter — lifecycle listener that maintains a
4
- * minimal pointer file at `temp/epic-<id>/checkpoint.json` after every
5
- * `*.end` lifecycle event (Story #2266 / Task #2268, Epic #2172).
6
- *
7
- * The writer replaces the legacy `epic-runner/checkpointer.js`
8
- * structured-comment checkpointer with a ledger-driven pointer: the
9
- * NDJSON ledger (`lifecycle.ndjson`) is the canonical run history, and
10
- * the pointer file is a tiny `{ lastCompletedSeqId, phase }` snapshot
11
- * the runner reads at resume time to seek the right offset into the
12
- * ledger.
13
- *
14
- * Subscription set: **every event whose name ends in `.end`**. The
15
- * frozen `EVENTS` tuple is computed from the lifecycle schema directory
16
- * via the helper export so the registration cannot drift from the
17
- * taxonomy without updating the schema set.
18
- *
19
- * Self-emit contract: after the pointer write succeeds the listener
20
- * emits `checkpoint.written` exactly once per observed `*.end`. The
21
- * self-emit is guarded by the per-instance `(event, seqId)` idempotency
22
- * set so the bus's resume window (re-emitting an event whose `emitted`
23
- * line landed but whose `completed` did not) cannot produce a duplicate
24
- * `checkpoint.written`. The seqId set persists for the lifetime of the
25
- * listener; cross-process restart relies on the on-disk pointer's
26
- * monotonicity (the second invocation observes `lastCompletedSeqId >=
27
- * payload.seqId` and short-circuits).
28
- *
29
- * Side-effect firewall: filesystem write + exactly one `bus.emit`. No
30
- * provider calls, no GitHub IO, no runner-state mutation.
31
- *
32
- * Registration order: the runner factory registers this listener
33
- * EARLY — before downstream listeners that may fail — so the pointer
34
- * file always advances even if a later listener throws on the SAME
35
- * `*.end`. The throw still flows through the bus's `onFailed` hook
36
- * (LedgerWriter records a `failed` line) but the pointer reflects the
37
- * last `*.end` that reached this listener, which is what the resume
38
- * coordinator needs to skip past completed events on the next start.
39
- */
40
-
41
- import { mkdirSync, writeFileSync } from 'node:fs';
42
- import path from 'node:path';
43
-
44
- /**
45
- * The pointer file name under `temp/epic-<id>/`. Stable and grep-able
46
- * so future tooling can locate the file without re-deriving the path.
47
- */
48
- export const POINTER_FILENAME = 'checkpoint.json';
49
-
50
- /**
51
- * The self-emitted lifecycle event name. Matches the schema id at
52
- * `.agents/schemas/lifecycle/checkpoint.written.schema.json`.
53
- */
54
- export const CHECKPOINT_WRITTEN_EVENT = 'checkpoint.written';
55
-
56
- /**
57
- * The frozen list of `*.end` events the writer subscribes to. Sourced
58
- * from the lifecycle taxonomy (`.agents/schemas/lifecycle/*.end.schema.json`).
59
- *
60
- * Listed explicitly rather than discovered at runtime so:
61
- * (a) the registration set is a code-visible contract — adding a new
62
- * `*.end` event to the schema set forces a code change here,
63
- * which is the seam reviewers grep for;
64
- * (b) construction stays synchronous (no readdir + parse) and the
65
- * constructor remains safe to call inside the runner factory.
66
- *
67
- * If a new `*.end` event is added to the schema directory, append it
68
- * here. The `checkpoint.written` event itself is intentionally NOT in
69
- * this list — the writer's own self-emit must not retrigger the writer
70
- * (that would loop), and `checkpoint.written` is not an `*.end` event
71
- * by naming convention.
72
- */
73
- export const SUBSCRIBED_END_EVENTS = Object.freeze([
74
- 'close-validate.end',
75
- 'code-review.end',
76
- 'epic.automerge.end',
77
- 'epic.cleanup.end',
78
- 'epic.close.end',
79
- 'epic.finalize.end',
80
- 'epic.plan.end',
81
- 'epic.snapshot.end',
82
- 'epic.watch.end',
83
- 'retro.end',
84
- // Epic #4475 (M4-A) — single-delivery slice boundary. A completed slice is
85
- // a resume checkpoint for the one long guarded session (the analogue of
86
- // story.dispatch.end for fan-out). Subscribed now so the pointer updates
87
- // once the M4-B executor emits slice.end through the bus; inert until then
88
- // (nothing emits it in M4-A).
89
- 'slice.end',
90
- 'story.dispatch.end',
91
- ]);
92
-
93
- /**
94
- * Resolve the pointer-file path for a given Epic. Exported so tests
95
- * can target the same file without re-deriving the layout.
96
- */
97
- export function resolvePointerPath({ tempRoot, epicId }) {
98
- if (typeof tempRoot !== 'string' || tempRoot.length === 0) {
99
- throw new TypeError(
100
- 'resolvePointerPath: tempRoot must be a non-empty string',
101
- );
102
- }
103
- if (!Number.isInteger(epicId) || epicId < 1) {
104
- throw new TypeError(
105
- 'resolvePointerPath: epicId must be a positive integer',
106
- );
107
- }
108
- return path.join(tempRoot, `epic-${epicId}`, POINTER_FILENAME);
109
- }
110
-
111
- /**
112
- * Build the pointer payload. Pulled out so tests can assert the exact
113
- * on-disk shape without parsing JSON every time.
114
- */
115
- export function buildPointerPayload({ lastCompletedSeqId, phase }) {
116
- return {
117
- lastCompletedSeqId,
118
- phase,
119
- };
120
- }
121
-
122
- /**
123
- * CheckpointPointerWriter listener.
124
- */
125
- export class CheckpointPointerWriter {
126
- /**
127
- * @param {object} opts
128
- * @param {object} opts.bus Lifecycle bus exposing `on()` + `emit()`.
129
- * @param {number} opts.epicId Epic ticket id.
130
- * @param {string} opts.tempRoot Absolute or repo-relative path; the
131
- * listener resolves `<tempRoot>/epic-<id>/checkpoint.json`.
132
- * @param {typeof writeFileSync} [opts.writeFn] Injectable writer for
133
- * tests.
134
- * @param {typeof mkdirSync} [opts.mkdirFn] Injectable directory
135
- * creator for tests.
136
- * @param {{ info?: Function, warn?: Function, debug?: Function }} [opts.logger]
137
- */
138
- constructor(opts = {}) {
139
- if (
140
- !opts.bus ||
141
- typeof opts.bus.on !== 'function' ||
142
- typeof opts.bus.emit !== 'function'
143
- ) {
144
- throw new TypeError(
145
- 'CheckpointPointerWriter requires a bus with on() and emit()',
146
- );
147
- }
148
- if (!Number.isInteger(opts.epicId) || opts.epicId < 1) {
149
- throw new TypeError('CheckpointPointerWriter requires a numeric epicId');
150
- }
151
- if (typeof opts.tempRoot !== 'string' || opts.tempRoot.length === 0) {
152
- throw new TypeError(
153
- 'CheckpointPointerWriter requires a non-empty tempRoot string',
154
- );
155
- }
156
- this.bus = opts.bus;
157
- this.epicId = opts.epicId;
158
- this.tempRoot = opts.tempRoot;
159
- this._writeFn = opts.writeFn ?? writeFileSync;
160
- this._mkdirFn = opts.mkdirFn ?? mkdirSync;
161
- this.logger = opts.logger ?? console;
162
- this._pointerPath = resolvePointerPath({
163
- tempRoot: this.tempRoot,
164
- epicId: this.epicId,
165
- });
166
- this._epicDir = path.dirname(this._pointerPath);
167
- /** @type {Set<string>} `${event}:${seqId}` idempotency cache. */
168
- this._seen = new Set();
169
- /**
170
- * Monotonic seqId guard. The pointer's `lastCompletedSeqId` MUST
171
- * be non-decreasing — observing a lower seqId after a higher one
172
- * means we are replaying an out-of-order event (resume window or
173
- * test misuse). The listener short-circuits on the regression and
174
- * logs a debug line; it does NOT throw, because a resume cycle
175
- * can legitimately replay older `emitted` lines before catching
176
- * up.
177
- */
178
- this._lastSeqId = 0;
179
- this.events = SUBSCRIBED_END_EVENTS;
180
- }
181
-
182
- /**
183
- * Resolved on-disk path for the pointer file. Exposed for tests.
184
- */
185
- get pointerPath() {
186
- return this._pointerPath;
187
- }
188
-
189
- /**
190
- * Register the listener on every subscribed `*.end` event. Returns
191
- * the array of unsubscribe callbacks the bus produced (parity with
192
- * `signals-appender.register()`).
193
- */
194
- register() {
195
- return this.events.map((event) =>
196
- this.bus.on(event, async (ctx) => this.handle(ctx)),
197
- );
198
- }
199
-
200
- /**
201
- * Bus listener body. Idempotent on `(event, seqId)`; emits
202
- * `checkpoint.written` exactly once after the pointer write
203
- * succeeds.
204
- */
205
- async handle({ event, seqId, payload: _payload }) {
206
- const key = `${event}:${seqId}`;
207
- if (this._seen.has(key)) {
208
- this.logger.debug?.(
209
- `[CheckpointPointerWriter] skip duplicate ${key} (idempotent)`,
210
- );
211
- return;
212
- }
213
- this._seen.add(key);
214
-
215
- // Resume / out-of-order guard. The on-disk pointer is the
216
- // canonical "highest seqId observed so far" — when a replay
217
- // delivers a seqId lower than what we already wrote, we keep the
218
- // pointer untouched and do NOT re-emit `checkpoint.written` for
219
- // the stale event. This makes the listener safe to register
220
- // BEFORE the resume coordinator has fully consumed the prior
221
- // ledger.
222
- if (seqId <= this._lastSeqId) {
223
- this.logger.debug?.(
224
- `[CheckpointPointerWriter] skip non-advancing seqId=${seqId} (lastCompletedSeqId=${this._lastSeqId})`,
225
- );
226
- return;
227
- }
228
-
229
- // The `phase` carried in the pointer is the event name itself —
230
- // every `*.end` event is by convention a phase boundary, so the
231
- // event name is the human-readable phase label the resume
232
- // coordinator displays ("we crashed after wave.end seqId=42").
233
- // Embedding the event name keeps the pointer self-describing
234
- // without requiring an extra `payload.phase` field on every
235
- // schema.
236
- const phase = event;
237
- const pointerPayload = buildPointerPayload({
238
- lastCompletedSeqId: seqId,
239
- phase,
240
- });
241
-
242
- try {
243
- this._mkdirFn(this._epicDir, { recursive: true });
244
- // `writeFileSync` with the default flag truncates — exactly
245
- // what we want: the pointer is a single-line atomic snapshot,
246
- // not an append log. The ledger is the append log; this file
247
- // is the cursor.
248
- this._writeFn(
249
- this._pointerPath,
250
- `${JSON.stringify(pointerPayload)}\n`,
251
- 'utf8',
252
- );
253
- } catch (err) {
254
- // A failed pointer write is recoverable — the ledger still has
255
- // the full history, and the next `*.end` will retry the
256
- // pointer. Surface as a warning and bail before the self-emit
257
- // so we don't claim progress we didn't persist.
258
- this.logger.warn?.(
259
- `[CheckpointPointerWriter] pointer write failed for ${key}: ${err?.message ?? err}`,
260
- );
261
- // Leave the seqId guard advanced for this `(event, seqId)` so
262
- // the in-process retry path doesn't loop on the same failure,
263
- // but DO NOT bump `_lastSeqId` — the next event may still want
264
- // to advance past `seqId`.
265
- return;
266
- }
267
- this._lastSeqId = seqId;
268
-
269
- // Self-emit AFTER the pointer write succeeds. Per the listener
270
- // README, listeners MAY emit follow-up events; the seqId guard
271
- // above ensures `checkpoint.written` fires exactly once per
272
- // observed `*.end`.
273
- await this.bus.emit(CHECKPOINT_WRITTEN_EVENT, pointerPayload);
274
- }
275
-
276
- /**
277
- * Test-only — clear the seqId cache so a single instance can
278
- * exercise resume scenarios without re-constructing the listener.
279
- */
280
- resetSeen() {
281
- this._seen.clear();
282
- this._lastSeqId = 0;
283
- }
284
- }