mandrel 1.93.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 (463) hide show
  1. package/.agents/README.md +59 -73
  2. package/.agents/agents/acceptance-critic.md +129 -0
  3. package/.agents/agents/story-worker.md +161 -0
  4. package/.agents/docs/SDLC.md +489 -1285
  5. package/.agents/docs/agentrc-reference.json +177 -67
  6. package/.agents/docs/configuration.md +108 -136
  7. package/.agents/docs/execution-reference.md +44 -22
  8. package/.agents/docs/quality-gates.md +13 -19
  9. package/.agents/docs/workflows.md +3 -3
  10. package/.agents/instructions.md +107 -108
  11. package/.agents/rules/ci-remediation.md +8 -12
  12. package/.agents/rules/git-conventions-reference.md +224 -0
  13. package/.agents/rules/git-conventions.md +42 -223
  14. package/.agents/rules/security-baseline.md +5 -0
  15. package/.agents/rules/testing-standards.md +106 -13
  16. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  17. package/.agents/schemas/agentrc.schema.json +71 -201
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  20. package/.agents/schemas/risk-verdict.schema.json +0 -13
  21. package/.agents/scripts/acceptance-eval.js +62 -18
  22. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  23. package/.agents/scripts/analyze-execution.js +1 -1
  24. package/.agents/scripts/audit-to-stories.js +7 -7
  25. package/.agents/scripts/boot-sweep.js +1 -1
  26. package/.agents/scripts/check-context-budget.js +62 -5
  27. package/.agents/scripts/check-lifecycle-lint.js +6 -9
  28. package/.agents/scripts/check-prepush-recovery.js +1 -1
  29. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  30. package/.agents/scripts/diagnose-friction.js +0 -6
  31. package/.agents/scripts/lib/Logger.js +6 -10
  32. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  33. package/.agents/scripts/lib/audit-suite/selector.js +5 -5
  34. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  35. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  36. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
  37. package/.agents/scripts/lib/baselines/reader.js +1 -6
  38. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  39. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  40. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
  41. package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
  42. package/.agents/scripts/lib/checks/index.js +2 -1
  43. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
  44. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  45. package/.agents/scripts/lib/cli-args.js +16 -7
  46. package/.agents/scripts/lib/close-validation/gates.js +160 -22
  47. package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
  48. package/.agents/scripts/lib/config/ci.js +6 -31
  49. package/.agents/scripts/lib/config/delivery-routing.js +103 -0
  50. package/.agents/scripts/lib/config/explain.js +57 -36
  51. package/.agents/scripts/lib/config/limits.js +17 -58
  52. package/.agents/scripts/lib/config/paths.js +0 -2
  53. package/.agents/scripts/lib/config/quality.js +1 -1
  54. package/.agents/scripts/lib/config/runners.js +17 -50
  55. package/.agents/scripts/lib/config/temp-paths.js +19 -14
  56. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  57. package/.agents/scripts/lib/config-resolver.js +3 -8
  58. package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
  59. package/.agents/scripts/lib/config-settings-schema-quality.js +17 -14
  60. package/.agents/scripts/lib/config-settings-schema.js +52 -38
  61. package/.agents/scripts/lib/dependency-parser.js +3 -2
  62. package/.agents/scripts/lib/doc-tiers.js +39 -4
  63. package/.agents/scripts/lib/duplicate-search.js +211 -41
  64. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  65. package/.agents/scripts/lib/findings/promote-finding.js +5 -5
  66. package/.agents/scripts/lib/framework-version.js +2 -3
  67. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  68. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
  69. package/.agents/scripts/lib/git-utils.js +32 -6
  70. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  71. package/.agents/scripts/lib/label-constants.js +10 -23
  72. package/.agents/scripts/lib/label-taxonomy.js +9 -43
  73. package/.agents/scripts/lib/observability/active-story-env.js +112 -3
  74. package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
  75. package/.agents/scripts/lib/observability/source-classifier.js +3 -3
  76. package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
  77. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  78. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
  79. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
  80. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
  81. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
  82. package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
  83. package/.agents/scripts/lib/orchestration/code-review.js +20 -268
  84. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  85. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
  86. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  87. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  88. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  89. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  90. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
  91. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
  92. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  93. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
  94. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  95. package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
  96. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  97. package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
  98. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
  99. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
  100. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  101. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
  102. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
  103. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
  104. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
  105. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
  106. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  107. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  108. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +14 -14
  109. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  110. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
  111. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
  112. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
  113. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
  114. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  115. package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
  116. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
  118. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  119. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
  120. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  121. package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
  122. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  124. package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
  131. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  132. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  134. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  135. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  136. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
  137. package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
  138. package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
  139. package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
  140. package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
  141. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  142. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
  143. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
  144. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
  145. package/.agents/scripts/lib/orchestration/ticket-validator.js +3 -8
  146. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
  147. package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
  148. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
  149. package/.agents/scripts/lib/planning-corpus.js +16 -11
  150. package/.agents/scripts/lib/preflight-runner.js +2 -2
  151. package/.agents/scripts/lib/provider-factory.js +1 -1
  152. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  153. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
  154. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  155. package/.agents/scripts/lib/story-adjacency.js +11 -14
  156. package/.agents/scripts/lib/story-body/story-body.js +124 -70
  157. package/.agents/scripts/lib/story-plan.js +2 -4
  158. package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
  159. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  160. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
  161. package/.agents/scripts/lib/validation-evidence.js +1 -1
  162. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
  163. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  164. package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
  165. package/.agents/scripts/lint-issue-body.js +71 -40
  166. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  167. package/.agents/scripts/notify.js +4 -3
  168. package/.agents/scripts/plan-context.js +64 -74
  169. package/.agents/scripts/plan-persist.js +121 -280
  170. package/.agents/scripts/plan-run-epilogue.js +97 -0
  171. package/.agents/scripts/post-structured-comment.js +38 -0
  172. package/.agents/scripts/providers/github/issues.js +17 -33
  173. package/.agents/scripts/providers/github/mappers.js +0 -12
  174. package/.agents/scripts/providers/github/tickets.js +2 -5
  175. package/.agents/scripts/resolve-plan-run.js +117 -0
  176. package/.agents/scripts/signals-view.js +24 -19
  177. package/.agents/scripts/single-story-close.js +11 -14
  178. package/.agents/scripts/single-story-confirm-merge.js +39 -23
  179. package/.agents/scripts/single-story-init.js +29 -20
  180. package/.agents/scripts/stories-wave-tick.js +6 -6
  181. package/.agents/scripts/story-plan.js +26 -47
  182. package/.agents/scripts/sync-claude-agents.js +165 -0
  183. package/.agents/scripts/update-ticket-state.js +37 -15
  184. package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
  185. package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
  186. package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
  187. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
  188. package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
  189. package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
  190. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
  191. package/.agents/skills/core/scope-triage/SKILL.md +28 -172
  192. package/.agents/skills/skills.index.json +8 -418
  193. package/.agents/starter-agentrc.json +0 -5
  194. package/.agents/templates/agent-protocol.md +9 -10
  195. package/.agents/workflows/audit-architecture.md +3 -3
  196. package/.agents/workflows/audit-clean-code.md +3 -3
  197. package/.agents/workflows/audit-dependencies.md +3 -3
  198. package/.agents/workflows/audit-devops.md +3 -3
  199. package/.agents/workflows/audit-documentation.md +5 -5
  200. package/.agents/workflows/audit-lighthouse.md +3 -3
  201. package/.agents/workflows/audit-navigability.md +3 -2
  202. package/.agents/workflows/audit-performance.md +3 -3
  203. package/.agents/workflows/audit-privacy.md +3 -3
  204. package/.agents/workflows/audit-quality.md +3 -3
  205. package/.agents/workflows/audit-security.md +3 -3
  206. package/.agents/workflows/audit-seo.md +3 -3
  207. package/.agents/workflows/audit-sre.md +3 -3
  208. package/.agents/workflows/audit-to-stories.md +20 -20
  209. package/.agents/workflows/audit-ux-ui.md +3 -3
  210. package/.agents/workflows/deliver.md +122 -131
  211. package/.agents/workflows/git-cleanup.md +3 -4
  212. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  213. package/.agents/workflows/helpers/acceptance-self-eval.md +52 -40
  214. package/.agents/workflows/helpers/code-review.md +70 -193
  215. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
  216. package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
  217. package/.agents/workflows/helpers/diagnose.md +10 -10
  218. package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
  219. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  220. package/.agents/workflows/helpers/signals.md +16 -16
  221. package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
  222. package/.agents/workflows/mandrel-update.md +3 -2
  223. package/.agents/workflows/plan.md +112 -145
  224. package/.agents/workflows/qa-assist.md +24 -30
  225. package/.agents/workflows/qa-explore.md +29 -38
  226. package/.agents/workflows/qa-run.md +2 -2
  227. package/README.md +9 -8
  228. package/docs/CHANGELOG.md +46 -0
  229. package/lib/cli/registry.js +95 -0
  230. package/lib/migrations/index.js +6 -5
  231. package/package.json +5 -3
  232. package/.agents/personas/architect.md +0 -113
  233. package/.agents/personas/devops-engineer.md +0 -38
  234. package/.agents/personas/engineer-mobile.md +0 -120
  235. package/.agents/personas/engineer-web.md +0 -111
  236. package/.agents/personas/engineer.md +0 -119
  237. package/.agents/personas/product.md +0 -94
  238. package/.agents/personas/project-manager.md +0 -114
  239. package/.agents/personas/qa-engineer.md +0 -95
  240. package/.agents/personas/refactorer.md +0 -113
  241. package/.agents/personas/security-engineer.md +0 -112
  242. package/.agents/personas/sre.md +0 -86
  243. package/.agents/personas/technical-writer.md +0 -101
  244. package/.agents/personas/ux-designer.md +0 -95
  245. package/.agents/schemas/dispatch-manifest.json +0 -232
  246. package/.agents/schemas/epic-spec.schema.json +0 -153
  247. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  248. package/.agents/scripts/dispatcher.js +0 -295
  249. package/.agents/scripts/epic-audit-prepare.js +0 -497
  250. package/.agents/scripts/epic-audit-recheck.js +0 -274
  251. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  252. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  253. package/.agents/scripts/epic-deliver-prepare.js +0 -590
  254. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  255. package/.agents/scripts/epic-plan-clarity.js +0 -211
  256. package/.agents/scripts/epic-plan-decompose.js +0 -54
  257. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  258. package/.agents/scripts/epic-plan-spec.js +0 -64
  259. package/.agents/scripts/epic-reconcile.js +0 -625
  260. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  261. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  262. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  263. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  264. package/.agents/scripts/lib/config/preflight.js +0 -58
  265. package/.agents/scripts/lib/config/retro.js +0 -77
  266. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  267. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  268. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  269. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -660
  270. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  271. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  272. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  273. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  274. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  275. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  276. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  277. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  278. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  279. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  280. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  281. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  282. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  283. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  284. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  285. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  286. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  287. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  288. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -388
  289. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  290. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  291. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  292. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  293. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  294. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  295. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  296. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  297. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  298. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  299. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  300. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  301. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  302. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  303. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  304. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  305. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  306. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  307. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  308. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  309. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  310. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  311. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  312. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  313. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
  314. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  315. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  316. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  317. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -278
  318. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  319. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  320. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  321. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  322. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  323. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  324. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  325. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  326. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  327. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  328. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  329. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  330. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  331. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  332. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  333. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  334. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  335. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
  336. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  337. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  338. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  339. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  340. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  341. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  342. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  343. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  344. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  345. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  346. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  347. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  348. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  349. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  350. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  351. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  352. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  353. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  354. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  355. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  356. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  357. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  358. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  359. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  360. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  361. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -301
  362. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  363. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  364. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  365. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  366. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  367. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  368. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  369. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  370. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  371. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  372. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  373. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  374. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  375. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  376. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  377. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  378. package/.agents/scripts/lib/spec/index.js +0 -36
  379. package/.agents/scripts/lib/spec/loader.js +0 -425
  380. package/.agents/scripts/lib/spec/state.js +0 -208
  381. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  382. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  383. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  384. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  385. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  386. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  387. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  388. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  389. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  390. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  391. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  392. package/.agents/scripts/lifecycle-emit.js +0 -510
  393. package/.agents/scripts/plan-critics.js +0 -199
  394. package/.agents/scripts/retro-run.js +0 -218
  395. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  396. package/.agents/scripts/story-close.js +0 -294
  397. package/.agents/scripts/story-init.js +0 -599
  398. package/.agents/scripts/story-phase.js +0 -369
  399. package/.agents/scripts/wave-tick.js +0 -335
  400. package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
  401. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
  402. package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
  403. package/.agents/skills/core/code-simplification/SKILL.md +0 -389
  404. package/.agents/skills/core/context-engineering/SKILL.md +0 -309
  405. package/.agents/skills/core/context-engineering/examples.md +0 -58
  406. package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
  407. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  408. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  409. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  410. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  411. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  412. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  413. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -413
  414. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  415. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
  416. package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
  417. package/.agents/skills/core/idea-refinement/examples.md +0 -437
  418. package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
  419. package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
  420. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
  421. package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
  422. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
  423. package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
  424. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
  425. package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
  426. package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
  427. package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
  428. package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
  429. package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
  430. package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
  431. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
  432. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
  433. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
  434. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
  435. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
  436. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
  437. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
  438. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
  439. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
  440. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
  441. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
  442. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
  443. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
  444. package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
  445. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
  446. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
  447. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
  448. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
  449. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
  450. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
  451. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
  452. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
  453. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
  454. package/.agents/workflows/helpers/deliver-epic.md +0 -955
  455. package/.agents/workflows/helpers/deliver-stories.md +0 -440
  456. package/.agents/workflows/helpers/epic-audit.md +0 -189
  457. package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
  458. package/.agents/workflows/helpers/epic-testing.md +0 -125
  459. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  460. package/.agents/workflows/helpers/plan-epic.md +0 -351
  461. package/.agents/workflows/helpers/plan-story.md +0 -251
  462. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  463. /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
