mandrel 1.94.0 → 2.1.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 (560) hide show
  1. package/.agents/README.md +116 -99
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +45 -51
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +498 -1287
  6. package/.agents/docs/agentrc-reference.json +185 -80
  7. package/.agents/docs/configuration.md +132 -190
  8. package/.agents/docs/execution-reference.md +51 -25
  9. package/.agents/docs/quality-gates.md +25 -33
  10. package/.agents/docs/workflows.md +8 -8
  11. package/.agents/instructions.md +113 -139
  12. package/.agents/rules/ci-remediation.md +11 -15
  13. package/.agents/rules/git-conventions-reference.md +48 -58
  14. package/.agents/rules/git-conventions.md +16 -22
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  16. package/.agents/schemas/agentrc.schema.json +83 -254
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  24. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  25. package/.agents/schemas/signal-event.schema.json +3 -3
  26. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  27. package/.agents/schemas/validation-evidence.schema.json +1 -1
  28. package/.agents/scripts/acceptance-eval.js +22 -66
  29. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  30. package/.agents/scripts/audit-to-stories.js +7 -7
  31. package/.agents/scripts/boot-sweep.js +1 -1
  32. package/.agents/scripts/bootstrap.js +3 -3
  33. package/.agents/scripts/check-dead-exports.js +43 -104
  34. package/.agents/scripts/check-doc-links.js +2 -2
  35. package/.agents/scripts/check-lifecycle-lint.js +7 -10
  36. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  37. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  38. package/.agents/scripts/deliver-recover.js +122 -0
  39. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  40. package/.agents/scripts/evidence-gate.js +20 -50
  41. package/.agents/scripts/generate-skills-index.js +17 -1
  42. package/.agents/scripts/generate-workflows-doc.js +4 -4
  43. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  44. package/.agents/scripts/lib/Logger.js +6 -10
  45. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  46. package/.agents/scripts/lib/audit-suite/selector.js +328 -28
  47. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  48. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  49. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -15
  50. package/.agents/scripts/lib/baselines/reader.js +1 -6
  51. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  52. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  53. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  54. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  55. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  56. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  57. package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
  58. package/.agents/scripts/lib/checks/index.js +3 -2
  59. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  60. package/.agents/scripts/lib/checks/state.js +17 -248
  61. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
  62. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  63. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  64. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  65. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  66. package/.agents/scripts/lib/cli-args.js +39 -9
  67. package/.agents/scripts/lib/close-validation/gates.js +15 -15
  68. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  69. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  70. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  71. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  72. package/.agents/scripts/lib/config/ci.js +6 -31
  73. package/.agents/scripts/lib/config/delivery-routing.js +52 -35
  74. package/.agents/scripts/lib/config/explain.js +61 -48
  75. package/.agents/scripts/lib/config/github.js +7 -5
  76. package/.agents/scripts/lib/config/limits.js +29 -80
  77. package/.agents/scripts/lib/config/paths.js +0 -2
  78. package/.agents/scripts/lib/config/quality.js +12 -15
  79. package/.agents/scripts/lib/config/runners.js +20 -66
  80. package/.agents/scripts/lib/config/temp-paths.js +30 -63
  81. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  82. package/.agents/scripts/lib/config-resolver.js +2 -7
  83. package/.agents/scripts/lib/config-settings-schema-delivery.js +55 -161
  84. package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
  85. package/.agents/scripts/lib/config-settings-schema.js +100 -60
  86. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  87. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  88. package/.agents/scripts/lib/dependency-parser.js +3 -2
  89. package/.agents/scripts/lib/doc-tiers.js +2 -2
  90. package/.agents/scripts/lib/duplicate-search.js +242 -41
  91. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  93. package/.agents/scripts/lib/format-generated-json.js +97 -0
  94. package/.agents/scripts/lib/framework-version.js +19 -190
  95. package/.agents/scripts/lib/gh-exec.js +8 -0
  96. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  97. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
  98. package/.agents/scripts/lib/git-utils.js +32 -20
  99. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  100. package/.agents/scripts/lib/json-utils.js +1 -2
  101. package/.agents/scripts/lib/label-constants.js +10 -38
  102. package/.agents/scripts/lib/label-taxonomy.js +10 -55
  103. package/.agents/scripts/lib/observability/active-story-env.js +44 -165
  104. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  105. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  106. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  107. package/.agents/scripts/lib/observability/source-classifier.js +5 -5
  108. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  109. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  110. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  111. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  112. package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
  113. package/.agents/scripts/lib/orchestration/code-review.js +78 -436
  114. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  115. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
  116. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  117. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  118. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  119. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  120. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  121. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  122. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  123. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  124. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  125. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
  126. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  127. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  128. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  129. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
  130. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  131. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  132. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  133. package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
  134. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  135. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  136. package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
  137. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  138. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
  139. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  140. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  141. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
  142. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
  143. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  144. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
  145. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
  146. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
  147. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  148. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  149. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  150. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +52 -51
  151. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  152. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
  153. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  154. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  155. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  156. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  157. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  158. package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
  159. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  160. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  161. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  162. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  163. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  164. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  165. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  166. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  167. package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  170. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  171. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
  172. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  173. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
  174. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  175. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  176. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
  177. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  178. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  179. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
  180. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  181. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  182. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  183. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  184. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  185. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  186. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  187. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
  188. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  189. package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
  190. package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
  191. package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
  192. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  193. package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
  194. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  195. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
  196. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
  197. package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
  198. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
  199. package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
  200. package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
  201. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  202. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  203. package/.agents/scripts/lib/planning-corpus.js +12 -281
  204. package/.agents/scripts/lib/preflight-runner.js +4 -4
  205. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  206. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  207. package/.agents/scripts/lib/signals/index.js +4 -17
  208. package/.agents/scripts/lib/signals/read.js +35 -35
  209. package/.agents/scripts/lib/signals/schema.js +8 -11
  210. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  211. package/.agents/scripts/lib/signals/write.js +0 -1
  212. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  213. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  214. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  215. package/.agents/scripts/lib/story-adjacency.js +17 -19
  216. package/.agents/scripts/lib/story-body/story-body.js +130 -75
  217. package/.agents/scripts/lib/story-plan.js +2 -4
  218. package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
  219. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  220. package/.agents/scripts/lib/test-env.js +14 -1
  221. package/.agents/scripts/lib/test-tiers.js +0 -3
  222. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
  223. package/.agents/scripts/lib/validation-evidence.js +32 -60
  224. package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
  225. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  226. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
  228. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  229. package/.agents/scripts/lint-issue-body.js +71 -40
  230. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  231. package/.agents/scripts/notify.js +4 -3
  232. package/.agents/scripts/plan-context.js +102 -80
  233. package/.agents/scripts/plan-persist.js +230 -279
  234. package/.agents/scripts/plan-run-epilogue.js +142 -0
  235. package/.agents/scripts/post-structured-comment.js +0 -38
  236. package/.agents/scripts/pr-watch-with-update.js +43 -22
  237. package/.agents/scripts/providers/github/compose.js +0 -1
  238. package/.agents/scripts/providers/github/errors.js +0 -19
  239. package/.agents/scripts/providers/github/issues.js +13 -39
  240. package/.agents/scripts/providers/github/mappers.js +5 -12
  241. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  242. package/.agents/scripts/providers/github/tickets.js +33 -156
  243. package/.agents/scripts/providers/github.js +17 -6
  244. package/.agents/scripts/resolve-stories.js +236 -0
  245. package/.agents/scripts/run-coverage.js +4 -1
  246. package/.agents/scripts/run-lint.js +2 -2
  247. package/.agents/scripts/run-verify.js +31 -2
  248. package/.agents/scripts/signals-view.js +25 -21
  249. package/.agents/scripts/single-story-close.js +178 -26
  250. package/.agents/scripts/single-story-confirm-merge.js +313 -24
  251. package/.agents/scripts/single-story-init.js +35 -30
  252. package/.agents/scripts/stories-wave-tick.js +85 -10
  253. package/.agents/scripts/story-plan.js +28 -49
  254. package/.agents/scripts/update-ticket-state.js +14 -65
  255. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  256. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  257. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  258. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  259. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  260. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  261. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  262. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  263. package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
  264. package/.agents/skills/core/scope-triage/SKILL.md +31 -172
  265. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  266. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  267. package/.agents/skills/skills.index.json +3 -53
  268. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  269. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  270. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  271. package/.agents/starter-agentrc.json +0 -5
  272. package/.agents/templates/agent-protocol.md +9 -10
  273. package/.agents/workflows/audit-architecture.md +6 -7
  274. package/.agents/workflows/audit-clean-code.md +7 -7
  275. package/.agents/workflows/audit-dependencies.md +3 -3
  276. package/.agents/workflows/audit-devops.md +3 -3
  277. package/.agents/workflows/audit-documentation.md +9 -10
  278. package/.agents/workflows/audit-lighthouse.md +11 -3
  279. package/.agents/workflows/audit-navigability.md +13 -2
  280. package/.agents/workflows/audit-performance.md +5 -6
  281. package/.agents/workflows/audit-privacy.md +3 -3
  282. package/.agents/workflows/audit-quality.md +11 -12
  283. package/.agents/workflows/audit-security.md +4 -5
  284. package/.agents/workflows/audit-seo.md +13 -3
  285. package/.agents/workflows/audit-sre.md +3 -3
  286. package/.agents/workflows/audit-to-stories.md +20 -20
  287. package/.agents/workflows/audit-ux-ui.md +10 -3
  288. package/.agents/workflows/deliver.md +177 -176
  289. package/.agents/workflows/git-cleanup.md +5 -6
  290. package/.agents/workflows/git-deliver.md +1 -1
  291. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  292. package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
  293. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  294. package/.agents/workflows/helpers/code-review.md +75 -196
  295. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
  296. package/.agents/workflows/helpers/deliver-story.md +606 -0
  297. package/.agents/workflows/helpers/diagnose.md +10 -10
  298. package/.agents/workflows/helpers/parallel-tooling.md +3 -3
  299. package/.agents/workflows/helpers/signals.md +16 -16
  300. package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
  301. package/.agents/workflows/mandrel-update.md +2 -1
  302. package/.agents/workflows/plan.md +277 -145
  303. package/.agents/workflows/qa-assist.md +27 -33
  304. package/.agents/workflows/qa-explore.md +29 -38
  305. package/.agents/workflows/qa-run.md +2 -6
  306. package/README.md +9 -8
  307. package/bin/mandrel.js +12 -1
  308. package/docs/CHANGELOG.md +70 -0
  309. package/lib/cli/registry.js +262 -19
  310. package/lib/cli/sync-agents.js +157 -0
  311. package/lib/cli/sync-commands.js +115 -6
  312. package/lib/cli/sync.js +168 -6
  313. package/lib/cli/update.js +105 -8
  314. package/lib/cli/version-helpers.js +131 -0
  315. package/lib/migrations/README.md +7 -5
  316. package/lib/migrations/index.js +12 -8
  317. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  318. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  319. package/package.json +2 -2
  320. package/.agents/agents/retro.md +0 -42
  321. package/.agents/personas/architect.md +0 -113
  322. package/.agents/personas/devops-engineer.md +0 -38
  323. package/.agents/personas/engineer.md +0 -33
  324. package/.agents/personas/project-manager.md +0 -114
  325. package/.agents/personas/qa-engineer.md +0 -95
  326. package/.agents/personas/security-engineer.md +0 -111
  327. package/.agents/personas/technical-writer.md +0 -101
  328. package/.agents/schemas/dispatch-manifest.json +0 -232
  329. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  330. package/.agents/schemas/epic-spec.schema.json +0 -153
  331. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  332. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  333. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  334. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  335. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  336. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  337. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  338. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  339. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  340. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  341. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  342. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  343. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  344. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  345. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  346. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  347. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  348. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  349. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  350. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  351. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  352. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  353. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  354. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  355. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  356. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  357. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  358. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  359. package/.agents/schemas/risk-verdict.schema.json +0 -66
  360. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  361. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  362. package/.agents/scripts/analyze-execution.js +0 -444
  363. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  364. package/.agents/scripts/check-prepush-recovery.js +0 -90
  365. package/.agents/scripts/dispatcher.js +0 -295
  366. package/.agents/scripts/epic-audit-prepare.js +0 -497
  367. package/.agents/scripts/epic-audit-recheck.js +0 -274
  368. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  369. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  370. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  371. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  372. package/.agents/scripts/epic-plan-clarity.js +0 -211
  373. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  374. package/.agents/scripts/epic-reconcile.js +0 -625
  375. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  376. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  377. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  378. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  379. package/.agents/scripts/lib/config/preflight.js +0 -58
  380. package/.agents/scripts/lib/config/retro.js +0 -77
  381. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  382. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  383. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  384. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  385. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  386. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
  387. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  388. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  389. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  390. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
  391. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  392. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  393. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  394. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  395. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  396. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  397. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  398. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  399. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  400. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  401. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  402. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  403. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  404. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  405. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  406. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  407. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  408. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +0 -105
  409. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  410. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  411. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  412. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  413. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  414. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  415. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  416. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  417. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  418. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  419. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  420. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  421. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  422. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  423. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  424. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  425. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  426. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +0 -400
  427. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  428. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  429. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  430. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  431. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  432. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  433. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  434. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  435. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  436. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  437. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  438. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  439. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  440. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  441. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  442. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  443. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  444. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  445. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  446. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  447. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  448. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  449. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  450. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  451. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  452. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  453. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  454. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  455. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  456. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  457. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  458. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  459. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  460. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  461. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
  462. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  463. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  464. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  465. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
  466. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  467. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  468. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  469. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  470. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  471. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  472. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  473. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  474. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  475. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  476. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  477. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  478. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  479. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  480. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  481. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  482. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  483. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  484. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  485. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  486. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  487. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  488. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  489. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  490. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  491. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  492. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  493. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  494. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  495. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  496. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  497. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  498. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  499. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  500. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  501. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  502. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  503. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  504. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  505. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  506. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  507. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  508. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  509. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  510. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  511. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  512. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  513. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  514. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  515. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  516. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  517. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  518. package/.agents/scripts/lib/spec/index.js +0 -36
  519. package/.agents/scripts/lib/spec/loader.js +0 -425
  520. package/.agents/scripts/lib/spec/state.js +0 -208
  521. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  522. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  523. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  524. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  525. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  526. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  527. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  528. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  529. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  530. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  531. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  532. package/.agents/scripts/lifecycle-emit.js +0 -510
  533. package/.agents/scripts/retro-run.js +0 -218
  534. package/.agents/scripts/slice-phase.js +0 -361
  535. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  536. package/.agents/scripts/story-close.js +0 -294
  537. package/.agents/scripts/story-init.js +0 -599
  538. package/.agents/scripts/story-phase.js +0 -369
  539. package/.agents/scripts/wave-tick.js +0 -464
  540. package/.agents/skills/core/analyze-execution/SKILL.md +0 -98
  541. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  542. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  543. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  544. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  545. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  546. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  547. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  548. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  549. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  550. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  551. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  552. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  553. package/.agents/workflows/helpers/epic-audit.md +0 -189
  554. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  555. package/.agents/workflows/helpers/epic-testing.md +0 -125
  556. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  557. package/.agents/workflows/helpers/plan-epic.md +0 -353
  558. package/.agents/workflows/helpers/plan-story.md +0 -251
  559. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  560. package/.agents/workflows/helpers/single-story-deliver.md +0 -557
