mandrel 1.58.0 → 1.60.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 (319) hide show
  1. package/.agents/README.md +100 -98
  2. package/.agents/docs/SDLC.md +140 -141
  3. package/.agents/docs/configuration.md +16 -16
  4. package/.agents/docs/workflows.md +7 -8
  5. package/.agents/instructions.md +12 -11
  6. package/.agents/personas/architect.md +1 -1
  7. package/.agents/personas/product.md +1 -1
  8. package/.agents/personas/project-manager.md +14 -14
  9. package/.agents/personas/technical-writer.md +1 -1
  10. package/.agents/rules/changelog-style.md +5 -5
  11. package/.agents/rules/git-conventions.md +3 -3
  12. package/.agents/schemas/agentrc.schema.json +3 -3
  13. package/.agents/schemas/audit-rules.json +20 -0
  14. package/.agents/schemas/dispatch-manifest.json +4 -4
  15. package/.agents/schemas/epic-spec.schema.json +15 -45
  16. package/.agents/schemas/lifecycle/README.md +1 -1
  17. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  20. package/.agents/schemas/validation-evidence.schema.json +1 -1
  21. package/.agents/scripts/README.md +1 -1
  22. package/.agents/scripts/acceptance-eval.js +21 -4
  23. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  24. package/.agents/scripts/analyze-execution.js +2 -2
  25. package/.agents/scripts/assert-branch.js +1 -3
  26. package/.agents/scripts/audit-to-stories.js +1 -1
  27. package/.agents/scripts/bootstrap.js +1 -1
  28. package/.agents/scripts/check-arch-cycles.js +360 -0
  29. package/.agents/scripts/check-doc-links.js +2 -3
  30. package/.agents/scripts/coverage-capture.js +24 -3
  31. package/.agents/scripts/diagnose-friction.js +1 -1
  32. package/.agents/scripts/dispatcher.js +2 -2
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/epic-audit-prepare.js +3 -3
  35. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  36. package/.agents/scripts/epic-deliver-preflight.js +11 -9
  37. package/.agents/scripts/epic-deliver-prepare.js +13 -5
  38. package/.agents/scripts/epic-execute-record-wave.js +5 -5
  39. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  40. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  41. package/.agents/scripts/epic-reconcile.js +11 -29
  42. package/.agents/scripts/evidence-gate.js +2 -2
  43. package/.agents/scripts/generate-workflows-doc.js +1 -1
  44. package/.agents/scripts/git-rebase-and-resolve.js +1 -1
  45. package/.agents/scripts/hierarchy-gate.js +40 -24
  46. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  47. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  48. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  49. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  50. package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
  51. package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
  52. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
  53. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
  54. package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
  55. package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
  56. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
  57. package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
  58. package/.agents/scripts/lib/baselines/writer.js +1 -1
  59. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  60. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  61. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  62. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  63. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  64. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  65. package/.agents/scripts/lib/close-validation/commands.js +188 -0
  66. package/.agents/scripts/lib/close-validation/gates.js +235 -0
  67. package/.agents/scripts/lib/close-validation/process.js +101 -0
  68. package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
  69. package/.agents/scripts/lib/close-validation/runner.js +325 -0
  70. package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
  71. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  72. package/.agents/scripts/lib/config/explain.js +1 -1
  73. package/.agents/scripts/lib/config/quality.js +6 -6
  74. package/.agents/scripts/lib/config/runners.js +2 -2
  75. package/.agents/scripts/lib/config/runtime.js +1 -1
  76. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  77. package/.agents/scripts/lib/config-resolver.js +2 -5
  78. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  79. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  80. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  81. package/.agents/scripts/lib/coverage-capture.js +147 -4
  82. package/.agents/scripts/lib/cpu-pool.js +14 -0
  83. package/.agents/scripts/lib/crap-utils.js +6 -11
  84. package/.agents/scripts/lib/duplicate-search.js +1 -1
  85. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  86. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
  87. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  88. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  89. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  90. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  91. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  93. package/.agents/scripts/lib/git-utils.js +24 -22
  94. package/.agents/scripts/lib/label-constants.js +3 -4
  95. package/.agents/scripts/lib/label-taxonomy.js +3 -8
  96. package/.agents/scripts/lib/maintainability-engine.js +1 -1
  97. package/.agents/scripts/lib/maintainability-utils.js +4 -187
  98. package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
  99. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
  100. package/.agents/scripts/lib/orchestration/code-review.js +95 -82
  101. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  102. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  103. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  104. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  105. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
  106. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  107. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
  108. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  109. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  110. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  111. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
  112. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
  113. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
  114. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
  115. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  116. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
  117. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  118. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  119. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  120. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  121. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
  122. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  123. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
  124. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
  125. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  126. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
  127. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
  128. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
  129. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  130. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
  131. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
  132. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  133. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  134. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  135. package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
  136. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  137. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  138. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  139. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
  140. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  141. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
  142. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  143. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  144. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  145. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  146. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  147. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  148. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  149. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  150. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
  151. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  152. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  153. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  154. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
  155. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  156. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
  157. package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
  158. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  159. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  160. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  161. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  162. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  163. package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
  164. package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
  165. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
  166. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
  167. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  170. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
  171. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
  172. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
  173. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  174. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  175. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  176. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
  177. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
  178. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
  179. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
  180. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  181. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
  182. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
  183. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
  184. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
  185. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
  186. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
  187. package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
  188. package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
  189. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  190. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  191. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
  192. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  193. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  194. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
  195. package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
  196. package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
  197. package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
  198. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  199. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
  200. package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
  201. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  202. package/.agents/scripts/lib/preflight-runner.js +1 -1
  203. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  204. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  205. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  206. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  207. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  208. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  209. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  210. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  211. package/.agents/scripts/lib/project-root.js +17 -0
  212. package/.agents/scripts/lib/signals/schema.js +1 -1
  213. package/.agents/scripts/lib/spec/index.js +1 -1
  214. package/.agents/scripts/lib/spec/loader.js +2 -2
  215. package/.agents/scripts/lib/spec/state.js +7 -16
  216. package/.agents/scripts/lib/story-adjacency.js +76 -0
  217. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  218. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  219. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  220. package/.agents/scripts/lib/story-lifecycle.js +9 -9
  221. package/.agents/scripts/lib/story-plan.js +1 -1
  222. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  223. package/.agents/scripts/lib/transpile.js +93 -0
  224. package/.agents/scripts/lib/wave-runner/tick.js +4 -153
  225. package/.agents/scripts/lib/workers/crap-worker.js +1 -1
  226. package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
  228. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
  229. package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
  230. package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
  231. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  232. package/.agents/scripts/lifecycle-emit.js +1 -1
  233. package/.agents/scripts/providers/github/board-add.js +1 -1
  234. package/.agents/scripts/providers/github/errors.js +1 -1
  235. package/.agents/scripts/providers/github/mappers.js +2 -2
  236. package/.agents/scripts/providers/github/tickets.js +114 -10
  237. package/.agents/scripts/resync-status-column.js +1 -1
  238. package/.agents/scripts/retro-run.js +2 -2
  239. package/.agents/scripts/run-lint.js +10 -1
  240. package/.agents/scripts/run-tests.js +24 -4
  241. package/.agents/scripts/single-story-init.js +1 -1
  242. package/.agents/scripts/stories-wave-tick.js +13 -10
  243. package/.agents/scripts/story-close.js +1 -1
  244. package/.agents/scripts/story-init.js +162 -26
  245. package/.agents/scripts/story-phase.js +5 -5
  246. package/.agents/scripts/story-plan.js +3 -3
  247. package/.agents/scripts/sync-branch-from-base.js +2 -2
  248. package/.agents/scripts/validate-docs-freshness.js +1 -1
  249. package/.agents/scripts/wave-tick.js +1 -1
  250. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  251. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  252. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  253. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  254. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  255. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  256. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  257. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  258. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  259. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  260. package/.agents/skills/skills.index.json +7 -7
  261. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
  262. package/.agents/templates/agent-protocol.md +2 -2
  263. package/.agents/workflows/agents-update.md +2 -2
  264. package/.agents/workflows/audit-architecture.md +2 -2
  265. package/.agents/workflows/audit-clean-code.md +2 -2
  266. package/.agents/workflows/audit-dependencies.md +1 -1
  267. package/.agents/workflows/audit-devops.md +1 -1
  268. package/.agents/workflows/audit-documentation.md +226 -0
  269. package/.agents/workflows/audit-lighthouse.md +1 -1
  270. package/.agents/workflows/audit-performance.md +2 -2
  271. package/.agents/workflows/audit-privacy.md +1 -1
  272. package/.agents/workflows/audit-quality.md +2 -2
  273. package/.agents/workflows/audit-security.md +2 -2
  274. package/.agents/workflows/audit-seo.md +1 -1
  275. package/.agents/workflows/audit-sre.md +1 -1
  276. package/.agents/workflows/audit-to-stories.md +10 -10
  277. package/.agents/workflows/audit-ux-ui.md +1 -1
  278. package/.agents/workflows/deliver.md +85 -0
  279. package/.agents/workflows/explain.md +3 -3
  280. package/.agents/workflows/git-merge-pr.md +1 -1
  281. package/.agents/workflows/git-pr-all.md +13 -10
  282. package/.agents/workflows/git-push.md +6 -3
  283. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  284. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  285. package/.agents/workflows/helpers/code-review.md +5 -5
  286. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
  287. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  288. package/.agents/workflows/helpers/diagnose.md +1 -1
  289. package/.agents/workflows/helpers/epic-audit.md +6 -6
  290. package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
  291. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  292. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  293. package/.agents/workflows/helpers/epic-testing.md +3 -3
  294. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  295. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  296. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  297. package/.agents/workflows/helpers/signals.md +1 -1
  298. package/.agents/workflows/helpers/single-story-deliver.md +12 -11
  299. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  300. package/.agents/workflows/onboard.md +21 -20
  301. package/.agents/workflows/plan.md +89 -0
  302. package/.agents/workflows/qa-explore.md +1 -1
  303. package/.agents/workflows/qa-run-harness.md +1 -1
  304. package/README.md +17 -20
  305. package/docs/CHANGELOG.md +1149 -0
  306. package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
  307. package/lib/cli/__tests__/update-reexec.test.js +513 -0
  308. package/lib/cli/init.js +338 -0
  309. package/lib/cli/update.js +413 -52
  310. package/package.json +3 -1
  311. package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
  312. package/.agents/scripts/lib/close-validation.js +0 -897
  313. package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
  314. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
  315. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  316. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
  317. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
  318. package/.agents/scripts/lib/task-utils.js +0 -26
  319. package/.agents/scripts/story-deliver-prepare.js +0 -267
