mandrel 2.0.0 → 2.2.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 (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  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/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -2,13 +2,12 @@
2
2
  /**
3
3
  * merge-block-class.js — Story #4426 (Epic #4425, slice 1: foundation).
4
4
  *
5
- * Shared block-class classifier consumed by BOTH delivery paths — the
6
- * epic-path must-land terminal step and the standalone
7
- * `single-story-close` must-land terminal step (the not-yet-landed
8
- * follow-on Stories under Epic #4425) so a headless delivery run that
9
- * finishes its work without a confirmed merge is attributable to exactly
10
- * one of four classes from the SAME decision logic, instead of each path
11
- * inventing its own ad hoc diagnosis.
5
+ * Shared block-class classifier for the `single-story-close` must-land
6
+ * terminal step and `deliver-recover`, so a headless delivery run that
7
+ * finishes its work without a confirmed merge is attributable to exactly one
8
+ * class from ONE decision logic, instead of each caller inventing its own ad
9
+ * hoc diagnosis. (It was written to serve an epic-path terminal too; the v2
10
+ * cutover left `single-story-close` as the only delivery path.)
12
11
  *
13
12
  * Block classes (Epic #4425 Goal; `predicate-refused` added by #4472):
14
13
  * - `checks-pending-timeout` The watch/poll budget was
@@ -16,6 +15,24 @@
16
15
  * were still pending/running — not
17
16
  * a hard block, the run simply ran
18
17
  * out of time.
18
+ * - `checks-failed` A required check went RED — red
19
+ * checks that branch protection does
20
+ * not require are NOT this class
21
+ * (see `failingChecksBlockMerge`). A
22
+ * definitive terminal the merge wait
23
+ * must fail fast on (Story #4543):
24
+ * before this class the in-close poll
25
+ * read only `state`/`mergedAt`, so a
26
+ * check that failed at minute one
27
+ * burned the entire budget and then
28
+ * classified as
29
+ * `branch-protection-human-required`
30
+ * (the exhaustion probe sees
31
+ * `mergeStateStatus: BLOCKED` with
32
+ * checks settled) — sending the
33
+ * operator to diagnose branch
34
+ * protection instead of the red check
35
+ * that is actually in their way.
19
36
  * - `branch-protection-human-required` GitHub reports the PR needs a
20
37
  * human action: a required review
21
38
  * that hasn't been granted, or a
@@ -47,20 +64,27 @@
47
64
  * this class makes it attributable.
48
65
  *
49
66
  * Pure function, no I/O: callers pass in the already-observed
50
- * arm-result / PR-probe / budget signals (from `AutomergeArmer`,
51
- * `MergeWatcher`, a raw `gh pr view` read, or the standalone
67
+ * arm-result / PR-probe / budget signals (from the close path's merge wait,
68
+ * a raw `gh pr view` read, or the standalone
52
69
  * `single-story-confirm-merge.js` poll) and get back a
53
70
  * `{ blockClass, reason }` verdict ready to hand to `emitMergeUnlanded`
54
71
  * (`emit-merge-unlanded.js`).
55
72
  */
56
73
 
74
+ import { failingChecksBlockMerge } from './merge-poll.js';
75
+
57
76
  /**
58
- * The four block classes named in the Epic #4425 Goal. Order is the
59
- * evaluation priority documented on `classifyMergeBlock` below, NOT an
60
- * arbitrary listing — earlier entries are checked first when a real input
61
- * happens to satisfy more than one heuristic.
77
+ * Every class `classifyMergeBlock` can return. Order is the evaluation
78
+ * priority documented on `classifyMergeBlock` below, NOT an arbitrary
79
+ * listing — earlier entries are checked first when a real input happens to
80
+ * satisfy more than one heuristic.
81
+ *
82
+ * Started as the four classes named in the Epic #4425 Goal; Story #4543
83
+ * added `checks-failed` so a red required check is attributable as itself
84
+ * rather than being absorbed by the timeout or branch-protection verdicts.
62
85
  */
63
86
  export const BLOCK_CLASSES = Object.freeze([
87
+ 'checks-failed',
64
88
  'checks-pending-timeout',
65
89
  'branch-protection-human-required',
66
90
  'arm-failure',
@@ -70,10 +94,12 @@ export const BLOCK_CLASSES = Object.freeze([
70
94
  /**
71
95
  * The full set of block-class values a `merge.unlanded` record may carry.
72
96
  * This is the classifier's four outputs PLUS `predicate-refused` (#4472),
73
- * which is emitted DIRECTLY by the AutomergePredicate / AutomergeArmer for a
74
- * headless refusal that never reached the poll-exhaustion classifier — so it
75
- * is a valid attribution value even though `classifyMergeBlock` never
76
- * produces it. `isValidBlockClass` (and the `merge.unlanded` schema enum)
97
+ * emitted DIRECTLY for a headless refusal that never reached the
98
+ * poll-exhaustion classifier — so it is a valid attribution value even though
99
+ * `classifyMergeBlock` never produces it. (The Epic-era listeners that used to
100
+ * emit it, AutomergePredicate and AutomergeArmer, are gone; the value stays
101
+ * because archived `merge.unlanded` records carry it and the schema enum
102
+ * must keep validating them.) `isValidBlockClass` (and the `merge.unlanded` schema enum)
77
103
  * validate against this broader set; the classifier's own reachability
78
104
  * invariant stays scoped to `BLOCK_CLASSES`.
79
105
  */
@@ -121,6 +147,12 @@ function describeApiRaceFallback(prProbe, budget) {
121
147
  if (prProbe?.error) {
122
148
  return `PR probe error: ${prProbe.error}`;
123
149
  }
150
+ // Red checks that do not gate the merge (step 1b declined them). Name the
151
+ // situation precisely: the operator must NOT be sent to fix the red check,
152
+ // because auto-merge was free to land this PR and did not.
153
+ if (prProbe?.checksStatus === 'failure') {
154
+ return `PR did not land although its failing checks are not required (mergeStateStatus=${prProbe?.mergeStateStatus ?? 'n/a'}); the red checks are not the block — check that auto-merge is still armed`;
155
+ }
124
156
  if (budget && budget.exhausted === true) {
125
157
  return `watch budget exhausted with an unrecognised checks status (${prProbe?.checksStatus ?? 'unknown'})`;
126
158
  }
@@ -137,6 +169,12 @@ function describeApiRaceFallback(prProbe, budget) {
137
169
  * rejection surfaced AT arm time still routes to
138
170
  * `branch-protection-human-required` rather than the generic
139
171
  * `arm-failure`.
172
+ * 1b. A red required check — `checks-failed` (Story #4543). Evaluated
173
+ * before every budget and probe signal because it is *definitive*:
174
+ * no amount of remaining budget turns a failed check green, and on a
175
+ * protected branch it also presents as `mergeStateStatus: 'BLOCKED'`,
176
+ * so leaving it to step 3 would attribute the operator's red test run
177
+ * to branch protection.
140
178
  * 2. Budget exhaustion while checks were still in flight —
141
179
  * `checks-pending-timeout`. Evaluated BEFORE the human-required
142
180
  * probe signals because on a protected branch GitHub reports
@@ -164,9 +202,10 @@ function describeApiRaceFallback(prProbe, budget) {
164
202
  * (`REVIEW_REQUIRED`, `APPROVED`, …).
165
203
  * @param {string} [input.prProbe.mergeStateStatus] GitHub merge-state
166
204
  * status (`BLOCKED`, `BEHIND`, `CLEAN`, …).
167
- * @param {string} [input.prProbe.checksStatus] Aggregate required-check
168
- * status observed on the last probe (`success` | `pending` |
169
- * `still-running` | `failure` | `unknown`).
205
+ * @param {string} [input.prProbe.checksStatus] Aggregate status across ALL
206
+ * checks observed on the last probe (`success` | `pending` |
207
+ * `still-running` | `failure` | `unknown`) — required-ness is decided by
208
+ * `mergeStateStatus`, not by this field.
170
209
  * @param {string} [input.prProbe.error] Set when the probe call itself
171
210
  * errored (network / API failure reading the PR).
172
211
  * @param {object} [input.budget] Poll-budget accounting.
@@ -205,6 +244,23 @@ export function classifyMergeBlock(input) {
205
244
  const checksPendingEvidence =
206
245
  checksStatus === 'pending' || checksStatus === 'still-running';
207
246
 
247
+ // 1b. A required check is RED. Definitive — no remaining budget makes a
248
+ // failed check pass — so this precedes both the budget branch and the
249
+ // BLOCKED-merge-state heuristic, which would otherwise attribute the red
250
+ // check to branch protection on any protected base.
251
+ //
252
+ // Gated on `failingChecksBlockMerge` rather than the raw rollup status:
253
+ // `checksStatus: 'failure'` covers optional checks too, and naming an
254
+ // optional red check as THE block sends the operator to fix a check that
255
+ // was never gating the merge. A red-but-not-gating PR that still failed to
256
+ // land falls through to the fallback, whose reason says exactly that.
257
+ if (failingChecksBlockMerge(prProbe)) {
258
+ return {
259
+ blockClass: 'checks-failed',
260
+ reason: `a required check failed (mergeStateStatus=${prProbe?.mergeStateStatus ?? 'n/a'})`,
261
+ };
262
+ }
263
+
208
264
  // 2. Budget exhausted while checks were still in flight. Ordered
209
265
  // before the human-required probe signals: `mergeStateStatus:
210
266
  // 'BLOCKED'` is the steady state on a protected branch while required
@@ -0,0 +1,104 @@
1
+ /**
2
+ * merge-poll.js — merge-wait constants and check-rollup derivation owned by
3
+ * the close path.
4
+ *
5
+ * Story #4545 — these three symbols used to live in the Epic-era
6
+ * `lifecycle/listeners/merge-watcher.js`. That listener class had no
7
+ * production caller after the v2.0.0 Story-only cutover, but it was not
8
+ * importer-less: the live close path (`single-story-close/phases/confirm-merge.js`)
9
+ * and `deliver-recover.js` both reached into it for the poll defaults and
10
+ * `deriveChecksStatus`. Relocating them here lets the listener go without
11
+ * leaving the close path importing a lifecycle module it does not otherwise
12
+ * participate in.
13
+ *
14
+ * Sits beside `merge-block-class.js`, its sole consumer pairing:
15
+ * `deriveChecksStatus` produces the `prProbe.checksStatus` value that
16
+ * `classifyMergeBlock` reads.
17
+ */
18
+
19
+ /**
20
+ * Default poll interval and cumulative budget for the merge wait. The schema
21
+ * in `.agents/schemas/agentrc.schema.json` exposes these as
22
+ * `delivery.mergeWatch.intervalSeconds` (default 30) and
23
+ * `delivery.mergeWatch.maxBudgetSeconds` (default 3600). Hard-coding the same
24
+ * numbers here keeps the close path self-contained when no config is wired in
25
+ * (e.g. unit tests).
26
+ */
27
+ export const DEFAULT_INTERVAL_SECONDS = 30;
28
+ export const DEFAULT_MAX_BUDGET_SECONDS = 3600;
29
+
30
+ /**
31
+ * Pure: derive an aggregate `checksStatus` (`success` | `still-running` |
32
+ * `failure` | `unknown`) from a `statusCheckRollup` array (`gh pr view --json
33
+ * statusCheckRollup` shape: `{ status, conclusion }` per check). Mirrors the
34
+ * values `classifyMergeBlock` expects on `prProbe.checksStatus`.
35
+ *
36
+ * **Scope: EVERY check reported on the PR, required or not.** The rollup
37
+ * carries no required-vs-optional discriminator (`gh`'s projection has no
38
+ * `isRequired` field), so `failure` here means "something on this PR is red",
39
+ * NOT "the merge is blocked". Use {@link failingChecksBlockMerge} before
40
+ * treating a `failure` as terminal.
41
+ */
42
+ export function deriveChecksStatus(statusCheckRollup) {
43
+ if (!Array.isArray(statusCheckRollup) || statusCheckRollup.length === 0) {
44
+ return 'unknown';
45
+ }
46
+ let anyPending = false;
47
+ for (const check of statusCheckRollup) {
48
+ const conclusion = String(check?.conclusion ?? '').toUpperCase();
49
+ const status = String(check?.status ?? '').toUpperCase();
50
+ if (['FAILURE', 'CANCELLED', 'TIMED_OUT', 'ERROR'].includes(conclusion)) {
51
+ return 'failure';
52
+ }
53
+ if (status !== 'COMPLETED') {
54
+ anyPending = true;
55
+ }
56
+ }
57
+ return anyPending ? 'still-running' : 'success';
58
+ }
59
+
60
+ /**
61
+ * The one `mergeStateStatus` value that means GitHub itself is gating the
62
+ * merge. See {@link failingChecksBlockMerge}.
63
+ */
64
+ const MERGE_GATED_STATE = 'BLOCKED';
65
+
66
+ /**
67
+ * Pure: does the PR's RED check status actually gate the merge?
68
+ *
69
+ * `deriveChecksStatus` aggregates the whole rollup, so it reports `failure`
70
+ * for a red check of any kind. Branch protection — and therefore GitHub
71
+ * native auto-merge — gates only on REQUIRED checks. A red optional check
72
+ * (an advisory bot, or a `CANCELLED` superseded workflow run, which the
73
+ * rollup derivation counts as a failure) says nothing about whether the PR
74
+ * will land: auto-merge lands it regardless. Treating that as terminal is
75
+ * what stranded a Story `agent::blocked` on a PR that merged anyway.
76
+ *
77
+ * `mergeStateStatus` is GitHub's own verdict, computed against the live
78
+ * branch-protection rules, so it supplies the required-vs-optional
79
+ * discrimination the rollup lacks:
80
+ *
81
+ * - `BLOCKED` — merging is gated. With red checks observed, the red
82
+ * required check is the gate.
83
+ * - `UNSTABLE` — "mergeable with non-passing commit status": the red
84
+ * checks are NOT required. Auto-merge will land it.
85
+ * - `CLEAN` / `BEHIND` / `UNKNOWN` / absent — not evidence that the red
86
+ * check gates the merge.
87
+ *
88
+ * Deliberately conservative: only `BLOCKED` returns `true`. A transient
89
+ * `UNKNOWN` (GitHub has not finished computing the merge state) or a token
90
+ * that cannot see the field degrades to "keep waiting" — the caller's poll
91
+ * budget still bounds the wait and the budget-exhausted classification still
92
+ * fires. The asymmetry is intentional: failing to fail fast costs poll time,
93
+ * whereas failing fast wrongly costs a merged-but-`agent::blocked` strand
94
+ * that only an operator can unpick.
95
+ *
96
+ * @param {{ checksStatus?: string, mergeStateStatus?: string }} [prProbe]
97
+ * @returns {boolean}
98
+ */
99
+ export function failingChecksBlockMerge(prProbe) {
100
+ if (prProbe?.checksStatus !== 'failure') return false;
101
+ return (
102
+ String(prProbe?.mergeStateStatus ?? '').toUpperCase() === MERGE_GATED_STATE
103
+ );
104
+ }
@@ -35,19 +35,82 @@ import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
35
35
  const SOURCE_TICKET_FETCH_CONCURRENCY = 4;
36
36
  /**
37
37
  * Envelope byte ceiling (regression guard for the design's named PR2 risk:
38
- * two envelopes → one bigger one). The folded envelope's bounded parts are:
39
- * the `applyBudget`-capped body (`planningContext.maxBytes` = 50 KB), the
40
- * tier-capped codebase snapshot (~35 KB skinny on this repo), the three
41
- * rendered system prompts (~15 KB), and the digest-first `docsContext`
42
- * (outline-only, or inline digest in one-pager/seed mode). Measured folded envelopes on this
43
- * repo land at ~42 KB; 256 KB (~64K tokens at the ≈4-chars/token estimate)
44
- * gives >2× headroom over a worst-case budgeted body + medium-tier snapshot
45
- * while staying an order of magnitude under the session budget. The test
46
- * suite asserts serialized envelopes stay under this value — raise it only
47
- * with a measured justification.
38
+ * two envelopes → one bigger one). This is the **only** live bound on
39
+ * envelope size: Story #4541 removed the `applyBudget` pass from
40
+ * `buildAuthoringContext`, because both builders below discard that budgeted
41
+ * body and ship the raw seed on `seed.content` instead — the budget bounded
42
+ * a field that never left the function.
43
+ *
44
+ * The envelope's bounded parts are: the tier-capped codebase snapshot
45
+ * (~35 KB skinny on this repo), the three rendered system prompts (~15 KB),
46
+ * and the digest-first `docsContext` (outline-only, or inline digest in
47
+ * one-pager/seed mode). The seed itself is operator-supplied and carried
48
+ * verbatim. Measured folded envelopes on this repo land at ~42 KB; 256 KB
49
+ * (~64K tokens at the ≈4-chars/token estimate) gives >2× headroom over a
50
+ * worst-case seed + medium-tier snapshot while staying an order of magnitude
51
+ * under the session budget. The test suite asserts serialized envelopes stay
52
+ * under this value — raise it only with a measured justification.
48
53
  */
49
54
  export const PLAN_CONTEXT_ENVELOPE_BYTE_CEILING = 256_000;
50
55
 
56
+ /** Fields named in the over-ceiling error, to point at what to trim. */
57
+ const OVERSIZE_REPORT_FIELDS = 3;
58
+
59
+ /**
60
+ * Fail closed when an assembled envelope exceeds
61
+ * {@link PLAN_CONTEXT_ENVELOPE_BYTE_CEILING}.
62
+ *
63
+ * Until now the ceiling was enforced *only* by a test assertion over this
64
+ * repo's own fixtures, which bounds nothing at runtime: the value it actually
65
+ * has to hold for is a consumer's seed or `--tickets` source bodies, and no
66
+ * test sees those. That left the documented planner-context cap resting
67
+ * entirely on `planning.context.maxBytes` — which resolved but was wired to
68
+ * nothing (its `applyBudget` pass lost its last caller in the v2 cutover), so
69
+ * in practice no bound existed at all on the path that needed one. That key
70
+ * and its budget module were removed outright in Story #4541; this ceiling is
71
+ * the replacement.
72
+ *
73
+ * Failing closed is the right direction here and matches how an over-budget
74
+ * `## Spec` is handled (`spec-spill.js`): an envelope this size does not
75
+ * degrade the planner gracefully, it silently produces garbage Stories from a
76
+ * truncated-by-the-host context. Better to refuse and say what to trim. The
77
+ * bound is deliberately a fixed framework constant rather than an operator
78
+ * knob — a cap the operator can raise past what the model can read is a cap
79
+ * that fails silently again.
80
+ *
81
+ * Deliberately **not** exported: its only external caller would be a test, and
82
+ * a test-only export is a production-dead one. It is reachable end to end
83
+ * through {@link buildPlanContext}, which is where the behaviour matters.
84
+ *
85
+ * @param {object} envelope
86
+ * @param {{ ceiling?: number }} [opts]
87
+ * @returns {object} `envelope`, unchanged, when it fits.
88
+ */
89
+ function assertPlanContextWithinCeiling(envelope, opts = {}) {
90
+ const ceiling = opts.ceiling ?? PLAN_CONTEXT_ENVELOPE_BYTE_CEILING;
91
+ const bytes = Buffer.byteLength(JSON.stringify(envelope) ?? '', 'utf-8');
92
+ if (bytes <= ceiling) return envelope;
93
+
94
+ const largest = Object.entries(envelope)
95
+ .map(([field, value]) => [
96
+ field,
97
+ Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8'),
98
+ ])
99
+ .sort((a, b) => b[1] - a[1])
100
+ .slice(0, OVERSIZE_REPORT_FIELDS)
101
+ .map(([field, size]) => `${field} (${Math.round(size / 1024)} KB)`)
102
+ .join(', ');
103
+
104
+ throw new Error(
105
+ `[plan-context] the assembled "${envelope?.mode}" envelope is ` +
106
+ `${Math.round(bytes / 1024)} KB, over the ` +
107
+ `${Math.round(ceiling / 1024)} KB planner-context ceiling. Largest ` +
108
+ `fields: ${largest}. Trim the seed, plan fewer --tickets source issues ` +
109
+ 'in one run, or narrow `planning.codebaseSnapshot`. Raising the ceiling ' +
110
+ 'needs a measured justification — see PLAN_CONTEXT_ENVELOPE_BYTE_CEILING.',
111
+ );
112
+ }
113
+
51
114
  /**
52
115
  * Compact, machine-readable descriptor of the `tickets.json` array the
53
116
  * authoring pass writes and `validateAndNormalizeTickets` gates at persist
@@ -61,10 +124,13 @@ export const TICKET_SCHEMA_DESCRIPTOR = Object.freeze({
61
124
  slug: 'string — ^[a-z0-9][a-z0-9-]*$ (hyphen-case, unique per decompose)',
62
125
  type: "string — literal 'story' (2-tier hierarchy: Epic → Story only)",
63
126
  title: 'string — short descriptive title',
64
- body: 'string — serialized Story-body markdown (never a JSON object)',
65
- acceptance: 'string[] — top-level testable criteria (not nested in body)',
66
- verify: 'string[] — top-level exact commands/test paths with (<tier>)',
67
- labels: "string[] — must include 'type::story' (no persona::* axis)",
127
+ body: 'string — serialized Story-body markdown (never a JSON object); omit the ## Acceptance / ## Verify sections, persist syncs them in',
128
+ acceptance:
129
+ 'string[] — top-level testable criteria; the machine contract, authored here and not in the body',
130
+ verify:
131
+ 'string[] — top-level exact commands/test paths with (<tier>); the machine contract, authored here and not in the body',
132
+ labels:
133
+ "string[]? — extra labels to apply; 'type::story' is applied automatically. agent::*, type::*, and persona::* are rejected (runtime-owned or retired axes)",
68
134
  depends_on: 'string[]? — sibling Story slugs that block execution',
69
135
  }),
70
136
  validatedBy:
@@ -295,17 +361,12 @@ export function buildDeliveryShapeSignal({ body } = {}) {
295
361
  * authoritative from day one); the decompose prompt reuses the existing
296
362
  * Story #4162 carrier including the risk-heuristics suffix.
297
363
  *
298
- * @param {{ heuristics?: string[], maxTickets?: number, epicId?: number|null }} args
364
+ * @param {{ heuristics?: string[], maxTickets?: number }} args
299
365
  * @returns {{ spec: string, acceptance: string, decompose: string }}
300
366
  */
301
- export function buildSystemPrompts({
302
- heuristics = [],
303
- maxTickets,
304
- epicId = null,
305
- } = {}) {
367
+ export function buildSystemPrompts({ heuristics = [], maxTickets } = {}) {
306
368
  const decompose = buildDecomposerSystemPrompt(heuristics, {
307
369
  maxTickets,
308
- epicId,
309
370
  });
310
371
  return {
311
372
  spec: renderTechSpecSystemPrompt(),
@@ -373,7 +434,6 @@ async function buildSeedFileModeEnvelope({
373
434
  provider,
374
435
  config,
375
436
  settings,
376
- fullContext,
377
437
  cwd,
378
438
  modeLabel = 'seed-file',
379
439
  }) {
@@ -401,7 +461,6 @@ async function buildSeedFileModeEnvelope({
401
461
  { ...settings, docsContextFiles: [] },
402
462
  {
403
463
  epic: { id: 0, title: seedFilePath ?? 'seed', body: content },
404
- fullContext,
405
464
  github: config.github ?? null,
406
465
  cwd,
407
466
  },
@@ -436,7 +495,6 @@ async function buildSeedFileModeEnvelope({
436
495
  systemPrompts: buildSystemPrompts({
437
496
  heuristics,
438
497
  maxTickets: limits.maxTickets,
439
- epicId: null,
440
498
  }),
441
499
  planState: null,
442
500
  // N=1 default: author one Story; skip Epic-scale decompose ceremony.
@@ -454,7 +512,6 @@ async function buildSeedModeEnvelope({
454
512
  provider,
455
513
  config,
456
514
  settings,
457
- fullContext,
458
515
  cwd,
459
516
  }) {
460
517
  if (typeof seedText !== 'string' || seedText.trim().length === 0) {
@@ -468,7 +525,6 @@ async function buildSeedModeEnvelope({
468
525
  provider,
469
526
  config,
470
527
  settings,
471
- fullContext,
472
528
  cwd,
473
529
  modeLabel: 'seed',
474
530
  });
@@ -528,7 +584,6 @@ async function buildTicketsModeEnvelope({
528
584
  provider,
529
585
  config,
530
586
  settings,
531
- fullContext,
532
587
  cwd,
533
588
  }) {
534
589
  if (!Array.isArray(ticketIds) || ticketIds.length === 0) {
@@ -558,7 +613,6 @@ async function buildTicketsModeEnvelope({
558
613
  title: sourceTickets[0]?.title ?? 'tickets',
559
614
  body: seed,
560
615
  },
561
- fullContext,
562
616
  github: config.github ?? null,
563
617
  cwd,
564
618
  },
@@ -594,7 +648,6 @@ async function buildTicketsModeEnvelope({
594
648
  systemPrompts: buildSystemPrompts({
595
649
  heuristics,
596
650
  maxTickets: limits.maxTickets,
597
- epicId: null,
598
651
  }),
599
652
  planState: null,
600
653
  planProfile:
@@ -610,6 +663,10 @@ async function buildTicketsModeEnvelope({
610
663
  /**
611
664
  * Build the single planner-context envelope.
612
665
  *
666
+ * Every mode returns through here, which makes this the one place the
667
+ * envelope's total size is decided — and therefore the only honest place to
668
+ * bound it (see {@link assertPlanContextWithinCeiling}).
669
+ *
613
670
  * @param {{
614
671
  * mode: 'seed-file'|'seed'|'tickets',
615
672
  * seedFilePath?: string,
@@ -632,7 +689,36 @@ export async function buildPlanContext({
632
689
  provider,
633
690
  config = {},
634
691
  settings = {},
635
- fullContext = false,
692
+ cwd,
693
+ }) {
694
+ return assertPlanContextWithinCeiling(
695
+ await buildPlanContextEnvelope({
696
+ mode,
697
+ seedFilePath,
698
+ seedFileContent,
699
+ seedText,
700
+ ticketIds,
701
+ provider,
702
+ config,
703
+ settings,
704
+ cwd,
705
+ }),
706
+ );
707
+ }
708
+
709
+ /**
710
+ * Mode dispatch for {@link buildPlanContext}. Split out so the ceiling check
711
+ * wraps every mode exactly once.
712
+ */
713
+ async function buildPlanContextEnvelope({
714
+ mode,
715
+ seedFilePath,
716
+ seedFileContent,
717
+ seedText,
718
+ ticketIds,
719
+ provider,
720
+ config,
721
+ settings,
636
722
  cwd,
637
723
  }) {
638
724
  if (mode === 'seed-file') {
@@ -647,7 +733,6 @@ export async function buildPlanContext({
647
733
  provider,
648
734
  config,
649
735
  settings,
650
- fullContext,
651
736
  cwd,
652
737
  modeLabel: 'seed-file',
653
738
  });
@@ -658,7 +743,6 @@ export async function buildPlanContext({
658
743
  provider,
659
744
  config,
660
745
  settings,
661
- fullContext,
662
746
  cwd,
663
747
  });
664
748
  }
@@ -668,7 +752,6 @@ export async function buildPlanContext({
668
752
  provider,
669
753
  config,
670
754
  settings,
671
- fullContext,
672
755
  cwd,
673
756
  });
674
757
  }
@@ -1,16 +1,16 @@
1
1
  /**
2
- * plan-critic-conditions.js — risk/size-conditional dispatch decisions for
2
+ * plan-critic-conditions.js — size/heuristic-conditional dispatch decisions for
3
3
  * the /plan author-step critics (Epic #4474 PR6, design §4).
4
4
  *
5
- * The collapsed plan flow keeps the consolidation (8.3) and pre-mortem
6
- * (8.5) critics as fresh-context sub-agent dispatches, but makes each
7
- * dispatch **conditional** instead of unconditional — the dominant plan
8
- * cost is turns × standing context, and an unconditional critic pays a
9
- * full sub-agent spawn even when it provably has nothing to find. This
10
- * module computes those decisions deterministically so the workflow never
11
- * judges its own dispatch conditions:
5
+ * The collapsed plan flow keeps the consolidation and pre-mortem critics as
6
+ * fresh-context sub-agent dispatches, but makes each dispatch
7
+ * **conditional** instead of unconditional — the dominant plan cost is
8
+ * turns × standing context, and an unconditional critic pays a full
9
+ * sub-agent spawn even when it provably has nothing to find. This module
10
+ * computes those decisions deterministically so the workflow never judges
11
+ * its own dispatch conditions:
12
12
  *
13
- * - **Consolidation (8.3)**: dispatch only when the existing
13
+ * - **Consolidation**: dispatch only when the existing
14
14
  * `evaluateConsolidationPrecondition` gate says `dispatch: true` AND
15
15
  * (the draft has more than `CONSOLIDATION_STORY_THRESHOLD` stories OR
16
16
  * the precondition confirmed a divergence from the Delivery Slicing
@@ -18,27 +18,29 @@
18
18
  * small draft is NOT a confirmed divergence — it skips, because a
19
19
  * ≤-threshold draft is small enough for gate #2's single-view review
20
20
  * to catch a distorted shape without a dedicated sub-agent.
21
- * - **Pre-mortem (8.5)**: dispatch when the risk verdict's overall level
22
- * is `high`, OR the ticket count is at least half of `maxTickets`, OR
23
- * any configured `planning.riskHeuristics` phrase matches the plan
24
- * text (case-insensitive substring).
21
+ * - **Pre-mortem**: dispatch when the ticket count is at least half
22
+ * of `maxTickets`, OR any configured `planning.riskHeuristics` phrase
23
+ * matches the plan text (case-insensitive substring). Story #4542 removed
24
+ * its third condition — the authored risk verdict's overall level — along
25
+ * with the verdict itself; both surviving conditions read the plan's own
26
+ * observable text and shape rather than a self-assessment.
25
27
  *
26
28
  * Under-firing risk (design PR6 note): the persist validators are
27
29
  * unchanged hard gates and G2's cohort re-measures plan quality; every
28
30
  * skip decision this module produces is logged to the plan-metrics ledger
29
- * (`appendCriticSkip`) by the callers so under-firing is auditable.
31
+ * (`appendCriticSkip`) by the caller so under-firing is auditable.
30
32
  *
31
- * Pure, synchronous, no I/O the folded pre-write phase inside
32
- * `plan-persist.js` owns reading the authored artifacts and the resolved
33
- * config.
33
+ * Pure, synchronous, no I/O. The single caller is `plan-critics-evaluate.js`,
34
+ * driven by the `plan-critics.js` CLI that `/plan` runs between Author and
35
+ * Persist (Story #4592); the CLI owns reading the authored artifacts and the
36
+ * resolved config.
34
37
  */
35
38
 
36
39
  import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
37
- import { deriveRiskEnvelope } from './planning-risk.js';
38
40
 
39
41
  /**
40
42
  * Draft-story count above which the consolidation critic fires even
41
- * without a confirmed slicing divergence (design §6 PR6: "> 5 stories").
43
+ * without a confirmed slicing divergence (#4474 PR6: "> 5 stories").
42
44
  */
43
45
  export const CONSOLIDATION_STORY_THRESHOLD = 5;
44
46
 
@@ -52,7 +54,7 @@ export const CONSOLIDATION_STORY_THRESHOLD = 5;
52
54
  */
53
55
 
54
56
  /**
55
- * Decide the 8.3 consolidation dispatch: precondition AND size/divergence.
57
+ * Decide the consolidation dispatch: precondition AND size/divergence.
56
58
  *
57
59
  * @param {object} input
58
60
  * @param {object[]} input.draftStories - The draft `tickets.json` array
@@ -105,14 +107,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
105
107
  }
106
108
 
107
109
  /**
108
- * Decide the 8.5 pre-mortem dispatch: high risk, or size ≥ ½ budget, or a
109
- * risk-heuristic phrase match.
110
+ * Decide the pre-mortem dispatch: size ≥ ½ budget, or a risk-heuristic
111
+ * phrase match.
110
112
  *
111
113
  * @param {object} input
112
- * @param {import('./planning-risk.js').RiskVerdict} input.riskVerdict -
113
- * The authored `risk-verdict.json` payload; the overall level is derived
114
- * deterministically from its axes (`deriveRiskEnvelope`), never trusted
115
- * as a free-standing field.
116
114
  * @param {number} input.ticketCount - Draft ticket count (0 in the
117
115
  * single-delivery shape — no tickets exist).
118
116
  * @param {number} input.maxTickets - The reviewability budget
@@ -120,11 +118,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
120
118
  * @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
121
119
  * phrases from the resolved config.
122
120
  * @param {string} [input.planText] - Concatenated plan text the heuristics
123
- * match against (tech spec + serialized tickets + risk summary).
121
+ * match against (tech spec + serialized tickets).
124
122
  * @returns {CriticDispatchDecision}
125
123
  */
126
124
  export function evaluatePremortemDispatch({
127
- riskVerdict,
128
125
  ticketCount,
129
126
  maxTickets,
130
127
  riskHeuristics = [],
@@ -137,13 +134,6 @@ export function evaluatePremortemDispatch({
137
134
  }
138
135
  const reasons = [];
139
136
 
140
- const { overallLevel } = deriveRiskEnvelope(riskVerdict);
141
- if (overallLevel === 'high') {
142
- reasons.push(
143
- 'Risk verdict overall level is high — predicted-rework findings are worth a fresh-context pass.',
144
- );
145
- }
146
-
147
137
  const count = Number.isInteger(ticketCount) ? ticketCount : 0;
148
138
  if (count * 2 >= maxTickets) {
149
139
  reasons.push(
@@ -172,7 +162,7 @@ export function evaluatePremortemDispatch({
172
162
  critic: 'pre-mortem',
173
163
  dispatch: false,
174
164
  reasons: [
175
- `Overall risk is ${overallLevel} (not high), ticket count ${count} is under half the budget (maxTickets ${maxTickets}), and no planning.riskHeuristics phrase matches the plan text.`,
165
+ `Ticket count ${count} is under half the budget (maxTickets ${maxTickets}) and no planning.riskHeuristics phrase matches the plan text.`,
176
166
  ],
177
167
  };
178
168
  }