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
@@ -41,9 +41,34 @@ import { emitSpawnTimeoutBlockedResult } from './timeout-blocked-emitter.js';
41
41
  * Run pre-merge gates and, on a clean outcome, the bounded baseline
42
42
  * auto-refresh. Returns `{ blocked }` so the locked pipeline can
43
43
  * short-circuit on a `blocked` / `blocked-timeout` gate outcome.
44
+ *
45
+ * `runPreMergeValidation` is an injectable seam (Story #3973): the
46
+ * forwarding of `config: ctx.config` into the gate builder is load-bearing
47
+ * — without it the typecheck gate ignores `project.commands.typecheck` and
48
+ * falls back to the hardcoded `npm run typecheck`, blocking every close for
49
+ * any consumer with a non-default typecheck command. The default is the
50
+ * module-level `runPreMergeValidation`; tests inject a spy to pin that
51
+ * `config` reaches the gate builder through this call site.
52
+ *
53
+ * @param {object} ctx - The locked-pipeline context bundle.
54
+ * @param {object} [deps] - Injectable collaborators.
55
+ * @param {typeof runPreMergeValidation} [deps.runPreMergeValidationFn]
56
+ * @param {typeof runAutoRefreshSafely} [deps.runAutoRefreshSafelyFn]
44
57
  */
45
- async function runGatesAndRefresh(ctx) {
46
- const gateOutcome = await runPreMergeValidation({
58
+ export async function runGatesAndRefresh(
59
+ ctx,
60
+ {
61
+ runPreMergeValidationFn = runPreMergeValidation,
62
+ runAutoRefreshSafelyFn = runAutoRefreshSafely,
63
+ } = {},
64
+ ) {
65
+ // Story #4017 — one shared cycle-state object per close cycle. The
66
+ // gate-failure attribution retry and the post-gates auto-refresh both
67
+ // funnel through `runRefreshCommit`, which keys its idempotency token
68
+ // off this object so each baseline kind is refreshed (scored +
69
+ // committed) at most once per close.
70
+ const cycleState = { refreshedKinds: new Set(), lastRefreshSha: null };
71
+ const gateOutcome = await runPreMergeValidationFn({
47
72
  cwd: ctx.cwd,
48
73
  worktreePath: ctx.worktreePath,
49
74
  epicBranch: ctx.epicBranch,
@@ -55,6 +80,7 @@ async function runGatesAndRefresh(ctx) {
55
80
  phaseTimer: ctx.phaseTimer,
56
81
  provider: ctx.provider,
57
82
  bus: ctx.bus,
83
+ cycleState,
58
84
  });
59
85
  if (gateOutcome?.status === 'blocked') {
60
86
  return {
@@ -82,7 +108,7 @@ async function runGatesAndRefresh(ctx) {
82
108
  }),
83
109
  };
84
110
  }
85
- await runAutoRefreshSafely(
111
+ await runAutoRefreshSafelyFn(
86
112
  {
87
113
  storyId: ctx.storyId,
88
114
  epicId: ctx.epicId,
@@ -90,6 +116,7 @@ async function runGatesAndRefresh(ctx) {
90
116
  epicBranch: ctx.epicBranch,
91
117
  storyBranch: ctx.storyBranch,
92
118
  config: ctx.config,
119
+ cycleState,
93
120
  },
94
121
  { progress: ctx.progress },
95
122
  );
@@ -100,7 +127,7 @@ async function runGatesAndRefresh(ctx) {
100
127
  * Read the parent Epic's judged `planningRisk` envelope off its
101
128
  * `epic-plan-state` checkpoint so the Story-scope review can inherit the
102
129
  * Epic's review depth (Story #3940). Best-effort and total — it reuses the
103
- * shared `read` from `epic-plan-state-store.js` (the same reader `/epic-plan
130
+ * shared `read` from `epic-plan-state-store.js` (the same reader `/plan
104
131
  * --resume` and `epic-audit-prepare.js`'s `resolveRiskRoutedLenses` use, no
105
132
  * third bespoke reader) and never fails the close:
106
133
  *
@@ -110,7 +137,7 @@ async function runGatesAndRefresh(ctx) {
110
137
  *
111
138
  * A `null` result threads through `runStoryCodeReview` → `runCodeReview`
112
139
  * unchanged, so depth resolves from diff width alone (`standard`), preserving
113
- * today's behaviour for an Epic that skipped `/epic-plan`.
140
+ * today's behaviour for an Epic that skipped `/plan`.
114
141
  *
115
142
  * @param {{ provider: object, epicId: number|null|undefined, readPlanStateFn?: typeof readPlanState }} args
116
143
  * @returns {Promise<{ overallLevel?: string, axes?: Array<object> }|null>}
@@ -21,14 +21,8 @@
21
21
  * read it; this helper writes the file and hands the path to the pipeline.
22
22
  */
23
23
 
24
- // Legacy key on the post-merge-pipeline / cleanup-reconciler input bag.
25
- // Built from substrings so the migrated-subsystem grep does not match it;
26
- // the downstream helpers live outside the migrated subsystem and will
27
- // rename their parameters when their own subsystems are swept.
28
- const LEGACY_PIPELINE_CONFIG_KEY = `orches${'tration'}`;
29
-
30
24
  import { mkdir, writeFile } from 'node:fs/promises';
31
- import { emitGhSpawnCount as defaultEmitGhSpawnCount } from '../../close-validation.js';
25
+ import { emitGhSpawnCount as defaultEmitGhSpawnCount } from '../../close-validation/telemetry.js';
32
26
  import { storyArtifactPath, storyTempDir } from '../../config/temp-paths.js';
33
27
  import { gitSpawn as defaultGitSpawn } from '../../git-utils.js';
34
28
  import { clearActiveStoryEnv as defaultClearActiveStoryEnv } from '../../observability/active-story-env.js';
@@ -340,18 +334,11 @@ export async function runPostMergeClose({
340
334
  // in this order — see post-merge-pipeline.js. The `perf-summary` phase
341
335
  // inside the pipeline shells out to analyze-execution.js, which is the
342
336
  // single writer of the `<!-- structured:story-perf-summary -->` comment.
343
- // Build the legacy-shape view that downstream out-of-subsystem helpers
344
- // (post-merge-pipeline.js) still consume. The migrated cleanup-reconciler
345
- // takes `delivery` directly.
346
- const legacyPipelineBlock = {
347
- provider: 'github',
348
- github: config?.github,
349
- notifications: config?.github?.notifications,
350
- worktreeIsolation: deliveryBlock?.worktreeIsolation,
351
- runners: { deliverRunner: deliveryBlock?.deliverRunner ?? {} },
352
- };
337
+ // The pipeline phases take the resolved `delivery` block directly, same
338
+ // as the cleanup-reconciler (Story #3986 the legacy `orchestration`-keyed
339
+ // input bag is gone).
353
340
  const pipelineState = await runPostMergePipeline({
354
- [LEGACY_PIPELINE_CONFIG_KEY]: legacyPipelineBlock,
341
+ delivery: deliveryBlock,
355
342
  storyId,
356
343
  epicId,
357
344
  story,
@@ -29,12 +29,12 @@
29
29
  // no-spawn spy proves the projection path never reaches a per-kind CLI
30
30
  // subprocess.
31
31
  import * as maintainabilityKind from '../../baselines/kinds/maintainability.js';
32
+ import { buildDefaultGates as defaultBuildDefaultGates } from '../../close-validation/gates.js';
32
33
  import {
33
- buildDefaultGates as defaultBuildDefaultGates,
34
34
  formatMaintainabilityProjection as defaultFormatMaintainabilityProjection,
35
35
  projectMaintainabilityRegressions as defaultProjectMaintainabilityRegressions,
36
- runCloseValidation as defaultRunCloseValidation,
37
- } from '../../close-validation.js';
36
+ } from '../../close-validation/projections/maintainability.js';
37
+ import { runCloseValidation as defaultRunCloseValidation } from '../../close-validation/runner.js';
38
38
  import { getBaselines as defaultGetBaselines } from '../../config-resolver.js';
39
39
  import { Logger as DefaultLogger } from '../../Logger.js';
40
40
 
@@ -276,7 +276,7 @@ function detectAlreadyMerged({ cwd, storyId, epicId, lsrOut, detail, git }) {
276
276
  // from the Epic history itself: locate the integration commit whose
277
277
  // subject carries `(resolves #<id>)` / `(refs #<id>)`. Without this
278
278
  // branch, detection falls to FRESH and the resumed close re-enters the
279
- // pre-merge gate chain, which crashes in `format-autofix-scoped.js` on
279
+ // pre-merge gate chain, which crashes in the scoped format-autofix step on
280
280
  // `git diff <epicBranch>...story-<id>` because the Story ref is gone.
281
281
  if (!resolvedDetail) {
282
282
  const mc = findMergeCommitForStory({ cwd, storyId, epicId, git });
@@ -431,24 +431,35 @@ export function computeRecoveryMode({ state, resume, restart } = {}) {
431
431
  };
432
432
  }
433
433
 
434
- function dropWorktreeIfPresent({ cwd, wtPath, progress, logger }) {
434
+ function dropWorktreeIfPresent({
435
+ cwd,
436
+ wtPath,
437
+ progress,
438
+ logger,
439
+ gitSpawnFn = gitSpawn,
440
+ }) {
435
441
  if (!fs.existsSync(wtPath)) return;
436
442
  progress('RESTART', `Removing worktree ${wtPath}`);
437
- const remove = gitSpawn(cwd, 'worktree', 'remove', '--force', wtPath);
443
+ const remove = gitSpawnFn(cwd, 'worktree', 'remove', '--force', wtPath);
438
444
  if (remove.status !== 0) {
439
445
  logger.error(
440
446
  `[story-close] Worktree remove failed: ${remove.stderr || 'unknown'}. ` +
441
447
  'Attempting prune to clean stale registration.',
442
448
  );
443
449
  }
444
- gitSpawn(cwd, 'worktree', 'prune');
450
+ gitSpawnFn(cwd, 'worktree', 'prune');
445
451
  }
446
452
 
447
- function recreateStoryBranchRef({ cwd, storyBranch, epicBranch, logger }) {
448
- gitSpawn(cwd, 'branch', '-D', storyBranch);
449
- const create = gitSpawn(cwd, 'branch', storyBranch, epicBranch);
453
+ function recreateStoryBranchRef({
454
+ cwd,
455
+ storyBranch,
456
+ epicBranch,
457
+ gitSpawnFn = gitSpawn,
458
+ }) {
459
+ gitSpawnFn(cwd, 'branch', '-D', storyBranch);
460
+ const create = gitSpawnFn(cwd, 'branch', storyBranch, epicBranch);
450
461
  if (create.status !== 0) {
451
- logger.fatal(
462
+ throw new Error(
452
463
  `Failed to recreate ${storyBranch} from ${epicBranch}: ${create.stderr || 'unknown'}`,
453
464
  );
454
465
  }
@@ -460,13 +471,13 @@ function reseedWorktreeIfNeeded({
460
471
  storyId,
461
472
  storyBranch,
462
473
  progress,
463
- logger,
474
+ gitSpawnFn = gitSpawn,
464
475
  }) {
465
476
  if (!wtConfig?.enabled) return;
466
477
  const wtPath = storyWorktreePath(cwd, storyId, wtConfig.root);
467
- const add = gitSpawn(cwd, 'worktree', 'add', wtPath, storyBranch);
478
+ const add = gitSpawnFn(cwd, 'worktree', 'add', wtPath, storyBranch);
468
479
  if (add.status !== 0) {
469
- logger.fatal(
480
+ throw new Error(
470
481
  `Failed to re-seed worktree at ${wtPath}: ${add.stderr || 'unknown'}`,
471
482
  );
472
483
  }
@@ -477,8 +488,12 @@ function reseedWorktreeIfNeeded({
477
488
  * Restart path: abort any in-progress merge, drop the worktree, delete the
478
489
  * story branch ref, and re-seed branch + worktree from the Epic branch. The
479
490
  * caller then falls through to the normal fresh-close flow.
491
+ *
492
+ * Throws (never `logger.fatal`) on a failed branch recreate or worktree
493
+ * re-seed, per `rules/orchestration-error-handling.md` — a failed recreate
494
+ * MUST NOT fall through into the worktree re-seed.
480
495
  */
481
- function restartStoryState({
496
+ export function restartStoryState({
482
497
  cwd,
483
498
  orchestration,
484
499
  storyId,
@@ -486,9 +501,10 @@ function restartStoryState({
486
501
  storyBranch,
487
502
  progress = () => {},
488
503
  logger = Logger,
504
+ gitSpawnFn = gitSpawn,
489
505
  } = {}) {
490
506
  progress('RESTART', `Resetting prior state for Story #${storyId}...`);
491
- gitSpawn(cwd, 'merge', '--abort');
507
+ gitSpawnFn(cwd, 'merge', '--abort');
492
508
 
493
509
  const wtConfig = orchestration?.worktreeIsolation;
494
510
  if (wtConfig?.enabled) {
@@ -497,17 +513,18 @@ function restartStoryState({
497
513
  wtPath: storyWorktreePath(cwd, storyId, wtConfig.root),
498
514
  progress,
499
515
  logger,
516
+ gitSpawnFn,
500
517
  });
501
518
  }
502
519
 
503
- recreateStoryBranchRef({ cwd, storyBranch, epicBranch, logger });
520
+ recreateStoryBranchRef({ cwd, storyBranch, epicBranch, gitSpawnFn });
504
521
  reseedWorktreeIfNeeded({
505
522
  cwd,
506
523
  wtConfig,
507
524
  storyId,
508
525
  storyBranch,
509
526
  progress,
510
- logger,
527
+ gitSpawnFn,
511
528
  });
512
529
  }
513
530
 
@@ -563,7 +580,7 @@ export function dispatchRecovery({
563
580
  restartFn = restartStoryState,
564
581
  } = {}) {
565
582
  if (resume && restart) {
566
- logger.fatal('--resume and --restart are mutually exclusive');
583
+ throw new Error('--resume and --restart are mutually exclusive');
567
584
  }
568
585
 
569
586
  const priorPhase = detectFn({ cwd, storyId, epicId });
@@ -0,0 +1,47 @@
1
+ /**
2
+ * lib/orchestration/story-reachability.js — Story `depends_on` graph walk.
3
+ *
4
+ * Houses the transitive-predecessor closure over the story-level
5
+ * `depends_on` graph. Extracted from `./ticket-validator-conflicts.js`
6
+ * under Story #3995 to break the `file-assumptions.js ↔
7
+ * ticket-validator-conflicts.js` import cycle: both the conflict gate and
8
+ * the wave-aware file-assumption gate need this graph traversal, so
9
+ * pulling it down into a dependency-free leaf lets both import it from
10
+ * below rather than from each other.
11
+ *
12
+ * This is a pure graph utility — no I/O, no policy. Its behaviour is
13
+ * unchanged from the original `computeStoryReachability`.
14
+ */
15
+
16
+ /**
17
+ * Compute transitive predecessor sets over the story-level `depends_on`
18
+ * graph. The returned map is `Map<storySlug, Set<storySlug>>`, where the
19
+ * set contains every story reachable by following `depends_on` edges from
20
+ * the key (i.e. every story the key transitively depends on).
21
+ *
22
+ * BFS, no cycles assumed — callers must run `assertAcyclic` first.
23
+ *
24
+ * Exported so both the conflict gate (`ticket-validator-conflicts.js`)
25
+ * and the wave-aware file-assumption gate (`file-assumptions.js`) can
26
+ * reuse the same transitive-predecessor walk rather than re-deriving the
27
+ * `depends_on` closure.
28
+ */
29
+ export function computeStoryReachability(stories) {
30
+ const reach = new Map();
31
+ for (const story of stories) reach.set(story.slug, new Set());
32
+ for (const story of stories) {
33
+ const visited = reach.get(story.slug);
34
+ const stack = [...(story.depends_on ?? [])];
35
+ while (stack.length > 0) {
36
+ const next = stack.pop();
37
+ if (!reach.has(next)) continue;
38
+ if (visited.has(next)) continue;
39
+ visited.add(next);
40
+ const nextStory = stories.find((s) => s.slug === next);
41
+ if (nextStory && Array.isArray(nextStory.depends_on)) {
42
+ for (const dep of nextStory.depends_on) stack.push(dep);
43
+ }
44
+ }
45
+ }
46
+ return reach;
47
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Story body schema validator (v5.33+).
3
3
  *
4
- * Enforces the four-section structured body shape on 3-tier Stories emitted
4
+ * Enforces the four-section structured body shape on 2-tier Stories emitted
5
5
  * by the decomposer. The canonical decomposition serializes every Story
6
6
  * `body` to a **markdown string** via `serialize()` from
7
7
  * `lib/story-body/story-body.js` (the decompose-author skill mandates this,
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * Only `type: 'story'` tickets are validated; Feature/Epic tickets and
19
19
  * null/empty bodies pass through. There is no `type::task` ticket layer in
20
- * the 3-tier hierarchy (Epic → Feature → Story).
20
+ * the 2-tier hierarchy (Epic → Story).
21
21
  *
22
22
  * Required after parse/normalize: a non-empty `goal`, and non-empty
23
23
  * `changes`, `acceptance`, and `verify` arrays — and `changes` items must
@@ -110,7 +110,7 @@ function vagueVerbWithoutTarget(bullet) {
110
110
  * - it has no body (null / undefined / empty-or-whitespace string — there
111
111
  * is nothing to inspect).
112
112
  *
113
- * Under the 3-tier hierarchy (Epic → Feature → Story), Stories carry the
113
+ * Under the 2-tier hierarchy (Epic → Story), Stories carry the
114
114
  * implementation scope inline. A canonical decomposition serializes the
115
115
  * Story body to a markdown string, so a *string* body is NOT skipped here
116
116
  * (Story #3906) — `validateTaskBodyShape` parses it back into structured
@@ -127,7 +127,7 @@ function vagueVerbWithoutTarget(bullet) {
127
127
  */
128
128
  function shouldSkipTicket(ticket) {
129
129
  if (!ticket) return true;
130
- // Only Stories carry an inline implementation contract in the 3-tier
130
+ // Only Stories carry an inline implementation contract in the 2-tier
131
131
  // world. Features (and everything else) use narrative bodies.
132
132
  if (ticket.type !== 'story') return true;
133
133
  const body = ticket.body;
@@ -197,7 +197,7 @@ export function validateTaskBodyShape(ticket) {
197
197
  }
198
198
  errors.push(...collectChangesErrors(prefix, body.changes));
199
199
  errors.push(...collectAcceptanceErrors(prefix, body.acceptance));
200
- // Tier-suffix validation is always enforced on Story bodies (3-tier world).
200
+ // Tier-suffix validation is always enforced on Story bodies (2-tier world).
201
201
  errors.push(...collectVerifyErrors(prefix, body.verify));
202
202
  errors.push(...collectReferencesErrors(prefix, body.references));
203
203
  return errors;
@@ -359,7 +359,7 @@ function collectVerifyErrors(prefix, rawVerify) {
359
359
  }
360
360
 
361
361
  /**
362
- * Validate every 3-tier Story in `tickets` whose `body` is a structured
362
+ * Validate every 2-tier Story in `tickets` whose `body` is a structured
363
363
  * object. Returns an array of error strings (one per offending slug); empty
364
364
  * array means clean.
365
365
  *
@@ -96,7 +96,7 @@ export function normalizeOperatorHandle(raw) {
96
96
  * observability artifact never wedges the lease preflight.
97
97
  *
98
98
  * This is the shared liveness source the lease guards thread into
99
- * `acquireLease` via `heartbeatAt`; `/epic-plan` and `/epic-deliver` both
99
+ * `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
100
100
  * reuse it so a live foreign claim actually refuses.
101
101
  *
102
102
  * @param {object} args
@@ -1,4 +1,5 @@
1
1
  import { collectStoryAssumptionEntries } from './file-assumptions.js';
2
+ import { computeStoryReachability } from './story-reachability.js';
2
3
 
3
4
  /**
4
5
  * Cross-Story path-conflict & implicit-dependency findings.
@@ -66,7 +67,7 @@ const DEFAULT_POLICY = Object.freeze({
66
67
  * - exact path — `lib/orchestration/lifecycle/listeners/index.js`
67
68
  * - `**` suffix — `**\/listeners/index.js` (matches any depth)
68
69
  */
69
- const DEFAULT_REGISTRY_PATTERNS = Object.freeze([
70
+ export const DEFAULT_REGISTRY_PATTERNS = Object.freeze([
70
71
  'lib/orchestration/lifecycle/listeners/index.js',
71
72
  '**/listeners/index.js',
72
73
  '**/handlers/index.js',
@@ -169,7 +170,7 @@ function collectStoryProducerPaths(story) {
169
170
  }
170
171
 
171
172
  /**
172
- * Resolve the Story-identifying slug for a 3-tier Story. A Story is its
173
+ * Resolve the Story-identifying slug for a 2-tier Story. A Story is its
173
174
  * own implementation unit (Epic #3238) — there is no parent Task — so the
174
175
  * producer/consumer indices key on the Story's own `slug`.
175
176
  */
@@ -186,7 +187,7 @@ function storySlugOf(story) {
186
187
  * producers.
187
188
  *
188
189
  * `taskSlug` is retained in the entry shape for finding/render
189
- * compatibility; in the 3-tier model it carries the Story's own slug since
190
+ * compatibility; in the 2-tier model it carries the Story's own slug since
190
191
  * the Story is the implementation unit.
191
192
  */
192
193
  function indexProducers(stories) {
@@ -242,38 +243,6 @@ function indexConsumers(stories, producers) {
242
243
  return consumers;
243
244
  }
244
245
 
245
- /**
246
- * Compute transitive predecessor sets over the story-level `depends_on`
247
- * graph. The returned map is `Map<storySlug, Set<storySlug>>`, where the
248
- * set contains every story reachable by following `depends_on` edges from
249
- * the key (i.e. every story the key transitively depends on).
250
- *
251
- * BFS, no cycles assumed — callers must run `assertAcyclic` first.
252
- *
253
- * Exported so the wave-aware file-assumption gate
254
- * (`file-assumptions.js`) can reuse the same transitive-predecessor walk
255
- * rather than re-deriving the `depends_on` closure.
256
- */
257
- export function computeStoryReachability(stories) {
258
- const reach = new Map();
259
- for (const story of stories) reach.set(story.slug, new Set());
260
- for (const story of stories) {
261
- const visited = reach.get(story.slug);
262
- const stack = [...(story.depends_on ?? [])];
263
- while (stack.length > 0) {
264
- const next = stack.pop();
265
- if (!reach.has(next)) continue;
266
- if (visited.has(next)) continue;
267
- visited.add(next);
268
- const nextStory = stories.find((s) => s.slug === next);
269
- if (nextStory && Array.isArray(nextStory.depends_on)) {
270
- for (const dep of nextStory.depends_on) stack.push(dep);
271
- }
272
- }
273
- }
274
- return reach;
275
- }
276
-
277
246
  function inSameWave(reach, slugA, slugB) {
278
247
  if (slugA === slugB) return false;
279
248
  const a = reach.get(slugA);
@@ -39,15 +39,6 @@ export const DEFAULT_TASK_SIZING = Object.freeze({
39
39
  maxAcceptance: 14,
40
40
  });
41
41
 
42
- /**
43
- * Soft prose guidance for how many Stories a Feature typically decomposes
44
- * into before the Feature scope smells like two Features. Rendered into the
45
- * decomposer prompt's Feature-count sentence from this single constant so
46
- * the prompt prose cannot drift from the SSOT module (Story #3874). Advisory
47
- * only — no validator finding keys off it.
48
- */
49
- export const SOFT_STORIES_PER_FEATURE = 7;
50
-
51
42
  /**
52
43
  * `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
53
44
  * deliverable-granularity definition of a Story (Story #3777). It is stated
@@ -339,7 +330,7 @@ function computeStorySizingFindings(story, sizing) {
339
330
  * `findings`; the AC-visible `errors[]` channel is the rendered
340
331
  * subset where `severity === 'hard'`.
341
332
  *
342
- * 3-tier (Epic #3238): each Story is its own implementation unit and
333
+ * 2-tier (Epic #3238): each Story is its own implementation unit and
343
334
  * carries the `body` (acceptance / changes / wide) that the sizing layers
344
335
  * score. There is no Task tier, so findings are computed directly over
345
336
  * `stories`.
@@ -200,9 +200,8 @@ function makeMemoizedGitRunner(runner) {
200
200
  * path was deleted between planning and decomposition) — refuse to decompose
201
201
  * because the resulting Task would be unimplementable as written.
202
202
  *
203
- * Only Tasks are scanned; Features and Stories carry narrative copy, not
204
- * implementation paths, and their bodies routinely reference docs/templates
205
- * the freshness regex would (correctly) ignore.
203
+ * Only Stories are scanned they are the implementation unit; the Epic
204
+ * carries narrative copy, not implementation paths.
206
205
  *
207
206
  * @param {object} opts
208
207
  * @param {object[]} opts.tickets - Validated ticket hierarchy.
@@ -416,7 +415,6 @@ function renderMissLine({ slug, path }) {
416
415
 
417
416
  function indexTicketsBySlug(tickets) {
418
417
  const ticketBySlug = new Map();
419
- const features = [];
420
418
  const stories = [];
421
419
  const slugAdjacency = new Map();
422
420
  for (const t of tickets) {
@@ -429,75 +427,37 @@ function indexTicketsBySlug(tickets) {
429
427
  ticketBySlug.set(t.slug, t);
430
428
  }
431
429
  slugAdjacency.set(t.slug, t.depends_on ?? []);
432
- if (t.type === 'feature') features.push(t);
433
- else if (t.type === 'story') stories.push(t);
430
+ if (t.type === 'story') stories.push(t);
434
431
  }
435
- return { ticketBySlug, features, stories, slugAdjacency };
432
+ return { ticketBySlug, stories, slugAdjacency };
436
433
  }
437
434
 
438
- function assertEachTypePresent({ features, stories }) {
439
- if (features.length === 0)
435
+ /**
436
+ * 2-tier invariant (Story #4041): the decomposer emits Stories only — every
437
+ * ticket in the backlog must be `type: "story"` and at least one must be
438
+ * present. Any other type (the retired `feature`/`task` tiers, or planner
439
+ * hallucinations) HARD-rejects the decomposition.
440
+ */
441
+ function assertAllTicketsAreStories({ tickets, stories }) {
442
+ const nonStories = (tickets ?? []).filter((t) => t.type !== 'story');
443
+ if (nonStories.length > 0) {
444
+ const list = nonStories
445
+ .map((t) => `"${t.title}" (${t.slug ?? '<no slug>'}, type: ${t.type})`)
446
+ .join(', ');
440
447
  throw new Error(
441
- 'Cross-Validation Failed: Backlog must contain at least one Feature.',
448
+ `Cross-Validation Failed: ${nonStories.length} ticket(s) are not Stories: ${list}. ` +
449
+ 'The 2-tier hierarchy (Epic → Story) admits type "story" only — there is no Feature or Task tier.',
442
450
  );
451
+ }
443
452
  if (stories.length === 0)
444
453
  throw new Error(
445
454
  'Cross-Validation Failed: Backlog must contain at least one Story.',
446
455
  );
447
456
  }
448
457
 
449
- function assertHierarchy({ stories, ticketBySlug }) {
450
- for (const story of stories) {
451
- if (!story.parent_slug)
452
- throw new Error(
453
- `Cross-Validation Failed: Story "${story.title}" must have a parent_slug.`,
454
- );
455
- const parent = ticketBySlug.get(story.parent_slug);
456
- if (!parent || parent.type !== 'feature')
457
- throw new Error(
458
- `Cross-Validation Failed: Story "${story.title}" parent must be a Feature.`,
459
- );
460
- }
461
- }
462
-
463
- /**
464
- * Deterministic invariant (Story #3777): a Feature MUST decompose into at
465
- * least two Stories. A single-Story Feature is the work of a Story, not a
466
- * Feature — the Feature wrapper is dead weight and signals decomposition at
467
- * module/task granularity rather than deliverable granularity. HARD-reject
468
- * the decomposition, naming the offending Feature(s) and telling the planner
469
- * to collapse them, in the same throw-on-violation style as the surrounding
470
- * hierarchy invariants.
471
- */
472
- function assertNoSingleStoryFeature({ features, stories }) {
473
- const storyCountByParent = new Map();
474
- for (const story of stories) {
475
- if (!story.parent_slug) continue;
476
- storyCountByParent.set(
477
- story.parent_slug,
478
- (storyCountByParent.get(story.parent_slug) ?? 0) + 1,
479
- );
480
- }
481
- const undersized = features.filter(
482
- (feature) => (storyCountByParent.get(feature.slug) ?? 0) < 2,
483
- );
484
- if (undersized.length === 0) return;
485
- const list = undersized
486
- .map((feature) => {
487
- const count = storyCountByParent.get(feature.slug) ?? 0;
488
- return `"${feature.title}" (${feature.slug}, ${count} ${count === 1 ? 'Story' : 'Stories'})`;
489
- })
490
- .join(', ');
491
- throw new Error(
492
- `Cross-Validation Failed: ${undersized.length} Feature(s) decompose into fewer than two Stories: ${list}. ` +
493
- 'Every Feature MUST contain at least two Stories — a single-Story Feature is the work of a Story, not a Feature. ' +
494
- 'Collapse each offending Feature: drop the Feature wrapper and attach its lone Story to a sibling Feature, or merge the Feature into another.',
495
- );
496
- }
497
-
498
458
  /**
499
459
  * Return true when a Story object carries inline acceptance + verify
500
- * arrays — the 3-tier shape (Epic #3078) where the Story is itself the
460
+ * arrays — the inline-contract shape (Epic #3078) where the Story is itself the
501
461
  * implementation unit and acceptance / verify live on the Story body
502
462
  * rather than in child Task tickets.
503
463
  *
@@ -506,7 +466,7 @@ function assertNoSingleStoryFeature({ features, stories }) {
506
466
  * `acceptance[]` (no `verify[]`) cannot be implemented without a
507
467
  * verification handle, and a Story with only `verify[]` (no
508
468
  * `acceptance[]`) carries no observable criterion. Requiring both is the
509
- * inline-contract invariant every Story must satisfy in the 3-tier model.
469
+ * inline-contract invariant every Story must satisfy.
510
470
  */
511
471
  function hasInlineAcceptanceAndVerify(story) {
512
472
  if (story === null || typeof story !== 'object') return false;
@@ -520,7 +480,7 @@ function hasInlineAcceptanceAndVerify(story) {
520
480
  }
521
481
 
522
482
  function assertEveryStoryHasInlineContract({ stories }) {
523
- // 3-tier (Epic #3078 / #3238): every Story is its own implementation
483
+ // Every Story is its own implementation
524
484
  // unit and MUST carry a non-empty inline contract — top-level
525
485
  // `acceptance[]` AND `verify[]`. A Story missing either is the legacy
526
486
  // 4-tier shape that expected child Tasks; there is no Task tier any
@@ -577,12 +537,9 @@ function attachFindingsAndErrors(tickets, findings, errors) {
577
537
  }
578
538
 
579
539
  export function validateAndNormalizeTickets(tickets, opts = {}) {
580
- const { ticketBySlug, features, stories, slugAdjacency } =
581
- indexTicketsBySlug(tickets);
540
+ const { ticketBySlug, stories, slugAdjacency } = indexTicketsBySlug(tickets);
582
541
 
583
- assertEachTypePresent({ features, stories });
584
- assertHierarchy({ stories, ticketBySlug });
585
- assertNoSingleStoryFeature({ features, stories });
542
+ assertAllTicketsAreStories({ tickets, stories });
586
543
  assertEveryStoryHasInlineContract({ stories });
587
544
  assertNoUnknownDeps({ tickets, ticketBySlug });
588
545
 
@@ -680,9 +637,7 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
680
637
  // Internal helpers exposed for unit tests; not part of the public surface.
681
638
  export const _internal = {
682
639
  indexTicketsBySlug,
683
- assertEachTypePresent,
684
- assertHierarchy,
685
- assertNoSingleStoryFeature,
640
+ assertAllTicketsAreStories,
686
641
  assertEveryStoryHasInlineContract,
687
642
  assertNoUnknownDeps,
688
643
  assertAcyclic,