@@ -0,0 +1,105 @@
1
+ /**
2
+ * review-providers/parse-findings.js — shared JSON-findings parser.
3
+ *
4
+ * Story #3981 — extracts the verbatim-duplicated parsing logic from
5
+ * `parseCodexFindings` (codex.js) and `parseSecurityReviewFindings`
6
+ * (security-review.js) into one templated parser. Both adapters emit
7
+ * JSON; the parser is liberal in what it accepts:
8
+ * - A bare array of finding objects.
9
+ * - An object with a `findings` array.
10
+ * - Either shape wrapped in an outer envelope with a `result` or
11
+ * `data` key (covers minor wire-format drift across versions
12
+ * without re-shimming).
13
+ *
14
+ * Each entry's severity is funnelled through the caller-supplied
15
+ * `mapSeverity` so the canonical enum is the only thing that reaches
16
+ * the renderer. Entries without a `title` or `body` are skipped — the
17
+ * orchestrator cannot post an empty finding, and silently dropping the
18
+ * entry is safer than fabricating one.
19
+ *
20
+ * Per-provider deltas ride in as options:
21
+ * - `errorPrefix` — prefix for the JSON-parse failure message.
22
+ * - `mapSeverity` — provider severity vocabulary → canonical enum.
23
+ * - `defaultCategory` — when set, entries missing a `category` get
24
+ * this value (security-review defaults to `'security'`); when
25
+ * omitted, `category` is only set when present (codex behavior).
26
+ *
27
+ * @typedef {import('./types.js').Finding} Finding
28
+ * @typedef {import('./types.js').Severity} Severity
29
+ */
30
+
31
+ /**
32
+ * Parse a provider's raw stdout into `Finding[]`.
33
+ *
34
+ * @param {string} rawStdout
35
+ * @param {{
36
+ * errorPrefix: string,
37
+ * mapSeverity: (raw: unknown) => Severity,
38
+ * defaultCategory?: string,
39
+ * }} options
40
+ * @returns {Finding[]}
41
+ * @throws {Error} when stdout is not parseable JSON.
42
+ */
43
+ export function parseProviderFindings(rawStdout, options) {
44
+ const { errorPrefix, mapSeverity, defaultCategory } = options;
45
+ const text = (rawStdout ?? '').trim();
46
+ if (text.length === 0) return [];
47
+
48
+ let parsed;
49
+ try {
50
+ parsed = JSON.parse(text);
51
+ } catch (err) {
52
+ throw new Error(`${errorPrefix}: ${err?.message ?? err}`);
53
+ }
54
+
55
+ // Unwrap a single layer of envelope when present.
56
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
57
+ if (Array.isArray(parsed.findings)) parsed = parsed.findings;
58
+ else if (parsed.result !== undefined) parsed = parsed.result;
59
+ else if (parsed.data !== undefined) parsed = parsed.data;
60
+ }
61
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
62
+ if (Array.isArray(parsed.findings)) parsed = parsed.findings;
63
+ }
64
+
65
+ if (!Array.isArray(parsed)) return [];
66
+
67
+ /** @type {Finding[]} */
68
+ const findings = [];
69
+ for (const entry of parsed) {
70
+ if (!entry || typeof entry !== 'object') continue;
71
+ const title =
72
+ typeof entry.title === 'string' && entry.title.trim().length > 0
73
+ ? entry.title.trim()
74
+ : null;
75
+ const body =
76
+ typeof entry.body === 'string' && entry.body.trim().length > 0
77
+ ? entry.body
78
+ : typeof entry.message === 'string' && entry.message.trim().length > 0
79
+ ? entry.message
80
+ : null;
81
+ if (!title || !body) continue;
82
+
83
+ /** @type {Finding} */
84
+ const finding = {
85
+ severity: mapSeverity(entry.severity),
86
+ title,
87
+ body,
88
+ };
89
+ const category =
90
+ typeof entry.category === 'string' && entry.category.length > 0
91
+ ? entry.category
92
+ : defaultCategory;
93
+ if (category !== undefined) {
94
+ finding.category = category;
95
+ }
96
+ if (typeof entry.file === 'string' && entry.file.length > 0) {
97
+ finding.file = entry.file;
98
+ }
99
+ if (Number.isInteger(entry.line) && entry.line > 0) {
100
+ finding.line = entry.line;
101
+ }
102
+ findings.push(finding);
103
+ }
104
+ return findings;
105
+ }
@@ -32,6 +32,7 @@
32
32
  */