@@ -1,57 +1,41 @@
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
- * (`.agents/scripts/lib/templates/decomposer-prompts.js`) and the authoring
12
- * SKILL (`.agents/skills/core/epic-plan-decompose-author/SKILL.md`) reference
13
- * these numbers rather than restating divergent ones — the prompt generates
14
- * 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`.
17
+ * (`.agents/scripts/lib/templates/decomposer-prompts.js`) generates its
18
+ * threshold sentence from this constant rather than restating divergent
19
+ * numbers, so the two surfaces cannot drift.
18
20
  *
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.
21
+ * Capacity model:
22
+ * - Plan-time **session mass** = authored tokens only (`estimateTokens` of
23
+ * the Story's goal/reason/acceptance/verify/slicing/spec/change paths).
24
+ * - Soft / hard ceilings are **absolute** authored-token counts (no
25
+ * `maxTokenBudget` denominator that envelope was retired).
26
+ * - The optional `wide` declaration lifts the hard session-mass rejection.
27
+ * - Cohesion remains the primary heuristic; capacity is a pathological-
28
+ * verbosity backstop only (not an operator-tunable agentrc knob).
33
29
  */
34
30
 
35
31
  import { parse as parseStoryBody } from '../story-body/story-body.js';
32
+ import { estimateTokens } from './context-envelope.js';
36
33
 
37
34
  /**
38
35
  * Normalize a Story's `body` to the structured object the sizing layers
39
36
  * score, mirroring `validateAcFreshness` / `collectStoryAssumptionEntries`
40
37
  * (Story #3302) and `resolveStructuredBody` in `task-body-validator.js`.
41
38
  *
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
39
  * @param {object} story
56
40
  * @returns {object|null}
57
41
  */
@@ -71,13 +55,8 @@ function resolveStoryBody(story) {
71
55
 
72
56
  /**
73
57
  * 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.
58
+ * authoritative top-level `story.acceptance` over the structured body's
59
+ * `acceptance`.
81
60
  *
82
61
  * @param {object} story
83
62
  * @returns {unknown[]}
@@ -89,17 +68,20 @@ function resolveAcceptance(story) {
89
68
  }
90
69
 
91
70
  /**
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.
71
+ * Resolve the verify-commands array for a Story, preferring the top-level
72
+ * `story.verify` over the structured body's `verify`.
98
73
  *
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.
74
+ * @param {object} story
75
+ * @returns {unknown[]}
76
+ */
77
+ function resolveVerify(story) {
78
+ if (Array.isArray(story?.verify)) return story.verify;
79
+ const body = resolveStoryBody(story);
80
+ return Array.isArray(body?.verify) ? body.verify : [];
81
+ }
82
+
83
+ /**
84
+ * Resolve the `depends_on` edge list for a Story.
103
85
  *
104
86
  * @param {object} story
105
87
  * @returns {string[]}
@@ -113,127 +95,72 @@ function resolveDependsOn(story) {
113
95
  : [];
114
96
  }
115
97
 
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,
98
+ /**
99
+ * Framework capacity constantnot operator-tunable via `.agentrc.json`
100
+ * (same collapse pattern as `maxTickets` / Story #4163).
101
+ *
102
+ * Absolute authored-token ceilings (no delivery-envelope denominator):
103
+ *
104
+ * - Soft 30_000: cohesion check when the ticket itself is already a
105
+ * substantial document. Normal capability Stories stay silent.
106
+ * - Hard 75_000: reject Spec novels unless `wide`.
107
+ * - Merge candidate 1_500: thin `depends_on` fragment (soft only).
108
+ *
109
+ * Cohesion / split policy / conflict advisories remain the primary sizing
110
+ * signal; these ceilings catch pathological ticket verbosity only.
111
+ */
112
+ export const DEFAULT_MODEL_CAPACITY = Object.freeze({
113
+ softSessionTokens: 30000,
114
+ hardSessionTokens: 75000,
115
+ mergeCandidateMaxSessionTokens: 1500,
139
116
  });
140
117
 
118
+ /**
119
+ * Resolve capacity ceilings, shallow-merging an optional override bag
120
+ * (tests / programmatic only). Pure; used by the validator and the
121
+ * decomposer prompt.
122
+ *
123
+ * @param {object} [capacity]
124
+ * @returns {{ softSessionTokens: number, hardSessionTokens: number, mergeCandidateMaxSessionTokens: number }}
125
+ */
126
+ export function resolveCapacityCeilings(capacity = DEFAULT_MODEL_CAPACITY) {
127
+ const merged = { ...DEFAULT_MODEL_CAPACITY, ...(capacity ?? {}) };
128
+ return {
129
+ softSessionTokens: merged.softSessionTokens,
130
+ hardSessionTokens: merged.hardSessionTokens,
131
+ mergeCandidateMaxSessionTokens: merged.mergeCandidateMaxSessionTokens,
132
+ };
133
+ }
134
+
141
135
  /**
142
136
  * `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
