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,625 +0,0 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file */
3
-
4
- /**
5
- * epic-reconcile.js — CLI surface for the structural epic-spec reconciler.
6
- *
7
- * Story #1496 (Epic #1182 / Tech Spec #1483). Thin wrapper that
8
- * composes the existing reconciler modules into the operator-facing
9
- * surface specified in the Tech Spec:
10
- *
11
- * node .agents/scripts/epic-reconcile.js <epicId>
12
- * [--dry-run] (default)
13
- * [--apply]
14
- * [--explicit-delete]
15
- * [--yes]
16
- *
17
- * Control flow (Tech Spec §"Reconciler control flow"):
18
- *
19
- * 1. Load `.agents/epics/<id>.yaml` via `lib/spec/loader.js`. Validation
20
- * failure exits 1 with the failing JSON Pointer.
21
- * 2. Load `.agents/epics/<id>.state.json` (absent → empty mapping).
22
- * 3. Fetch the live GH state via the configured `ITicketingProvider`.
23
- * 4. Compute the plan via `epic-spec-reconciler-diff.js#diff`.
24
- * 5. Render the plan via `epic-spec-reconciler-format.js#formatPlan`.
25
- * 6. On `--dry-run` (default) — print and exit 0.
26
- * On `--apply`:
27
- * - When the plan carries close ops without `--explicit-delete`,
28
- * exit 2.
29
- * - When the plan is non-empty, in a TTY, prompt unless `--yes`.
30
- * Non-TTY without `--yes` aborts (exit 1).
31
- * - Invoke `apply()` with the appropriate gate flags.
32
- *
33
- * Exit codes (Story #1496 / Task #1521):
34
- * - 0 No diff or successful apply.
35
- * - 1 Validation error (`SpecValidationError`, `SpecParseError`,
36
- * `SpecNotFoundError`), or a non-TTY `--apply` without `--yes`,
37
- * or an apply-phase provider failure.
38
- * - 2 Close operation requires `--explicit-delete` but the flag was
39
- * absent.
40
- *
41
- * The CLI is intentionally thin: every behaviour that matters for
42
- * downstream automation is rooted in the underlying modules. This file
43
- * adds three things on top: argument parsing, the confirmation gate,
44
- * and the exit-code contract.
45
- */
46
-
47
- import readline from 'node:readline';
48
- import { parseArgs } from 'node:util';
49
-
50
- import { runAsCli } from './lib/cli-utils.js';
51
- import { resolveConfig } from './lib/config-resolver.js';
52
- import { Logger } from './lib/Logger.js';
53
- import {
54
- ApplyGateViolation,
55
- apply,
56
- } from './lib/orchestration/epic-spec-reconciler-apply.js';
57
- import { diff } from './lib/orchestration/epic-spec-reconciler-diff.js';
58
- import { formatPlan } from './lib/orchestration/epic-spec-reconciler-format.js';
59
- import { isEmptyPlan } from './lib/orchestration/epic-spec-reconciler-ops.js';
60
- import { createProvider } from './lib/provider-factory.js';
61
- import {
62
- loadSpec,
63
- loadState,
64
- SpecNotFoundError,
65
- SpecParseError,
66
- SpecValidationError,
67
- } from './lib/spec/index.js';
68
-
69
- /**
70
- * Exit-code contract (see header). Exported for tests so they can assert
71
- * the constants without re-deriving the integers from prose.
72
- */
73
- export const EXIT_CODES = Object.freeze({
74
- OK: 0,
75
- VALIDATION_ERROR: 1,
76
- EXPLICIT_DELETE_REQUIRED: 2,
77
- });
78
-
79
- /**
80
- * Default writer for log/output streams. Tests inject sinks so they can
81
- * inspect what the CLI printed without intercepting process.stdout.
82
- */
83
- function defaultStdout(line) {
84
- process.stdout.write(`${line}\n`);
85
- }
86
-
87
- function defaultStderr(line) {
88
- process.stderr.write(`${line}\n`);
89
- }
90
-
91
- /**
92
- * Parse the CLI argv into a typed bag. Exported for tests.
93
- *
94
- * @param {string[]} argv argv slice (excluding `node` and the script path).
95
- * @returns {{
96
- * epicId: number|null,
97
- * dryRun: boolean,
98
- * apply: boolean,
99
- * explicitDelete: boolean,
100
- * yes: boolean,
101
- * raw: object
102
- * }}
103
- */
104
- export function parseCli(argv) {
105
- const { values, positionals } = parseArgs({
106
- args: argv,
107
- allowPositionals: true,
108
- options: {
109
- 'dry-run': { type: 'boolean', default: false },
110
- apply: { type: 'boolean', default: false },
111
- 'explicit-delete': { type: 'boolean', default: false },
112
- yes: { type: 'boolean', default: false },
113
- },
114
- strict: false,
115
- });
116
-
117
- // Default behaviour: when neither --apply nor --dry-run is set, the
118
- // CLI runs in dry-run mode (Tech Spec: "dry-run default"). When both
119
- // are passed, --dry-run wins — safer to no-op than to apply by accident.
120
- const explicitApply = values.apply === true;
121
- const explicitDryRun = values['dry-run'] === true;
122
- const dryRun = explicitDryRun || !explicitApply;
123
-
124
- const epicArg = positionals[0];
125
- const epicId =
126
- epicArg != null && /^\d+$/.test(String(epicArg))
127
- ? Number.parseInt(String(epicArg), 10)
128
- : null;
129
-
130
- return {
131
- epicId,
132
- dryRun,
133
- apply: explicitApply && !explicitDryRun,
134
- explicitDelete: values['explicit-delete'] === true,
135
- yes: values.yes === true,
136
- raw: values,
137
- };
138
- }
139
-
140
- /**
141
- * Project the GH state observation into the shape the diff engine
142
- * expects: `{ [issueNumber]: { title, body?, labels?, state } }`.
143
- *
144
- * Enumerates the Epic's children via the **server-side-scoped**
145
- * `getSubTickets(epicId)` (native sub-issue GraphQL graph → checklist
146
- * links → reverse-search) rather than `getTickets(epicId)`'s repo-wide
147
- * `state=all` scan-and-filter (Story #3455). The structural diff engine
148
- * never branches on `obs.state` — Close detection is driven by
149
- * spec-vs-mapping, `fieldChanges` compares only `title/body/labels/wave`,
150
- * and `ghState` is consulted only to look up an issue by the number
151
- * already in the state-file mapping — so dropping the repo-wide
152
- * closed-issue over-fetch changes nothing but cost (and removes the
153
- * latent spurious-Update risk on a re-plan of a partially-delivered
154
- * Epic). The provider's returned children come back as full ticket
155
- * objects (`id, title, body, labels, state`); the diff engine ignores
156
- * fields it doesn't use.
157
- *
158
- * @param {object} provider
159
- * @param {number} epicId
160
- * @returns {Promise<Record<number, {title:string,body?:string,labels?:string[],state?:string}>>}
161
- */
162
- export async function fetchGhState(provider, epicId) {
163
- const ghState = {};
164
- // Epic itself (best-effort — older providers may stub getEpic; treat
165
- // a thrown call as "epic not observable" and continue with children).
166
- try {
167
- if (typeof provider.getEpic === 'function') {
168
- const epic = await provider.getEpic(epicId);
169
- if (epic && typeof epic.id === 'number') {
170
- ghState[epic.id] = {
171
- title: epic.title,
172
- body: epic.body ?? '',
173
- labels: epic.labels ?? [],
174
- state: epic.state ?? 'open',
175
- };
176
- }
177
- }
178
- } catch (_err) {
179
- /* swallow — epic body observation is non-fatal for diff */
180
- }
181
-
182
- if (typeof provider.getSubTickets === 'function') {
183
- const tickets = await provider.getSubTickets(epicId);
184
- for (const t of tickets ?? []) {
185
- if (typeof t.id !== 'number') continue;
186
- ghState[t.id] = {
187
- title: t.title,
188
- body: t.body ?? '',
189
- labels: t.labels ?? [],
190
- state: t.state ?? 'open',
191
- };
192
- }
193
- }
194
- return ghState;
195
- }
196
-
197
- /**
198
- * Walk the spec and yield one `{ slug, entity, title, parentSlug,
199
- * dependsOn }` record per logical entity (epic → story), mirroring the
200
- * diff engine's `flattenSpec`. Local to the CLI so the reseed pass
201
- * (below) can map spec slugs onto live GH issues without importing the
202
- * diff engine's private walker. Pure.
203
- *
204
- * @param {object} spec
205
- * @returns {Array<{slug: string, entity: string, title: string, parentSlug: string|null, dependsOn: string[]}>}
206
- */
207
- export function flattenSpecForReseed(spec) {
208
- const out = [];
209
- if (!spec || typeof spec !== 'object') return out;
210
- if (spec.epic && typeof spec.epic === 'object') {
211
- out.push({
212
- slug: 'epic',
213
- entity: 'epic',
214
- title: String(spec.epic.title ?? ''),
215
- parentSlug: null,
216
- dependsOn: [],
217
- });
218
- }
219
- for (const story of spec.stories ?? []) {
220
- out.push({
221
- slug: story.slug,
222
- entity: 'story',
223
- title: String(story.title ?? ''),
224
- parentSlug: 'epic',
225
- dependsOn: story.dependsOn ?? [],
226
- });
227
- }
228
- return out;
229
- }
230
-
231
- /**
232
- * Story #3905 — recover the slug→issue mapping from live GitHub state
233
- * when `state.json` is missing or incomplete.
234
- *
235
- * The reconciler's idempotency on `--resume` rests entirely on the
236
- * gitignored `temp/epic-<id>/<id>.state.json` slug→issue map. When that
237
- * file is absent (a fresh checkout, a reaped temp dir, the exact
238
- * situation `--resume` exists to recover from), `loadState` returns an
239
- * empty mapping, the diff engine sees every spec slug as unmapped, and
240
- * `apply` recreates the entire Story set on top of the existing
241
- * one — duplicating every child. This is precisely the failure `--resume`
242
- * is meant to prevent.
243
- *
244
- * This pass closes the gap: for every spec slug that is **not** already
245
- * in the mapping, it looks for an **open** GH issue whose title matches
246
- * the spec entity's title (and whose issue number is not already claimed
247
- * by another mapping entry). A match seeds a mapping entry carrying the
248
- * structural edges (`entity`, `parentSlug`, `dependsOn`) the diff engine
249
- * reads, so the slug diffs as an Update/no-op rather than a Create.
250
- *
251
- * Pure: it mutates and returns a shallow clone of `state.mapping`; it
252
- * performs no I/O (the caller supplies the already-fetched `ghState`).
253
- * Title matching is intentionally conservative — an unmatched slug is
254
- * left unmapped so a genuinely-missing child is still created (the
255
- * partial-persist case `--resume` also serves). Returns the reseeded
256
- * state plus the list of slugs it recovered so the caller can log them.
257
- *
258
- * @param {{epicId: number, mapping: Record<string, object>}} state
259
- * @param {object} spec
260
- * @param {Record<string|number, {title?: string, state?: string}>} ghState
261
- * @returns {{ state: object, reseeded: Array<{slug: string, issueNumber: number}> }}
262
- */
263
- export function reseedMappingFromGh(state, spec, ghState) {
264
- const reseeded = [];
265
- const mapping = { ...(state?.mapping ?? {}) };
266
- if (!spec || typeof spec !== 'object' || !ghState) {
267
- return { state: { ...state, mapping }, reseeded };
268
- }
269
-
270
- // Issue numbers already claimed by the current mapping must not be
271
- // re-bound to a second slug.
272
- const claimed = new Set();
273
- for (const entry of Object.values(mapping)) {
274
- if (entry && typeof entry.issueNumber === 'number') {
275
- claimed.add(entry.issueNumber);
276
- }
277
- }
278
-
279
- // Index OPEN gh issues by title → [issueNumber]. Closed issues are
280
- // skipped: a `--force`/`--resume` should never re-bind a slug to a
281
- // tombstoned issue.
282
- const openByTitle = new Map();
283
- for (const [num, obs] of Object.entries(ghState)) {
284
- const issueNumber = Number(num);
285
- if (!Number.isInteger(issueNumber)) continue;
286
- if (obs?.state && obs.state !== 'open') continue;
287
- const title = typeof obs?.title === 'string' ? obs.title : null;
288
- if (title == null) continue;
289
- if (!openByTitle.has(title)) openByTitle.set(title, []);
290
- openByTitle.get(title).push(issueNumber);
291
- }
292
-
293
- for (const entity of flattenSpecForReseed(spec)) {
294
- // Epic #4474 (PR3) — a mapping entry WITHOUT an issue number is a
295
- // partial-failure tombstone (the apply's state writer persists every
296
- // spec slug; only completed creations carry `issueNumber`). Treat it
297
- // as unmapped here so a title-matched live issue reseeds it instead
298
- // of leaving a numberless entry the diff engine can neither update
299
- // nor (post-fix) skip-create.
300
- if (Number.isInteger(mapping[entity.slug]?.issueNumber)) continue; // already mapped
301
- const candidates = openByTitle.get(entity.title) ?? [];
302
- // Pick the lowest unclaimed candidate for determinism.
303
- const match = candidates
304
- .filter((n) => !claimed.has(n))
305
- .sort((a, b) => a - b)[0];
306
- if (typeof match !== 'number') continue;
307
- claimed.add(match);
308
- mapping[entity.slug] = {
309
- issueNumber: match,
310
- contentHash: '',
311
- lastObservedAgentState: null,
312
- entity: entity.entity,
313
- parentSlug: entity.parentSlug,
314
- ...(entity.dependsOn.length > 0 ? { dependsOn: entity.dependsOn } : {}),
315
- };
316
- reseeded.push({ slug: entity.slug, issueNumber: match });
317
- }
318
-
319
- return { state: { ...state, mapping }, reseeded };
320
- }
321
-
322
- /**
323
- * Prompt the operator for confirmation on the supplied plan. Resolves
324
- * `true` when the user answers `y`/`yes` (case-insensitive), `false`
325
- * otherwise. Uses `readline` against `process.stdin`/`process.stdout` so
326
- * stubs (test) and the real TTY both work.
327
- *
328
- * @param {object} [opts]
329
- * @param {NodeJS.ReadableStream} [opts.input]
330
- * @param {NodeJS.WritableStream} [opts.output]
331
- * @returns {Promise<boolean>}
332
- */
333
- export function confirmInteractive(opts = {}) {
334
- const input = opts.input ?? process.stdin;
335
- const output = opts.output ?? process.stdout;
336
- return new Promise((resolve) => {
337
- const rl = readline.createInterface({ input, output });
338
- rl.question('Apply this plan? [y/N] ', (answer) => {
339
- rl.close();
340
- const trimmed = String(answer ?? '')
341
- .trim()
342
- .toLowerCase();
343
- resolve(trimmed === 'y' || trimmed === 'yes');
344
- });
345
- });
346
- }
347
-
348
- /**
349
- * Identify whether the plan carries any close ops. The exit-code
350
- * contract gates close ops behind `--explicit-delete`; this predicate
351
- * is the canonical "needs --explicit-delete" check. Exported so the
352
- * contract tests can pin the predicate without re-deriving it.
353
- *
354
- * @param {object} plan
355
- * @returns {boolean}
356
- */
357
- export function planHasCloses(plan) {
358
- return Array.isArray(plan?.closes) && plan.closes.length > 0;
359
- }
360
-
361
- /**
362
- * Render the operator-facing message printed on the exit-2 path. Names
363
- * every close-op slug so the operator can audit precisely what would be
364
- * removed before re-running with `--explicit-delete`. Exported so the
365
- * contract tests assert the rendering without re-implementing it.
366
- *
367
- * @param {object} plan
368
- * @returns {string}
369
- */
370
- export function renderExplicitDeleteMessage(plan) {
371
- const closes = Array.isArray(plan?.closes) ? plan.closes : [];
372
- const slugList = closes
373
- .map((op) => `#${op.issueNumber ?? '?'} (${op.slug})`)
374
- .join(', ');
375
- return (
376
- `Plan carries ${closes.length} close operation(s): ${slugList}. ` +
377
- `Re-run with --explicit-delete to apply.`
378
- );
379
- }
380
-
381
- /**
382
- * Format the spec-validation failure for stderr — names the failing
383
- * JSON Pointer and the underlying schema message so operators can fix
384
- * the spec without re-running with --verbose.
385
- *
386
- * @param {SpecValidationError|SpecParseError|SpecNotFoundError} err
387
- * @returns {string}
388
- */
389
- function formatSpecError(err) {
390
- if (err instanceof SpecValidationError) {
391
- // Render every issue on its own line so scripts that grep stderr for
392
- // a JSON Pointer find each failure individually. The first issue is
393
- // duplicated into the headline so existing log scrapers reading line 1
394
- // keep working.
395
- const issues = Array.isArray(err.issues) ? err.issues : [];
396
- if (issues.length === 0) {
397
- return `Spec validation failed: ${err.message}`;
398
- }
399
- const head = issues[0];
400
- const lines = [
401
- `Spec validation failed at ${head.path}: ${head.message}`,
402
- ...issues.slice(1).map((iss) => ` · ${iss.path}: ${iss.message}`),
403
- ];
404
- return lines.join('\n');
405
- }
406
- if (err instanceof SpecParseError) {
407
- return `Spec YAML parse error in ${err.filePath}: ${err.cause?.message ?? err.message}`;
408
- }
409
- if (err instanceof SpecNotFoundError) {
410
- return `Spec file missing for epic ${err.epicId}: ${err.filePath}`;
411
- }
412
- return `Spec error: ${err.message ?? err}`;
413
- }
414
-
415
- /**
416
- * Run one reconcile cycle for a given Epic. Pure dependency-injection
417
- * surface — every external collaborator (provider, loaders, formatter,
418
- * apply, confirm prompt, log sinks) is overridable so the contract tests
419
- * can drive the CLI without hitting the network, the file system, or a
420
- * real TTY.
421
- *
422
- * @param {{
423
- * epicId: number,
424
- * dryRun: boolean,
425
- * apply: boolean,
426
- * explicitDelete: boolean,
427
- * yes: boolean,
428
- * }} args
429
- * @param {{
430
- * provider?: object,
431
- * loadSpec?: typeof loadSpec,
432
- * loadState?: typeof loadState,
433
- * diff?: typeof diff,
434
- * apply?: typeof apply,
435
- * formatPlan?: typeof formatPlan,
436
- * fetchGhState?: typeof fetchGhState,
437
- * confirm?: typeof confirmInteractive,
438
- * isTty?: () => boolean,
439
- * stdout?: (line: string) => void,
440
- * stderr?: (line: string) => void,
441
- * loaderOpts?: object,
442
- * }} [deps]
443
- * @returns {Promise<{exitCode: number, plan?: object, applyResult?: object}>}
444
- */
445
- export async function runReconcile(args, deps = {}) {
446
- const stdout = deps.stdout ?? defaultStdout;
447
- const stderr = deps.stderr ?? defaultStderr;
448
- const loadSpecFn = deps.loadSpec ?? loadSpec;
449
- const loadStateFn = deps.loadState ?? loadState;
450
- const diffFn = deps.diff ?? diff;
451
- const applyFn = deps.apply ?? apply;
452
- const formatFn = deps.formatPlan ?? formatPlan;
453
- const fetchGhStateFn = deps.fetchGhState ?? fetchGhState;
454
- const confirmFn = deps.confirm ?? confirmInteractive;
455
- const isTtyFn = deps.isTty ?? (() => Boolean(process.stdin.isTTY));
456
- const loaderOpts = deps.loaderOpts ?? {};
457
-
458
- if (!Number.isInteger(args.epicId) || args.epicId <= 0) {
459
- stderr('[epic-reconcile] Error: epic id is required and must be positive');
460
- stderr(
461
- 'Usage: epic-reconcile.js <epicId> [--dry-run] [--apply] [--explicit-delete] [--yes]',
462
- );
463
- return { exitCode: EXIT_CODES.VALIDATION_ERROR };
464
- }
465
-
466
- // 1. Load + validate the spec.
467
- let spec;
468
- try {
469
- spec = loadSpecFn(args.epicId, loaderOpts);
470
- } catch (err) {
471
- stderr(`[epic-reconcile] ${formatSpecError(err)}`);
472
- return { exitCode: EXIT_CODES.VALIDATION_ERROR };
473
- }
474
-
475
- // 2. Load the state (absent file → empty mapping).
476
- let state;
477
- try {
478
- state = loadStateFn(args.epicId, loaderOpts);
479
- } catch (_err) {
480
- state = { epicId: args.epicId, mapping: {} };
481
- }
482
- if (!state || typeof state !== 'object') {
483
- state = { epicId: args.epicId, mapping: {} };
484
- }
485
-
486
- // 2a. Seed the synthetic `epic` slug mapping. The diff engine's
487
- // `flattenSpec` unconditionally yields an entity for the epic, and
488
- // `applyCreate` carries a long-standing comment asserting the epic
489
- // "is bootstrapped before reconciliation". On a fresh apply with no
490
- // state.json, nothing previously did that — so diff emitted a Create
491
- // op for the epic and `provider.createTicket` materialised a duplicate
492
- // GH issue (Story #1820). The CLI is the right seam to seed it: keeps
493
- // diff/apply pure and one-shot per reconcile run.
494
- if (!state.mapping || typeof state.mapping !== 'object') {
495
- state.mapping = {};
496
- }
497
- if (!state.mapping.epic) {
498
- state.mapping.epic = {
499
- issueNumber: args.epicId,
500
- contentHash: '',
501
- lastObservedAgentState: null,
502
- entity: 'epic',
503
- parentSlug: null,
504
- };
505
- }
506
-
507
- // 3. Resolve provider, fetch GH state.
508
- let provider = deps.provider;
509
- if (!provider) {
510
- const config = deps.config ?? resolveConfig({});
511
- provider = createProvider(config);
512
- }
513
- const ghState = await fetchGhStateFn(provider, args.epicId);
514
-
515
- // 3a. Reseed the slug→issue mapping from live GH state for any spec
516
- // slug the state file did not cover (Story #3905). Without this, a
517
- // `--resume` (or any apply) run with a missing/empty `state.json` but
518
- // open children present would diff every spec slug as a Create and
519
- // duplicate the entire Feature/Story tree. Title-matched recovery turns
520
- // those spurious Creates into Updates/no-ops. Pure — no I/O.
521
- const { state: reseededState, reseeded } = reseedMappingFromGh(
522
- state,
523
- spec,
524
- ghState,
525
- );
526
- state = reseededState;
527
- if (reseeded.length > 0) {
528
- stderr(
529
- `[epic-reconcile] Reseeded ${reseeded.length} slug→issue mapping(s) from live GitHub state ` +
530
- `(state.json was missing/incomplete): ${reseeded
531
- .map((r) => `${r.slug}→#${r.issueNumber}`)
532
- .join(', ')}.`,
533
- );
534
- }
535
-
536
- // 4. Compute the plan.
537
- const plan = diffFn({ spec, state, ghState });
538
-
539
- // 5. Render.
540
- stdout(formatFn(plan));
541
-
542
- // 6a. Dry-run path — print and exit 0 regardless of plan size.
543
- if (args.dryRun) {
544
- return { exitCode: EXIT_CODES.OK, plan };
545
- }
546
-
547
- // 6b. Empty plan: nothing to apply. Exit 0 without prompting.
548
- if (isEmptyPlan(plan)) {
549
- return { exitCode: EXIT_CODES.OK, plan };
550
- }
551
-
552
- // 6c. Explicit-delete pre-flight: any close op requires the flag. We
553
- // gate at the CLI surface (before apply) so the exit code is stable
554
- // regardless of whether apply's discriminator would also reject — the
555
- // task contract pins exit 2 to "would close without --explicit-delete",
556
- // not "apply engine rejected".
557
- if (planHasCloses(plan) && !args.explicitDelete) {
558
- stderr(`[epic-reconcile] ${renderExplicitDeleteMessage(plan)}`);
559
- return { exitCode: EXIT_CODES.EXPLICIT_DELETE_REQUIRED, plan };
560
- }
561
-
562
- // 6d. Confirmation gate.
563
- if (!args.yes) {
564
- if (!isTtyFn()) {
565
- stderr(
566
- '[epic-reconcile] --apply requires either --yes or an interactive TTY.',
567
- );
568
- return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan };
569
- }
570
- const confirmed = await confirmFn();
571
- if (!confirmed) {
572
- stdout('[epic-reconcile] Aborted by operator.');
573
- return { exitCode: EXIT_CODES.OK, plan };
574
- }
575
- }
576
-
577
- // 6e. Apply.
578
- let applyResult;
579
- try {
580
- applyResult = await applyFn(plan, provider, {
581
- epicId: args.epicId,
582
- spec,
583
- priorState: state,
584
- explicitDelete: args.explicitDelete,
585
- });
586
- } catch (err) {
587
- if (
588
- err instanceof ApplyGateViolation &&
589
- err.reason === 'explicit-delete-required'
590
- ) {
591
- stderr(`[epic-reconcile] ${err.message}`);
592
- return { exitCode: EXIT_CODES.EXPLICIT_DELETE_REQUIRED, plan };
593
- }
594
- stderr(`[epic-reconcile] Apply failed: ${err.message ?? err}`);
595
- return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan };
596
- }
597
-
598
- if (applyResult?.failure) {
599
- stderr(
600
- `[epic-reconcile] Apply completed with partial failure: ${applyResult.failure.message ?? applyResult.failure}`,
601
- );
602
- return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan, applyResult };
603
- }
604
-
605
- stdout('[epic-reconcile] Apply complete.');
606
- return { exitCode: EXIT_CODES.OK, plan, applyResult };
607
- }
608
-
609
- /**
610
- * CLI entry point. Parses argv, delegates to `runReconcile`, and exits
611
- * with the contract-defined integer.
612
- */
613
- async function main(argv = process.argv.slice(2)) {
614
- const args = parseCli(argv);
615
- const { exitCode } = await runReconcile(args);
616
- process.exit(exitCode);
617
- }
618
-
619
- runAsCli(import.meta.url, main, {
620
- source: 'epic-reconcile',
621
- onError: (err) => {
622
- Logger.error('[epic-reconcile] Fatal error:', err?.message ?? err);
623
- process.exit(EXIT_CODES.VALIDATION_ERROR);
624
- },
625
- });