33
33
 
34
34
  import { spawnSync } from 'node:child_process';
35
+ import { parseProviderFindings } from './parse-findings.js';
35
36
  import { renderDepthDirective } from './review-depth.js';
36
37
 
37
38
  /**
@@ -138,65 +139,12 @@ export function mapSecurityReviewSeverity(raw) {
138
139
  * @throws {Error} when stdout is not parseable JSON.
139
140
  */
140
141
  export function parseSecurityReviewFindings(rawStdout) {
141
- const text = (rawStdout ?? '').trim();
142
- if (text.length === 0) return [];
143
-
144
- let parsed;
145
- try {
146
- parsed = JSON.parse(text);
147
- } catch (err) {
148
- throw new Error(
149
- `[security-review] Failed to parse /security-review stdout as JSON: ${
150
- err?.message ?? err
151
- }`,
152
- );
153
- }
154
-
155
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
156
- if (Array.isArray(parsed.findings)) parsed = parsed.findings;
157
- else if (parsed.result !== undefined) parsed = parsed.result;
158
- else if (parsed.data !== undefined) parsed = parsed.data;
159
- }
160
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
161
- if (Array.isArray(parsed.findings)) parsed = parsed.findings;
162
- }
163
-
164
- if (!Array.isArray(parsed)) return [];
165
-
166
- /** @type {Finding[]} */
167
- const findings = [];
168
- for (const entry of parsed) {
169
- if (!entry || typeof entry !== 'object') continue;
170
- const title =
171
- typeof entry.title === 'string' && entry.title.trim().length > 0
172
- ? entry.title.trim()
173
- : null;
174
- const body =
175
- typeof entry.body === 'string' && entry.body.trim().length > 0
176
- ? entry.body
177
- : typeof entry.message === 'string' && entry.message.trim().length > 0
178
- ? entry.message
179
- : null;
180
- if (!title || !body) continue;
181
- /** @type {Finding} */
182
- const finding = {
183
- severity: mapSecurityReviewSeverity(entry.severity),
184
- title,
185
- body,
186
- category:
187
- typeof entry.category === 'string' && entry.category.length > 0
188
- ? entry.category
189
- : 'security',
190
- };
191
- if (typeof entry.file === 'string' && entry.file.length > 0) {
192
- finding.file = entry.file;
193
- }
194
- if (Number.isInteger(entry.line) && entry.line > 0) {
195
- finding.line = entry.line;
196
- }
197
- findings.push(finding);
198
- }
199
- return findings;
142
+ return parseProviderFindings(rawStdout, {
143
+ errorPrefix:
144
+ '[security-review] Failed to parse /security-review stdout as JSON',
145
+ mapSeverity: mapSecurityReviewSeverity,
146
+ defaultCategory: 'security',
147
+ });
200
148
  }