143
137
  * 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.
138
+ * ONCE here and consumed by BOTH the decomposer prompt template and the
139
+ * authoring SKILL.
169
140
  */
170
141
  export const DELIVERABLE_GRANULARITY_GUIDANCE = Object.freeze({
171
- // The one-sentence definition of Story granularity.
172
142
  definition:
173
143
  '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
144
  singleConsumerRule:
176
145
  '**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
146
  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.',
147
+ '**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
148
  });
181
149
 
182
150
  /**
183
151
  * `AUTHORING_ALTITUDE_GUIDANCE` is the **single source of truth** for the
184
152
  * 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.
153
+ * Contract (Story #4272).
202
154
  */
203
155
  export const AUTHORING_ALTITUDE_GUIDANCE = Object.freeze({
204
- // The binding-vs-advisory altitude statement.
205
156
  altitude:
206
157
  '**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
158
  advisoryCaveat:
209
159
  "**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
160
  newFileContract:
212
161
  '**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
162
  });
214
163
 
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
164
  const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
238
165
  /\bretention window\b/i,
239
166
  /\brate[ -]limit\b/i,
@@ -244,25 +171,8 @@ const UNANCHORED_CONSTANT_PATTERNS = Object.freeze([
244
171
  /\bquota\b/i,
245
172
  ]);