@@ -1,135 +1,31 @@
1
1
  /**
2
- * run-plan-persist.js — single GitHub-write surface for the /plan collapse
3
- * (Epic #4474, PR3 + PR4 modes).
2
+ * run-plan-persist.js — flat Story persist for the v2 `/plan` collapse
3
+ * (Stage 3 `docs/roadmap.md`).
4
4
  *
5
- * Implements the ordered, fail-closed superset persist that replaced the
6
- * retired 12-phase pipeline's separate persist halves
7
- * (design §1 Step 3 + §2 mode matrix, issue #4474):
5
+ * Ordered, fail-closed pipeline:
8
6
  *
9
- * 1. args (owned by the `plan-persist.js` CLI shell)
10
- * 2. section gate `validateSpecSections` runs BEFORE the lease and
11
- * BEFORE any provider call; a rejection makes zero GitHub calls.
12
- * 3. risk-verdict validation (CLI-owned `loadRiskVerdict`) +
13
- * mode-coherence hard error (`resolveDeliveryMode`): fan-out requires
14
- * tickets; `deliveryShape: "single"` refuses any tickets payload —
15
- * the single mode's validator/DAG skip is fenced by construction,
16
- * unreachable when tickets are present; `--amend` requires tickets
17
- * carrying `op` fields and refuses the single shape.
18
- * 4. ticket validator + file-assumption gate + DAG + sizing + budget
19
- * (fan-out and amend; amend validates the MERGED set — existing
20
- * keeps + adds + modifies, closes excluded; all git-local, still
21
- * zero provider calls). Skipped entirely in single mode (no tickets
22
- * exist to validate).
23
- * 4.5. deterministic draft reachability (Epic #4474 PR6, design §4 —
24
- * the 8.4 critic demoted into persist): route-glob scan of the
25
- * draft set vs `planning.navigation.navRegistry`, mirroring the
26
- * `--paranoid` F7 healthcheck mechanics. Orphan surfaces are a
27
- * NAMED SOFT FAILURE (`code: PLAN_REACHABILITY_ORPHANS`, CLI exit
28
- * 3) raised before any provider call — the author appends the
29
- * single reachability Story in one targeted amend and re-runs the
30
- * persist once. Silent no-op when `planning.navigation` is
31
- * unconfigured; skip decisions are appended to the plan-metrics
32
- * ledger (`kind: critic-skip`) for audit.
33
- * 5. ideation fold — `renderEpicBody` / `openEpicFromOnePager` create
34
- * the Epic when the run starts from a one-pager (the first provider
35
- * call of the run). Amend additionally resolves every
36
- * modify/keep/close slug to its live issue and enforces the close-op
37
- * confirmation gate (exit 2 without `--explicit-delete`) BEFORE the
38
- * lease and before any mutation.
39
- * 6. Epic lease (KEEP — documented double-create at ~80 creations).
40
- * From here the lease is released on EVERY exit path (success, gate
41
- * failure, throw) via try/finally.
42
- * 7. managed Tech Spec / Acceptance Table sections + risk-verdict
43
- * structured comment + spec-freshness advisory.
44
- * 8. mode-split mutation:
45
- * - fan-out: story creation via the structural reconciler
46
- * (idempotent per-slug creation; the reconciler's state file is
47
- * the per-slug resume ledger), bracketed by checkpoint-v2 writes
48
- * so a rate-limit crash resumes losslessly with `--resume`;
49
- * - single: NO story tree — the `delivery::single` routing marker is
50
- * applied instead (inert until #4475 lands the deliver-side
51
- * reader), and `decompose = { ticketCount: 0, shape: "single" }`
52
- * is checkpointed so delivery-time consumers never misread absence
53
- * as unplanned;
54
- * - amend: op-mapped delta — close ops close, modify ops
55
- * close-and-recreate, add ops create, keep ops are untouched by
56
- * construction; the state ledger and blocked-by edges are rebuilt
57
- * over the merged set.
58
- * 9. inline post-plan healthcheck (the `agent::ready` exit condition,
59
- * Story #2921).
60
- * 10. single terminal `agent::ready` flip — the intermediate
61
- * `agent::review-spec` flip is retired on this surface (its readers
62
- * were visibility-only; the /deliver start gate needs only
63
- * `agent::ready`).
64
- * 11. checkpoint v2 + single `plan-summary` comment carrying the dry-run
65
- * wave table as closing text (replaces the Phase 9 dispatcher
66
- * round-trip and the Phase 12 notify). The single-mode summary
67
- * records `{ deliveryShape: "single", sliceCount, routingReasons }`.
68
- * 12. temp cleanup ONLY at terminal success — a failed run leaves
69
- * techspec/acceptance/risk-verdict/tickets artifacts on disk so a
70
- * `--force`/`--resume` re-persist reuses them (fixes the
71
- * `plan-phase-cleanup.js` mid-pipeline deletion defect).
7
+ * 1. Risk-verdict presence (schema validation is CLI-owned)
8
+ * 2. Ticket validator + file-assumption + DAG + capacity + budget
9
+ * 3. Draft reachability (named soft failure, exit 3)
10
+ * 4. Split-policy partition (`assertAcceptancePartition`) + spec fold/spill
11
+ * 5. Create Story issues (`type::story` + `agent::ready`; `plan-run::`
12
+ * label when N>1)
13
+ * 6. Upsert `risk-verdict` + `story-plan-state` on every created Story;
14
+ * upsert `plan-summary` on the primary Story
15
+ * 7. Temp cleanup at terminal success only
72
16
  *
73
- * Checkpoint v2: same `epic-plan-state` structured comment, `version: 2`,
74
- * with the `planningRisk` / `riskVerdict` / `reviewRouting` / `spec` /
75
- * `decompose` fields byte-compatible with v1 so the four delivery-time
76
- * consumers — `lib/orchestration/code-review.js` (review depth),
77
- * `epic-audit-prepare.js` (audit-lens routing),
78
- * `story-close/phases/locked-pipeline.js` (parent-risk inheritance), and
79
- * the decompose context reader — read it without modification. The only
80
- * additions are the `version` bump and the additive `persist` progress
81
- * block; consumers key on field presence, never on `version`.
17
+ * Hard cutover: no Epic parent, no reconciler, no `deliveryShape`, no
18
+ * `--amend` tree cascades. Those surfaces die with Stages 4–5 for any
19
+ * remaining epic-delivery readers.
82
20
  *
83
21
  * @module lib/orchestration/plan-persist/run-plan-persist
84
22
  */