201
149
 
202
150
  /**
@@ -22,10 +22,8 @@
22
22
  * that mock the upstream module URLs.
23
23
  */
24
24
 
25
- import {
26
- buildDefaultGates as defaultBuildDefaultGates,
27
- runCloseValidation as defaultRunCloseValidation,
28
- } from '../../../close-validation.js';
25
+ import { buildDefaultGates as defaultBuildDefaultGates } from '../../../close-validation/gates.js';
26
+ import { runCloseValidation as defaultRunCloseValidation } from '../../../close-validation/runner.js';
29
27
  import { Logger } from '../../../Logger.js';
30
28
 
31
29
  /**
@@ -10,7 +10,7 @@
10
10
 
11
11
  import path from 'node:path';
12
12
  import { parseSprintArgs } from '../../../cli-args.js';
13
- import { PROJECT_ROOT } from '../../../config-resolver.js';
13
+ import { PROJECT_ROOT } from '../../../project-root.js';
14
14
 
15
15
  /**
16
16
  * Resolve a flag value from an explicit override, a parsed CLI arg, or a
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * phases/wrong-tree-guard.js — detect worktree/main-checkout edit divergence.
3
3
  *
4
- * Story #3364 — `/single-story-deliver` (and `/story-deliver`) materializes a
4
+ * Story #3364 — `/single-story-deliver` (and `/deliver`) materializes a
5
5
  * per-Story worktree and instructs the agent to `cd` into it before editing.
6
6
  * On Windows that guidance is silently insufficient: `cd <workCwd>` steers the
7
7
  * Bash tool's working directory, but the path-based Edit/Write tools operate on
@@ -1,9 +1,7 @@
1
1
  import nodeFs from 'node:fs';
2
2
  import path from 'node:path';
3
- import {
4
- buildDefaultGates,
5
- runCloseValidation,
6
- } from '../../close-validation.js';
3
+ import { buildDefaultGates } from '../../close-validation/gates.js';
4
+ import { runCloseValidation } from '../../close-validation/runner.js';
7
5
  import { resolveConfig } from '../../config-resolver.js';
8
6
  import { getStoryBranch, gitSync } from '../../git-utils.js';
9
7
  import { Logger } from '../../Logger.js';
@@ -39,16 +39,20 @@
39
39
  */
