mandrel 1.94.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/.agents/README.md +59 -73
  2. package/.agents/agents/story-worker.md +12 -13
  3. package/.agents/docs/SDLC.md +489 -1285
  4. package/.agents/docs/agentrc-reference.json +177 -67
  5. package/.agents/docs/configuration.md +104 -138
  6. package/.agents/docs/execution-reference.md +17 -20
  7. package/.agents/docs/quality-gates.md +13 -19
  8. package/.agents/docs/workflows.md +3 -3
  9. package/.agents/instructions.md +70 -81
  10. package/.agents/rules/ci-remediation.md +8 -12
  11. package/.agents/rules/git-conventions-reference.md +6 -7
  12. package/.agents/rules/git-conventions.md +16 -22
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +54 -214
  15. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  16. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  17. package/.agents/schemas/risk-verdict.schema.json +0 -13
  18. package/.agents/scripts/analyze-execution.js +1 -1
  19. package/.agents/scripts/audit-to-stories.js +7 -7
  20. package/.agents/scripts/boot-sweep.js +1 -1
  21. package/.agents/scripts/check-lifecycle-lint.js +6 -9
  22. package/.agents/scripts/check-prepush-recovery.js +1 -1
  23. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  24. package/.agents/scripts/lib/Logger.js +6 -10
  25. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  26. package/.agents/scripts/lib/audit-suite/selector.js +5 -5
  27. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  28. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  29. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
  30. package/.agents/scripts/lib/baselines/reader.js +1 -6
  31. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  32. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  33. package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
  34. package/.agents/scripts/lib/checks/index.js +2 -1
  35. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
  36. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  37. package/.agents/scripts/lib/cli-args.js +16 -7
  38. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  39. package/.agents/scripts/lib/config/ci.js +6 -31
  40. package/.agents/scripts/lib/config/delivery-routing.js +45 -29
  41. package/.agents/scripts/lib/config/explain.js +55 -36
  42. package/.agents/scripts/lib/config/limits.js +17 -58
  43. package/.agents/scripts/lib/config/paths.js +0 -2
  44. package/.agents/scripts/lib/config/quality.js +1 -1
  45. package/.agents/scripts/lib/config/runners.js +17 -50
  46. package/.agents/scripts/lib/config/temp-paths.js +19 -14
  47. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  48. package/.agents/scripts/lib/config-resolver.js +2 -7
  49. package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
  50. package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
  51. package/.agents/scripts/lib/config-settings-schema.js +52 -38
  52. package/.agents/scripts/lib/dependency-parser.js +3 -2
  53. package/.agents/scripts/lib/doc-tiers.js +2 -2
  54. package/.agents/scripts/lib/duplicate-search.js +211 -41
  55. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  56. package/.agents/scripts/lib/findings/promote-finding.js +5 -5
  57. package/.agents/scripts/lib/framework-version.js +2 -3
  58. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  59. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
  60. package/.agents/scripts/lib/git-utils.js +32 -6
  61. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  62. package/.agents/scripts/lib/label-constants.js +10 -23
  63. package/.agents/scripts/lib/label-taxonomy.js +9 -43
  64. package/.agents/scripts/lib/observability/active-story-env.js +1 -1
  65. package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
  66. package/.agents/scripts/lib/observability/source-classifier.js +3 -3
  67. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  68. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
  69. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
  70. package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
  71. package/.agents/scripts/lib/orchestration/code-review.js +20 -268
  72. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  73. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
  74. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  75. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  76. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  77. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  78. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
  79. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
  80. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  81. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
  82. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  83. package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
  84. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  85. package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
  86. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
  87. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  88. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
  89. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
  90. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
  91. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
  92. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
  93. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  94. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  95. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
  96. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  97. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
  98. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
  99. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
  100. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
  101. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  102. package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
  103. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  104. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
  105. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  106. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
  107. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  108. package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
  109. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  110. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  111. package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
  112. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
  117. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  118. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  119. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  121. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  122. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
  123. package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
  124. package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
  125. package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
  126. package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
  127. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  128. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
  129. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
  130. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
  131. package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
  132. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
  133. package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
  134. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
  135. package/.agents/scripts/lib/planning-corpus.js +16 -11
  136. package/.agents/scripts/lib/preflight-runner.js +2 -2
  137. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  138. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
  139. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  140. package/.agents/scripts/lib/story-adjacency.js +11 -14
  141. package/.agents/scripts/lib/story-body/story-body.js +124 -70
  142. package/.agents/scripts/lib/story-plan.js +2 -4
  143. package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
  144. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  145. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
  146. package/.agents/scripts/lib/validation-evidence.js +1 -1
  147. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
  148. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  149. package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
  150. package/.agents/scripts/lint-issue-body.js +71 -40
  151. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  152. package/.agents/scripts/notify.js +4 -3
  153. package/.agents/scripts/plan-context.js +64 -74
  154. package/.agents/scripts/plan-persist.js +121 -280
  155. package/.agents/scripts/plan-run-epilogue.js +97 -0
  156. package/.agents/scripts/providers/github/issues.js +17 -33
  157. package/.agents/scripts/providers/github/mappers.js +0 -12
  158. package/.agents/scripts/providers/github/tickets.js +2 -5
  159. package/.agents/scripts/resolve-plan-run.js +117 -0
  160. package/.agents/scripts/signals-view.js +24 -19
  161. package/.agents/scripts/single-story-close.js +11 -14
  162. package/.agents/scripts/single-story-confirm-merge.js +39 -23
  163. package/.agents/scripts/single-story-init.js +29 -20
  164. package/.agents/scripts/stories-wave-tick.js +6 -6
  165. package/.agents/scripts/story-plan.js +26 -47
  166. package/.agents/scripts/update-ticket-state.js +6 -15
  167. package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
  168. package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
  169. package/.agents/skills/core/scope-triage/SKILL.md +28 -172
  170. package/.agents/skills/skills.index.json +3 -43
  171. package/.agents/starter-agentrc.json +0 -5
  172. package/.agents/templates/agent-protocol.md +9 -10
  173. package/.agents/workflows/audit-architecture.md +3 -3
  174. package/.agents/workflows/audit-clean-code.md +3 -3
  175. package/.agents/workflows/audit-dependencies.md +3 -3
  176. package/.agents/workflows/audit-devops.md +3 -3
  177. package/.agents/workflows/audit-documentation.md +5 -5
  178. package/.agents/workflows/audit-lighthouse.md +3 -3
  179. package/.agents/workflows/audit-navigability.md +3 -2
  180. package/.agents/workflows/audit-performance.md +3 -3
  181. package/.agents/workflows/audit-privacy.md +3 -3
  182. package/.agents/workflows/audit-quality.md +3 -3
  183. package/.agents/workflows/audit-security.md +3 -3
  184. package/.agents/workflows/audit-seo.md +3 -3
  185. package/.agents/workflows/audit-sre.md +3 -3
  186. package/.agents/workflows/audit-to-stories.md +20 -20
  187. package/.agents/workflows/audit-ux-ui.md +3 -3
  188. package/.agents/workflows/deliver.md +122 -174
  189. package/.agents/workflows/git-cleanup.md +3 -4
  190. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  191. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
  192. package/.agents/workflows/helpers/code-review.md +70 -193
  193. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
  194. package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
  195. package/.agents/workflows/helpers/diagnose.md +10 -10
  196. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  197. package/.agents/workflows/helpers/signals.md +16 -16
  198. package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
  199. package/.agents/workflows/mandrel-update.md +2 -1
  200. package/.agents/workflows/plan.md +112 -145
  201. package/.agents/workflows/qa-assist.md +24 -30
  202. package/.agents/workflows/qa-explore.md +29 -38
  203. package/.agents/workflows/qa-run.md +2 -2
  204. package/README.md +9 -8
  205. package/docs/CHANGELOG.md +30 -0
  206. package/lib/migrations/index.js +6 -5
  207. package/package.json +2 -2
  208. package/.agents/agents/retro.md +0 -42
  209. package/.agents/personas/architect.md +0 -113
  210. package/.agents/personas/devops-engineer.md +0 -38
  211. package/.agents/personas/engineer.md +0 -33
  212. package/.agents/personas/project-manager.md +0 -114
  213. package/.agents/personas/qa-engineer.md +0 -95
  214. package/.agents/personas/security-engineer.md +0 -111
  215. package/.agents/personas/technical-writer.md +0 -101
  216. package/.agents/schemas/dispatch-manifest.json +0 -232
  217. package/.agents/schemas/epic-spec.schema.json +0 -153
  218. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  219. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  220. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  221. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  222. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  223. package/.agents/scripts/dispatcher.js +0 -295
  224. package/.agents/scripts/epic-audit-prepare.js +0 -497
  225. package/.agents/scripts/epic-audit-recheck.js +0 -274
  226. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  227. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  228. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  229. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  230. package/.agents/scripts/epic-plan-clarity.js +0 -211
  231. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  232. package/.agents/scripts/epic-reconcile.js +0 -625
  233. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  234. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  235. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  236. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  237. package/.agents/scripts/lib/config/preflight.js +0 -58
  238. package/.agents/scripts/lib/config/retro.js +0 -77
  239. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  240. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  241. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  242. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  243. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  244. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  245. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  246. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  247. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  248. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  249. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  250. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  251. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  252. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  253. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  254. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  255. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  256. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  257. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  258. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  259. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  260. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  261. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  262. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  263. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  264. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  265. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  266. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  267. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  268. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  269. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  270. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  271. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  272. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  273. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  274. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  275. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  276. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  277. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  278. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  279. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  280. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  281. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  282. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  283. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  284. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  285. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  286. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  287. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  288. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  289. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  290. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  291. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  292. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  293. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  295. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  296. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  297. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  298. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  299. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  300. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  301. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  302. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  303. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  304. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  305. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  306. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  307. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  308. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  309. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  310. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  311. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  312. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  313. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  314. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  315. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  316. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  317. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  318. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  319. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  320. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  321. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  322. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  323. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  324. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  325. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  326. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  327. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  328. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  329. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  330. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  331. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  332. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  333. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  334. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  335. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  336. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  337. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  338. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  339. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  340. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  341. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  342. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  343. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  344. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  345. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  346. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  347. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  348. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  349. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  350. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  351. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  352. package/.agents/scripts/lib/spec/index.js +0 -36
  353. package/.agents/scripts/lib/spec/loader.js +0 -425
  354. package/.agents/scripts/lib/spec/state.js +0 -208
  355. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  356. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  357. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  358. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  359. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  360. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  361. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  362. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  363. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  364. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  365. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  366. package/.agents/scripts/lifecycle-emit.js +0 -510
  367. package/.agents/scripts/retro-run.js +0 -218
  368. package/.agents/scripts/slice-phase.js +0 -361
  369. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  370. package/.agents/scripts/story-close.js +0 -294
  371. package/.agents/scripts/story-init.js +0 -599
  372. package/.agents/scripts/story-phase.js +0 -369
  373. package/.agents/scripts/wave-tick.js +0 -464
  374. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  375. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  376. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  377. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  378. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  379. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  380. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  381. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  382. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  383. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  384. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  385. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  386. package/.agents/workflows/helpers/epic-audit.md +0 -189
  387. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  388. package/.agents/workflows/helpers/epic-testing.md +0 -125
  389. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  390. package/.agents/workflows/helpers/plan-epic.md +0 -353
  391. package/.agents/workflows/helpers/plan-story.md +0 -251
  392. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  393. /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
