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,673 +0,0 @@
1
- // .agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js
2
- /**
3
- * Finalizer — lifecycle listener that owns the finalize-phase side
4
- * effects gated on a successful acceptance reconciliation. Story #2253
5
- * / Task #2254 (Epic #2172); promoted to a fully bus-owned writer in
6
- * Story #2894 / Task #2917 (Epic #2880).
7
- *
8
- * Subscribes to:
9
- * - `acceptance.reconcile.ok` → run finalize.
10
- * - `acceptance.reconcile.waived` → run finalize. Story #2893 split
11
- * the `acceptance::n-a` waiver path out of `.skipped` so the
12
- * Finalizer can route waived Epics through to PR creation while
13
- * `.skipped` (empty-spec) still terminates without a PR.
14
- * `.failed` is already routed to `epic.blocked` via the
15
- * AcceptanceReconciler and never reaches the Finalizer.
16
- *
17
- * Side effects executed inside `handle()`:
18
- * 1. Emit `epic.finalize.start`.
19
- * 2. Auto-graduate non-blocking findings in a SINGLE pass over the
20
- * unified `verification-results` comment (best-effort; never throws).
21
- * Story #4411 folded the former code-review + audit-results
22
- * structured-comment contracts into one comment; the lens-aware
23
- * audit-results graduator is the single canonical reader (its
24
- * 🟢→`suggestion` severity vocabulary matches `findings-renderer.js`).
25
- * Running a second code-review pass over the same comment would file
26
- * every non-blocking finding twice, so only the audit-results
27
- * graduation runs here.
28
- * 3. Idempotency probe — `gh pr list --head epic/<id>` returns any
29
- * existing PR URL. If one exists, short-circuit to `pr.created`
30
- * + `epic.finalize.end` carrying the existing URL.
31
- * 4. Otherwise, invoke `runFinalizeFn`. The production default
32
- * (`composeBusOwnedFinalize`) chains
33
- * a. `openOrLocatePr({ epicId, headBranch, baseBranch })`
34
- * b. `markPrReady({ pr })` when `delivery.ci.earlyPr` is on
35
- * (Story #4359) — the wave-1 draft is flipped ready-for-review;
36
- * skipped when `earlyPr` is off (the PR was opened here, never a
37
- * draft).
38
- * c. `postHandoffComment({ epicId, prNumber, prUrl, provider })`
39
- * and returns `{ prNumber, prUrl, handoff }`. (Story #4324 retired
40
- * the `closePlanningTickets` sweep with the context-ticket classes —
41
- * there are no planning tickets to close.)
42
- * 5. Emit `pr.created` then `epic.finalize.end`.
43
- *
44
- * Why the Finalizer does NOT emit `epic.merge.ready` (Story #3367):
45
- * `epic.merge.ready` is the SOLE trigger AutomergeArmer subscribes to,
46
- * and AutomergeArmer's `epic.merge.armed` cascades synchronously
47
- * through MergeWatcher → Cleaner → BranchCleaner (branch reap). If the
48
- * Finalizer emitted `epic.merge.ready` directly, firing `epic.close.end`
49
- * would run that entire destructive cascade in one synchronous pass —
50
- * arming auto-merge and reaping the `epic/<id>` branch BEFORE the PR is
51
- * merged, and BYPASSING the AutomergePredicate disqualification gate
52
- * (which only ever fires on `epic.watch.end`). The Finalizer's job ends
53
- * at opening the PR (`pr.created` + `epic.finalize.end`). The auto-merge
54
- * arm flows ONLY through the gated path: `pr.created` → `Watcher` →
55
- * `epic.watch.end` → `AutomergePredicate` → `epic.merge.ready` →
56
- * `AutomergeArmer`. AutomergePredicate is the SOLE emitter of
57
- * `epic.merge.ready` (merge-gate-ordering invariant).
58
- *
59
- * Idempotency contract (AC-10): two-layer defence.
60
- * 1. Per-instance `Set<string>` of `${event}:${seqId}` keys — bus-
61
- * level replays short-circuit.
62
- * 2. The `gh pr list --head` probe + `openOrLocatePr`'s internal
63
- * locate path — both defend against cross-process re-runs
64
- * (`/deliver` restarted on the same branch after a crash).
65
- *
66
- * Side-effect firewall: the listener emits on the bus, shells out to
67
- * `gh`/`git` (via the helpers), and upserts the `epic-handoff`
68
- * structured comment via `postHandoffComment`. It does NOT mutate Epic
69
- * state labels or call `notify` directly — those listeners receive
70
- * `pr.created` / `epic.finalize.end` and own their own side effects.
71
- */
72
-
73
- import { spawnSync } from 'node:child_process';
74
- import { getCiDelivery } from '../../../config/ci.js';
75
- import {
76
- graduateAuditResults as defaultGraduateAuditResults,
77
- isAutoFileEnabled as isAuditResultsAutoFileEnabled,
78
- } from '../../../feedback-loop/audit-results-graduator.js';
79
- import { parsePrNumberFromUrl } from '../../../github-url.js';
80
- import {
81
- markPrReady as defaultMarkPrReady,
82
- openOrLocatePr as defaultOpenOrLocatePr,
83
- } from '../../finalize/open-or-locate-pr.js';
84
- import { postHandoffComment as defaultPostHandoffComment } from '../../finalize/post-handoff-comment.js';
85
- import { probeRemoteBranch as defaultProbeRemoteBranch } from '../../remote-verifier.js';
86
-
87
- /**
88
- * Build the production default `runFinalizeFn` that composes the
89
- * bus-owned finalize helpers. Exported as a factory so the listener can
90
- * inject the provider (which the default needs but tests inject by
91
- * supplying their own `runFinalizeFn` outright).
92
- *
93
- * Returns `{ prNumber, prUrl, handoff }` on success, or
94
- * `{ blocker: { reason, detail } }` when a step fails with an
95
- * unrecoverable error that should keep the Epic at `agent::blocked`.
96
- *
97
- * Story #4359 (Epic #4355) — early-PR draft mode. When `earlyPr` is on
98
- * (the default resolved via `getCiDelivery`), the Epic PR already exists
99
- * as a draft (opened at wave 1). Finalize then **locates** it (the
100
- * `openOrLocatePr` probe short-circuits to `created: false`) and flips it
101
- * ready-for-review via `markPrReady` rather than opening the draft as a
102
- * `--draft`. When `earlyPr` is off, finalize opens the PR here with no
103
- * draft — the pre-Story close-time timing. In both modes the title/body
104
- * contract is identical and `markPrReady` is a no-op on an already-ready
105
- * PR, so replay stays idempotent.
106
- *
107
- * Issue #4483 — deterministic land-or-block backstop. Before opening (or
108
- * readying) the PR, finalize asserts the delivery branch `epic/<id>`
109
- * actually exists on origin. A delivery that was never pushed — e.g. an
110
- * agent that built the Epic inline on local `main` and skipped the
111
- * orchestration — MUST surface as an explicit
112
- * `delivery-branch-missing-on-origin` blocker (which keeps the Epic at
113
- * `agent::blocked`), never a declared success. The probe is bounded
114
- * (timeout + SIGKILL) so a hung remote degrades to a blocker too.
115
- *
116
- * @param {{
117
- * provider?: object|null,
118
- * earlyPr?: boolean,
119
- * openOrLocatePrFn?: typeof defaultOpenOrLocatePr,
120
- * markPrReadyFn?: typeof defaultMarkPrReady,
121
- * postHandoffCommentFn?: typeof defaultPostHandoffComment,
122
- * probeRemoteBranchFn?: typeof defaultProbeRemoteBranch,
123
- * }} deps
124
- */
125
- export function composeBusOwnedFinalize(deps = {}) {
126
- const openOrLocatePrFn = deps.openOrLocatePrFn ?? defaultOpenOrLocatePr;
127
- const markPrReadyFn = deps.markPrReadyFn ?? defaultMarkPrReady;
128
- const postHandoffCommentFn =
129
- deps.postHandoffCommentFn ?? defaultPostHandoffComment;
130
- const probeRemoteBranchFn =
131
- deps.probeRemoteBranchFn ?? defaultProbeRemoteBranch;
132
- const provider = deps.provider ?? null;
133
- const earlyPr = deps.earlyPr !== false;
134
-
135
- return async function runBusOwnedFinalize({ epicId, cwd } = {}) {
136
- if (!Number.isInteger(epicId) || epicId < 1) {
137
- return {
138
- blocker: {
139
- reason: 'invalid-epicId',
140
- detail: `runFinalizeFn called with invalid epicId ${epicId}`,
141
- },
142
- };
143
- }
144
-
145
- // Issue #4483 backstop — the delivery branch MUST be on origin before
146
- // finalize declares any success. A never-pushed branch is the silent
147
- // local-main failure shape; block explicitly with the probe detail.
148
- let branchProbe;
149
- try {
150
- branchProbe = probeRemoteBranchFn({ branch: `epic/${epicId}`, cwd });
151
- } catch (err) {
152
- branchProbe = { exists: false, detail: err?.message ?? String(err) };
153
- }
154
- if (!branchProbe.exists) {
155
- return {
156
- blocker: {
157
- reason: 'delivery-branch-missing-on-origin',
158
- detail: `epic/${epicId} is not on origin — the delivery was never pushed; refusing to finalize (issue #4483). Probe: ${branchProbe.detail}`,
159
- },
160
- };
161
- }
162
-
163
- let openResult;
164
- try {
165
- openResult = await openOrLocatePrFn({
166
- epicId,
167
- headBranch: `epic/${epicId}`,
168
- baseBranch: 'main',
169
- cwd,
170
- });
171
- } catch (err) {
172
- return {
173
- blocker: {
174
- reason: 'open-or-locate-pr-failed',
175
- detail: err?.message ?? String(err),
176
- },
177
- };
178
- }
179
- if (
180
- !openResult ||
181
- !Number.isInteger(openResult.prNumber) ||
182
- typeof openResult.url !== 'string'
183
- ) {
184
- return {
185
- blocker: {
186
- reason: 'open-or-locate-pr-empty',
187
- detail: 'openOrLocatePr returned no { prNumber, url } envelope',
188
- },
189
- };
190
- }
191
-
192
- // Story #4359: when earlyPr is on, the located PR is the wave-1 draft —
193
- // flip it ready-for-review. `gh pr ready` on an already-ready PR is a
194
- // no-op, so a replay (or an earlyPr-off PR that was never a draft) is
195
- // safe. A failure here is a hard blocker: leaving the PR a draft would
196
- // silently park the merge gate.
197
- if (earlyPr) {
198
- try {
199
- await markPrReadyFn({ pr: openResult.url, cwd });
200
- } catch (err) {
201
- return {
202
- blocker: {
203
- reason: 'mark-pr-ready-failed',
204
- detail: err?.message ?? String(err),
205
- },
206
- };
207
- }
208
- }
209
-
210
- // The handoff comment requires a provider. The lifecycle-emit CLI
211
- // may construct the Finalizer without one; in that case the step
212
- // short-circuits (the run itself still succeeds — the PR is open and
213
- // the bus can arm auto-merge).
214
- let handoff = null;
215
- if (provider) {
216
- try {
217
- handoff = await postHandoffCommentFn({
218
- epicId,
219
- prNumber: openResult.prNumber,
220
- prUrl: openResult.url,
221
- provider,
222
- });
223
- } catch (err) {
224
- // Handoff is best-effort — failing to upsert the marker comment
225
- // should not roll back the PR open. Surface as a non-blocker so
226
- // the Finalizer still emits pr.created and arms downstream.
227
- handoff = {
228
- marker: 'epic-handoff',
229
- commentId: null,
230
- error: err?.message ?? String(err),
231
- };
232
- }
233
- }
234
-
235
- return {
236
- epicId,
237
- prNumber: openResult.prNumber,
238
- prUrl: openResult.url,
239
- created: openResult.created,
240
- handoff,
241
- };
242
- };
243
- }
244
-
245
- /**
246
- * Parse `gh pr list --head <branch> --json url --jq '.[0].url'` output
247
- * into a PR URL or null. Pure — exported for tests so the regex pin is
248
- * explicit and reviewable.
249
- *
250
- * Accepted forms:
251
- * - `https://github.com/owner/repo/pull/123\n` — typical happy path.
252
- * - empty / whitespace — no PR open.
253
- * - JSON array `[{"url":"…"}]` — when the caller did not pass `--jq`.
254
- */
255
- export function extractPrUrl(stdout) {
256
- const trimmed = String(stdout || '').trim();
257
- if (trimmed.length === 0) return null;
258
- if (trimmed.startsWith('[')) {
259
- try {
260
- const parsed = JSON.parse(trimmed);
261
- if (Array.isArray(parsed) && parsed.length > 0) {
262
- const url = parsed[0]?.url;
263
- if (typeof url === 'string' && /^https:\/\/github\.com\//.test(url)) {
264
- return url;
265
- }
266
- }
267
- return null;
268
- } catch {
269
- return null;
270
- }
271
- }
272
- const match = trimmed.match(/^https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
273
- return match ? match[0] : null;
274
- }
275
-
276
- /**
277
- * Default bounded timeout for the finalize idempotency probe. Story #4415
278
- * (Epic #4406) — a hung `gh pr list` spawn previously blocked finalize
279
- * forever; the probe now SIGKILLs the child at this bound so a stalled
280
- * `gh` cannot park the merge gate.
281
- */
282
- export const GH_PR_LIST_TIMEOUT_MS = 30000;
283
-
284
- /**
285
- * Default `gh` spawn used by the listener's idempotency probe.
286
- * Mirrors the `shell: false` contract `openOrLocatePr` and the other
287
- * listener helpers use so a future Windows audit doesn't have to grep
288
- * across two modules. Bounded by `timeoutMs` (default
289
- * {@link GH_PR_LIST_TIMEOUT_MS}) + `killSignal: 'SIGKILL'` so a hung `gh`
290
- * spawn cannot block finalize indefinitely (Story #4415). Exported so
291
- * tests can stub.
292
- */
293
- export function ghPrListHead({
294
- epicBranch,
295
- cwd,
296
- spawnFn = spawnSync,
297
- timeoutMs = GH_PR_LIST_TIMEOUT_MS,
298
- }) {
299
- const result = spawnFn(
300
- 'gh',
301
- ['pr', 'list', '--head', epicBranch, '--json', 'url', '--jq', '.[0].url'],
302
- {
303
- cwd,
304
- encoding: 'utf-8',
305
- shell: false,
306
- timeout: timeoutMs,
307
- killSignal: 'SIGKILL',
308
- },
309
- );
310
- return {
311
- status: result.status ?? 1,
312
- stdout: result.stdout ?? '',
313
- stderr: result.stderr ?? '',
314
- };
315
- }
316
-
317
- /**
318
- * Finalizer listener.
319
- */
320
- export class Finalizer {
321
- /**
322
- * @param {object} opts
323
- * @param {object} opts.bus
324
- * @param {number} opts.epicId
325
- * @param {string} [opts.cwd]
326
- * @param {boolean} [opts.fullScope] forwarded to `runFinalizeFn`;
327
- * defaults to false (diff-scope).
328
- * @param {Function} [opts.runFinalizeFn] override of the composed
329
- * bus-owned finalize default; tests inject their own stub.
330
- * @param {Function} [opts.ghPrListHeadFn] override of the
331
- * idempotency probe.
332
- * @param {object} [opts.provider] Ticketing provider forwarded to
333
- * the default `runFinalizeFn` (for `postHandoffComment`) and to the
334
- * graduators.
335
- * @param {object} [opts.config] Resolved agent config; forwarded to
336
- * the graduators.
337
- * @param {{owner:string,repo:string}} [opts.currentRepo]
338
- * @param {{owner:string,repo:string}} [opts.frameworkRepo]
339
- * @param {Function} [opts.graduateAuditResultsFn]
340
- * @param {{ info?: Function, warn?: Function, debug?: Function }} [opts.logger]
341
- */
342
- constructor(opts = {}) {
343
- if (
344
- !opts.bus ||
345
- typeof opts.bus.on !== 'function' ||
346
- typeof opts.bus.emit !== 'function'
347
- ) {
348
- throw new TypeError('Finalizer requires a bus with on() and emit()');
349
- }
350
- if (!Number.isInteger(opts.epicId) || opts.epicId < 1) {
351
- throw new TypeError('Finalizer requires a numeric epicId');
352
- }
353
- this.bus = opts.bus;
354
- this.epicId = opts.epicId;
355
- this.cwd = opts.cwd ?? process.cwd();
356
- this.fullScope = opts.fullScope === true;
357
- this.provider = opts.provider ?? null;
358
- // Story #4359: gate the finalize PR-open/ready branch on
359
- // `delivery.ci.earlyPr` (default true) resolved via getCiDelivery.
360
- // On → the wave-1 draft is located and marked ready; off → the PR is
361
- // opened here at close time (pre-Story timing).
362
- const earlyPr = getCiDelivery(opts.config ?? null).earlyPr;
363
- this.runFinalizeFn =
364
- opts.runFinalizeFn ??
365
- composeBusOwnedFinalize({ provider: this.provider, earlyPr });
366
- this.ghPrListHeadFn = opts.ghPrListHeadFn ?? ghPrListHead;
367
- // ultrareview bug_007: the existing-PR short-circuit must run the
368
- // handoff-comment upsert (idempotent) so crash-recovery replays
369
- // don't skip it. Stored as an instance override so tests can swap
370
- // it with a no-op stub.
371
- this.postHandoffCommentFn =
372
- opts.postHandoffCommentFn ?? defaultPostHandoffComment;
373
- this.config = opts.config ?? null;
374
- this.currentRepo = opts.currentRepo ?? null;
375
- this.frameworkRepo = opts.frameworkRepo ?? null;
376
- this.graduateAuditResultsFn =
377
- opts.graduateAuditResultsFn ?? defaultGraduateAuditResults;
378
- this.logger = opts.logger ?? console;
379
- /** @type {Set<string>} `${event}:${seqId}` idempotency cache. */
380
- this._seen = new Set();
381
- /**
382
- * Classification log — every `acceptance.reconcile.ok` or `.waived`
383
- * we observe lands here with the outcome (`opened`, `existing`,
384
- * `failed`, `skipped-duplicate`). Mirrors the BlockerHandler /
385
- * Reconciler "no silent skip" surface.
386
- */
387
- this.classifications = [];
388
- this.events = Object.freeze([
389
- 'acceptance.reconcile.ok',
390
- 'acceptance.reconcile.waived',
391
- ]);
392
- }
393
-
394
- register() {
395
- return this.events.map((event) =>
396
- this.bus.on(event, async (ctx) => this.handle(ctx)),
397
- );
398
- }
399
-
400
- async handle({ event, seqId, payload: _payload }) {
401
- const key = `${event}:${seqId}`;
402
- if (this._seen.has(key)) {
403
- this.classifications.push({
404
- event,
405
- seqId,
406
- outcome: 'skipped',
407
- reason: 'duplicate-seqId',
408
- });
409
- this.logger.debug?.(`[Finalizer] skip duplicate ${key} (idempotent)`);
410
- return;
411
- }
412
- this._seen.add(key);
413
-
414
- const epicId = this.epicId;
415
- const epicBranch = `epic/${epicId}`;
416
-
417
- // 1. Announce finalize.start.
418
- try {
419
- await this.bus.emit('epic.finalize.start', { epicId });
420
- } catch (err) {
421
- this.classifications.push({
422
- event,
423
- seqId,
424
- outcome: 'failed',
425
- reason: `start-emit-failed:${err?.message ?? err}`,
426
- });
427
- this.logger.warn?.(
428
- `[Finalizer] epic.finalize.start emit failed: ${err?.message ?? err}`,
429
- );
430
- return;
431
- }
432
-
433
- // 1b. Auto-graduate non-blocking findings in a SINGLE pass over the
434
- // unified `verification-results` comment (Story #2615; unified in
435
- // Story #4411). The lens-aware audit-results graduator is the sole
436
- // canonical reader — a second code-review pass over the same comment
437
- // would file every non-blocking finding twice.
438
- await this._runAuditResultsGraduation();
439
-
440
- // 2. Idempotency probe — does a PR already exist on the head
441
- // branch? If yes, short-circuit to a pr.created emit with the
442
- // existing URL. This is the AC-10 contract for the most-risky
443
- // non-trivial idempotency case (cross-process re-run after a
444
- // crash between `gh pr create` and `pr.created` emit). The
445
- // bus-owned default `runFinalizeFn` also re-checks via
446
- // `openOrLocatePr`; the listener-level probe is defence in
447
- // depth and keeps the existing test surface stable.
448
- const probe = this.ghPrListHeadFn({ epicBranch, cwd: this.cwd });
449
- if (probe.status === 0) {
450
- const existingUrl = extractPrUrl(probe.stdout);
451
- if (existingUrl) {
452
- this.logger.info?.(
453
- `[Finalizer] PR already open for ${epicBranch}: ${existingUrl} — short-circuiting create.`,
454
- );
455
- const prNumber = parsePrNumberFromUrl(existingUrl);
456
- // ultrareview bug_007: even on the short-circuit path we MUST
457
- // run the handoff-comment upsert. The helper is idempotent —
458
- // postHandoffComment edits an existing marker comment in place.
459
- // Without this call, a crash-recovery replay against an
460
- // already-open PR never posts the handoff comment, even though
461
- // epic.merge.ready is still emitted.
462
- try {
463
- await this.postHandoffCommentFn({
464
- epicId: this.epicId,
465
- prNumber,
466
- prUrl: existingUrl,
467
- provider: this.provider,
468
- cwd: this.cwd,
469
- });
470
- } catch (err) {
471
- this.logger.warn?.(
472
- `[Finalizer] postHandoffComment on short-circuit failed (swallowed; replay will retry): ${err?.message ?? err}`,
473
- );
474
- }
475
- await this._emitFinalize({
476
- event,
477
- seqId,
478
- prUrl: existingUrl,
479
- prNumber,
480
- epicBranch,
481
- base: this._resolveBase(),
482
- outcome: 'existing',
483
- });
484
- return;
485
- }
486
- } else {
487
- this.logger.warn?.(
488
- `[Finalizer] gh pr list probe failed (status=${probe.status}): ${probe.stderr} — proceeding with create.`,
489
- );
490
- }
491
-
492
- // 3. Run the composed bus-owned finalize (or the test-injected
493
- // `runFinalizeFn`). The default chains openOrLocatePr →
494
- // postHandoffComment in order and returns
495
- // `{ prNumber, prUrl, handoff }`.
496
- let finalize;
497
- try {
498
- finalize = await this.runFinalizeFn({
499
- epicId,
500
- cwd: this.cwd,
501
- fullScope: this.fullScope,
502
- loggerImpl: this.logger,
503
- });
504
- } catch (err) {
505
- this.classifications.push({
506
- event,
507
- seqId,
508
- outcome: 'failed',
509
- reason: `finalize-threw:${err?.message ?? err}`,
510
- });
511
- this.logger.warn?.(
512
- `[Finalizer] runFinalizeFn threw (swallowed): ${err?.message ?? err}`,
513
- );
514
- return;
515
- }
516
-
517
- if (finalize?.blocker) {
518
- this.classifications.push({
519
- event,
520
- seqId,
521
- outcome: 'failed',
522
- reason: `blocker:${finalize.blocker.reason}`,
523
- });
524
- this.logger.warn?.(
525
- `[Finalizer] finalize reported blocker (${finalize.blocker.reason}): ${finalize.blocker.detail ?? ''}`,
526
- );
527
- return;
528
- }
529
- const prUrl = finalize?.prUrl;
530
- if (typeof prUrl !== 'string' || prUrl.length === 0) {
531
- this.classifications.push({
532
- event,
533
- seqId,
534
- outcome: 'failed',
535
- reason: 'no-pr-url',
536
- });
537
- this.logger.warn?.(
538
- '[Finalizer] runFinalizeFn returned no prUrl — cannot emit pr.created.',
539
- );
540
- return;
541
- }
542
- const prNumber =
543
- typeof finalize?.prNumber === 'number'
544
- ? finalize.prNumber
545
- : parsePrNumberFromUrl(prUrl);
546
-
547
- await this._emitFinalize({
548
- event,
549
- seqId,
550
- prUrl,
551
- prNumber,
552
- epicBranch,
553
- base: this._resolveBase(),
554
- outcome: 'opened',
555
- });
556
- }
557
-
558
- /**
559
- * Invoke the audit-results graduator best-effort — the SINGLE canonical
560
- * graduation pass over the unified `verification-results` comment. Wired
561
- * into finalize so that non-blocking findings (high/medium/low/suggestion)
562
- * get auto-filed as routed follow-up issues — Story #2615 / Epic #2586,
563
- * unified into one pass in Story #4411 / Epic #4405. The lens-aware
564
- * audit-results graduator is canonical because its 🟢→`suggestion`
565
- * severity vocabulary matches `findings-renderer.js`; running a parallel
566
- * code-review pass over the same comment would double-file every finding.
567
- */
568
- async _runAuditResultsGraduation() {
569
- if (!this.provider || !this.currentRepo) {
570
- this.logger.debug?.(
571
- '[Finalizer] audit-results graduation skipped: provider or currentRepo not wired',
572
- );
573
- return;
574
- }
575
- if (!isAuditResultsAutoFileEnabled(this.config)) {
576
- this.logger.debug?.(
577
- '[Finalizer] audit-results graduation skipped: auditResultsAutoFile toggle disabled',
578
- );
579
- return;
580
- }
581
- try {
582
- const summary = await this.graduateAuditResultsFn({
583
- epicId: this.epicId,
584
- provider: this.provider,
585
- config: this.config,
586
- currentRepo: this.currentRepo,
587
- frameworkRepo: this.frameworkRepo,
588
- cwd: this.cwd,
589
- logger: this.logger,
590
- });
591
- const filed = Array.isArray(summary?.filed) ? summary.filed.length : 0;
592
- const skipped = Array.isArray(summary?.skipped)
593
- ? summary.skipped.length
594
- : 0;
595
- const errors = Array.isArray(summary?.errors) ? summary.errors.length : 0;
596
- this.logger.info?.(
597
- `[Finalizer] audit-results graduation: filed=${filed} skipped=${skipped} errors=${errors}`,
598
- );
599
- if (errors > 0) {
600
- this.logger.warn?.(
601
- `[Finalizer] audit-results graduator errors: ${summary.errors.join('; ')}`,
602
- );
603
- }
604
- } catch (err) {
605
- this.logger.warn?.(
606
- `[Finalizer] audit-results graduator threw (swallowed): ${err?.message ?? err}`,
607
- );
608
- }
609
- }
610
-
611
- /**
612
- * Emit the canonical finalize-success pair: `pr.created` →
613
- * `epic.finalize.end` in strict order. Helper carved out so the
614
- * existing-PR short-circuit and the freshly-opened path share the
615
- * same emit sequence.
616
- *
617
- * The Finalizer deliberately STOPS at `epic.finalize.end` and never
618
- * emits `epic.merge.ready` (Story #3367). Emitting `epic.merge.ready`
619
- * here would let `epic.close.end` cascade synchronously into the
620
- * auto-merge arm + branch reap, bypassing the AutomergePredicate gate.
621
- * The auto-merge arm flows ONLY through `pr.created` → Watcher →
622
- * `epic.watch.end` → AutomergePredicate → `epic.merge.ready`.
623
- *
624
- * `prNumber` stays in the classification log for trace/audit even
625
- * though it is no longer threaded onto an `epic.merge.ready` payload.
626
- */
627
- async _emitFinalize({
628
- event,
629
- seqId,
630
- prUrl,
631
- prNumber,
632
- epicBranch,
633
- base,
634
- outcome,
635
- }) {
636
- this.classifications.push({ event, seqId, outcome, prUrl, prNumber });
637
- try {
638
- await this.bus.emit('pr.created', {
639
- prUrl,
640
- head: epicBranch,
641
- base,
642
- });
643
- } catch (err) {
644
- this.logger.warn?.(
645
- `[Finalizer] pr.created emit failed (swallowed): ${err?.message ?? err}`,
646
- );
647
- }
648
- try {
649
- await this.bus.emit('epic.finalize.end', {
650
- epicId: this.epicId,
651
- prUrl,
652
- });
653
- } catch (err) {
654
- this.logger.warn?.(
655
- `[Finalizer] epic.finalize.end emit failed (swallowed): ${err?.message ?? err}`,
656
- );
657
- }
658
- }
659
-
660
- /**
661
- * Resolve the base branch for the PR. Always `main` today; pulled
662
- * into a helper so the listener owns the decision (and tests can
663
- * stub when a non-`main` base is wired in).
664
- */
665
- _resolveBase() {
666
- return 'main';
667
- }
668
-
669
- reset() {
670
- this._seen.clear();
671
- this.classifications = [];
672
- }
673
- }