40
40
 
41
41
  import {
42
- acquireLease,
43
- normalizeOperatorHandle,
44
- releaseLease,
45
- } from './ticket-lease.js';
42
+ acquireLeaseFailClosed,
43
+ resolveOperatorFromCandidates,
44
+ } from './lease-guard-shared.js';
45
+ import { releaseLease } from './ticket-lease.js';
46
46
 
47
47
  /**
48
48
  * Resolve the operator handle used as the lease owner from resolved config.
49
- * Routes through the shared `normalizeOperatorHandle` so a leading `@` is
49
+ * Routes through the shared lease-guard kernel
50
+ * (`lease-guard-shared.resolveOperatorFromCandidates`) so a leading `@` is
50
51
  * stripped (the assignees API expects bare logins, not `@`-prefixed mentions)
51
- * and the self-held-claim comparison matches.
52
+ * and the self-held-claim comparison matches. The standalone surface's
53
+ * missing-handle policy is `'throw'` (intentional divergence from the plan
54
+ * path's `'null'`): init has no best-effort leg that can degrade, so an
55
+ * unowned lease must refuse immediately.
52
56
  *
53
57
  * @param {object} config Resolved `.agentrc.json` config.
54
58
  * @returns {string} Bare operator handle.
@@ -58,17 +62,16 @@ import {
58
62
  * path cannot safely serialise concurrent runs.
59
63
  */
60
64
  export function resolveOperator(config) {
61
- const handle = normalizeOperatorHandle(config?.github?.operatorHandle);
62
- if (handle === null) {
63
- throw new Error(
65
+ return resolveOperatorFromCandidates({
66
+ candidates: [config?.github?.operatorHandle],
67
+ missingHandleBehavior: 'throw',
68
+ missingHandleMessage:
64
69
  'single-story lease: no operator identity is configured. ' +
65
- 'github.operatorHandle is unset or still the shipped `@[USERNAME]` ' +
66
- 'placeholder, so the standalone Story lease has no owner. Set your own ' +
67
- 'handle in .agentrc.local.json (e.g. { "github": { "operatorHandle": ' +
68
- '"@your-login" } }) and re-run.',
69
- );
70
- }
71
- return handle;
70
+ 'github.operatorHandle is unset or still the shipped `@[USERNAME]` ' +
71
+ 'placeholder, so the standalone Story lease has no owner. Set your own ' +
72
+ 'handle in .agentrc.local.json (e.g. { "github": { "operatorHandle": ' +
73
+ '"@your-login" } }) and re-run.',
74
+ });
72
75
  }
73
76
 