@@ -1,57 +1,42 @@
1
1
  /**
2
- * Story sizing model — a small flat set of knobs plus the optional `wide`
3
- * declaration that lifts the hard file-width rejection. Co-located with
4
- * `ticket-validator.js`, but kept as its own module so the validator's
5
- * primary file stays under the maintainability ceiling. The validator
6
- * imports `computeSizingFindings` and `renderHardFindingError` and stitches
7
- * the result onto its return value as `findings` / `errors`.
2
+ * Story sizing model — v2 model-capacity split advisory.
8
3
  *
9
- * `DEFAULT_TASK_SIZING` is the **single source of truth** for the sizing
4
+ * Per-Story file/AC ceilings (`DEFAULT_TASK_SIZING.softFiles` /
5
+ * `hardFiles` / `softAcceptanceCount`) are gone. A Story is sized by
6
+ * **cohesion first**; the numeric backstop only asks whether the authored
7
+ * Story ticket itself is pathologically verbose. See `docs/roadmap.md`
8
+ * § v2.0.0 design decision 2.
9
+ *
10
+ * Co-located with `ticket-validator.js`, but kept as its own module so the
11
+ * validator's primary file stays under the maintainability ceiling. The
12
+ * validator imports `computeSizingFindings` and `renderHardFindingError` and
13
+ * stitches the result onto its return value as `findings` / `errors`.
14
+ *
15
+ * `DEFAULT_MODEL_CAPACITY` is the **single source of truth** for the capacity
10
16
  * thresholds. The decomposer prompt template
11
17
  * (`.agents/scripts/lib/templates/decomposer-prompts.js`) and the authoring
12
18
  * SKILL (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`) reference
13
19
  * these numbers rather than restating divergent ones — the prompt generates
14
20
  * its threshold sentence from this constant so the two surfaces cannot drift.
15
- * Keep it in lockstep with `.agents/schemas/agentrc.schema.json`
16
- * (`$defs.taskSizing`) and the JS mirror in
17
- * `.agents/scripts/lib/config-settings-schema.js`.
18
21
  *
19
- * Sizing model (Story #3760 — profile-matrix collapse; Story #3874 — one
20
- * uniform relaxed profile; the hard acceptance ceiling was removed after the
21
- * Epic #4355 decomposition experiment showed it forced fragmentation):
22
- * - Flat knobs: `softFiles` (~15), `hardFiles` (~30),
23
- * `softAcceptanceCount` (~10). No per-profile ceiling map, no parallel
24
- * `testSurface` axis, no selector and no second profile. Acceptance
25
- * mass is advisory-only.
26
- * - The four-profile `sizingProfile` enum is replaced by a single optional
27
- * `wide` declaration carrying a one-line human-readable reason. Declaring
28
- * `wide` with a reason lifts the `hardFiles` rejection; no Story is
29
- * rejected for width when `wide` is declared.
30
- * - Cohesion is the primary heuristic (one Story = one coherent change with
31
- * one reason to exist); the numeric ceilings are a backstop. See the
32
- * decomposer prompt and authoring SKILL.
22
+ * Capacity model:
23
+ * - Plan-time **session mass** = authored tokens only (`estimateTokens` of
24
+ * the Story's goal/reason/acceptance/verify/slicing/spec/change paths).
25
+ * - Soft / hard ceilings are **absolute** authored-token counts (no
26
+ * `maxTokenBudget` denominator that envelope was retired).
27
+ * - The optional `wide` declaration lifts the hard session-mass rejection.
28
+ * - Cohesion remains the primary heuristic; capacity is a pathological-
29
+ * verbosity backstop only (not an operator-tunable agentrc knob).
33
30
  */