85
23
 
86
- import { spawnSync as defaultSpawnSync } from 'node:child_process';
87
- import path from 'node:path';
24
+ import { rm } from 'node:fs/promises';
88
25
 
89
- import { runPlanHealthcheck as defaultRunPlanHealthcheck } from '../../../epic-plan-healthcheck.js';
90
- import { verifyBddRunnerPendingTag } from '../../bdd-runner-detect.js';
91
26
  import { getLimits, PROJECT_ROOT } from '../../config-resolver.js';
92
- import { openEpicFromOnePager } from '../../epic-plan-ideation.js';
93
27
  import { gitSpawn } from '../../git-utils.js';
94
28
  import { Logger } from '../../Logger.js';
95
- import {
96
- AGENT_LABELS,
97
- DELIVERY_LABELS,
98
- TYPE_LABELS,
99
- } from '../../label-constants.js';
100
- import { cleanupPhaseTempFiles } from '../../plan-phase-cleanup.js';
101
- import { loadState, writeSpec, writeState } from '../../spec/index.js';
102
- import {
103
- reconcileSubIssueLinks,
104
- setBlockedByDependencies,
105
- setEpicLabel,
106
- warnTicketCapNearLimit,
107
- } from '../epic-plan-decompose/phases/creation.js';
108
- import {
109
- enforceFanOutGate,
110
- runHealthcheckGate,
111
- surfaceSoftConflictFindings,
112
- } from '../epic-plan-decompose/phases/persist.js';
113
- import {
114
- buildEpicSpecInput,
115
- validateTickets,
116
- } from '../epic-plan-decompose/phases/persist-helpers.js';
117
- import {
118
- RECONCILE_CLI,
119
- spawnReconcilerApply,
120
- } from '../epic-plan-decompose/phases/reconcile-spawn.js';
121
- import {
122
- acquireEpicPlanLease,
123
- assertNoOpenPlanChildren,
124
- releaseEpicPlanLease,
125
- } from '../epic-plan-lease-guard.js';
126
- import { planEpic } from '../epic-plan-spec/phases/plan-epic.js';
127
- import { runSpecFreshnessCheck } from '../epic-plan-spec/phases/spec-freshness.js';
128
- import {
129
- initialize as initializePlanState,
130
- read as readPlanState,
131
- write as writePlanState,
132
- } from '../epic-plan-state-store.js';
133
29
  import { evaluatePlanCritics } from '../plan-critics-evaluate.js';
134
30
  import {
135
31
  appendCriticSkip,
@@ -143,182 +39,128 @@ import {
143
39
  } from '../plan-reachability.js';
144
40
  import { resolveReviewRouting } from '../plan-review-routing.js';
145
41
  import { deriveRiskEnvelope } from '../planning-risk.js';
146
- import { renderSpec } from '../spec-renderer.js';
147
- import {
148
- formatMissingSectionMessage,
149
- validateSpecSections,
150
- } from '../spec-section-validator.js';
151
42
  import { upsertStructuredComment } from '../ticketing.js';
152
43
  import {
153
- applyAmendOps,
154
- buildMergedTicketSet,
155
- enforceAmendCloseGate,
156
- partitionAmendTickets,
157
- renderAmendPlanDiff,
158
- resolveAmendTargets,
159
- } from './amend.js';
160
- import { countDeliverySlices, resolveDeliveryMode } from './delivery-mode.js';
44
+ enforceFanOutGate,
45
+ surfaceSoftConflictFindings,
46
+ } from './fan-out-gate.js';
47
+ import { validateTickets } from './persist-helpers.js';
48
+ import { assemblePlanStories, createStoryIssues } from './story-ops.js';
161
49
  import {
162
50
  buildPlanSummaryCommentBody,
163
51
  buildWaveTable,
164
52
  PLAN_SUMMARY_COMMENT_TYPE,
165
53
  } from './summary.js';
166
54
 
167
- /** Checkpoint schema version written by this surface. */
168
- export const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
55
+ /** Checkpoint schema version written on each Story's story-plan-state. */
56
+ const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
169
57
 
170
- // Mode-coherence resolution (design §1 Step 3 item 3 + §2 mode matrix)
171
- // lives in `delivery-mode.js`; re-exported here so the CLI's stable public
172
- // API keeps a single import root for the persist surface.
173
- export { resolveDeliveryMode };
58
+ /** Structured-comment type for the per-plan Story checkpoint. */
59
+ const STORY_PLAN_STATE_TYPE = 'story-plan-state';
174
60
 
175
61
  /**
176
- * Merge-write the epic-plan-state checkpoint at schema v2. Reads the
177
- * current checkpoint (or initializes a fresh skeleton), shallow-merges
178
- * `patch`, and stamps `version: 2`. Field shapes for `planningRisk`,
179
- * `riskVerdict`, `reviewRouting`, `spec`, and `decompose` are byte-compatible
180
- * with v1 — v2 is additive only.
62
+ * Write the `story-plan-state` checkpoint on a Story.
181
63
  *
182
64
  * @param {object} provider
183
- * @param {number} epicId
184
- * @param {object} patch
185
- * @returns {Promise<object>} the written checkpoint payload
65
+ * @param {number} storyId
66
+ * @param {object} state
186
67
  */