74
77
  /**
@@ -99,31 +102,25 @@ export async function acquireStoryLease({
99
102
  now,
100
103
  }) {
101
104
  const owner = operator ?? resolveOperator(config);
102
- // Fail closed: with no live-heartbeat source on the standalone path, treat a
103
- // foreign assignee as a live claim. Anchoring `heartbeatAt` to the same
104
- // `now` the primitive evaluates against makes `isClaimLive` return true for
105
- // any foreign owner, so `acquireLease` refuses unless `steal` is set.
106
- const resolvedNow =
107
- typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
108
- const result = await acquireLease({
105
+ // Fail closed: with no live-heartbeat source on the standalone path, the
106
+ // shared kernel anchors `heartbeatAt` to the same `now` the primitive
107
+ // evaluates against, so `isClaimLive` returns true for any foreign owner
108
+ // and `acquireLease` refuses unless `steal` is set.
109
+ return acquireLeaseFailClosed({
109
110
  provider,
110
111
  ticketId: storyId,
111
112
  operator: owner,
112
- heartbeatAt: resolvedNow,
113
113
  steal,
114
114
  config,
115
- now: resolvedNow,
116
- });
117
- if (!result.acquired) {
118
- throw new Error(
115
+ now,
116
+ anchorHeartbeatToNow: true,
117
+ renderRefusal: (result) =>
119
118
  `single-story lease: Story #${storyId} is currently held by @${result.owner}. ` +
120
- 'Another /single-story-deliver run owns this Story. Coordinate with that ' +
121
- 'operator, or re-run with --steal to forcibly transfer the claim once you ' +
122
- 'have confirmed the other run is dead. (The standalone path has no Epic ' +
123
- 'heartbeat ledger, so a foreign assignee always blocks unless stolen.)',
124
- );
125
- }
126
- return result;
119
+ 'Another /single-story-deliver run owns this Story. Coordinate with that ' +
120
+ 'operator, or re-run with --steal to forcibly transfer the claim once you ' +
121
+ 'have confirmed the other run is dead. (The standalone path has no Epic ' +
122
+ 'heartbeat ledger, so a foreign assignee always blocks unless stolen.)',
123
+ });
127
124
  }
128
125
 
129
126
  /**
@@ -12,9 +12,8 @@
12
12
 
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
-
16
- import { PROJECT_ROOT } from '../config-resolver.js';
17
15
  import { Logger } from '../Logger.js';
16
+ import { PROJECT_ROOT } from '../project-root.js';
18
17
 
19
18
  const POLICY_HEADING_RE = /^## Policy Capsule\s*$/;
20
19
  const ANY_H2_RE = /^## /;
@@ -2,7 +2,7 @@
2
2
  * spec-freshness.js — Tech Spec post-author cross-validation against the
3
3
  * current codebase.
4
4
  *
5
- * `/epic-plan` Phase 7 authors PRD + Tech Spec from documentation alone.
5
+ * `/plan` Phase 7 authors PRD + Tech Spec from documentation alone.
6
6
  * When `project.docsContextFiles` (architecture.md, etc.) drift from
7
7
  * the real source tree, the Architect persona happily cites modules and
8
8
  * paths that no longer exist. The mismatch only surfaces at delivery time,
@@ -1,30 +1,29 @@
1
1
  /**
2
2
  * lib/orchestration/spec-renderer.js — tickets.json → epic-spec.yaml
3
- * projection (3-tier shape, Epic #3163).
3
+ * projection (2-tier shape, Story #4041).
4
4
  *
5
5
  * The decomposer (`epic-plan-decompose-author`) produces a flat array
6
- * of ticket objects of two shapes (feature / story) keyed by stable
7
- * `slug`s and linked by `parent_slug` + `depends_on`. The spec
8
- * reconciler consumes the declarative structural representation
9
- * defined by `.agents/schemas/epic-spec.schema.json` — a nested
10
- * `{ epic, features: [{ stories: [...] }] }` tree with Story-level
11
- * `wave`, `dependsOn`, and inline `acceptance[]` / `verify[]` arrays
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
12
  * projected from the decomposer's edges.
13
13
  *
14
- * Under the 3-tier hierarchy (Epic #3078), Stories have no Task
15
- * children — acceptance and verify steps live inline on the Story.
16
- * Epic #3163 deletes the historical 4-tier Task emission code path
17
- * from this renderer (and its companion producers / consumers) in a
18
- * single hard cutover (see `.agents/rules/git-conventions.md` §
19
- * "Contract Cutovers — No Shim Layer"). A `task` ticket in the
20
- * decomposer input is rejected at index time.
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").
21
20
  *
22
21
  * `renderSpec(tickets, opts)` is the pure projection between those
23
22
  * two shapes. It:
24
23
  *
25
- * 1. Indexes the flat array by slug, partitioning into features /
26
- * stories. Any unrecognised type (including the historical
27
- * `task`) raises immediately.
24
+ * 1. Indexes the flat array by slug. Any unrecognised type
25
+ * (including the historical `feature` / `task`) raises
26
+ * immediately.
28
27
  * 2. Filters Story `depends_on` edges down to inter-Story
29
28
  * references in the same Epic.
30
29
  * 3. Layers Stories into waves via `Graph.assignLayers` (depth in
@@ -33,9 +32,9 @@
33
32
  * convention (`build-wave-dag.js` produces the same layering at
34
33
  * dispatch time from the live GH state, so the spec's waves are
35
34
  * observationally identical to what dispatch will compute).
36
- * 4. Walks the hierarchy in decomposer-declared order (feature →
37
- * story), preserving the order the LLM emitted so the
38
- * reconciler's diff stays human-readable.
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.
39
38
  * 5. Strips `agent::*` labels from every entity. The decomposer
40
39
  * doesn't normally write them, but they can leak via reverse-
41
40
  * bootstrap from live GH state — and the schema forbids them
@@ -162,7 +161,7 @@ function sanitizeLabels(labels) {
162
161
  * Convert a decomposer body value into a spec `body` string. The
163
162
  * decomposer schema admits two shapes for a Story body:
164
163
  *
165
- * - A short string (preferred under the 3-tier hierarchy).
164
+ * - A short string (preferred under the 2-tier hierarchy).
166
165
  * - A structured object (`{ goal, changes[], acceptance[],
167
166
  * verify[] }`) carried over from the legacy 4-tier Task body
168
167
  * shape, when the decomposer chooses to inline the Story's
@@ -215,16 +214,12 @@ function assignNonEmpty(target, key, value) {
215
214
  }
216
215
 
217
216
  /**
218
- * Partition the flat ticket array into per-type maps keyed by slug.
219
- * Returns the ordered slug lists alongside the lookup map so the
220
- * renderer can walk the hierarchy in the decomposer's emit order
221
- * (parents-before-children is guaranteed by the validator's
222
- * topological sort, but we don't need that invariant here — we walk
223
- * features in array order, then look up their children by parent_slug
224
- * in the order the decomposer emitted them).
217
+ * Index the flat ticket array by slug. Returns the ordered Story slug
218
+ * list alongside the lookup map so the renderer can walk Stories in
219
+ * the decomposer's emit order.
225
220
  *
226
- * Under the 3-tier hierarchy (Epic #3078), only `feature` and `story`
227
- * types are recognised. Any other type — including the historical
221
+ * Under the 2-tier hierarchy (Story #4041), only the `story` type is
222
+ * recognised. Any other type — including the historical `feature` /
228
223
  * `task` — falls through to the unknown-type guard and raises
229
224
  * immediately; there is no silent drop.
230
225
  *
@@ -232,7 +227,6 @@ function assignNonEmpty(target, key, value) {
232
227
  */