34
31
 
35
32
  import { parse as parseStoryBody } from '../story-body/story-body.js';
33
+ import { estimateTokens } from './context-envelope.js';
36
34
 
37
35
  /**
38
36
  * Normalize a Story's `body` to the structured object the sizing layers
39
37
  * score, mirroring `validateAcFreshness` / `collectStoryAssumptionEntries`
40
38
  * (Story #3302) and `resolveStructuredBody` in `task-body-validator.js`.
41
39
  *
42
- * The decomposer emits `body` as the canonical serialized **string**
43
- * (`decomposer-prompts.js`), but the sizing layers historically read
44
- * `story.body` only when it was already an object — so on the production
45
- * string shape `changes` / `wide` fell through to empty and the `hardFiles`
46
- * / unanchored-constant backstops emitted nothing. A defensive parse here
47
- * restores parity:
48
- * - **string body** → parsed via `parseStoryBody`; an unparseable string
49
- * yields `null` (the gate degrades to "no structured signal", never
50
- * throws mid-validation).
51
- * - **object body** → returned verbatim (a caller may pass the
52
- * pre-serialize shape directly; `parse` round-trips it).
53
- * - **null / other** → `null`.
54
- *
55
40
  * @param {object} story
56
41
  * @returns {object|null}
57
42
  */