187
- export async function writeCheckpointV2(provider, epicId, patch) {
188
- const current =
189
- (await readPlanState({ provider, epicId })) ??
190
- (await initializePlanState({ provider, epicId }));
191
- // One-level deep merge for object-valued blocks (`spec`, `decompose`,
192
- // `persist`, …) so a partial patch (e.g. `persist: { completedAt }`)
193
- // refines rather than replaces the block — same discipline the v1
194
- // writers applied by hand with `...currentState.decompose`.
195
- const merged = { ...current };
196
- for (const [key, value] of Object.entries(patch ?? {})) {
197
- const existing = merged[key];
198
- if (
199
- value !== null &&
200
- typeof value === 'object' &&
201
- !Array.isArray(value) &&
202
- existing !== null &&
203
- typeof existing === 'object' &&
204
- !Array.isArray(existing)
205
- ) {
206
- merged[key] = { ...existing, ...value };
207
- } else {
208
- merged[key] = value;
209
- }
68
+ export async function writeCheckpointV2(provider, storyId, state) {
69
+ if (!Number.isInteger(storyId)) {
70
+ throw new TypeError('writeCheckpointV2 requires a numeric storyId');
210
71
  }
211
- return writePlanState({
212
- provider,
213
- epicId,
214
- state: {
215
- ...merged,
216
- version: PLAN_CHECKPOINT_SCHEMA_VERSION_V2,
217
- },
218
- });
72
+ const body = [
73
+ '### story-plan-state',
74
+ '',
75
+ '```json',
76
+ JSON.stringify(
77
+ {
78
+ version: PLAN_CHECKPOINT_SCHEMA_VERSION_V2,
79
+ storyId,
80
+ ...state,
81
+ },
82
+ null,
83
+ 2,
84
+ ),
85
+ '```',
86
+ ].join('\n');
87
+ await upsertStructuredComment(provider, storyId, STORY_PLAN_STATE_TYPE, body);
88
+ return state;
219
89
  }
220
90
 
221
- /**
222
- * Resolve (or create) the Epic this persist run targets.
223
- *
224
- * Ideation mode (`onePagerContent` present): folds the former Phase 3/4
225
- * ideation steps in — `openEpicFromOnePager` renders the Epic body from the
226
- * one-pager via the canonical template and opens the Issue with the
227
- * `type::epic` label. This is deliberately the FIRST provider call of the
228
- * run (after every deterministic gate), so a gate rejection never leaves an
229
- * orphaned Epic behind.
230
- *
231
- * Existing-Epic mode: fetches and type-asserts the Epic.
232
- *
233
- * @returns {Promise<{ epicId: number, epic: object, created: boolean }>}
234
- */
235
- async function resolveTargetEpic({
236
- epicId,
237
- onePagerContent,
238
- templateContent,
239
- provider,
240
- }) {
241
- if (onePagerContent) {
242
- if (typeof provider.createIssue !== 'function') {
243
- throw new Error(
244
- '[plan-persist] provider does not expose createIssue; cannot open ' +
245
- 'an Epic from a one-pager.',
246
- );
247
- }
248
- const created = await openEpicFromOnePager({
249
- onePager: onePagerContent,
250
- template: templateContent,
251
- createIssue: (payload) => provider.createIssue(payload),
252
- });
253
- Logger.info(
254
- `[plan-persist] Opened Epic #${created.id} from one-pager ("${created.title}").`,
91
+ function enforceTicketValidation(validated, { config, settings, cwd }) {
92
+ const validationErrors = validated.errors ?? [];
93
+ const assumptionFailures = validationErrors.filter((error) =>
94
+ error.startsWith('File assumption mismatch:'),
95
+ );
96
+ const blockingErrors = validationErrors.filter(
97
+ (error) => !error.startsWith('File assumption mismatch:'),
98
+ );
99
+ if (blockingErrors.length > 0) {
100
+ throw new Error(
101
+ `[plan-persist] ticket validation failed with ${blockingErrors.length} ` +
102
+ `hard error(s):\n${blockingErrors.map((error) => ` - ${error}`).join('\n')}`,
255
103
  );
256
- const epic = await provider.getEpic(created.id);
257
- if (!epic) {
258
- throw new Error(
259
- `[plan-persist] Epic #${created.id} was created but could not be re-fetched.`,
260
- );
261
- }
262
- return { epicId: created.id, epic, created: true };
263
104
  }
264
-
265
- const epic = await provider.getEpic(epicId);
266
- if (!epic) {
267
- throw new Error(`[plan-persist] Epic #${epicId} not found.`);
268
- }
269
- if (!epic.labels?.includes(TYPE_LABELS.EPIC)) {
105
+ if (assumptionFailures.length === 0) return;
106
+ const gateBaseRef = config?.baseBranch ?? settings?.baseBranch ?? 'main';
107
+ const refResolves =
108
+ gitSpawn(
109
+ cwd ?? process.cwd(),
110
+ 'rev-parse',
111
+ '--verify',
112
+ '--quiet',
113
+ `${gateBaseRef}^{commit}`,
114
+ ).status === 0;
115
+ if (refResolves) {
270
116
  throw new Error(
271
- `[plan-persist] Ticket #${epicId} is not a ${TYPE_LABELS.EPIC}.`,
117
+ `[plan-persist] file-assumption gate: ${assumptionFailures.length} ` +
118
+ `mismatch(es):\n${assumptionFailures.map((error) => ` - ${error}`).join('\n')}`,
272
119
  );
273
120
  }
274
- return { epicId, epic, created: false };
121
+ Logger.warn(
122
+ `[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
123
+ `does not resolve — ${assumptionFailures.length} finding(s) downgraded.`,
124
+ );
125
+ }
126
+
127
+ function riskVerdictCommentBody(riskVerdict) {
128
+ return [
129
+ '### risk-verdict',
130
+ '',
131
+ '```json',
132
+ JSON.stringify(riskVerdict, null, 2),
133
+ '```',
134
+ ].join('\n');
275
135
  }
276
136
 
277
137
  /**
278
- * Execute the collapsed persist end to end (module doc has the 12-step
279
- * order). Modes: fan-out (default), single (`deliveryShape: "single"`),
280
- * amend (`--amend`).
138
+ * Execute the flat Story persist end to end.
281
139
  *
282
140
  * @param {{
283
- * epicId?: number|null,
284
- * provider: import('../../ITicketingProvider.js').ITicketingProvider,
141
+ * provider: object,
285
142
  * artifacts: {
286
- * techSpecContent: string,
287
- * acceptanceSpecContent?: string|null,
288
- * riskVerdict: import('../planning-risk.js').RiskVerdict,
289
- * tickets?: Array<object>|null,
290
- * onePagerContent?: string|null,
291
- * templateContent?: string|null,
143
+ * stories: Array<object>,
144
+ * riskVerdict: object,
145
+ * techSpecContent?: string|null,
146
+ * planAcceptance?: string[]|null,
292
147
  * },
293
148
  * config?: object,
294
- * settings?: { baseBranch?: string, paths?: { tempRoot?: string } },
149
+ * settings?: object,
295
150
  * opts?: {
296
- * force?: boolean,
297
- * resume?: boolean,
298
- * amend?: boolean,
299
- * explicitDelete?: boolean,
300
- * steal?: boolean,
301
151
  * forceReview?: boolean,
302
152
  * allowOverBudget?: boolean,
303
153
  * allowLargeFanOut?: boolean,
304
- * // test seams (production callers must not set these)
305
- * skipHealthcheck?: boolean,
306
154
  * skipCleanup?: boolean,
307
- * spawnSync?: typeof defaultSpawnSync,
308
- * reconcileCli?: string,
309
- * writeSpecFn?: typeof writeSpec,
310
- * renderSpecFn?: typeof renderSpec,
311
- * loadStateFn?: typeof loadState,
312
- * writeStateFn?: typeof writeState,
313
- * runHealthcheckFn?: typeof defaultRunPlanHealthcheck,
314
- * bddProbeFn?: typeof verifyBddRunnerPendingTag,
315
- * fanOutCounter?: (arg: { path: string }) => number,
155
+ * dryRun?: boolean,
156
+ * planRunId?: string,
157
+ * planDir?: string,
158
+ * fanOutCounter?: Function,
316
159
  * cwd?: string,
317
160
  * },
318
161
  * }} input
319
162
  */
320
163
  export async function runPlanPersist({
321
- epicId: requestedEpicId = null,
322
164
  provider,
323
165
  artifacts,
324
166
  config = {},
@@ -326,227 +168,94 @@ export async function runPlanPersist({
326
168
  opts = {},
327
169
  }) {
328
170
  const {
329
- techSpecContent,
330
- acceptanceSpecContent = null,
171
+ stories: rawStories = null,
331
172
  riskVerdict,
332
- tickets = null,
333
- onePagerContent = null,
334
- templateContent = null,
173
+ techSpecContent = null,
174
+ planAcceptance = null,
335
175
  } = artifacts ?? {};
336
176
  const {
337
- force = false,
338
- resume = false,
339
- amend = false,
340
- explicitDelete = false,
341
- steal = false,
342
177
  forceReview = false,
343
178
  allowOverBudget = false,
344
179
  allowLargeFanOut = false,
345
- skipHealthcheck = false,
346
180
  skipCleanup = false,
347
- spawnSync = defaultSpawnSync,
348
- reconcileCli = RECONCILE_CLI,
349
- writeSpecFn = writeSpec,
350
- renderSpecFn = renderSpec,
351
- loadStateFn = loadState,
352
- writeStateFn = writeState,
353
- runHealthcheckFn = defaultRunPlanHealthcheck,
354
- bddProbeFn = verifyBddRunnerPendingTag,
181
+ dryRun = false,
182
+ planRunId,
183
+ planDir = null,
355
184
  fanOutCounter = undefined,
356
185
  cwd = PROJECT_ROOT,
357
186
  } = opts;
358
187
 
359
- // ---- Step 1: argument coherence (flag parsing itself is CLI-owned). ----
360
- if (force && resume) {
361
- throw new Error(
362
- '[plan-persist] --force and --resume are mutually exclusive.',
363
- );
364
- }
365
- if (amend && (force || resume)) {
366
- throw new Error(
367
- '[plan-persist] --amend is mutually exclusive with --force/--resume ' +
368
- '— the amend delta is already an incremental re-persist.',
369
- );
370
- }
371
- if (amend && onePagerContent) {
372
- throw new Error(
373
- '[plan-persist] --amend requires --epic <id> — there is no existing ' +
374
- 'plan to amend in ideation mode.',
375
- );
376
- }
377
- if (onePagerContent && resume) {
188
+ if (!riskVerdict || !Array.isArray(riskVerdict.axes)) {
378
189
  throw new Error(
379
- '[plan-persist] --resume requires --epic <id>the Epic already ' +
380
- "exists after the first attempt (its number is in the failed run's " +
381
- 'output); an ideation --resume would open a duplicate.',
190
+ '[plan-persist] risk verdict is requiredauthor risk-verdict.json ' +
191
+ 'and pass it with --risk-verdict.',
382
192
  );
383
193
  }
384
- if (onePagerContent && !templateContent) {
194
+ if ('deliveryShape' in (riskVerdict ?? {})) {
385
195
  throw new Error(
386
- '[plan-persist] ideation mode requires the epic-from-idea template ' +
387
- 'content (templateContent).',
196
+ '[plan-persist] risk-verdict.deliveryShape was removed in v2 Stage 3 — ' +
197
+ 'delete the field; persist always creates Story issue(s).',
388
198
  );
389
199
  }
390
- if (!onePagerContent && !Number.isInteger(requestedEpicId)) {
200
+ if (!Array.isArray(rawStories) || rawStories.length === 0) {
391
201
  throw new Error(
392
- '[plan-persist] either --epic <id> or --one-pager <path> is required.',
202
+ '[plan-persist] stories payload must be a non-empty array ' +
203
+ '(--stories <file>). Default is one Story.',
393
204
  );
394
205
  }
395
206
 
396
- // ---- Step 2: section gate — BEFORE the lease, BEFORE any provider call.
397
- // A rejection here has made zero GitHub calls (locked in by the
398
- // fail-closed-ordering test).
399
- const sectionCheck = validateSpecSections({ body: techSpecContent });
400
- if (!sectionCheck.ok) {
207
+ const maxTickets = getLimits(config).maxTickets;
208
+ if (rawStories.length > maxTickets && !allowOverBudget) {
401
209
  throw new Error(
402
- formatMissingSectionMessage({
403
- techspecPath: 'authored Tech Spec (--tech-spec)',
404
- missing: sectionCheck.missing,
405
- }),
210
+ `[plan-persist] Stories (${rawStories.length}) exceed the reviewability ` +
211
+ `budget (${maxTickets}). Re-scope, or rerun with --allow-over-budget.`,
406
212
  );
407
213
  }
408
-
409
- // ---- Step 3: risk-verdict presence (schema validation is CLI-owned via
410
- // loadRiskVerdict) + mode-coherence hard error. ----
411
- if (!riskVerdict || !Array.isArray(riskVerdict.axes)) {
412
- throw new Error(
413
- '[plan-persist] risk verdict is required — author risk-verdict.json ' +
414
- 'and pass it with --risk-verdict.',
214
+ if (rawStories.length > maxTickets && allowOverBudget) {
215
+ Logger.warn(
216
+ `[plan-persist] Persisting an over-budget plan: ${rawStories.length} ` +
217
+ `Stories vs. budget ${maxTickets} (--allow-over-budget).`,
415
218
  );
416
219
  }
417
- const mode = resolveDeliveryMode(riskVerdict, tickets, { amend });
418
-
419
- // ---- Step 4: ticket validator + file-assumption gate + DAG + sizing +
420
- // budget (fan-out and amend; git-local — still no provider call). In
421
- // amend mode every gate runs over the MERGED set — keeps + adds +
422
- // modifies, closes excluded — so the DAG is validated against the tree
423
- // that will actually exist post-amend. The skip branch below is fenced
424
- // by construction: `resolveDeliveryMode` hard-refuses `deliveryShape:
425
- // "single"` with any tickets payload, so single mode can only reach here
426
- // with no tickets to validate. ----
427
- let validated = null;
428
- let amendPartition = null;
429
- let reachability = null;
430
- let gateSet = null;
431
- if (mode !== 'single') {
432
- amendPartition = mode === 'amend' ? partitionAmendTickets(tickets) : null;
433
- gateSet = mode === 'amend' ? buildMergedTicketSet(tickets) : tickets;
434
- const maxTickets = getLimits(config).maxTickets;
435
- if (gateSet.length > maxTickets && !allowOverBudget) {
436
- throw new Error(
437
- `[plan-persist] Tickets (${gateSet.length}) exceed the reviewability ` +
438
- `budget (${maxTickets}). Re-scope the Epic into a smaller plan, or ` +
439
- 'rerun with --allow-over-budget after confirming the over-budget ' +
440
- 'rationale on the Epic.',
441
- );
442
- }
443
- warnTicketCapNearLimit(gateSet, maxTickets, 'plan-persist');
444
- if (gateSet.length > maxTickets && allowOverBudget) {
445
- Logger.warn(
446
- `[plan-persist] Persisting an over-budget decomposition: ${gateSet.length} ` +
447
- `tickets vs. budget ${maxTickets} (operator override --allow-over-budget).`,
448
- );
449
- }
450
- Logger.info(
451
- `[plan-persist] Running cross-validation on ${gateSet.length} tickets` +
452
- `${mode === 'amend' ? ' (merged amend set)' : ''}...`,
453
- );
454
- validated = validateTickets(gateSet, config, { fanOutCounter, cwd });
455
- enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
456
- surfaceSoftConflictFindings(validated.findings, 'plan-persist');
457
220
 
458
- // File-assumption gate (#4474 PR7 — coverage regression fix). The
459
- // validator batches per-Story `{ path, assumption }` mismatches against
460
- // the base branch onto `validated.errors`; the retired 12-phase flow
461
- // gated that channel in the workflow's re-prompt loop, so the collapsed
462
- // CLI must gate it here or the check is silently advisory. Fan-out and
463
- // shared-editor findings keep their own policy channels above — this
464
- // rejects only the deterministic assumption mismatches, still before
465
- // any provider call. Guarded on ref resolvability: in a checkout where
466
- // the base branch ref does not resolve (shallow CI fetches, detached
467
- // test sandboxes) every path probes "absent" and the findings are
468
- // noise, so they downgrade to warnings instead of hard-failing.
469
- const assumptionFailures = (validated.errors ?? []).filter((e) =>
470
- e.startsWith('File assumption mismatch:'),
471
- );
472
- if (assumptionFailures.length > 0) {
473
- const gateBaseRef = config?.baseBranch ?? 'main';
474
- const refResolves =
475
- gitSpawn(
476
- cwd ?? process.cwd(),
477
- 'rev-parse',
478
- '--verify',
479
- '--quiet',
480
- `${gateBaseRef}^{commit}`,
481
- ).status === 0;
482
- if (refResolves) {
483
- throw new Error(
484
- `[plan-persist] file-assumption gate: ${assumptionFailures.length} ` +
485
- `mismatch(es) between declared assumptions and the base branch:\n` +
486
- `${assumptionFailures.map((e) => ` - ${e}`).join('\n')}\n` +
487
- 'Fix the Story change declarations (or the plan) and re-run the persist.',
488
- );
489
- }
490
- Logger.warn(
491
- `[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
492
- `does not resolve in this checkout — ${assumptionFailures.length} ` +
493
- 'finding(s) downgraded to warnings.',
494
- );
495
- for (const e of assumptionFailures) {
496
- Logger.warn(`[plan-persist] ${e}`);
497
- }
498
- }
221
+ Logger.info(
222
+ `[plan-persist] Running cross-validation on ${rawStories.length} Story ticket(s)...`,
223
+ );
224
+ const validated = validateTickets(rawStories, config, {
225
+ fanOutCounter,
226
+ cwd,
227
+ modelCapacity: opts.modelCapacity,
228
+ });
229
+ enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
230
+ surfaceSoftConflictFindings(validated.findings, 'plan-persist');
231
+ enforceTicketValidation(validated, { config, settings, cwd });
499
232
 
500
- // ---- Step 4.5: deterministic draft reachability (#4474 PR6 — the 8.4
501
- // critic demoted into persist). Still git-local, zero provider calls,
502
- // so the one-targeted-amend recovery re-runs a clean persist. ----
503
- reachability = evaluateDraftReachability({ tickets: gateSet, config });
504
- if (reachability.status === 'orphans') {
505
- const err = new Error(renderReachabilityOrphans(reachability));
506
- err.code = 'PLAN_REACHABILITY_ORPHANS';
507
- err.orphans = reachability.orphans;
508
- throw err;
509
- }
510
- Logger.info(`[plan-persist] reachability: ${reachability.reasons[0]}`);
511
- } else {
512
- reachability = {
513
- status: 'skipped',
514
- reasons: [
515
- 'single-delivery shape — no draft story tree to scan for orphan surfaces.',
516
- ],
517
- orphans: [],
518
- scanned: 0,
519
- };
233
+ const reachability = evaluateDraftReachability({
234
+ tickets: rawStories,
235
+ config,
236
+ });
237
+ if (reachability.status === 'orphans') {
238
+ const err = new Error(renderReachabilityOrphans(reachability));
239
+ err.code = 'PLAN_REACHABILITY_ORPHANS';
240
+ err.orphans = reachability.orphans;
241
+ throw err;
520
242
  }
243
+ Logger.info(`[plan-persist] reachability: ${reachability.reasons[0]}`);
521
244
  if (reachability.status === 'skipped') {
522
- // Audit trail for the skip decision (#4474 PR6). Best-effort by
523
- // contract — a failed append never fails the persist. Logged before
524
- // the first provider call so even an ideation run that later fails
525
- // still records the decision (ideation has no Epic id yet, so the
526
- // record lands on the standalone stream).
527
245
  await appendCriticSkip(
528
246
  {
529
247
  critic: 'reachability',
530
248
  reasons: reachability.reasons,
531
249
  cli: 'plan-persist',
532
- epicId: requestedEpicId,
533
250
  },
534
251
  config,
535
252
  );
536
253
  }
537
254
 
538
- // ---- Step 4.7: folded critic dispatch evaluation (#4496 fix 6 — the
539
- // former standalone `plan-critics.js` turn). Deterministic and git-local,
540
- // still zero provider calls: the verdicts are printed (and returned on
541
- // the result) as part of the pre-write phase, and every skip decision is
542
- // appended to the plan-metrics ledger exactly as the standalone CLI did,
543
- // so under-firing stays auditable without a separate invocation on the
544
- // headless path. Advisory by construction — the deterministic validators
545
- // above remain the unchanged hard gates. ----
546
255
  const critics = evaluatePlanCritics({
547
- techSpecContent,
256
+ techSpecContent: techSpecContent ?? '',
548
257
  riskVerdict,
549
- tickets: gateSet,
258
+ tickets: rawStories,
550
259
  config,
551
260
  });
552
261
  for (const decision of [critics.consolidation, critics.premortem]) {
@@ -556,460 +265,120 @@ export async function runPlanPersist({
556
265
  decision.reasons.join('; '),
557
266
  );
558
267
  if (!decision.dispatch) {
559
- // Best-effort by contract — a failed append never fails the persist.
560
268
  await appendCriticSkip(
561
269
  {
562
270
  critic: decision.critic,
563
271
  reasons: decision.reasons,
564
272
  cli: 'plan-persist',
565
- epicId: requestedEpicId,
566
273
  },
567
274
  config,
568
275
  );
569
276
  }
570
277
  }
571
278
 
572
- // ---- Step 5: ideation fold / Epic resolution (first provider call). ----
573
- const { epicId, epic, created } = await resolveTargetEpic({
574
- epicId: requestedEpicId,
575
- onePagerContent,
576
- templateContent,
577
- provider,
279
+ // Split policy + inline Spec fold (over-budget Specs fail closed no docs/).
280
+ const { stories } = assemblePlanStories(rawStories, {
281
+ sharedSpec: techSpecContent,
282
+ planAcceptance: planAcceptance ?? undefined,
578
283
  });
579
284
 
580
- // Amend pre-mutation resolution: every modify/keep/close slug must
581
- // resolve to a live issue, and close ops require --explicit-delete
582
- // (exit 2 with the dry-run diff otherwise — the epic-reconcile.js
583
- // contract). Runs BEFORE the lease and before any mutation.
584
- let amendTargets = null;
585
- if (mode === 'amend') {
586
- const priorState = loadStateFn(epicId);
587
- amendTargets = await resolveAmendTargets({
588
- partition: amendPartition,
589
- stateMapping: priorState.mapping,
590
- provider,
591
- });
592
- enforceAmendCloseGate({
593
- epicId,
594
- targets: amendTargets,
595
- adds: amendPartition.add,
596
- explicitDelete,
597
- });
598
- Logger.info(
599
- renderAmendPlanDiff({
600
- epicId,
601
- targets: amendTargets,
602
- adds: amendPartition.add,
603
- }),
604
- );
605
- }
606
-
607
- // ---- Step 6: Epic lease. Every path after a successful acquire runs
608
- // through the finally below, so the lease is released on success, on a
609
- // gate failure, and on a throw alike. ----
610
- await acquireEpicPlanLease({ provider, epicId, config, steal });
611
-
612
- try {
613
- // Refuse a duplicate story tree unless this is a deliberate re-persist
614
- // (`--force` closes + recreates via the reconciler's close ops;
615
- // `--resume` continues a partial persist). Amend is exempt by
616
- // definition — its whole purpose is mutating the existing open tree.
617
- if (mode !== 'amend') {
618
- await assertNoOpenPlanChildren({
619
- provider,
620
- epicId,
621
- force: force || resume,
622
- });
623
- }
624
-
625
- await initializePlanState({ provider, epicId });
626
-
627
- // ---- Step 7: managed sections + risk comment + freshness advisory. ----
628
- // BDD-runner probe (Story #4145): best-effort; a probe failure degrades
629
- // to "runner present" and never blocks the persist.
630
- let bddRunner = null;
631
- try {
632
- bddRunner = await bddProbeFn({ cwd: PROJECT_ROOT });
633
- } catch (err) {
634
- Logger.warn(
635
- `[plan-persist] BDD runner probe skipped (${err.message}); ` +
636
- 'acceptance disposition derived from risk axes only.',
637
- );
638
- }
639
- const planningRisk = deriveRiskEnvelope(riskVerdict, { bddRunner });
640
- if (planningRisk.acceptanceWaivedReason) {
641
- Logger.info(
642
- `[plan-persist] Acceptance disposition forced to not-applicable for ` +
643
- `Epic #${epicId}: ${planningRisk.acceptanceWaivedReason}`,
644
- );
645
- }
646
-
647
- // Amend always overwrites the managed sections — the amended Tech Spec
648
- // IS the delta's spec half (planEpic would otherwise short-circuit
649
- // `already-planned` on the pre-amend sections).
650
- const planResult = await planEpic(
651
- epicId,
652
- provider,
653
- { techSpecContent, acceptanceSpecContent },
654
- settings,
655
- { force: force || mode === 'amend', planningRisk },
656
- );
657
-
658
- const reviewRouting = resolveReviewRouting({ planningRisk, forceReview });
659
- Logger.info(`[plan-persist] Review routing: ${reviewRouting.decision}.`);
660
-
661
- await upsertStructuredComment(
662
- provider,
663
- epicId,
664
- 'risk-verdict',
665
- buildRiskVerdictCommentBody({ epicId, riskVerdict, planningRisk }),
666
- );
667
-
668
- const baseBranchRef = settings?.baseBranch ?? 'main';
669
- const tempRoot = path.resolve(
670
- PROJECT_ROOT,
671
- settings?.paths?.tempRoot ?? 'temp',
672
- );
673
- const freshness = await runSpecFreshnessCheck({
674
- epicId,
675
- techSpecContent,
676
- baseBranchRef,
677
- tempRoot,
678
- provider,
679
- });
680
-
681
- // Spec-half checkpoint (v2). A crash after this point resumes with the
682
- // sections already folded (planEpic short-circuits `already-planned`).
683
- await writeCheckpointV2(provider, epicId, {
684
- planningRisk,
685
- riskVerdict,
686
- reviewRouting: {
687
- decision: reviewRouting.decision,
688
- requiresStop: reviewRouting.requiresStop,
689
- forceReviewApplied: reviewRouting.forceReviewApplied,
690
- },
691
- spec: {
692
- techSpecPersisted:
693
- planResult?.techSpecPersisted === true ||
694
- planResult?.reason === 'already-planned',
695
- acceptanceTable: planResult?.acceptanceTable ?? 'none',
696
- completedAt: new Date().toISOString(),
697
- },
698
- persist: {
699
- mode,
700
- cli: 'plan-persist',
701
- startedAt: new Date().toISOString(),
702
- completedAt: null,
703
- },
704
- });
705
-
706
- // ---- Step 8: mode-split mutation. ----
707
- let reconcile = null;
708
- let specFilePath = null;
709
- let ticketCount = 0;
710
- let single = null;
711
- let amendSummary = null;
285
+ const planningRisk = deriveRiskEnvelope(riskVerdict);
286
+ const reviewRouting = resolveReviewRouting({
287
+ planningRisk,
288
+ forceReview,
289
+ });
712
290
 
713
- if (mode === 'fan-out') {
714
- // Story creation via the structural reconciler.
715
- Logger.info(
716
- `[plan-persist] Rendering spec for Epic #${epicId} (${validated.length} tickets)...`,
717
- );
718
- const spec = renderSpecFn(validated, {
719
- epic: buildEpicSpecInput(epic, epicId),
720
- });
721
- specFilePath = writeSpecFn(epicId, spec, { epicsDir: undefined });
722
- Logger.info(`[plan-persist] Wrote spec → ${specFilePath}`);
291
+ const { created, planRunLabel } = await createStoryIssues({
292
+ provider,
293
+ stories,
294
+ opts: { planRunId, dryRun },
295
+ });
723
296
 
724
- // Pre-creation checkpoint: marks creation in flight so a rate-limit
725
- // crash mid-creation leaves a checkpoint pointing at the spec + the
726
- // reconciler's per-slug state file (the resume ledger). `--resume`
727
- // re-runs the reconciler, which creates only the missing slugs.
728
- await writeCheckpointV2(provider, epicId, {
729
- decompose: { ticketCount: null, completedAt: null },
730
- });
297
+ const primary = created[0];
298
+ const waveTable = buildWaveTable(
299
+ stories.map((s) => ({
300
+ slug: s.slug,
301
+ title: s.title,
302
+ depends_on: s.depends_on,
303
+ })),
304
+ );
731
305
 
732
- Logger.info(
733
- `[plan-persist] Spawning epic-reconcile.js --apply --yes for Epic #${epicId}...`,
734
- );
735
- reconcile = spawnReconcilerApply({
736
- spawnSync,
737
- reconcileCli,
738
- epicId,
739
- cwd,
740
- explicitDelete: force,
741
- });
306
+ let planMetricsLine = null;
307
+ try {
308
+ const metrics = summarizePlanMetrics(await readPlanMetrics(config));
309
+ planMetricsLine = renderPlanMetricsSummaryLine(metrics);
310
+ } catch {
311
+ planMetricsLine = null;
312
+ }
742
313
 
743
- await reconcileSubIssueLinks(epicId, provider);
314
+ const summaryBody = buildPlanSummaryCommentBody({
315
+ epicId: primary.id,
316
+ ticketCount: created.length,
317
+ planningRisk,
318
+ reviewRouting,
319
+ freshness: { stale: 0, ambiguous: 0 },
320
+ healthcheck: { skipped: true },
321
+ waveTable,
322
+ mode: 'stories',
323
+ planMetricsLine,
324
+ stories: created,
325
+ planRunLabel,
326
+ });
744
327
 
745
- const postReconcileState = loadStateFn(epicId);
746
- await setBlockedByDependencies(
747
- epicId,
328
+ if (!dryRun) {
329
+ for (const story of created) {
330
+ await upsertStructuredComment(
748
331
  provider,
749
- spec,
750
- postReconcileState.mapping,
332
+ story.id,
333
+ 'risk-verdict',
334
+ riskVerdictCommentBody(riskVerdict),
751
335
  );
752
-
753
- ticketCount = tickets.length;
754
- // Post-creation checkpoint (the former recordCheckpoint half).
755
- await writeCheckpointV2(provider, epicId, {
756
- decompose: {
757
- ticketCount,
758
- shape: 'fan-out',
759
- completedAt: new Date().toISOString(),
760
- },
761
- });
762
-
763
- // A force re-persist over a former single-delivery plan flips the
764
- // routing shape — drop the stale marker so #4475's reader never sees
765
- // a fan-out tree labelled single.
766
- await removeSingleDeliveryMarker(provider, epicId, epic);
767
- } else if (mode === 'single') {
768
- // Single-delivery: NO story tree. The delivery::single routing
769
- // marker (inert until #4475's deliver-side reader) plus the Delivery
770
- // Slicing table of the persisted Tech Spec are the plan.
771
- single = {
772
- deliveryShape: 'single',
773
- sliceCount: countDeliverySlices(techSpecContent),
774
- routingReasons: riskVerdict.deliveryShapeRationale
775
- ? [riskVerdict.deliveryShapeRationale]
776
- : [],
777
- };
778
- Logger.info(
779
- `[plan-persist] Single-delivery mode: applying ${DELIVERY_LABELS.SINGLE} ` +
780
- `to Epic #${epicId} (no story tree).`,
781
- );
782
- await provider.updateTicket(epicId, {
783
- labels: { add: [DELIVERY_LABELS.SINGLE], remove: [] },
784
- });
785
- // Explicit zero-ticket checkpoint so delivery-time consumers read a
786
- // deliberate single-shape plan, never an unplanned absence.
787
- await writeCheckpointV2(provider, epicId, {
788
- decompose: {
789
- ticketCount: 0,
790
- shape: 'single',
791
- completedAt: new Date().toISOString(),
792
- },
793
- });
794
- } else {
795
- // Amend delta: close-and-recreate is scoped to modify/close slugs
796
- // only; keeps are untouched by construction (no code path receives
797
- // them); adds are created fresh. The state ledger and blocked-by
798
- // edges are rebuilt over the merged set.
799
- const spec = renderSpecFn(validated, {
800
- epic: buildEpicSpecInput(epic, epicId),
801
- });
802
- specFilePath = writeSpecFn(epicId, spec, { epicsDir: undefined });
803
- Logger.info(`[plan-persist] Wrote amended spec → ${specFilePath}`);
804
-
805
- await writeCheckpointV2(provider, epicId, {
806
- decompose: { ticketCount: null, completedAt: null },
807
- });
808
-
809
- const validatedBySlug = new Map(validated.map((t) => [t.slug, t]));
810
- const applied = await applyAmendOps({
811
- epicId,
812
- provider,
813
- targets: amendTargets,
814
- validatedBySlug,
815
- });
816
-
817
- // Rebuild the state ledger over the merged set: prior mapping minus
818
- // closed/replaced slugs, plus the fresh create/recreate numbers.
819
- const priorState = loadStateFn(epicId);
820
- const mergedMapping = { ...(priorState.mapping ?? {}) };
821
- for (const slug of applied.closedSlugs) delete mergedMapping[slug];
822
- Object.assign(mergedMapping, applied.mapping);
823
- writeStateFn(epicId, {
824
- epicId,
825
- mapping: mergedMapping,
826
- lastReconciledAt: new Date().toISOString(),
827
- });
828
-
829
- await reconcileSubIssueLinks(epicId, provider);
830
- await setBlockedByDependencies(epicId, provider, spec, mergedMapping);
831
-
832
- ticketCount = validated.length;
833
- amendSummary = {
834
- closed: applied.closed,
835
- recreated: applied.recreated,
836
- created: applied.created,
837
- keptCount: amendTargets.keep.length,
838
- };
839
- await writeCheckpointV2(provider, epicId, {
840
- decompose: {
841
- ticketCount,
842
- shape: 'fan-out',
336
+ await writeCheckpointV2(provider, story.id, {
337
+ planningRisk,
338
+ riskVerdict,
339
+ reviewRouting,
340
+ persist: {
843
341
  completedAt: new Date().toISOString(),
342
+ storyCount: created.length,
343
+ planRunLabel,
344
+ primaryStoryId: primary.id,
345
+ stories: created.map((createdStory) => ({
346
+ slug: createdStory.slug,
347
+ id: createdStory.id,
348
+ })),
844
349
  },
845
350
  });
846
-
847
- // An amended plan is fan-out-shaped; drop a stale single marker.
848
- await removeSingleDeliveryMarker(provider, epicId, epic);
849
- }
850
-
851
- // ---- Step 9: inline healthcheck — the agent::ready exit condition. ----
852
- const healthcheck = skipHealthcheck
853
- ? { ok: true, skipped: true }
854
- : await runHealthcheckGate({
855
- epicId,
856
- epic,
857
- runHealthcheckFn,
858
- tag: 'plan-persist',
859
- });
860
-
861
- // ---- Step 10: single terminal agent::ready flip. This surface never
862
- // writes agent::review-spec — the HITL review gate sits BEFORE persist
863
- // in the collapsed flow, so the intermediate label has no reader. ----
864
- Logger.info(
865
- `[plan-persist] Flipping Epic #${epicId} to ${AGENT_LABELS.READY}...`,
866
- );
867
- await setEpicLabel(provider, epicId, AGENT_LABELS.READY);
868
-
869
- // ---- Step 11: final checkpoint v2 + single plan-summary comment with
870
- // the dry-run wave table as closing text (single mode records the
871
- // { deliveryShape, sliceCount, routingReasons } routing record
872
- // instead). ----
873
- const waveTable = mode === 'single' ? [] : buildWaveTable(validated);
874
- const checkpoint = await writeCheckpointV2(provider, epicId, {
875
- persist: { completedAt: new Date().toISOString() },
876
- });
877
- // G2 measurement receipt (Epic #4474 PR7): roll the plan-metrics ledger
878
- // into the summary comment so turns-per-plan / per-mode counts / critic
879
- // skips are readable off the Epic. Best-effort — a missing ledger
880
- // yields no line. The in-flight persist invocation itself is stamped by
881
- // the CLI wrapper *after* this function returns, so it appears in the
882
- // stdout JSON (and any later re-persist), not in this comment.
883
- let planMetricsLine = null;
884
- try {
885
- const metricsSummary = summarizePlanMetrics(
886
- await readPlanMetrics(epicId, config),
887
- );
888
- if (metricsSummary) {
889
- planMetricsLine = renderPlanMetricsSummaryLine(metricsSummary);
890
- }
891
- } catch (err) {
892
- Logger.warn(
893
- `[plan-persist] plan-metrics summary line skipped: ${err.message}`,
894
- );
895
351
  }
896
352
  await upsertStructuredComment(
897
353
  provider,
898
- epicId,
354
+ primary.id,
899
355
  PLAN_SUMMARY_COMMENT_TYPE,
900
- buildPlanSummaryCommentBody({
901
- epicId,
902
- ticketCount,
903
- planningRisk,
904
- reviewRouting,
905
- freshness,
906
- healthcheck,
907
- waveTable,
908
- mode,
909
- planMetricsLine,
910
- single,
911
- amend: amendSummary,
912
- }),
356
+ summaryBody,
913
357
  );
358
+ }
914
359
 
915
- // ---- Step 12: temp cleanup ONLY at terminal success. A failed run
916
- // leaves every authored artifact on disk for --force/--resume reuse. ----
917
- const cleanup = skipCleanup
918
- ? { deleted: [], missing: [], failed: [], skipped: true }
919
- : await cleanupPhaseTempFiles({ phase: 'persist', epicId });
920
- Logger.info(
921
- `[plan-persist] ✅ Persist complete for Epic #${epicId} (${mode}). ` +
922
- `${ticketCount} ticket(s) persisted; Epic is ${AGENT_LABELS.READY}.`,
923
- );
924
- if (cleanup.deleted.length > 0) {
925
- Logger.info(
926
- `[plan-persist] 🧹 Cleaned up ${cleanup.deleted.length} temp file(s).`,
927
- );
360
+ if (!skipCleanup && planDir) {
361
+ try {
362
+ await rm(planDir, { recursive: true, force: true });
363
+ } catch (err) {
364
+ Logger.warn(`[plan-persist] temp cleanup skipped: ${err.message}`);
928
365
  }
929
-
930
- return {
931
- epicId,
932
- epicCreated: created,
933
- mode,
934
- ticketCount,
935
- checkpoint,
936
- planningRisk,
937
- reviewRouting,
938
- freshness,
939
- healthcheck,
940
- reachability,
941
- critics,
942
- reconcile,
943
- specPath: specFilePath,
944
- waveTable,
945
- single,
946
- amend: amendSummary,
947
- cleanup,
948
- labelTransition: 'ready',
949
- };
950
- } finally {
951
- // Lease release on EVERY exit path (success, gate failure, throw).
952
- // Best-effort by contract — releaseEpicPlanLease never throws.
953
- await releaseEpicPlanLease({ provider, epicId, config });
954
366
  }
955
- }
956
367
 
957
- /**
958
- * Drop a stale `delivery::single` marker when a fan-out-shaped persist
959
- * (full re-persist or amend) lands over a formerly single-delivery plan.
960
- * No-op — and no API call — when the fetched Epic never carried it.
961
- *
962
- * @param {object} provider
963
- * @param {number} epicId
964
- * @param {{ labels?: string[] }} epic the Epic as fetched at step 5
965
- */
966
- async function removeSingleDeliveryMarker(provider, epicId, epic) {
967
- if (!epic?.labels?.includes(DELIVERY_LABELS.SINGLE)) return;
968
368
  Logger.info(
969
- `[plan-persist] Removing stale ${DELIVERY_LABELS.SINGLE} marker from ` +
970
- `Epic #${epicId} (plan is fan-out-shaped now).`,
369
+ `[plan-persist] Persisted ${created.length} Story(ies)` +
370
+ (planRunLabel ? ` under ${planRunLabel}` : '') +
371
+ `; primary #${primary.id} is agent::ready.`,
971
372
  );
972
- await provider.updateTicket(epicId, {
973
- labels: { add: [], remove: [DELIVERY_LABELS.SINGLE] },
974
- });
975
- }
976
373
 
977
- /**
978
- * Render the `risk-verdict` structured-comment body. Lifted from
979
- * `epic-plan-spec/phases/run-spec-phase.js` so the collapsed surface posts
980
- * the byte-identical audit-trail comment (axis table + fenced-JSON record)
981
- * downstream tooling parses.
982
- *
983
- * @param {{ epicId: number, riskVerdict: import('../planning-risk.js').RiskVerdict, planningRisk: import('../planning-risk.js').PlanningRiskEnvelope }} input
984
- * @returns {string}
985
- */
986
- function buildRiskVerdictCommentBody({ epicId, riskVerdict, planningRisk }) {
987
- const axisRows = planningRisk.axes.map(
988
- (entry) => `| ${entry.axis} | ${entry.level} | ${entry.rationale} |`,
989
- );
990
- const axisTable =
991
- axisRows.length > 0
992
- ? ['| Axis | Level | Rationale |', '| --- | --- | --- |', ...axisRows]
993
- : ['_No risk axes apply (planner-asserted)._'];
994
- const record = {
995
- kind: 'risk-verdict',
996
- epicId,
997
- verdict: riskVerdict,
374
+ return {
375
+ stories: created,
376
+ primaryStoryId: primary.id,
377
+ planRunLabel,
998
378
  planningRisk,
379
+ reviewRouting,
380
+ critics,
381
+ reachability,
382
+ waveTable,
999
383
  };
1000
- const waiverNote = planningRisk.acceptanceWaivedReason
1001
- ? ['', `> ⚠️ **Acceptance waived** — ${planningRisk.acceptanceWaivedReason}`]
1002
- : [];
1003
- return [
1004
- `### 🧭 Planning Risk Verdict — ${planningRisk.overallLevel} · ${planningRisk.gateDecision}`,
1005
- '',
1006
- riskVerdict.summary,
1007
- '',
1008
- ...axisTable,
1009
- ...waiverNote,
1010
- '',
1011
- '```json',
1012
- JSON.stringify(record, null, 2),
1013
- '```',
1014
- ].join('\n');
1015
384
  }