233
228
  function indexTickets(tickets) {
234
229
  const bySlug = new Map();
235
- const featureSlugs = [];
236
230
  const storySlugs = [];
237
231
 
238
232
  for (const t of tickets) {
@@ -247,15 +241,14 @@ function indexTickets(tickets) {
247
241
  throw new Error(`[spec-renderer] duplicate slug "${slug}"`);
248
242
  }
249
243
  bySlug.set(slug, t);
250
- if (t.type === 'feature') featureSlugs.push(slug);
251
- else if (t.type === 'story') storySlugs.push(slug);
244
+ if (t.type === 'story') storySlugs.push(slug);
252
245
  else {
253
246
  throw new Error(
254
247
  `[spec-renderer] ticket "${slug}" has unknown type "${t.type}"`,
255
248
  );
256
249
  }
257
250
  }
258
- return { bySlug, featureSlugs, storySlugs };
251
+ return { bySlug, storySlugs };
259
252
  }
260
253
 
261
254
  /**
@@ -283,9 +276,8 @@ function layerStories(storySlugs, bySlug) {
283
276
  * Project the decomposer ticket array into the structural spec object.
284
277
  *
285
278
  * @param {Array<object>} tickets — flat ticket array as emitted by the
286
- * decomposer Skill (`type` {feature, story}, `slug`,
287
- * `parent_slug`, `depends_on`, `title`, `body`, `labels`,
288
- * `acceptance`, `verify`).
279
+ * decomposer Skill (`type` = story, `slug`, `depends_on`, `title`,
280
+ * `body`, `labels`, `acceptance`, `verify`).
289
281
  * @param {object} opts
290
282
  * @param {{id: number, title: string, body?: string, labels?: string[]}} opts.epic
291
283
  * — Epic descriptor (the decomposer doesn't emit the Epic row; it's
@@ -297,7 +289,7 @@ function layerStories(storySlugs, bySlug) {
297
289
  * @param {boolean} [opts.validate=true] — when `false`, skip final
298
290
  * schema validation (used by tests that intentionally craft invalid
299
291
  * inputs).
300
- * @returns {object} spec — `{ epic, features, gates? }` matching
292
+ * @returns {object} spec — `{ epic, stories, gates? }` matching
301
293
  * `.agents/schemas/epic-spec.schema.json`.
302
294
  */
