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,209 +0,0 @@
1
- /**
2
- * push-epic-retry.js — Bounded retry for concurrent epic-branch pushes.
3
- *
4
- * story-close holds a per-Epic filesystem lock for the rebase / merge /
5
- * push sequence, but that lock is local to one machine. N sprint sessions on
6
- * different machines (or different Claude Code web workers) will not see each
7
- * other's lock and can race on `origin/epic/<id>`. Whoever pushes second will
8
- * be rejected with a non-fast-forward error.
9
- *
10
- * This module provides `pushEpicWithRetry`: first push attempt is a direct
11
- * `git push` — single-session behaviour is byte-identical to pre-change code.
12
- * Only on a non-fast-forward rejection do we enter the retry loop: fetch the
13
- * advanced remote, reset local epic onto `origin/<epic>`, reapply the story
14
- * merge, and retry the push. A real content conflict during re-apply is
15
- * non-recoverable: we abort the merge (restoring a clean tree) and throw a
16
- * `PushRetryConflictError` naming the conflicting files.
17
- */
18
-
19
- import { DEFAULT_STORY_MERGE_RETRY } from './config/runners.js';
20
-
21
- /**
22
- * Stderr patterns git emits when `git push` is rejected because the remote
23
- * tip has advanced beyond our local branch head. Only these patterns trigger
24
- * a retry; every other push error surfaces immediately so we do not mask
25
- * permission denials, protected-branch rejections, or network errors behind
26
- * a retry loop.
27
- */
28
- const NON_FAST_FORWARD_PATTERNS = [
29
- /non-fast-forward/i,
30
- /fetch first/i,
31
- /failed to push some refs/i,
32
- /\[rejected\]/,
33
- /Updates were rejected/i,
34
- ];
35
-
36
- export function isNonFastForwardPush(stderr) {
37
- if (!stderr) return false;
38
- return NON_FAST_FORWARD_PATTERNS.some((p) => p.test(stderr));
39
- }
40
-
41
- /**
42
- * Thrown when the retry loop's re-apply step (`git merge --no-ff story-<id>`
43
- * onto the freshly-fetched epic tip) hits a real content conflict. The
44
- * caller should surface this to the operator — we do not attempt automated
45
- * resolution. The merge is aborted before throwing so the working tree is
46
- * clean and recoverable.
47
- */
48
- export class PushRetryConflictError extends Error {
49
- constructor(conflictFiles, gitStderr) {
50
- const fileList = conflictFiles.length
51
- ? conflictFiles.join(', ')
52
- : '(unknown)';
53
- super(
54
- `Content conflict while reapplying story merge onto origin/epic. ` +
55
- `Conflicting file(s): ${fileList}. ` +
56
- `The merge has been aborted and the working tree is clean — no ` +
57
- `half-merged files remain. Resolve manually by rebasing the story ` +
58
- `branch onto the updated epic, committing, and re-running ` +
59
- `story-close.\n\n` +
60
- `git stderr:\n${gitStderr}`,
61
- );
62
- this.name = 'PushRetryConflictError';
63
- this.conflictFiles = conflictFiles;
64
- }
65
- }
66
-
67
- /** Re-apply the story merge during a non-ff retry, optionally pinning the
68
- * conventional-commit subject so the post-merge `(resolves #N)` grep keeps
69
- * finding the merge commit (without it, git falls back to its default
70
- * `Merge branch …` subject and the grep misses). */
71
- function runRetryMerge({ cwd, git, storyBranch, mergeMessage }) {
72
- return mergeMessage
73
- ? git.gitSpawn(cwd, 'merge', '--no-ff', '-m', mergeMessage, storyBranch)
74
- : git.gitSpawn(cwd, 'merge', '--no-ff', '--no-edit', storyBranch);
75
- }
76
-
77
- /** Collect the list of files with unmerged paths after a failed merge. */
78
- function collectConflictFiles(cwd, git) {
79
- const result = git.gitSpawn(cwd, 'diff', '--name-only', '--diff-filter=U');
80
- if (result.status !== 0 || !result.stdout) return [];
81
- return result.stdout
82
- .split(/\r?\n/)
83
- .map((s) => s.trim())
84
- .filter(Boolean);
85
- }
86
-
87
- /**
88
- * Push `epicBranch` with a bounded retry on non-fast-forward rejections.
89
- *
90
- * The first attempt is a direct push (no fetch, no reapply) so single-session
91
- * runs behave identically to the pre-change path. Retries fetch the advanced
92
- * remote, reset local `<epicBranch>` to `origin/<epicBranch>`, and reapply
93
- * `<storyBranch>` via `merge --no-ff --no-edit`.
94
- *
95
- * @param {object} opts
96
- * @param {string} opts.cwd
97
- * @param {string} opts.epicBranch
98
- * @param {string} opts.storyBranch
99
- * @param {{ maxAttempts?: number, backoffMs?: number[] }} [opts.storyMergeRetry]
100
- * @param {{ gitSpawn: (cwd: string, ...args: string[]) => { status: number, stdout: string, stderr: string } }} opts.git
101
- * @param {(ms: number) => Promise<void>} [opts.sleep]
102
- * @param {(msg: string) => void} [opts.log]
103
- * @returns {Promise<{ ok: boolean, attempts: number, reason?: string, result: object }>}
104
- * @throws {PushRetryConflictError} On content conflict during re-apply.
105
- */
106
- export async function pushEpicWithRetry({
107
- cwd,
108
- epicBranch,
109
- storyBranch,
110
- storyMergeRetry,
111
- git,
112
- sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
113
- log = () => {},
114
- mergeMessage = null,
115
- }) {
116
- if (!cwd) throw new Error('pushEpicWithRetry: cwd is required');
117
- if (!epicBranch) throw new Error('pushEpicWithRetry: epicBranch is required');
118
- if (!storyBranch)
119
- throw new Error('pushEpicWithRetry: storyBranch is required');
120
- if (!git || typeof git.gitSpawn !== 'function') {
121
- throw new Error('pushEpicWithRetry: git.gitSpawn injection is required');
122
- }
123
-
124
- const maxAttempts =
125
- storyMergeRetry?.maxAttempts ?? DEFAULT_STORY_MERGE_RETRY.maxAttempts;
126
- const backoffMs =
127
- storyMergeRetry?.backoffMs ?? DEFAULT_STORY_MERGE_RETRY.backoffMs;
128
-
129
- let lastResult;
130
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
131
- lastResult = git.gitSpawn(cwd, 'push', '--no-verify', 'origin', epicBranch);
132
- if (lastResult.status === 0) {
133
- return { ok: true, attempts: attempt, result: lastResult };
134
- }
135
-
136
- if (!isNonFastForwardPush(lastResult.stderr)) {
137
- return {
138
- ok: false,
139
- attempts: attempt,
140
- result: lastResult,
141
- reason: 'non-retryable-push-error',
142
- };
143
- }
144
-
145
- if (attempt === maxAttempts) {
146
- return {
147
- ok: false,
148
- attempts: attempt,
149
- result: lastResult,
150
- reason: 'retry-exhausted',
151
- };
152
- }
153
-
154
- const backoff = backoffMs[Math.min(attempt - 1, backoffMs.length - 1)] ?? 0;
155
- log(
156
- `[push-epic-retry] Push rejected (non-fast-forward) on attempt ` +
157
- `${attempt}/${maxAttempts}; fetching remote and reapplying after ` +
158
- `${backoff}ms backoff.`,
159
- );
160
- await sleep(backoff);
161
-
162
- const fetchResult = git.gitSpawn(cwd, 'fetch', 'origin', epicBranch);
163
- if (fetchResult.status !== 0) {
164
- return {
165
- ok: false,
166
- attempts: attempt,
167
- result: fetchResult,
168
- reason: 'fetch-failed',
169
- };
170
- }
171
-
172
- const resetResult = git.gitSpawn(
173
- cwd,
174
- 'reset',
175
- '--hard',
176
- `origin/${epicBranch}`,
177
- );
178
- if (resetResult.status !== 0) {
179
- return {
180
- ok: false,
181
- attempts: attempt,
182
- result: resetResult,
183
- reason: 'reset-failed',
184
- };
185
- }
186
-
187
- const mergeResult = runRetryMerge({
188
- cwd,
189
- git,
190
- storyBranch,
191
- mergeMessage,
192
- });
193
- if (mergeResult.status !== 0) {
194
- const conflicts = collectConflictFiles(cwd, git);
195
- git.gitSpawn(cwd, 'merge', '--abort');
196
- throw new PushRetryConflictError(
197
- conflicts,
198
- mergeResult.stderr || mergeResult.stdout || '',
199
- );
200
- }
201
- }
202
-
203
- return {
204
- ok: false,
205
- attempts: maxAttempts,
206
- result: lastResult,
207
- reason: 'retry-exhausted',
208
- };
209
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * lib/spec/index.js — public surface for the spec I/O module.
3
- *
4
- * Re-exports the loader (and, in Wave 1, future spec utilities) so
5
- * downstream consumers — the reconciler, the rewritten /plan, the
6
- * wave-runner — can `import * from '../lib/spec/index.js'` without
7
- * reaching into individual submodules.
8
- *
9
- * The module intentionally re-exports only the public surface defined
10
- * by Story #1491; the loader's internal helpers (`_resetValidatorCacheForTests`,
11
- * `sortKeysDeep`, `renderStateJson`) remain importable from
12
- * `./loader.js` directly for tests but are not part of the consumer
13
- * contract.
14
- */
15
-
16
- export {
17
- loadSpec,
18
- loadState,
19
- SpecNotFoundError,
20
- SpecParseError,
21
- SpecValidationError,
22
- specPath,
23
- statePath,
24
- writeSpec,
25
- writeState,
26
- } from './loader.js';
27
-
28
- export {
29
- buildState,
30
- canonicalise,
31
- canonicalStringify,
32
- hashSpecEntry,
33
- iterSpecEntries,
34
- projectMapping,
35
- sha256Hex,
36
- } from './state.js';
@@ -1,425 +0,0 @@
1
- /**
2
- * lib/spec/loader.js — spec + state file I/O for the epic-spec reconciler.
3
- *
4
- * Owns the two files that bracket the structural SSOT migration (Epic
5
- * #1182 / Tech Spec #1483):
6
- *
7
- * - `temp/epic-<epic-id>/<epic-id>.yaml` — declarative spec (regenerated)
8
- * - `temp/epic-<epic-id>/<epic-id>.state.json` — slug→issue mapping (observed)
9
- *
10
- * Both files live under the per-Epic ephemeral tree `temp/epic-<id>/`
11
- * (already gitignored) so /plan reruns don't churn a tracked path
12
- * and so concurrent Epics never collide on a single shared directory.
13
- * Tests inject `opts.epicsDir` to point at a sandbox; the default for
14
- * production callers is derived from `lib/config/temp-paths.js#epicTempDir`.
15
- *
16
- * The module is intentionally a thin, dependency-light I/O layer:
17
- *
18
- * • `loadSpec(epicId)` → parses YAML, validates against
19
- * `.agents/schemas/epic-spec.schema.json` (Ajv2020). Throws a
20
- * `SpecValidationError` carrying the offending JSON Pointer paths
21
- * when the spec is structurally invalid. Missing file throws
22
- * `SpecNotFoundError`.
23
- * • `loadState(epicId)` → returns `{ epicId, mapping: {} }` (with
24
- * `lastReconciledAt` omitted) when the state file is missing, so
25
- * callers can start reconciling against a fresh Epic without a
26
- * pre-existing state file.
27
- * • `writeState(epicId, state)` → writes pretty-printed JSON with
28
- * deterministically sorted keys (recursive). Repeated writes of an
29
- * equivalent state produce a byte-identical file. Trailing newline
30
- * included so the file behaves well under git + POSIX `cat`.
31
- *
32
- * The loader does **not** make any GitHub calls; it is pure file I/O
33
- * over the two on-disk artefacts. The reconciler (Wave 1) layers diff
34
- * + apply on top of this surface.
35
- *
36
- * All public functions accept an optional `{ epicsDir, schemaPath, fs }`
37
- * options bag so tests can point the loader at a sandbox directory
38
- * without monkey-patching `process.cwd()` or the project schema path.
39
- *
40
- * Cross-references:
41
- * - Schema: `.agents/schemas/epic-spec.schema.json` (Story #1490)
42
- * - Fixtures: `tests/fixtures/epic-specs/*.json` (Story #1490)
43
- * - Tech Spec §"`.agents/epics/<epic-id>.yaml` (spec)" + §"state.json"
44
- */
45
-
46
- import {
47
- existsSync as defaultExistsSync,
48
- mkdirSync as defaultMkdirSync,
49
- readFileSync as defaultReadFileSync,
50
- writeFileSync as defaultWriteFileSync,
51
- } from 'node:fs';
52
- import path from 'node:path';
53
- import { fileURLToPath } from 'node:url';
54
- import Ajv2020 from 'ajv/dist/2020.js';
55
- import addFormats from 'ajv-formats';
56
- import yaml from 'js-yaml';
57
-
58
- import { epicTempDir } from '../config/temp-paths.js';
59
-
60
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
61
-
62
- // scripts/lib/spec/ → scripts/lib/ → scripts/ → .agents/
63
- const PROJECT_AGENTS_DIR = path.resolve(__dirname, '..', '..', '..');
64
- const DEFAULT_SCHEMA_PATH = path.join(
65
- PROJECT_AGENTS_DIR,
66
- 'schemas',
67
- 'epic-spec.schema.json',
68
- );
69
-
70
- // Resolve the default per-Epic spec directory under `temp/epic-<id>/`.
71
- // Caller-injected `opts.epicsDir` still wins for tests and any external
72
- // tooling that wants to point at a sandbox. Production callers omit the
73
- // option and route through this helper.
74
- function defaultEpicsDir(epicId) {
75
- return epicTempDir(epicId);
76
- }
77
-
78
- const defaultFsAdapter = Object.freeze({
79
- existsSync: defaultExistsSync,
80
- mkdirSync: defaultMkdirSync,
81
- readFileSync: defaultReadFileSync,
82
- writeFileSync: defaultWriteFileSync,
83
- });
84
-
85
- let cachedValidator = null;
86
- let cachedValidatorKey = null;
87
-
88
- /**
89
- * Compile (and cache) the Ajv2020 validator for the epic-spec schema.
90
- * Cached by absolute schema path so tests can swap to a sandbox schema.
91
- *
92
- * @param {string} schemaPath
93
- * @param {{ readFileSync: typeof defaultReadFileSync }} fs
94
- * @returns {(data: unknown) => boolean}
95
- */
96
- function getValidator(schemaPath, fs) {
97
- if (cachedValidator && cachedValidatorKey === schemaPath) {
98
- return cachedValidator;
99
- }
100
- const ajv = new Ajv2020({ allErrors: true, strict: false });
101
- addFormats(ajv);
102
- const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
103
- cachedValidator = ajv.compile(schema);
104
- cachedValidatorKey = schemaPath;
105
- return cachedValidator;
106
- }
107
-
108
- /**
109
- * Test-only hook: drop the cached validator so a subsequent call
110
- * recompiles. Safe to leave exported — production code never invokes it.
111
- */
112
- export function _resetValidatorCacheForTests() {
113
- cachedValidator = null;
114
- cachedValidatorKey = null;
115
- }
116
-
117
- /**
118
- * Structured error raised by `loadSpec` when the YAML parses but fails
119
- * schema validation. The Ajv error list is normalised to an array of
120
- * `{ path, message }` so callers (reconciler CLI, tests) can render the
121
- * offending JSON Pointer without re-parsing the Ajv envelope.
122
- */
123
- export class SpecValidationError extends Error {
124
- /**
125
- * @param {string} epicId
126
- * @param {Array<{path: string, message: string, params?: object}>} issues
127
- */
128
- constructor(epicId, issues) {
129
- const head = issues[0] ?? { path: '/', message: 'unknown' };
130
- super(
131
- `Spec for epic ${epicId} failed schema validation at ${head.path}: ${head.message}`,
132
- );
133
- this.name = 'SpecValidationError';
134
- this.epicId = epicId;
135
- this.issues = issues;
136
- }
137
- }
138
-
139
- /**
140
- * Raised by `loadSpec` when the on-disk YAML file does not exist.
141
- */
142
- export class SpecNotFoundError extends Error {
143
- /**
144
- * @param {string} epicId
145
- * @param {string} filePath
146
- */
147
- constructor(epicId, filePath) {
148
- super(`Spec file missing for epic ${epicId}: ${filePath}`);
149
- this.name = 'SpecNotFoundError';
150
- this.epicId = epicId;
151
- this.filePath = filePath;
152
- }
153
- }
154
-
155
- /**
156
- * Raised by `loadSpec` when the file exists but is not parseable YAML.
157
- */
158
- export class SpecParseError extends Error {
159
- /**
160
- * @param {string} epicId
161
- * @param {string} filePath
162
- * @param {Error} cause
163
- */
164
- constructor(epicId, filePath, cause) {
165
- super(
166
- `Spec file for epic ${epicId} is not valid YAML (${filePath}): ${cause.message}`,
167
- );
168
- this.name = 'SpecParseError';
169
- this.epicId = epicId;
170
- this.filePath = filePath;
171
- this.cause = cause;
172
- }
173
- }
174
-
175
- function resolveOpts(epicId, opts = {}) {
176
- return {
177
- epicsDir: opts.epicsDir ?? defaultEpicsDir(epicId),
178
- schemaPath: opts.schemaPath ?? DEFAULT_SCHEMA_PATH,
179
- fs: opts.fs ?? defaultFsAdapter,
180
- };
181
- }
182
-
183
- /**
184
- * Resolve the on-disk spec path for `epicId` under the configured
185
- * epics dir. Exported for tests and for the reconciler CLI's error
186
- * messages.
187
- *
188
- * @param {number|string} epicId
189
- * @param {{epicsDir?: string}} [opts]
190
- * @returns {string}
191
- */
192
- export function specPath(epicId, opts = {}) {
193
- const { epicsDir } = resolveOpts(epicId, opts);
194
- return path.join(epicsDir, `${String(epicId)}.yaml`);
195
- }
196
-
197
- /**
198
- * Resolve the on-disk state path for `epicId` under the configured
199
- * epics dir.
200
- *
201
- * @param {number|string} epicId
202
- * @param {{epicsDir?: string}} [opts]
203
- * @returns {string}
204
- */
205
- export function statePath(epicId, opts = {}) {
206
- const { epicsDir } = resolveOpts(epicId, opts);
207
- return path.join(epicsDir, `${String(epicId)}.state.json`);
208
- }
209
-
210
- /**
211
- * Convert Ajv's error array into the loader's `{ path, message }`
212
- * shape. Ajv2020 uses `instancePath` for the JSON Pointer into the
213
- * data; for `required` errors it leaves the missing property in
214
- * `params.missingProperty` rather than the path, so we append it so
215
- * the caller sees `/epic` instead of `` (root) for the canonical
216
- * `epic required` failure.
217
- *
218
- * @param {Array<{instancePath:string,message:string,keyword:string,params?:Record<string,unknown>}>} ajvErrors
219
- * @returns {Array<{path:string,message:string,params?:object}>}
220
- */
221
- function normaliseAjvErrors(ajvErrors) {
222
- return ajvErrors.map((err) => {
223
- let p = err.instancePath || '/';
224
- if (
225
- err.keyword === 'required' &&
226
- typeof err.params?.missingProperty === 'string'
227
- ) {
228
- const sep = p === '/' ? '' : '/';
229
- p = `${p}${sep}${err.params.missingProperty}`;
230
- }
231
- return {
232
- path: p,
233
- message: err.message ?? 'validation failed',
234
- params: err.params,
235
- };
236
- });
237
- }
238
-
239
- /**
240
- * Load and validate the spec YAML for `epicId`. Returns the parsed
241
- * JavaScript object on success. Throws `SpecNotFoundError`,
242
- * `SpecParseError`, or `SpecValidationError` otherwise.
243
- *
244
- * @param {number|string} epicId
245
- * @param {{epicsDir?: string, schemaPath?: string, fs?: typeof defaultFsAdapter}} [opts]
246
- * @returns {object}
247
- */
248
- export function loadSpec(epicId, opts = {}) {
249
- const { schemaPath, fs } = resolveOpts(epicId, opts);
250
- const filePath = specPath(epicId, opts);
251
-
252
- if (!fs.existsSync(filePath)) {
253
- throw new SpecNotFoundError(String(epicId), filePath);
254
- }
255
-
256
- const raw = fs.readFileSync(filePath, 'utf8');
257
- let parsed;
258
- try {
259
- parsed = yaml.load(raw, { filename: filePath });
260
- } catch (err) {
261
- throw new SpecParseError(String(epicId), filePath, err);
262
- }
263
-
264
- if (parsed == null || typeof parsed !== 'object') {
265
- throw new SpecValidationError(String(epicId), [
266
- {
267
- path: '/',
268
- message: 'spec root must be an object',
269
- },
270
- ]);
271
- }
272
-
273
- const validate = getValidator(schemaPath, fs);
274
- const ok = validate(parsed);
275
- if (!ok) {
276
- throw new SpecValidationError(
277
- String(epicId),
278
- normaliseAjvErrors(validate.errors ?? []),
279
- );
280
- }
281
-
282
- return parsed;
283
- }
284
-
285
- /**
286
- * Empty-state default. `loadState` returns this shape when the state
287
- * file does not exist; callers can rely on `mapping` being a plain
288
- * object (never undefined).
289
- *
290
- * @param {number|string} epicId
291
- * @returns {{epicId: number, mapping: Record<string, never>}}
292
- */
293
- function emptyState(epicId) {
294
- return { epicId: Number(epicId), mapping: {} };
295
- }
296
-
297
- /**
298
- * Load the state file for `epicId`. Returns an empty mapping when the
299
- * file is missing (the canonical "fresh Epic" case the reconciler
300
- * faces on first apply). Throws if the file exists but is not valid
301
- * JSON.
302
- *
303
- * @param {number|string} epicId
304
- * @param {{epicsDir?: string, fs?: typeof defaultFsAdapter}} [opts]
305
- * @returns {{epicId: number, mapping: object, lastReconciledAt?: string}}
306
- */
307
- export function loadState(epicId, opts = {}) {
308
- const { fs } = resolveOpts(epicId, opts);
309
- const filePath = statePath(epicId, opts);
310
-
311
- if (!fs.existsSync(filePath)) {
312
- return emptyState(epicId);
313
- }
314
-
315
- const raw = fs.readFileSync(filePath, 'utf8');
316
- return JSON.parse(raw);
317
- }
318
-
319
- /**
320
- * Recursively sort object keys (arrays preserve order). Returns a new
321
- * value with the same shape — leaves are returned unchanged.
322
- *
323
- * Exported for `state-writer.js` so the hashing path can share the
324
- * exact same canonicalisation as the file writer.
325
- *
326
- * @param {unknown} value
327
- * @returns {unknown}
328
- */
329
- export function sortKeysDeep(value) {
330
- if (Array.isArray(value)) {
331
- return value.map(sortKeysDeep);
332
- }
333
- if (value && typeof value === 'object') {
334
- const out = {};
335
- for (const key of Object.keys(value).sort()) {
336
- out[key] = sortKeysDeep(value[key]);
337
- }
338
- return out;
339
- }
340
- return value;
341
- }
342
-
343
- /**
344
- * Render `state` to deterministic JSON. Public so the test suite can
345
- * assert the byte-identical-roundtrip property without re-implementing
346
- * the formatter.
347
- *
348
- * @param {object} state
349
- * @returns {string} pretty-printed JSON, terminated by a single newline.
350
- */
351
- export function renderStateJson(state) {
352
- return `${JSON.stringify(sortKeysDeep(state), null, 2)}\n`;
353
- }
354
-
355
- /**
356
- * Write the state file for `epicId`. Creates the parent directory
357
- * lazily. Object keys are recursively sorted so re-writing the same
358
- * logical state produces a byte-identical file (AC: "diffs stay
359
- * stable", "byte-identical when written twice").
360
- *
361
- * Returns the absolute path written so callers can log it.
362
- *
363
- * @param {number|string} epicId
364
- * @param {object} state
365
- * @param {{epicsDir?: string, fs?: typeof defaultFsAdapter}} [opts]
366
- * @returns {string}
367
- */
368
- export function writeState(epicId, state, opts = {}) {
369
- const { epicsDir, fs } = resolveOpts(epicId, opts);
370
- if (!fs.existsSync(epicsDir)) {
371
- fs.mkdirSync(epicsDir, { recursive: true });
372
- }
373
- const filePath = statePath(epicId, opts);
374
- fs.writeFileSync(filePath, renderStateJson(state), 'utf8');
375
- return filePath;
376
- }
377
-
378
- /**
379
- * Write the spec YAML file for `epicId`. Creates the parent directory
380
- * lazily and emits a top-level `$schema` reference so editors with
381
- * YAML-schema autocomplete (e.g. the Red Hat YAML extension) resolve
382
- * the schema from the file itself.
383
- *
384
- * Story #1498 / Task #1525 introduced this writer so the rewritten
385
- * `/plan` halves can persist the spec from the decomposer's
386
- * ticket-array projection (`renderSpec`) without reaching into raw
387
- * `js-yaml` calls scattered across the planning scripts.
388
- *
389
- * The function validates the spec via the same Ajv2020 compiler the
390
- * loader caches — a malformed spec is rejected synchronously instead of
391
- * being persisted and tripping `loadSpec` on the next reconciler run.
392
- *
393
- * @param {number|string} epicId
394
- * @param {object} spec spec object matching `epic-spec.schema.json`.
395
- * @param {{epicsDir?: string, schemaPath?: string, fs?: typeof defaultFsAdapter}} [opts]
396
- * @returns {string} the absolute path written.
397
- */
398
- export function writeSpec(epicId, spec, opts = {}) {
399
- const { epicsDir, schemaPath, fs } = resolveOpts(epicId, opts);
400
- if (!spec || typeof spec !== 'object') {
401
- throw new TypeError('[writeSpec] spec must be an object');
402
- }
403
- const validate = getValidator(schemaPath, fs);
404
- const ok = validate(spec);
405
- if (!ok) {
406
- throw new SpecValidationError(
407
- String(epicId),
408
- normaliseAjvErrors(validate.errors ?? []),
409
- );
410
- }
411
- if (!fs.existsSync(epicsDir)) {
412
- fs.mkdirSync(epicsDir, { recursive: true });
413
- }
414
- const filePath = specPath(epicId, opts);
415
- // Lazy require: `js-yaml` is already a runtime dep of the loader, but
416
- // keeping the import top-level would force every consumer of `loader.js`
417
- // to pay the parse cost even when they only need state helpers.
418
- const yamlDump = yaml.dump(spec, {
419
- noRefs: true,
420
- sortKeys: false,
421
- lineWidth: 120,
422
- });
423
- fs.writeFileSync(filePath, yamlDump, 'utf8');
424
- return filePath;
425
- }