246
173
 
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
174
  const CONCRETE_VALUE_RE = /\d/;
256
175
 
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
176
  function makeUnanchoredConstant(slug, criterion) {
267
177
  return {
268
178
  kind: 'unanchored-constant',
@@ -273,21 +183,8 @@ function makeUnanchoredConstant(slug, criterion) {
273
183
  };
274
184
  }
275
185
 
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
186
  function computeUnanchoredConstantFindings(story) {
286
187
  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
188
  const acceptance = resolveAcceptance(story);
292
189
  for (const item of acceptance) {
293
190
  const criterion = String(item ?? '');
@@ -302,19 +199,6 @@ function computeUnanchoredConstantFindings(story) {
302
199
  return out;
303
200
  }
304
201
 
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
202
  function makeMissingReasonToExist(slug) {
319
203
  return {
320
204
  kind: 'missing-reason-to-exist',
@@ -325,17 +209,6 @@ function makeMissingReasonToExist(slug) {
325
209
  };
326
210
  }
327
211
 
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
212
  function computeMissingReasonToExistFinding(story) {
340
213
  const body = resolveStoryBody(story);
341
214
  const reason = body?.reason_to_exist;
@@ -343,82 +216,31 @@ function computeMissingReasonToExistFinding(story) {
343
216
  return hasReason ? [] : [makeMissingReasonToExist(story.slug)];
344
217
  }
345
218
 
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) {
219
+ function makeMergeCandidate(slug, sessionMass, dependsOn) {
358
220
  const siblings = dependsOn.map((d) => `"${d}"`).join(', ');
359
221
  return {
360
222
  kind: 'merge-candidate',
361
223
  severity: 'soft',
362
224
  ticketSlug: slug,
363
- fileCount,
364
- acceptanceCount,
225
+ sessionMass,
365
226
  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.`,
227
+ 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
228
  };
368
229
  }
369
230
 
370
231
  /**
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.
232
+ * Returns true when a `changes[]` entry is a glob pattern.
409
233
  */
410
234
  function isGlobBullet(bullet) {
411
- const s = bullet?.path ?? bullet;
235
+ const s = bullet?.path;
412
236
  return typeof s === 'string' && s.includes('*');
413
237
  }
414
238
 
415
239
  /**
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).
240
+ * Extract the path from a single object-form `changes` entry.
419
241
  */
420
242
  function extractChangeBulletPath(bullet) {
421
- const s = typeof bullet === 'string' ? bullet : (bullet?.path ?? null);
243
+ const s = bullet?.path ?? null;
422
244
  if (!s) return null;
423
245
  const colonIdx = s.indexOf(':');
424
246
  if (colonIdx <= 0) return /[\\/.]/.test(s) ? s.trim() : null;
@@ -427,44 +249,91 @@ function extractChangeBulletPath(bullet) {
427
249
  }
428
250
 
429
251
  /**
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.
252
+ * Analyse the `changes[]` array and return fileCount / hasGlobs / path texts.
436
253
  */
437
254
  function analyseChanges(changes) {
438
255
  const paths = new Set();
256
+ const pathTexts = [];
439
257
  let hasGlobs = false;
440
258
  for (const bullet of changes) {
441
259
  if (isGlobBullet(bullet)) {
442
260
  hasGlobs = true;
261
+ const globText = bullet?.path ?? '';
262
+ if (globText) pathTexts.push(String(globText));
443
263
  continue;
444
264
  }
445
265
  const path = extractChangeBulletPath(bullet);
446
- if (path) paths.add(path);
266
+ if (path) {
267
+ paths.add(path);
268
+ pathTexts.push(path);
269
+ }
447
270
  }
448
- return { fileCount: paths.size, hasGlobs };
271
+ return { fileCount: paths.size, hasGlobs, pathTexts };
272
+ }
273
+
274
+ /**
275
+ * Estimate the plan-time session mass (tokens) for a Story.
276
+ *
277
+ * Session mass is **authored prose only** — `estimateTokens` of the Story's
278
+ * goal / reason / acceptance / verify / slicing / spec / change-path text.
279
+ * File count and AC count do not add delivery-cost proxies (those recreated
280
+ * the retired count ceilings). Glob entries contribute their text but do not
281
+ * imply known width.
282
+ *
283
+ * @param {object} story
284
+ * @param {object} [_capacity] Unused; retained so call sites that still pass
285
+ * a capacity bag keep working. Mass no longer depends on capacity knobs.
286
+ * @returns {{ sessionMass: number, authoredTokens: number, acceptanceCount: number, fileCount: number, hasGlobs: boolean, changesAnalysis: object }}
287
+ */
288
+ export function estimateStorySessionMass(
289
+ story,
290
+ _capacity = DEFAULT_MODEL_CAPACITY,
291
+ ) {
292
+ const body = resolveStoryBody(story);
293
+ const acceptance = resolveAcceptance(story);
294
+ const verify = resolveVerify(story);
295
+ const changes = Array.isArray(body?.changes) ? body.changes : [];
296
+ const changesAnalysis = analyseChanges(changes);
297
+
298
+ const authoredParts = [
299
+ body?.goal,
300
+ body?.reason_to_exist,
301
+ typeof body?.spec === 'string' ? body.spec : '',
302
+ ...acceptance.map((a) => String(a ?? '')),
303
+ ...verify.map((v) => String(v ?? '')),
304
+ typeof body?.slicing === 'string' ? body.slicing : '',
305
+ ...changesAnalysis.pathTexts,
306
+ ].filter((p) => typeof p === 'string' && p.length > 0);
307
+
308
+ const authoredTokens = estimateTokens(authoredParts.join('\n'));
309
+
310
+ return {
311
+ sessionMass: authoredTokens,
312
+ authoredTokens,
313
+ acceptanceCount: acceptance.length,
314
+ fileCount: changesAnalysis.fileCount,
315
+ hasGlobs: changesAnalysis.hasGlobs,
316
+ changesAnalysis,
317
+ };
449
318
  }
450
319
 
451
- function makeOversized(slug, field, observed, ceiling) {
320
+ function makeOversized(slug, observed, ceiling) {
452
321
  return {
453
322
  kind: 'oversized-task',
454
323
  severity: 'hard',
455
324
  ticketSlug: slug,
456
- field,
325
+ field: 'sessionMass',
457
326
  observed,
458
327
  ceiling,
459
328
  };
460
329
  }
461
330
 
462
- function makeSoftWidth(slug, field, observed, soft) {
331
+ function makeSoftSessionPressure(slug, observed, soft) {
463
332
  return {
464
- kind: 'soft-task-width',
333
+ kind: 'soft-session-pressure',
465
334
  severity: 'soft',
466
335
  ticketSlug: slug,
467
- field,
336
+ field: 'sessionMass',
468
337
  observed,
469
338
  soft,
470
339
  };
@@ -472,9 +341,7 @@ function makeSoftWidth(slug, field, observed, soft) {
472
341
 
473
342
  /**
474
343
  * 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.
344
+ * A `wide` declaration lifts the hard session-mass rejection.
478
345
  */
479
346
  function isDeclaredWide(wide) {
480
347
  return (
@@ -485,108 +352,59 @@ function isDeclaredWide(wide) {
485
352
  );
486
353
  }
487
354
 
355
+ function computeMergeCandidateFinding(story, mass, ceilings) {
356
+ if (mass.hasGlobs) return [];
357
+ const dependsOn = resolveDependsOn(story);
358
+ if (dependsOn.length === 0) return [];
359
+ if (mass.sessionMass > ceilings.mergeCandidateMaxSessionTokens) return [];
360
+ return [makeMergeCandidate(story.slug, mass.sessionMass, dependsOn)];
361
+ }
362
+
488
363
  /**
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.
364
+ * Compute the hard + soft capacity findings for a single Story.
498
365
  */
499
- function computeStorySizingFindings(story, sizing) {
366
+ function computeStorySizingFindings(story, capacity, ceilings) {
500
367
  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
368
  const body = resolveStoryBody(story);
506
- const acceptance = resolveAcceptance(story);
507
- const changes = Array.isArray(body?.changes) ? body.changes : [];
508
369
  const declaredWide = isDeclaredWide(body?.wide ?? null);
509
- const changesAnalysis = analyseChanges(changes);
370
+ const mass = estimateStorySessionMass(story, capacity);
510
371
 
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
372
  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
373
  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
- );
374
+ out.push(...computeMergeCandidateFinding(story, mass, ceilings));
375
+
376
+ // Glob entries mark the change footprint as unknown-width. A non-wide Story
377
+ // carrying globs still gets an informational nudge to declare `wide` when
378
+ // the authored mass alone does not already trip a capacity finding.
379
+ if (mass.hasGlobs && !declaredWide) {
380
+ out.push({
381
+ kind: 'wide-undeclared',
382
+ severity: 'soft',
383
+ ticketSlug: story.slug,
384
+ reason: 'glob-changes',
385
+ });
551
386
  }
552
387
 
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
- }
388
+ const { softSessionTokens, hardSessionTokens } = ceilings;
569
389
 
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) {
390
+ if (mass.sessionMass > hardSessionTokens && !declaredWide) {
391
+ out.push(makeOversized(story.slug, mass.sessionMass, hardSessionTokens));
392
+ } else if (mass.sessionMass > softSessionTokens) {
576
393
  if (declaredWide) {
577
- // Declared-wide Stories still surface the width as advisory signal.
578
394
  out.push(
579
- makeSoftWidth(story.slug, 'fileCount', fileCount, sizing.softFiles),
395
+ makeSoftSessionPressure(
396
+ story.slug,
397
+ mass.sessionMass,
398
+ softSessionTokens,
399
+ ),
580
400
  );
581
401
  } 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
402
  out.push({
585
403
  kind: 'wide-undeclared',
586
404
  severity: 'soft',
587
405
  ticketSlug: story.slug,
588
- fileCount,
589
- softFiles: sizing.softFiles,
406
+ sessionMass: mass.sessionMass,
407
+ softSessionTokens,
590
408
  });
591
409
  }
592
410
  }
@@ -596,23 +414,20 @@ function computeStorySizingFindings(story, sizing) {
596
414
 
597
415
  /**
598
416
  * 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`.
417
+ * hierarchy.
607
418
  *
608
- * @param {{ stories: object[], sizing?: object }} input
419
+ * @param {{ stories: object[], capacity?: object }} input
609
420
  * @returns {object[]}
610
421
  */
611
- export function computeSizingFindings({ stories, sizing }) {
612
- const merged = { ...DEFAULT_TASK_SIZING, ...(sizing ?? {}) };
422
+ export function computeSizingFindings({ stories, capacity }) {
423
+ const merged = {
424
+ ...DEFAULT_MODEL_CAPACITY,
425
+ ...(capacity ?? {}),
426
+ };
427
+ const ceilings = resolveCapacityCeilings(merged);
613
428
  const findings = [];
614
429
  for (const story of stories ?? []) {
615
- findings.push(...computeStorySizingFindings(story, merged));
430
+ findings.push(...computeStorySizingFindings(story, merged, ceilings));
616
431
  }
617
432
  return findings;
618
433
  }
@@ -622,7 +437,7 @@ export function computeSizingFindings({ stories, sizing }) {
622
437
  */
623
438
  export function renderHardFindingError(finding) {
624
439
  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.`;
440
+ 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
441
  }
627
- return `Task "${finding.ticketSlug}" tripped hard finding ${finding.kind}.`;
442
+ return `Story "${finding.ticketSlug}" tripped hard finding ${finding.kind}.`;
628
443
  }