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,197 +0,0 @@
1
- /**
2
- * lib/orchestration/retro-runner.js — In-process Retro module (sequencer).
3
- *
4
- * Story #1155 (Epic #1142, 5.40.0) — extracts the helper-driven
5
- * `epic-retro` invocation into a callable module so the
6
- * `/deliver` runner can fire Phase E without a separate LLM
7
- * helper turn. (Story #2259, Epic #2172: the legacy deliver-runner
8
- * CLI was retired once delivery moved entirely into the slash
9
- * command.) The retro fires before `/deliver`'s finalize step
10
- * opens the PR — the operator's PR-merge is the final human gate, not
11
- * the retro itself.
12
- *
13
- * Story #3001 — the runner body was decomposed into `./retro/phases/`
14
- * (one file per functional unit) following the established pattern at
15
- * `./git-cleanup/phases/`, `./story-close/phases/`, and
16
- * `./post-merge/phases/`. This file is now a thin sequencer that owns
17
- * the `runRetro` entry point and re-exports the phase-level helpers so
18
- * existing import sites stay unchanged.
19
- *
20
- * Public API:
21
- * - `runRetro({ epicId, provider, logger })` → `{ posted, compact, scorecard, body, defectClasses }`.
22
- * - `composeRetroBody(input)` (pure, exported for tests).
23
- * - `gatherRetroSignals({ epicId, provider })` (exported for tests).
24
- * - `appendChecksSection(body, findings)` (pure, exported for tests).
25
- * - `deriveDefectClasses(routedProposals)` (pure, exported for tests).
26
- *
27
- * Story #4135 (Epic #4131, F11) — the runner now derives a
28
- * **recurring-defect-class signal** from the routed-proposal actionable
29
- * items (categories that recurred ≥2 times across review/deliver-caught
30
- * friction, or a force-flagged `agent::blocked` category). The derived
31
- * classes ride on the `runRetro` envelope as `defectClasses[]` and are
32
- * stamped onto the proposed `gh issue create` commands as `friction::<class>`
33
- * labels (the routed-proposals composer already emits that label), which is
34
- * the durable substrate the `/plan` Phase 0 prior-feedback fetcher reads
35
- * back to surface recurring classes to the planner. The derivation is
36
- * **no-op-safe**: absent or empty routed proposals yield an empty array and
37
- * no behavioural change to the existing retro/post path.
38
- *
39
- * Behaviour:
40
- * - Reads child Stories' `story-perf-summary` comments to aggregate
41
- * `frictionByCategory` totals (Story #1046 unified-summary path).
42
- * - Reads child Stories' label sets to count HITL pause events
43
- * (`agent::blocked`).
44
- * - Reads the Epic's `parked-follow-ons` structured comment for the
45
- * parked + recut counts (with a fallback to per-Story body grep on the
46
- * `<!-- recut-of: #N -->` marker).
47
- * - Selects the compact (clean-manifest) or full retro shape via
48
- * `isCleanManifest`.
49
- * - Posts the composed markdown as a `retro` structured comment on the
50
- * Epic, terminated with the `retro-complete: <ISO>` HTML marker.
51
- * - **Never** routes through `notify.js` — GitHub is the sole retro
52
- * archive; the webhook must not see the retro body.
53
- */
54
-
55
- import nodeFs from 'node:fs';
56
-
57
- import { runChecks } from '../checks/index.js';
58
- import { assembleState } from '../checks/state.js';
59
- import { composeRetroBody } from './retro/phases/compose-body.js';
60
- import { gatherRetroSignals } from './retro/phases/gather-signals.js';
61
- import { composeAndPostRetro } from './retro/phases/post-and-mirror.js';
62
- import { upsertStructuredComment } from './ticketing.js';
63
-
64
- // Re-export phase-level helpers so existing import sites stay unchanged.
65
- export { appendChecksSection } from './retro/phases/checks.js';
66
- export {
67
- composeRetroBody,
68
- deriveDefectClasses,
69
- } from './retro/phases/compose-body.js';
70
- export { gatherRetroSignals } from './retro/phases/gather-signals.js';
71
-
72
- /**
73
- * Public: compose and post the retro structured comment on the Epic.
74
- *
75
- * Story #1290 (Epic #1143) — at /deliver Phase 5, the runner invokes
76
- * the self-healing checks registry with `scope: 'retro'` and
77
- * `autoFix: false`. The retro is **read-only by construction**: the
78
- * registry runner enforces the invariant by throwing if any caller flips
79
- * `autoFix: true` under `scope: 'retro'`. Findings are appended to the
80
- * retro body via `appendChecksSection`, which suppresses the section when
81
- * findings are empty so the compact "🟢 Clean sprint" shape is preserved.
82
- *
83
- * Story #2252 — when `opts.bus` is supplied the runner emits
84
- * `retro.start` immediately on entry and `retro.end` immediately before
85
- * returning the envelope. On throw the helper emits `retro.end` with
86
- * `posted: false` before re-throwing so the ledger always carries the
87
- * closing boundary.
88
- *
89
- * @param {{
90
- * epicId: number,
91
- * provider: object,
92
- * logger?: { info?: Function, warn?: Function },
93
- * forceFull?: boolean,
94
- * timestamp?: string,
95
- * bus?: object|null,
96
- * now?: () => number,
97
- * manualInterventions?: number,
98
- * frameworkRepo?: string,
99
- * consumerRepo?: string,
100
- * config?: object|null,
101
- * gatherFn?: typeof gatherRetroSignals,
102
- * composeFn?: typeof composeRetroBody,
103
- * upsertFn?: typeof upsertStructuredComment,
104
- * runChecksFn?: typeof runChecks,
105
- * assembleStateFn?: typeof assembleState,
106
- * cwd?: string,
107
- * }} opts
108
- * @returns {Promise<{
109
- * posted: boolean,
110
- * compact: boolean,
111
- * scorecard: object,
112
- * body: string,
113
- * findings: object[],
114
- * defectClasses: Array<{ category: string, occurrences: number, source: 'framework'|'consumer', label: string }>,
115
- * commentId?: number,
116
- * }>}
117
- */
118
- export async function runRetro(opts = {}) {
119
- const {
120
- epicId,
121
- provider,
122
- logger,
123
- forceFull = false,
124
- timestamp,
125
- bus,
126
- now = Date.now,
127
- manualInterventions = 0,
128
- frameworkRepo,
129
- consumerRepo,
130
- config = null,
131
- gatherFn = gatherRetroSignals,
132
- composeFn = composeRetroBody,
133
- upsertFn = upsertStructuredComment,
134
- runChecksFn = runChecks,
135
- assembleStateFn = assembleState,
136
- cwd,
137
- fsImpl = nodeFs,
138
- perfThresholds = null,
139
- } = opts;
140
-
141
- if (!Number.isInteger(epicId) || epicId <= 0) {
142
- throw new TypeError('runRetro: epicId is required (positive integer).');
143
- }
144
- if (!provider) {
145
- throw new TypeError('runRetro: provider is required.');
146
- }
147
- // Epic #2646 Story C (Task #2700) — `bus` is now a hard input. The
148
- // previous guarded `emitLifecycleSafe` helper that tolerated a null
149
- // bus is gone.
150
- if (!bus || typeof bus.emit !== 'function') {
151
- throw new TypeError('runRetro: bus is required (object with emit()).');
152
- }
153
-
154
- logger?.info?.(`[retro-runner] Composing retro for Epic #${epicId}...`);
155
- const startedAt = typeof now === 'function' ? now() : Date.now();
156
- await bus.emit('retro.start', { epicId });
157
- let retroPathWritten = null;
158
- try {
159
- return await composeAndPostRetro({
160
- epicId,
161
- provider,
162
- logger,
163
- forceFull,
164
- timestamp,
165
- bus,
166
- now,
167
- manualInterventions,
168
- frameworkRepo,
169
- consumerRepo,
170
- config,
171
- gatherFn,
172
- composeFn,
173
- upsertFn,
174
- runChecksFn,
175
- assembleStateFn,
176
- cwd,
177
- fsImpl,
178
- startedAt,
179
- perfThresholds,
180
- onMirrorWritten: (p) => {
181
- retroPathWritten = p;
182
- },
183
- });
184
- } catch (err) {
185
- // Surface the closing boundary even on throw — the ledger must
186
- // always show a matched start/end pair.
187
- const endedAt = typeof now === 'function' ? now() : Date.now();
188
- const payload = {
189
- epicId,
190
- posted: false,
191
- durationMs: Math.max(0, Math.floor(endedAt - startedAt)),
192
- };
193
- if (retroPathWritten) payload.retroPath = retroPathWritten;
194
- await bus.emit('retro.end', payload);
195
- throw err;
196
- }
197
- }
@@ -1,447 +0,0 @@
1
- /**
2
- * lib/orchestration/spec-renderer.js — tickets.json → epic-spec.yaml
3
- * projection (2-tier shape, Story #4041).
4
- *
5
- * The decomposer (`epic-plan-decompose-author`) produces a flat array
6
- * of Story ticket objects keyed by stable `slug`s and linked by
7
- * `depends_on`. The spec reconciler consumes the declarative
8
- * structural representation defined by
9
- * `.agents/schemas/epic-spec.schema.json` — a flat
10
- * `{ epic, stories: [...] }` shape with Story-level `wave`,
11
- * `dependsOn`, and inline `acceptance[]` / `verify[]` arrays
12
- * projected from the decomposer's edges.
13
- *
14
- * Under the 2-tier hierarchy (Story #4041), Stories have no Feature
15
- * parent and no Task children — acceptance and verify steps live
16
- * inline on the Story, and Stories attach directly to the Epic. Any
17
- * `feature` or `task` ticket in the decomposer input is rejected at
18
- * index time (hard cutover; see `.agents/rules/git-conventions.md` §
19
- * "Contract Cutovers — No Shim Layer").
20
- *
21
- * `renderSpec(tickets, opts)` is the pure projection between those
22
- * two shapes. It:
23
- *
24
- * 1. Indexes the flat array by slug. Any unrecognised type
25
- * (including the historical `feature` / `task`) raises
26
- * immediately.
27
- * 2. Filters Story `depends_on` edges down to inter-Story
28
- * references in the same Epic.
29
- * 3. Layers Stories into waves via `Graph.assignLayers` (depth in
30
- * the story-only DAG = wave index). Stories with no inbound
31
- * edges sit at `wave: 0`, matching the wave-runner's runtime
32
- * convention (`build-wave-dag.js` produces the same layering at
33
- * dispatch time from the live GH state, so the spec's waves are
34
- * observationally identical to what dispatch will compute).
35
- * 4. Walks the Stories in decomposer-declared order, preserving
36
- * the order the LLM emitted so the reconciler's diff stays
37
- * human-readable.
38
- * 5. Strips `agent::*` labels from every entity. The decomposer
39
- * doesn't normally write them, but they can leak via reverse-
40
- * bootstrap from live GH state — and the schema forbids them
41
- * (the reconciler explicitly enforces the structural/agent
42
- * label split).
43
- * 6. Projects each Story's inline `acceptance[]` / `verify[]`
44
- * arrays onto the rendered Story. The decomposer may emit
45
- * these either at the top level of the ticket (preferred) or
46
- * nested under a structured `body` object — the renderer reads
47
- * both and prefers the top-level fields when present.
48
- * 7. Validates the produced object against the spec schema before
49
- * returning. A renderer bug that emits a malformed spec is
50
- * caught synchronously rather than failing later in `loadSpec`.
51
- *
52
- * Pure — no I/O. The validator is compiled once per process and
53
- * cached by absolute schema path (same cache the loader uses
54
- * internally; this module re-derives the path from its own location
55
- * so the renderer imposes no new resolution surface).
56
- *
57
- * Round-trip: parse a tickets fixture → render → write to YAML →
58
- * reload via `loadSpec` → the reloaded shape is structurally
59
- * identical to the renderer output (modulo YAML's omission of
60
- * `undefined` keys). Verified by `tests/scripts/spec-renderer.test.js`.
61
- */
62
-
63
- import { existsSync, readFileSync } from 'node:fs';
64
- import path from 'node:path';
65
- import { fileURLToPath } from 'node:url';
66
- import Ajv2020 from 'ajv/dist/2020.js';
67
- import addFormats from 'ajv-formats';
68
- import { assignLayers } from '../Graph.js';
69
-
70
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
71
-
72
- // scripts/lib/orchestration/ → scripts/lib/ → scripts/ → .agents/
73
- const DEFAULT_SCHEMA_PATH = path.resolve(
74
- __dirname,
75
- '..',
76
- '..',
77
- '..',
78
- 'schemas',
79
- 'epic-spec.schema.json',
80
- );
81
-
82
- const AGENT_LABEL_PREFIX = 'agent::';
83
-
84
- let cachedValidator = null;
85
- let cachedValidatorKey = null;
86
-
87
- function getValidator(schemaPath) {
88
- if (cachedValidator && cachedValidatorKey === schemaPath) {
89
- return cachedValidator;
90
- }
91
- const ajv = new Ajv2020({ allErrors: true, strict: false });
92
- addFormats(ajv);
93
- const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
94
- cachedValidator = ajv.compile(schema);
95
- cachedValidatorKey = schemaPath;
96
- return cachedValidator;
97
- }
98
-
99
- /**
100
- * Test-only hook: drop the cached validator so a subsequent call
101
- * recompiles. The renderer reuses one Ajv instance per process for
102
- * cost reasons; tests that swap to a sandbox schema must reset it.
103
- */
104
- export function _resetRendererValidatorCacheForTests() {
105
- cachedValidator = null;
106
- cachedValidatorKey = null;
107
- }
108
-
109
- /**
110
- * Raised when the rendered spec object fails schema validation. The
111
- * Ajv errors are normalised to `{ path, message }` so callers can
112
- * report the offending JSON Pointer without unwrapping Ajv's envelope.
113
- */
114
- export class SpecRenderValidationError extends Error {
115
- /**
116
- * @param {Array<{path: string, message: string, params?: object}>} issues
117
- */
118
- constructor(issues) {
119
- const head = issues[0] ?? { path: '/', message: 'unknown' };
120
- super(
121
- `Rendered spec failed schema validation at ${head.path}: ${head.message}`,
122
- );
123
- this.name = 'SpecRenderValidationError';
124
- this.issues = issues;
125
- }
126
- }
127
-
128
- function normaliseAjvErrors(ajvErrors) {
129
- return ajvErrors.map((err) => {
130
- let p = err.instancePath || '/';
131
- if (
132
- err.keyword === 'required' &&
133
- typeof err.params?.missingProperty === 'string'
134
- ) {
135
- const sep = p === '/' ? '' : '/';
136
- p = `${p}${sep}${err.params.missingProperty}`;
137
- }
138
- return {
139
- path: p,
140
- message: err.message ?? 'validation failed',
141
- params: err.params,
142
- };
143
- });
144
- }
145
-
146
- function sanitizeLabels(labels) {
147
- if (!Array.isArray(labels)) return undefined;
148
- const out = [];
149
- const seen = new Set();
150
- for (const raw of labels) {
151
- if (typeof raw !== 'string' || raw.length === 0) continue;
152
- if (raw.startsWith(AGENT_LABEL_PREFIX)) continue;
153
- if (seen.has(raw)) continue;
154
- seen.add(raw);
155
- out.push(raw);
156
- }
157
- return out.length > 0 ? out : undefined;
158
- }
159
-
160
- /**
161
- * Descriptor table for the structured-body → markdown projection, in
162
- * canonical emit order (`## Goal`, `## Changes`, `## Acceptance`,
163
- * `## Verify`). Each descriptor reads one body field and returns the
164
- * section's markdown block when the field is present and non-empty, or `null`
165
- * to omit it. Adding a section is a one-line data edit rather than a new
166
- * branch in {@link renderBody}.
167
- *
168
- * @type {Array<{ field: string, render: (value: unknown) => string | null }>}
169
- */
170
- const SPEC_BODY_SECTIONS = [
171
- {
172
- field: 'goal',
173
- render: (goal) =>
174
- typeof goal === 'string' && goal.length > 0 ? `## Goal\n${goal}` : null,
175
- },
176
- {
177
- field: 'changes',
178
- render: (changes) =>
179
- Array.isArray(changes) && changes.length > 0
180
- ? `## Changes\n${changes.map((c) => `- ${String(c)}`).join('\n')}`
181
- : null,
182
- },
183
- {
184
- field: 'acceptance',
185
- render: (acceptance) =>
186
- Array.isArray(acceptance) && acceptance.length > 0
187
- ? `## Acceptance\n${acceptance.map((a) => `- [ ] ${String(a)}`).join('\n')}`
188
- : null,
189
- },
190
- {
191
- field: 'verify',
192
- render: (verify) =>
193
- Array.isArray(verify) && verify.length > 0
194
- ? `## Verify\n${verify.map((v) => `- ${String(v)}`).join('\n')}`
195
- : null,
196
- },
197
- ];
198
-
199
- /**
200
- * Convert a decomposer body value into a spec `body` string. The
201
- * decomposer schema admits two shapes for a Story body:
202
- *
203
- * - A short string (preferred under the 2-tier hierarchy).
204
- * - A structured object (`{ goal, changes[], acceptance[],
205
- * verify[] }`) carried over from the legacy 4-tier Task body
206
- * shape, when the decomposer chooses to inline the Story's
207
- * Goal/Changes alongside its acceptance/verify arrays.
208
- *
209
- * The spec schema only models `body` as a string. For structured
210
- * bodies, render a compact markdown projection that preserves the
211
- * original sections so the reconciler's downstream issue-body apply
212
- * produces the same body the executing agent reads. For string
213
- * bodies, pass through unchanged. `undefined` / empty values drop
214
- * the field (the schema allows omission).
215
- *
216
- * The renderer does NOT round-trip structured bodies — by design,
217
- * the spec is the canonical surface once it's authored, and
218
- * structured bodies collapse into the markdown form on first
219
- * projection.
220
- *
221
- * @param {unknown} body
222
- * @returns {string | undefined}
223
- */
224
- function renderBody(body) {
225
- if (body == null) return undefined;
226
- if (typeof body === 'string') {
227
- return body.length > 0 ? body : undefined;
228
- }
229
- if (typeof body !== 'object') return undefined;
230
-
231
- const sections = [];
232
- for (const descriptor of SPEC_BODY_SECTIONS) {
233
- const block = descriptor.render(body[descriptor.field]);
234
- if (block !== null) sections.push(block);
235
- }
236
- return sections.length > 0 ? sections.join('\n\n') : undefined;
237
- }
238
-
239
- function assignNonEmpty(target, key, value) {
240
- if (value === undefined) return;
241
- target[key] = value;
242
- }
243
-
244
- /**
245
- * Index the flat ticket array by slug. Returns the ordered Story slug
246
- * list alongside the lookup map so the renderer can walk Stories in
247
- * the decomposer's emit order.
248
- *
249
- * Under the 2-tier hierarchy (Story #4041), only the `story` type is
250
- * recognised. Any other type — including the historical `feature` /
251
- * `task` — falls through to the unknown-type guard and raises
252
- * immediately; there is no silent drop.
253
- *
254
- * @param {Array<object>} tickets
255
- */
256
- function indexTickets(tickets) {
257
- const bySlug = new Map();
258
- const storySlugs = [];
259
-
260
- for (const t of tickets) {
261
- if (!t || typeof t !== 'object') continue;
262
- const slug = t.slug;
263
- if (typeof slug !== 'string' || slug.length === 0) {
264
- throw new Error(
265
- `[spec-renderer] ticket missing slug: ${JSON.stringify(t).slice(0, 120)}`,
266
- );
267
- }
268
- if (bySlug.has(slug)) {
269
- throw new Error(`[spec-renderer] duplicate slug "${slug}"`);
270
- }
271
- bySlug.set(slug, t);
272
- if (t.type === 'story') storySlugs.push(slug);
273
- else {
274
- throw new Error(
275
- `[spec-renderer] ticket "${slug}" has unknown type "${t.type}"`,
276
- );
277
- }
278
- }
279
- return { bySlug, storySlugs };
280
- }
281
-
282
- /**
283
- * Build the slug-keyed story dependency graph used for wave layering.
284
- * Drops every edge that does not reference another story slug in the
285
- * same Epic (defensive — Story slugs that were typo'd or self-edges
286
- * collapse here rather than polluting the wave count).
287
- *
288
- * @returns {{adjacency: Map<string, string[]>, layers: Map<string, number>}}
289
- */
290
- function layerStories(storySlugs, bySlug) {
291
- const storySet = new Set(storySlugs);
292
- const adjacency = new Map();
293
- for (const slug of storySlugs) {
294
- const story = bySlug.get(slug);
295
- const deps = Array.isArray(story.depends_on) ? story.depends_on : [];
296
- const filtered = deps.filter((d) => storySet.has(d) && d !== slug);
297
- adjacency.set(slug, filtered);
298
- }
299
- const layers = assignLayers(adjacency);
300
- return { adjacency, layers };
301
- }
302
-
303
- /**
304
- * Project the decomposer ticket array into the structural spec object.
305
- *
306
- * @param {Array<object>} tickets — flat ticket array as emitted by the
307
- * decomposer Skill (`type` = story, `slug`, `depends_on`, `title`,
308
- * `body`, `labels`, `acceptance`, `verify`).
309
- * @param {object} opts
310
- * @param {{id: number, title: string, body?: string, labels?: string[]}} opts.epic
311
- * — Epic descriptor (the decomposer doesn't emit the Epic row; it's
312
- * supplied by the caller, which has the live Epic ticket in hand).
313
- * @param {{baseline?: string, config?: string}} [opts.gates]
314
- * — Optional gates section, passed through verbatim into the spec.
315
- * @param {string} [opts.schemaPath] — override for the schema path
316
- * (tests).
317
- * @param {boolean} [opts.validate=true] — when `false`, skip final
318
- * schema validation (used by tests that intentionally craft invalid
319
- * inputs).
320
- * @returns {object} spec — `{ epic, stories, gates? }` matching
321
- * `.agents/schemas/epic-spec.schema.json`.
322
- */
323
- function validateRenderSpecInputs(tickets, opts) {
324
- if (!Array.isArray(tickets)) {
325
- throw new TypeError('[spec-renderer] tickets must be an array');
326
- }
327
- if (!opts || typeof opts !== 'object' || !opts.epic) {
328
- throw new TypeError('[spec-renderer] opts.epic is required');
329
- }
330
- const epic = opts.epic;
331
- if (!Number.isInteger(epic.id) || epic.id < 1) {
332
- throw new TypeError(
333
- '[spec-renderer] opts.epic.id must be a positive integer',
334
- );
335
- }
336
- if (typeof epic.title !== 'string' || epic.title.length === 0) {
337
- throw new TypeError('[spec-renderer] opts.epic.title must be a string');
338
- }
339
- }
340
-
341
- function pickStringArray(primary, fallback) {
342
- const src = Array.isArray(primary)
343
- ? primary
344
- : Array.isArray(fallback)
345
- ? fallback
346
- : null;
347
- if (!src) return null;
348
- const filtered = src.filter((s) => typeof s === 'string' && s.length > 0);
349
- return filtered.length > 0 ? filtered : null;
350
- }
351
-
352
- function extractStoryAcceptanceVerify(story) {
353
- // Stories carry inline
354
- // acceptance[] / verify[] arrays directly on the ticket. The
355
- // decomposer may emit these either at the top level of the ticket
356
- // (preferred) or nested under a structured `body` object (legacy
357
- // shape shared with the historical Task body). Read from both and
358
- // prefer the top-level fields when present.
359
- const fromBody =
360
- story.body && typeof story.body === 'object' && !Array.isArray(story.body)
361
- ? story.body
362
- : null;
363
- const out = {};
364
- const acceptance = pickStringArray(story.acceptance, fromBody?.acceptance);
365
- if (acceptance) out.acceptance = acceptance;
366
- const verify = pickStringArray(story.verify, fromBody?.verify);
367
- if (verify) out.verify = verify;
368
- return out;
369
- }
370
-
371
- function buildStoryOut({ story, layers, storySet }) {
372
- const deps = Array.isArray(story.depends_on) ? story.depends_on : [];
373
- const dependsOn = [
374
- ...new Set(deps.filter((d) => storySet.has(d) && d !== story.slug)),
375
- ];
376
-
377
- const out = {
378
- slug: story.slug,
379
- title: story.title,
380
- wave: layers.get(story.slug) ?? 0,
381
- };
382
- assignNonEmpty(out, 'body', renderBody(story.body));
383
- if (dependsOn.length > 0) out.dependsOn = dependsOn;
384
- assignNonEmpty(out, 'labels', sanitizeLabels(story.labels));
385
- const av = extractStoryAcceptanceVerify(story);
386
- if (av.acceptance) out.acceptance = av.acceptance;
387
- if (av.verify) out.verify = av.verify;
388
- return out;
389
- }
390
-
391
- function buildEpicOut(epic) {
392
- const out = { id: epic.id, title: epic.title };
393
- assignNonEmpty(out, 'body', renderBody(epic.body));
394
- assignNonEmpty(out, 'labels', sanitizeLabels(epic.labels));
395
- return out;
396
- }
397
-
398
- function buildGatesOut(gates) {
399
- if (!gates || typeof gates !== 'object') return null;
400
- const out = {};
401
- if (typeof gates.baseline === 'string' && gates.baseline.length > 0) {
402
- out.baseline = gates.baseline;
403
- }
404
- if (typeof gates.config === 'string' && gates.config.length > 0) {
405
- out.config = gates.config;
406
- }
407
- return Object.keys(out).length > 0 ? out : null;
408
- }
409
-
410
- function validateSpec(spec, schemaPath) {
411
- const effectiveSchemaPath = schemaPath ?? DEFAULT_SCHEMA_PATH;
412
- if (!existsSync(effectiveSchemaPath)) {
413
- throw new Error(
414
- `[spec-renderer] schema not found at ${effectiveSchemaPath}`,
415
- );
416
- }
417
- const validator = getValidator(effectiveSchemaPath);
418
- if (!validator(spec)) {
419
- throw new SpecRenderValidationError(
420
- normaliseAjvErrors(validator.errors ?? []),
421
- );
422
- }
423
- }
424
-
425
- export function renderSpec(tickets, opts = {}) {
426
- validateRenderSpecInputs(tickets, opts);
427
- const { epic, gates, schemaPath, validate = true } = opts;
428
-
429
- const { bySlug, storySlugs } = indexTickets(tickets);
430
- const { layers } = layerStories(storySlugs, bySlug);
431
- const storySet = new Set(storySlugs);
432
-
433
- const stories = storySlugs.map((storySlug) =>
434
- buildStoryOut({
435
- story: bySlug.get(storySlug),
436
- layers,
437
- storySet,
438
- }),
439
- );
440
-
441
- const spec = { epic: buildEpicOut(epic), stories };
442
- const gatesOut = buildGatesOut(gates);
443
- if (gatesOut) spec.gates = gatesOut;
444
-
445
- if (validate) validateSpec(spec, schemaPath);
446
- return spec;
447
- }