@@ -71,13 +56,8 @@ function resolveStoryBody(story) {
71
56
 
72
57
  /**
73
58
  * Resolve the acceptance-criteria array for a Story, preferring the
74
- * authoritative top-level `story.acceptance` (the binding contract the
75
- * validator already requires every Story to carry inline) over the
76
- * structured body's `acceptance`. Reading the top-level array makes the
77
- * acceptance ceiling correct regardless of body shape — a string body whose
78
- * structured `acceptance` is only reachable after a parse, or an object body
79
- * (Story #4271). Falls back to `resolveStoryBody(story).acceptance` only when
80
- * the top-level array is absent.
59
+ * authoritative top-level `story.acceptance` over the structured body's
60
+ * `acceptance`.
81
61
  *
82
62
  * @param {object} story
83
63
  * @returns {unknown[]}
@@ -89,17 +69,20 @@ function resolveAcceptance(story) {
89
69
  }
90
70
 
91
71
  /**
92
- * Resolve the `depends_on` edge list for a Story. `depends_on` may live at the
93
- * top level (the validator normalizes it there — see `ticket-validator.js`) or
94
- * inside the structured body (the shape `story-body.js` parses out). Prefer the
95
- * top-level array, falling back to the resolved body's `depends_on`. Returns
96
- * only non-empty string slugs — mirroring the `story-body.js` normalization so
97
- * a blank / non-string edge never counts as a real dependency.
72
+ * Resolve the verify-commands array for a Story, preferring the top-level
73
+ * `story.verify` over the structured body's `verify`.
98
74
  *
99
- * The cross-Story validator already rejects any `depends_on` slug that does not
100
- * match a sibling in the same decomposition, so every edge that survives to the
101
- * sizing layer is guaranteed to point at a sibling. The merge-candidate
102
- * heuristic therefore only needs to know whether the list is non-empty.
75
+ * @param {object} story
76
+ * @returns {unknown[]}
77
+ */
78
+ function resolveVerify(story) {
79
+ if (Array.isArray(story?.verify)) return story.verify;
80
+ const body = resolveStoryBody(story);
81
+ return Array.isArray(body?.verify) ? body.verify : [];
82
+ }
83
+
84
+ /**
85
+ * Resolve the `depends_on` edge list for a Story.
103
86
  *
104
87
  * @param {object} story
105
88
  * @returns {string[]}
@@ -113,127 +96,72 @@ function resolveDependsOn(story) {
113
96
  : [];
114
97
  }
115
98
 
116
- export const DEFAULT_TASK_SIZING = Object.freeze({
117
- // Typical-Story warning thresholds (softemit advisory findings).
118
- // Story #4162 raised `softFiles` 8 → 15: a capability-sized Story routinely
119
- // touches a dozen-plus files for one cohesive reason, so the advisory width
120
- // nudge fired far too eagerly and biased the decomposer toward over-slicing.
121
- // The hard `hardFiles` rejection (30) is unchanged.
122
- softFiles: 15,
123
- softAcceptanceCount: 10,
124
- // Hard ceiling (rejection unless lifted via `wide`). Acceptance mass has
125
- // no hard ceiling: the former `maxAcceptance` rejection forced careful,
126
- // fine-grained specs to fragment one coherent capability into dependent
127
- // slices (observed on Epic #4355), so it was removed the delivery-
128
- // schedule simulation in the decomposer prompt owns that judgment now.
129
- hardFiles: 30,
130
- // Under-size (merge-candidate) thresholds (Story #4312). A Story with a
131
- // footprint at or below BOTH ceilings that also carries at least one
132
- // `depends_on` edge to a sibling looks like a dependent fragment rather than
133
- // a capability slice — the machine-checkable form of the single-consumer
134
- // merge rule. Emitted as a `soft` advisory only; never a rejection. A tiny
135
- // ORPHAN Story (no `depends_on`) stays silent — small orthogonal slices are
136
- // legitimate.
137
- mergeCandidateMaxFiles: 3,
138
- mergeCandidateMaxAcceptance: 4,
99
+ /**
100
+ * Framework capacity constantnot operator-tunable via `.agentrc.json`
101
+ * (same collapse pattern as `maxTickets` / Story #4163).
102
+ *
103
+ * Absolute authored-token ceilings (no delivery-envelope denominator):
104
+ *
105
+ * - Soft 30_000: cohesion check when the ticket itself is already a
106
+ * substantial document. Normal capability Stories stay silent.
107
+ * - Hard 75_000: reject Spec novels unless `wide`.
108
+ * - Merge candidate 1_500: thin `depends_on` fragment (soft only).
109
+ *
110
+ * Cohesion / split policy / conflict advisories remain the primary sizing
111
+ * signal; these ceilings catch pathological ticket verbosity only.
112
+ */
113
+ export const DEFAULT_MODEL_CAPACITY = Object.freeze({
114
+ softSessionTokens: 30000,
115
+ hardSessionTokens: 75000,
116
+ mergeCandidateMaxSessionTokens: 1500,
139
117
  });
140
118
 
119
+ /**
120
+ * Resolve capacity ceilings, shallow-merging an optional override bag
121
+ * (tests / programmatic only). Pure; used by the validator and the
122
+ * decomposer prompt.
123
+ *
124
+ * @param {object} [capacity]
125
+ * @returns {{ softSessionTokens: number, hardSessionTokens: number, mergeCandidateMaxSessionTokens: number }}
126
+ */
127
+ export function resolveCapacityCeilings(capacity = DEFAULT_MODEL_CAPACITY) {
128
+ const merged = { ...DEFAULT_MODEL_CAPACITY, ...(capacity ?? {}) };
129
+ return {
130
+ softSessionTokens: merged.softSessionTokens,
131
+ hardSessionTokens: merged.hardSessionTokens,
132
+ mergeCandidateMaxSessionTokens: merged.mergeCandidateMaxSessionTokens,
133
+ };
134
+ }
135
+
141
136
  /**
142
137
  * `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
143
138
  * deliverable-granularity definition of a Story (Story #3777). It is stated
144
- * ONCE here and consumed by BOTH the decomposer prompt template
145
- * (`.agents/scripts/lib/templates/decomposer-prompts.js`, which interpolates
146
- * the string verbatim) AND the authoring SKILL
147
- * (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`, whose prose
148
- * mirrors these sentences). The SKILL cannot import JS, so the
149
- * `epic-plan-decompose-author` smoke test and the `ticket-decomposer` prompt
150
- * test both assert the canonical phrasing on each surface — a divergent
151
- * restatement fails those gates. This reuses the #3760 single-source
152
- * mechanism (one constant, two surfaces, drift-gated by tests).
153
- *
154
- * The definition: a Story is a **capability slice a frontier model delivers
155
- * and self-verifies in one pass** — a shippable slice a reviewer would accept
156
- * as a single PR — not a single module or file. Module-level slices fold into
157
- * the capability they belong to, and a Story whose only consumer is one
158
- * sibling Story is merged into that sibling (single-consumer merge rule).
159
- *
160
- * The `envelopeFloor` sentence is the **soft** complement to the sizing
161
- * section's envelope framing (Story #4313): the delivery envelope
162
- * (`maxTokenBudget`) bounds the TOP of a Story, but under-utilizing it is
163
- * itself a merge signal. It is deliberately prose-only — an illustrative
164
- * fraction, not a threshold constant, and no validator finding backs it (the
165
- * mechanical backstop is the `merge-candidate` finding). It names the
166
- * per-Story delivery-session cost (hydration, branch, PR, review, CI) so the
167
- * "models one-shot bigger things now" planning assumption is stated, not
168
- * implicit.
139
+ * ONCE here and consumed by BOTH the decomposer prompt template and the
140
+ * authoring SKILL.
169
141
  */
170
142
  export const DELIVERABLE_GRANULARITY_GUIDANCE = Object.freeze({
171
- // The one-sentence definition of Story granularity.
172
143
  definition:
173
144
  'A Story is a **capability slice a frontier model delivers and self-verifies in one pass** — a shippable slice a reviewer would accept as a single PR, a capability or user-visible surface, **not a single module or file**. Fold module-level slices into the capability they belong to rather than emitting one Story per module.',
174
- // The single-consumer merge rule.
175
145
  singleConsumerRule:
176
146
  '**Single-consumer merge rule.** A Story whose only consumer is one sibling Story should be **merged into that sibling** rather than emitted separately — a single-consumer downstream slice is not its own unit of work.',
177
- // The envelope-floor heuristic (soft; no validator finding — Story #4313).
178
147
  envelopeFloor:
179
- '**Envelope floor under-utilizing the envelope is a merge signal.** A Story that would plausibly consume well under a third of the delivery envelope (`maxTokenBudget`) and is neither parallel-deliverable nor orthogonal to its siblings should be **merged into its consumer**. The fraction is illustrative, not a thresholdthe point is that modern frontier models one-shot capability-sized changes, so a chain of small dependent Stories needlessly pays a full delivery session (hydration, branch, PR, review, CI) per link. Merge such links up unless a parallelism or orthogonality reason justifies the separate slice.',
148
+ '**Thin dependent slices are a merge signal.** A Story that is neither parallel-deliverable nor orthogonal to its siblings especially a short `depends_on` fragment whose only job is to feed one consumershould be **merged into its consumer**. Modern frontier models one-shot capability-sized changes, so a chain of small dependent Stories needlessly pays a full delivery session (branch, PR, review, CI) per link. Merge such links up unless a parallelism or orthogonality reason justifies the separate slice.',
180
149
  });
181
150
 
182
151
  /**
183
152
  * `AUTHORING_ALTITUDE_GUIDANCE` is the **single source of truth** for the
184
153
  * binding-vs-advisory authoring altitude (Epic #4131 F8) and the New-File
185
- * Contract (Story #4272). It is stated ONCE here and consumed by BOTH the
186
- * decomposer prompt template
187
- * (`.agents/scripts/lib/templates/decomposer-prompts.js`, which interpolates
188
- * the strings verbatim into the rendered system prompt) AND the authoring
189
- * SKILL (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`, whose
190
- * prose mirrors these sentences). The SKILL cannot import JS, so the
191
- * `ticket-decomposer` prompt test asserts the canonical phrasing on both
192
- * surfaces — a divergent restatement fails that gate. This reuses the #3777
193
- * single-source mechanism (one constant, two surfaces, drift-gated by tests).
194
- *
195
- * The altitude: `acceptance[]` / `verify[]` are the **binding contract** (the
196
- * sole definition of "done"); `changes[]` / `references[]` are an **advisory
197
- * implementation sketch** the executor MAY revise. Author acceptance to assert
198
- * the **outcome** independent of file layout — never pin an incidental helper
199
- * name or private path into an acceptance item. The advisory sketch is still
200
- * validated (base-branch probes, New-File Contract) and never licenses
201
- * skipping `acceptance[]` / `verify[]` or any `rules/security-baseline.md` MUST.
154
+ * Contract (Story #4272).
202
155
  */
203
156
  export const AUTHORING_ALTITUDE_GUIDANCE = Object.freeze({
204
- // The binding-vs-advisory altitude statement.
205
157
  altitude:
206
158
  '**Binding contract vs advisory sketch.** `acceptance[]` and `verify[]` are the Story\'s **binding contract** — the executor MUST satisfy them exactly, and they are the only definition of "done." `changes[]` and `references[]` are an **advisory implementation sketch**: your best prediction of the file footprint, which the executor MAY revise when the real codebase diverges from the sketch. Author `acceptance[]` / `verify[]` to assert the **outcome** independent of any one file layout — never pin an incidental implementation detail (an internal helper name, a private file path) into an acceptance item that the advisory `changes[]` is free to reshape; assert the observable behaviour instead.',
207
- // The advisory-does-not-mean-unvalidated caveat.
208
159
  advisoryCaveat:
209
160
  "**Advisory does not mean unvalidated.** `changes[]` paths still pass the base-branch file-assumption probes (a `creates` against an existing path still fails), the New-File Contract still holds, and the executor's latitude to revise the approach never licenses skipping `acceptance[]` / `verify[]` or relaxing any `rules/security-baseline.md` MUST.",
210
- // The New-File Contract.
211
161
  newFileContract:
212
162
  '**New-File Contract.** Any path named in a Story\'s `goal`, `acceptance`, or `verify` that does NOT already exist on `main` MUST also appear in that Story\'s `changes[]` with `assumption: "creates"`; otherwise the freshness validator rejects the decompose — even when the Story is the one authoring the file.',
213
163
  });
214
164
 
215
- /**
216
- * Configuration-constant phrase patterns the `unanchored-constant` heuristic
217
- * scans Story acceptance criteria for. Each entry matches the *kind* of
218
- * tunable an implementing agent would otherwise have to context-hop to the
219
- * Tech Spec (or an ADR) to resolve: a retention window, a rate limit, a
220
- * timeout, an age cutoff, a bounded window, a per-unit cap, or a quota.
221
- *
222
- * The list is the single source of truth for the heuristic and intentionally
223
- * narrow — it targets the small set of config constants that recur across
224
- * Stories and that an author can almost always inline as a concrete value at
225
- * authoring time. Anchored with `\b` word boundaries so substrings inside
226
- * unrelated prose ("maximum", "timeouts handled elsewhere") don't over-match.
227
- *
228
- * Covered phrases (acceptance criterion of Story #3855):
229
- * - `retention window`
230
- * - `rate limit` / `rate-limit`
231
- * - `timeout`
232
- * - `older than`
233
- * - `within N` (a bounded count/duration window)
234
- * - `max ... per ...` (a per-unit cap, e.g. "max requests per second")
235
- * - `quota`
236
- */
237
165
  const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
238
166
  /\bretention window\b/i,
239
167
  /\brate[ -]limit\b/i,
@@ -244,25 +172,8 @@ const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
244
172
  /\bquota\b/i,
245
173
  ]);