303
295
  function validateRenderSpecInputs(tickets, opts) {
@@ -318,18 +310,6 @@ function validateRenderSpecInputs(tickets, opts) {
318
310
  }
319
311
  }
320
312
 
321
- function bucketStoriesByFeature({ storySlugs, bySlug }) {
322
- const storiesByFeature = new Map();
323
- for (const slug of storySlugs) {
324
- const story = bySlug.get(slug);
325
- const parent = story.parent_slug;
326
- if (typeof parent !== 'string' || !bySlug.has(parent)) continue;
327
- if (!storiesByFeature.has(parent)) storiesByFeature.set(parent, []);
328
- storiesByFeature.get(parent).push(slug);
329
- }
330
- return storiesByFeature;
331
- }
332
-
333
313
  function pickStringArray(primary, fallback) {
334
314
  const src = Array.isArray(primary)
335
315
  ? primary
@@ -342,7 +322,7 @@ function pickStringArray(primary, fallback) {
342
322
  }
343
323
 
344
324
  function extractStoryAcceptanceVerify(story) {
345
- // Under the 3-tier hierarchy (Epic #3078), Stories carry inline
325
+ // Stories carry inline
346
326
  // acceptance[] / verify[] arrays directly on the ticket. The
347
327
  // decomposer may emit these either at the top level of the ticket
348
328
  // (preferred) or nested under a structured `body` object (legacy
@@ -380,20 +360,6 @@ function buildStoryOut({ story, layers, storySet }) {
380
360
  return out;
381
361
  }
382
362
 
383
- function buildFeatureOut({ feature, storySlugs, bySlug, layers, storySet }) {
384
- const stories = storySlugs.map((storySlug) =>
385
- buildStoryOut({
386
- story: bySlug.get(storySlug),
387
- layers,
388
- storySet,
389
- }),
390
- );
391
- const out = { slug: feature.slug, title: feature.title, stories };
392
- assignNonEmpty(out, 'body', renderBody(feature.body));
393
- assignNonEmpty(out, 'labels', sanitizeLabels(feature.labels));
394
- return out;
395
- }
396
-
397
363
  function buildEpicOut(epic) {
398
364
  const out = { id: epic.id, title: epic.title };
399
365
  assignNonEmpty(out, 'body', renderBody(epic.body));
@@ -432,22 +398,19 @@ export function renderSpec(tickets, opts = {}) {
432
398
  validateRenderSpecInputs(tickets, opts);
433
399
  const { epic, gates, schemaPath, validate = true } = opts;
434
400
 
435
- const { bySlug, featureSlugs, storySlugs } = indexTickets(tickets);
401
+ const { bySlug, storySlugs } = indexTickets(tickets);
436
402
  const { layers } = layerStories(storySlugs, bySlug);
437
- const storiesByFeature = bucketStoriesByFeature({ storySlugs, bySlug });
438
403
  const storySet = new Set(storySlugs);
439
404
 
440
- const features = featureSlugs.map((featureSlug) =>
441
- buildFeatureOut({
442
- feature: bySlug.get(featureSlug),
443
- storySlugs: storiesByFeature.get(featureSlug) ?? [],
444
- bySlug,
405
+ const stories = storySlugs.map((storySlug) =>
406
+ buildStoryOut({
407
+ story: bySlug.get(storySlug),
445
408
  layers,
446
409
  storySet,
447
410
  }),
448
411
  );
449
412
 
450
- const spec = { epic: buildEpicOut(epic), features };
413
+ const spec = { epic: buildEpicOut(epic), stories };
451
414
  const gatesOut = buildGatesOut(gates);
452
415
  if (gatesOut) spec.gates = gatesOut;
453
416
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * spec-section-validator.js — Phase 7.5 Tech Spec post-authoring section gate.
3
3
  *
4
- * `/epic-plan` Phase 7 authors a Tech Spec from documentation and the PRD.
4
+ * `/plan` Phase 7 authors a Tech Spec from documentation and the PRD.
5
5
  * Phase 8.3 (Holistic Consolidation) then reconciles the draft ticket array
6
6
  * against the Tech Spec's `## Delivery Slicing` section, which the
7
7
  * decompose-author skill uses as the capability-boundary anchor. When the