246
174
 
247
- /**
248
- * Matches a concrete numeric value anywhere in an acceptance criterion. A
249
- * bare digit run is enough to count as "anchored": `"older than 90 days"`,
250
- * `"5 req/s"`, `"within 30 minutes"`, and `"max 100 per hour"` all carry a
251
- * digit, so they are not flagged. Spelled-out small numbers are intentionally
252
- * NOT treated as anchors — the heuristic nudges authors toward an explicit
253
- * numeral the implementing agent can copy verbatim.
254
- */
255
175
  const CONCRETE_VALUE_RE = /\d/;
256
176
 
257
- /**
258
- * Soft, advisory `unanchored-constant` finding (Story #3855). Surfaces a
259
- * Story acceptance criterion that references a configuration constant
260
- * (retention window, rate limit, timeout, threshold) without stating a
261
- * concrete numeric value inline — so an implementing agent doesn't have to
262
- * context-hop to the Tech Spec or guess. Advisory only: `normalizeTickets`
263
- * still returns successfully and the finding rides the advisory nudges array
264
- * alongside the sizing soft findings.
265
- */
266
177
  function makeUnanchoredConstant(slug, criterion) {
267
178
  return {
268
179
  kind: 'unanchored-constant',
@@ -273,21 +184,8 @@ function makeUnanchoredConstant(slug, criterion) {
273
184
  };
274
185
  }
275
186
 
276
- /**
277
- * Scan a single Story's acceptance criteria for unanchored configuration
278
- * constants. A criterion trips the finding when it matches one of
279
- * `UNANCHORED_CONSTANT_PATTERNS` and carries NO concrete numeric value
280
- * (`CONCRETE_VALUE_RE`) — the digit is the signal the author already inlined
281
- * the value, so a criterion like `"older than 90 days"` is left alone while
282
- * `"older than the retention window"` is flagged. One finding per offending
283
- * criterion.
284
- */
285
187
  function computeUnanchoredConstantFindings(story) {
286
188
  const out = [];
287
- // Read the authoritative top-level `story.acceptance` (the binding
288
- // contract), falling back to the structured body's acceptance only when the
289
- // top-level array is absent. This is correct regardless of body shape —
290
- // string or object (Story #4271).
291
189
  const acceptance = resolveAcceptance(story);
292
190
  for (const item of acceptance) {
293
191
  const criterion = String(item ?? '');
@@ -302,19 +200,6 @@ function computeUnanchoredConstantFindings(story) {
302
200
  return out;
303
201
  }
304
202
 
305
- /**
306
- * Soft, advisory `missing-reason-to-exist` finding (Story #4273). Surfaces a
307
- * Story whose body carries no non-empty `reason_to_exist` — the
308
- * machine-checkable form of the cohesion rule (**one Story = one coherent
309
- * change with one reason to exist**). `reason_to_exist` is marked REQUIRED by
310
- * the decomposer prompt and is the field the `epic-plan-consolidate` critic
311
- * gates on, but that critic is an honor-system LLM check with no runtime
312
- * backstop. This deterministic finding is the cheap backstop.
313
- *
314
- * Severity is `soft` (not a hard reject) so existing `reason_to_exist`-less
315
- * standalone / audit Stories are surfaced as an advisory nudge rather than
316
- * blocked — matching the `unanchored-constant` finding's advisory contract.
317
- */
318
203
  function makeMissingReasonToExist(slug) {
319
204
  return {
320
205
  kind: 'missing-reason-to-exist',
@@ -325,17 +210,6 @@ function makeMissingReasonToExist(slug) {
325
210
  };
326
211
  }
327
212
 
328
- /**
329
- * Emit a soft `missing-reason-to-exist` finding when the Story body resolves
330
- * to no non-empty `reason_to_exist`. The body parser
331
- * (`story-body/story-body.js`) already normalizes `reason_to_exist` to a
332
- * non-empty trimmed string or `null`, so reading `body.reason_to_exist` after
333
- * `resolveStoryBody` is correct regardless of body shape — a serialized
334
- * **string** body (the production decomposer shape) or an object body
335
- * (Story #4271). A body that fails to parse resolves to `null` and trips the
336
- * finding, which is the right advisory signal: the author should re-emit a
337
- * parseable body carrying the field. One finding per Story.
338
- */
339
213
  function computeMissingReasonToExistFinding(story) {
340
214
  const body = resolveStoryBody(story);
341
215
  const reason = body?.reason_to_exist;
@@ -343,82 +217,31 @@ function computeMissingReasonToExistFinding(story) {
343
217
  return hasReason ? [] : [makeMissingReasonToExist(story.slug)];
344
218
  }
345
219
 
346
- /**
347
- * Soft, advisory `merge-candidate` finding (Story #4312). Surfaces a Story that
348
- * looks like a dependent fragment rather than a capability slice — the
349
- * machine-checkable form of the single-consumer merge rule
350
- * (`DELIVERABLE_GRANULARITY_GUIDANCE.singleConsumerRule`). Never a rejection:
351
- * `severity: 'soft'`, so it rides the advisory `findings[]` channel and never
352
- * enters the hard `errors[]` array.
353
- *
354
- * The rendered message names the depended-on sibling slug(s) and recommends
355
- * merging into the consumer, mirroring the tone of the `wide-undeclared` nudge.
356
- */
357
- function makeMergeCandidate(slug, fileCount, acceptanceCount, dependsOn) {
220
+ function makeMergeCandidate(slug, sessionMass, dependsOn) {
358
221
  const siblings = dependsOn.map((d) => `"${d}"`).join(', ');
359
222
  return {
360
223
  kind: 'merge-candidate',
361
224
  severity: 'soft',
362
225
  ticketSlug: slug,
363
- fileCount,
364
- acceptanceCount,
226
+ sessionMass,
365
227
  dependsOn,
366
- message: `Story "${slug}" is a thin dependent slice (${fileCount} declared file(s), ${acceptanceCount} acceptance item(s)) that depends on sibling(s) ${siblings}. A Story whose only role is to feed one sibling is not its own unit of work — consider merging it into the consumer (single-consumer merge rule) rather than shipping it as a separate slice.`,
228
+ message: `Story "${slug}" is a thin dependent slice (estimated session mass ${sessionMass} tokens) that depends on sibling(s) ${siblings}. A Story whose only role is to feed one sibling is not its own unit of work — consider merging it into the consumer (single-consumer merge rule) rather than shipping it as a separate slice.`,
367
229
  };
368
230
  }
369
231
 
370
232
  /**
371
- * Emit a soft `merge-candidate` finding when a Story meets the under-size
372
- * heuristic (Story #4312): footprint ≤ `mergeCandidateMaxFiles` declared
373
- * `changes[]` files AND ≤ `mergeCandidateMaxAcceptance` acceptance items AND at
374
- * least one `depends_on` edge to a sibling. All three conditions MUST hold — a
375
- * tiny ORPHAN Story (no `depends_on`) stays silent because small orthogonal
376
- * slices are legitimate. Glob entries mark the footprint as unknown-width, so a
377
- * glob-carrying Story is never a merge candidate.
378
- *
379
- * @param {object} story
380
- * @param {{ fileCount: number, hasGlobs: boolean }} changesAnalysis
381
- * @param {number} acceptanceCount
382
- * @param {object} sizing
383
- * @returns {object[]}
384
- */
385
- function computeMergeCandidateFinding(
386
- story,
387
- changesAnalysis,
388
- acceptanceCount,
389
- sizing,
390
- ) {
391
- if (changesAnalysis.hasGlobs) return [];
392
- const dependsOn = resolveDependsOn(story);
393
- if (dependsOn.length === 0) return [];
394
- if (changesAnalysis.fileCount > sizing.mergeCandidateMaxFiles) return [];
395
- if (acceptanceCount > sizing.mergeCandidateMaxAcceptance) return [];
396
- return [
397
- makeMergeCandidate(
398
- story.slug,
399
- changesAnalysis.fileCount,
400
- acceptanceCount,
401
- dependsOn,
402
- ),
403
- ];
404
- }
405
-
406
- /**
407
- * Returns true when a `changes[]` entry is a glob pattern. Handles both the
408
- * canonical PathEntry object form `{ path, assumption }` and legacy strings.
233
+ * Returns true when a `changes[]` entry is a glob pattern.
409
234
  */
410
235
  function isGlobBullet(bullet) {
411
- const s = bullet?.path ?? bullet;
236
+ const s = bullet?.path;
412
237
  return typeof s === 'string' && s.includes('*');
413
238
  }
414
239
 
415
240
  /**
416
- * Extract the path-shaped head from a single `changes` entry. Handles the
417
- * canonical PathEntry object form (path used directly) and the legacy
418
- * `"<path>: <verb>"` string form (slices on the first colon).
241
+ * Extract the path from a single object-form `changes` entry.
419
242
  */
420
243
  function extractChangeBulletPath(bullet) {
421
- const s = typeof bullet === 'string' ? bullet : (bullet?.path ?? null);
244
+ const s = bullet?.path ?? null;
422
245
  if (!s) return null;
423
246
  const colonIdx = s.indexOf(':');
424
247
  if (colonIdx <= 0) return /[\\/.]/.test(s) ? s.trim() : null;
@@ -427,44 +250,91 @@ function extractChangeBulletPath(bullet) {
427
250
  }
428
251
 
429
252
  /**
430
- * Analyse the `changes[]` array and return:
431
- * - `fileCount` — number of unique non-glob path-shaped heads
432
- * - `hasGlobs` — true when at least one bullet is a glob pattern
433
- *
434
- * Handles both the canonical PathEntry object form `{ path, assumption }` and
435
- * the legacy string form via the underlying helpers.
253
+ * Analyse the `changes[]` array and return fileCount / hasGlobs / path texts.
436
254
  */
437
255
  function analyseChanges(changes) {
438
256
  const paths = new Set();
257
+ const pathTexts = [];
439
258
  let hasGlobs = false;
440
259
  for (const bullet of changes) {
441
260
  if (isGlobBullet(bullet)) {
442
261
  hasGlobs = true;
262
+ const globText = bullet?.path ?? '';
263
+ if (globText) pathTexts.push(String(globText));
443
264
  continue;
444
265
  }
445
266
  const path = extractChangeBulletPath(bullet);
446
- if (path) paths.add(path);
267
+ if (path) {
268
+ paths.add(path);
269
+ pathTexts.push(path);
270
+ }
447
271
  }
448
- return { fileCount: paths.size, hasGlobs };
272
+ return { fileCount: paths.size, hasGlobs, pathTexts };
273
+ }
274
+
275
+ /**
276
+ * Estimate the plan-time session mass (tokens) for a Story.
277
+ *
278
+ * Session mass is **authored prose only** — `estimateTokens` of the Story's
279
+ * goal / reason / acceptance / verify / slicing / spec / change-path text.
280
+ * File count and AC count do not add delivery-cost proxies (those recreated
281
+ * the retired count ceilings). Glob entries contribute their text but do not
282
+ * imply known width.
283
+ *
284
+ * @param {object} story
285
+ * @param {object} [_capacity] Unused; retained so call sites that still pass
286
+ * a capacity bag keep working. Mass no longer depends on capacity knobs.
287
+ * @returns {{ sessionMass: number, authoredTokens: number, acceptanceCount: number, fileCount: number, hasGlobs: boolean, changesAnalysis: object }}
288
+ */
289
+ export function estimateStorySessionMass(
290
+ story,
291
+ _capacity = DEFAULT_MODEL_CAPACITY,
292
+ ) {
293
+ const body = resolveStoryBody(story);
294
+ const acceptance = resolveAcceptance(story);
295
+ const verify = resolveVerify(story);
296
+ const changes = Array.isArray(body?.changes) ? body.changes : [];
297
+ const changesAnalysis = analyseChanges(changes);
298
+
299
+ const authoredParts = [
300
+ body?.goal,
301
+ body?.reason_to_exist,
302
+ typeof body?.spec === 'string' ? body.spec : '',
303
+ ...acceptance.map((a) => String(a ?? '')),
304
+ ...verify.map((v) => String(v ?? '')),
305
+ typeof body?.slicing === 'string' ? body.slicing : '',
306
+ ...changesAnalysis.pathTexts,
307
+ ].filter((p) => typeof p === 'string' && p.length > 0);
308
+
309
+ const authoredTokens = estimateTokens(authoredParts.join('\n'));
310
+
311
+ return {
312
+ sessionMass: authoredTokens,
313
+ authoredTokens,
314
+ acceptanceCount: acceptance.length,
315
+ fileCount: changesAnalysis.fileCount,
316
+ hasGlobs: changesAnalysis.hasGlobs,
317
+ changesAnalysis,
318
+ };
449
319
  }
450
320
 
451
- function makeOversized(slug, field, observed, ceiling) {
321
+ function makeOversized(slug, observed, ceiling) {
452
322
  return {
453
323
  kind: 'oversized-task',
454
324
  severity: 'hard',
455
325
  ticketSlug: slug,
456
- field,
326
+ field: 'sessionMass',
457
327
  observed,
458
328
  ceiling,
459
329
  };
460
330
  }
461
331
 
462
- function makeSoftWidth(slug, field, observed, soft) {
332
+ function makeSoftSessionPressure(slug, observed, soft) {
463
333
  return {
464
- kind: 'soft-task-width',
334
+ kind: 'soft-session-pressure',
465
335
  severity: 'soft',
466
336
  ticketSlug: slug,
467
- field,
337
+ field: 'sessionMass',
468
338
  observed,
469
339
  soft,
470
340
  };
@@ -472,9 +342,7 @@ function makeSoftWidth(slug, field, observed, soft) {
472
342
 
473
343
  /**
474
344
  * Returns true when the Story declares itself `wide` with a non-empty reason.
475
- * A `wide` declaration lifts the `hardFiles` rejection: a legitimately broad
476
- * change states why it is broad rather than being silently capped. The
477
- * reason is the only payload — there is no longer a profile enum to match.
345
+ * A `wide` declaration lifts the hard session-mass rejection.
478
346
  */
479
347
  function isDeclaredWide(wide) {
480
348
  return (
@@ -485,108 +353,59 @@ function isDeclaredWide(wide) {
485
353
  );
486
354
  }
487
355
 
356
+ function computeMergeCandidateFinding(story, mass, ceilings) {
357
+ if (mass.hasGlobs) return [];
358
+ const dependsOn = resolveDependsOn(story);
359
+ if (dependsOn.length === 0) return [];
360
+ if (mass.sessionMass > ceilings.mergeCandidateMaxSessionTokens) return [];
361
+ return [makeMergeCandidate(story.slug, mass.sessionMass, dependsOn)];
362
+ }
363
+
488
364
  /**
489
- * Compute the hard + soft sizing findings for a single Story. Layers:
490
- * - acceptance ceiling (`maxAcceptance`) + soft warn (`softAcceptanceCount`)
491
- * - file-width ceiling (`hardFiles`) + soft warn (`softFiles`)
492
- * - the optional `wide` declaration, which lifts the hard file ceiling
493
- * - glob-awareness (glob entries are unknown-width; numeric ceiling skipped)
494
- *
495
- * Cohesion is the primary authoring heuristic (see the decomposer prompt and
496
- * SKILL); these numeric findings are the backstop. A wide Story declares
497
- * `wide` with a reason instead of being rejected for width.
365
+ * Compute the hard + soft capacity findings for a single Story.
498
366
  */
499
- function computeStorySizingFindings(story, sizing) {
367
+ function computeStorySizingFindings(story, capacity, ceilings) {
500
368
  const out = [];
501
- // Story #4271: normalize the body so the canonical serialized **string**
502
- // shape the decomposer emits is scored at parity with the pre-serialize
503
- // object shape. The acceptance ceiling reads the authoritative top-level
504
- // `story.acceptance` (the binding contract), not `body.acceptance`.
505
369
  const body = resolveStoryBody(story);
506
- const acceptance = resolveAcceptance(story);
507
- const changes = Array.isArray(body?.changes) ? body.changes : [];
508
370
  const declaredWide = isDeclaredWide(body?.wide ?? null);
509
- const changesAnalysis = analyseChanges(changes);
371
+ const mass = estimateStorySessionMass(story, capacity);
510
372
 
511
- // Soft, advisory: flag acceptance criteria that reference a configuration
512
- // constant without inlining a concrete value (Story #3855). Independent of
513
- // the numeric sizing layers below — purely an authoring nudge.
514
373
  out.push(...computeUnanchoredConstantFindings(story));
515
-
516
- // Soft, advisory: flag a Story body that carries no non-empty
517
- // `reason_to_exist` (Story #4273). The decomposer prompt marks the field
518
- // REQUIRED and the consolidate critic gates on it, but that critic has no
519
- // runtime backstop — this deterministic finding is the cheap backstop.
520
- // Independent of the numeric sizing layers below.
521
374
  out.push(...computeMissingReasonToExistFinding(story));
522
-
523
- // Soft, advisory: flag a thin dependent slice (Story #4312) — the symmetric
524
- // under-size backstop to the over-size ceilings. A tiny footprint with a
525
- // `depends_on` edge to a sibling is a merge candidate under the
526
- // single-consumer merge rule. Independent of the numeric sizing layers below.
527
- out.push(
528
- ...computeMergeCandidateFinding(
529
- story,
530
- changesAnalysis,
531
- acceptance.length,
532
- sizing,
533
- ),
534
- );
535
-
536
- // Acceptance mass is advisory-only (Story #4312's under-size heuristic is
537
- // the merge signal; the former hard `maxAcceptance` rejection is removed).
538
- // A long binding contract is a re-check-cohesion nudge, never by itself a
539
- // decomposition error — cohesion and the delivery envelope govern Story
540
- // size, and the delivery-schedule simulation in the decomposer prompt owns
541
- // the fragmentation/consolidation judgment.
542
- if (acceptance.length > sizing.softAcceptanceCount) {
543
- out.push(
544
- makeSoftWidth(
545
- story.slug,
546
- 'acceptance',
547
- acceptance.length,
548
- sizing.softAcceptanceCount,
549
- ),
550
- );
375
+ out.push(...computeMergeCandidateFinding(story, mass, ceilings));
376
+
377
+ // Glob entries mark the change footprint as unknown-width. A non-wide Story
378
+ // carrying globs still gets an informational nudge to declare `wide` when
379
+ // the authored mass alone does not already trip a capacity finding.
380
+ if (mass.hasGlobs && !declaredWide) {
381
+ out.push({
382
+ kind: 'wide-undeclared',
383
+ severity: 'soft',
384
+ ticketSlug: story.slug,
385
+ reason: 'glob-changes',
386
+ });
551
387
  }
552
388
 
553
- const { fileCount, hasGlobs } = changesAnalysis;
554
-
555
- // Glob entries mark the Story as unknown-width: a glob cannot be bounded by
556
- // the numeric ceiling, so skip it. A non-wide Story carrying globs gets an
557
- // informational nudge to either narrow the change or declare `wide`.
558
- if (hasGlobs) {
559
- if (!declaredWide) {
560
- out.push({
561
- kind: 'wide-undeclared',
562
- severity: 'soft',
563
- ticketSlug: story.slug,
564
- reason: 'glob-changes',
565
- });
566
- }
567
- return out;
568
- }
389
+ const { softSessionTokens, hardSessionTokens } = ceilings;
569
390
 
570
- // File-width: a single ceiling. `wide` (with a reason) lifts the hard cap.
571
- if (fileCount > sizing.hardFiles && !declaredWide) {
572
- out.push(
573
- makeOversized(story.slug, 'fileCount', fileCount, sizing.hardFiles),
574
- );
575
- } else if (fileCount > sizing.softFiles) {
391
+ if (mass.sessionMass > hardSessionTokens && !declaredWide) {
392
+ out.push(makeOversized(story.slug, mass.sessionMass, hardSessionTokens));
393
+ } else if (mass.sessionMass > softSessionTokens) {
576
394
  if (declaredWide) {
577
- // Declared-wide Stories still surface the width as advisory signal.
578
395
  out.push(
579
- makeSoftWidth(story.slug, 'fileCount', fileCount, sizing.softFiles),
396
+ makeSoftSessionPressure(
397
+ story.slug,
398
+ mass.sessionMass,
399
+ softSessionTokens,
400
+ ),
580
401
  );
581
402
  } else {
582
- // Wide footprint with no `wide` declaration: informational nudge to
583
- // either merge the Story down (cohesion) or declare `wide` with a reason.
584
403
  out.push({
585
404
  kind: 'wide-undeclared',
586
405
  severity: 'soft',
587
406
  ticketSlug: story.slug,
588
- fileCount,
589
- softFiles: sizing.softFiles,
407
+ sessionMass: mass.sessionMass,
408
+ softSessionTokens,
590
409
  });
591
410
  }
592
411
  }
@@ -596,23 +415,20 @@ function computeStorySizingFindings(story, sizing) {
596
415
 
597
416
  /**
598
417
  * Compute the full structured findings array for a normalized ticket
599
- * hierarchy. The validator stitches this onto its return value as
600
- * `findings`; the AC-visible `errors[]` channel is the rendered
601
- * subset where `severity === 'hard'`.
602
- *
603
- * 2-tier (Epic #3238): each Story is its own implementation unit and
604
- * carries the `body` (acceptance / changes / wide) that the sizing layers
605
- * score. There is no Task tier, so findings are computed directly over
606
- * `stories`.
418
+ * hierarchy.
607
419
  *
608
- * @param {{ stories: object[], sizing?: object }} input
420
+ * @param {{ stories: object[], capacity?: object }} input
609
421
  * @returns {object[]}
610
422
  */
611
- export function computeSizingFindings({ stories, sizing }) {
612
- const merged = { ...DEFAULT_TASK_SIZING, ...(sizing ?? {}) };
423
+ export function computeSizingFindings({ stories, capacity }) {
424
+ const merged = {
425
+ ...DEFAULT_MODEL_CAPACITY,
426
+ ...(capacity ?? {}),
427
+ };
428
+ const ceilings = resolveCapacityCeilings(merged);
613
429
  const findings = [];
614
430
  for (const story of stories ?? []) {
615
- findings.push(...computeStorySizingFindings(story, merged));
431
+ findings.push(...computeStorySizingFindings(story, merged, ceilings));
616
432
  }
617
433
  return findings;
618
434
  }
@@ -622,7 +438,7 @@ export function computeSizingFindings({ stories, sizing }) {
622
438
  */
623
439
  export function renderHardFindingError(finding) {
624
440
  if (finding.kind === 'oversized-task') {
625
- return `Task "${finding.ticketSlug}" exceeds the ${finding.field} ceiling: observed ${finding.observed}, max ${finding.ceiling}. Merge the Story down to one cohesive change, or declare \`wide\` with a one-line reason.`;
441
+ return `Story "${finding.ticketSlug}" exceeds the session-capacity ceiling: observed ${finding.observed} estimated tokens, max ${finding.ceiling}. Size the Story to what one guarded session can deliver and self-verify, or declare \`wide\` with a one-line reason.`;
626
442
  }
627
- return `Task "${finding.ticketSlug}" tripped hard finding ${finding.kind}.`;
443
+ return `Story "${finding.ticketSlug}" tripped hard finding ${finding.kind}.`;
628
444
  }