mandrel 2.0.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 (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  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/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. 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:
@@ -373,7 +439,6 @@ async function buildSeedFileModeEnvelope({
373
439
  provider,
374
440
  config,
375
441
  settings,
376
- fullContext,
377
442
  cwd,
378
443
  modeLabel = 'seed-file',
379
444
  }) {
@@ -401,7 +466,6 @@ async function buildSeedFileModeEnvelope({
401
466
  { ...settings, docsContextFiles: [] },
402
467
  {
403
468
  epic: { id: 0, title: seedFilePath ?? 'seed', body: content },
404
- fullContext,
405
469
  github: config.github ?? null,
406
470
  cwd,
407
471
  },
@@ -454,7 +518,6 @@ async function buildSeedModeEnvelope({
454
518
  provider,
455
519
  config,
456
520
  settings,
457
- fullContext,
458
521
  cwd,
459
522
  }) {
460
523
  if (typeof seedText !== 'string' || seedText.trim().length === 0) {
@@ -468,7 +531,6 @@ async function buildSeedModeEnvelope({
468
531
  provider,
469
532
  config,
470
533
  settings,
471
- fullContext,
472
534
  cwd,
473
535
  modeLabel: 'seed',
474
536
  });
@@ -528,7 +590,6 @@ async function buildTicketsModeEnvelope({
528
590
  provider,
529
591
  config,
530
592
  settings,
531
- fullContext,
532
593
  cwd,
533
594
  }) {
534
595
  if (!Array.isArray(ticketIds) || ticketIds.length === 0) {
@@ -558,7 +619,6 @@ async function buildTicketsModeEnvelope({
558
619
  title: sourceTickets[0]?.title ?? 'tickets',
559
620
  body: seed,
560
621
  },
561
- fullContext,
562
622
  github: config.github ?? null,
563
623
  cwd,
564
624
  },
@@ -610,6 +670,10 @@ async function buildTicketsModeEnvelope({
610
670
  /**
611
671
  * Build the single planner-context envelope.
612
672
  *
673
+ * Every mode returns through here, which makes this the one place the
674
+ * envelope's total size is decided — and therefore the only honest place to
675
+ * bound it (see {@link assertPlanContextWithinCeiling}).
676
+ *
613
677
  * @param {{
614
678
  * mode: 'seed-file'|'seed'|'tickets',
615
679
  * seedFilePath?: string,
@@ -632,7 +696,36 @@ export async function buildPlanContext({
632
696
  provider,
633
697
  config = {},
634
698
  settings = {},
635
- fullContext = false,
699
+ cwd,
700
+ }) {
701
+ return assertPlanContextWithinCeiling(
702
+ await buildPlanContextEnvelope({
703
+ mode,
704
+ seedFilePath,
705
+ seedFileContent,
706
+ seedText,
707
+ ticketIds,
708
+ provider,
709
+ config,
710
+ settings,
711
+ cwd,
712
+ }),
713
+ );
714
+ }
715
+
716
+ /**
717
+ * Mode dispatch for {@link buildPlanContext}. Split out so the ceiling check
718
+ * wraps every mode exactly once.
719
+ */
720
+ async function buildPlanContextEnvelope({
721
+ mode,
722
+ seedFilePath,
723
+ seedFileContent,
724
+ seedText,
725
+ ticketIds,
726
+ provider,
727
+ config,
728
+ settings,
636
729
  cwd,
637
730
  }) {
638
731
  if (mode === 'seed-file') {
@@ -647,7 +740,6 @@ export async function buildPlanContext({
647
740
  provider,
648
741
  config,
649
742
  settings,
650
- fullContext,
651
743
  cwd,
652
744
  modeLabel: 'seed-file',
653
745
  });
@@ -658,7 +750,6 @@ export async function buildPlanContext({
658
750
  provider,
659
751
  config,
660
752
  settings,
661
- fullContext,
662
753
  cwd,
663
754
  });
664
755
  }
@@ -668,7 +759,6 @@ export async function buildPlanContext({
668
759
  provider,
669
760
  config,
670
761
  settings,
671
- fullContext,
672
762
  cwd,
673
763
  });
674
764
  }
@@ -1,5 +1,5 @@
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
5
  * The collapsed plan flow keeps the consolidation (8.3) and pre-mortem
@@ -18,10 +18,12 @@
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 (8.5)**: 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
@@ -34,7 +36,6 @@
34
36
  */
35
37
 
36
38
  import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
37
- import { deriveRiskEnvelope } from './planning-risk.js';
38
39
 
39
40
  /**
40
41
  * Draft-story count above which the consolidation critic fires even
@@ -105,14 +106,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
105
106
  }
106
107
 
107
108
  /**
108
- * Decide the 8.5 pre-mortem dispatch: high risk, or size ≥ ½ budget, or a
109
- * risk-heuristic phrase match.
109
+ * Decide the 8.5 pre-mortem dispatch: size ≥ ½ budget, or a risk-heuristic
110
+ * phrase match.
110
111
  *
111
112
  * @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
113
  * @param {number} input.ticketCount - Draft ticket count (0 in the
117
114
  * single-delivery shape — no tickets exist).
118
115
  * @param {number} input.maxTickets - The reviewability budget
@@ -120,11 +117,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
120
117
  * @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
121
118
  * phrases from the resolved config.
122
119
  * @param {string} [input.planText] - Concatenated plan text the heuristics
123
- * match against (tech spec + serialized tickets + risk summary).
120
+ * match against (tech spec + serialized tickets).
124
121
  * @returns {CriticDispatchDecision}
125
122
  */
126
123
  export function evaluatePremortemDispatch({
127
- riskVerdict,
128
124
  ticketCount,
129
125
  maxTickets,
130
126
  riskHeuristics = [],
@@ -137,13 +133,6 @@ export function evaluatePremortemDispatch({
137
133
  }
138
134
  const reasons = [];
139
135
 
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
136
  const count = Number.isInteger(ticketCount) ? ticketCount : 0;
148
137
  if (count * 2 >= maxTickets) {
149
138
  reasons.push(
@@ -172,7 +161,7 @@ export function evaluatePremortemDispatch({
172
161
  critic: 'pre-mortem',
173
162
  dispatch: false,
174
163
  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.`,
164
+ `Ticket count ${count} is under half the budget (maxTickets ${maxTickets}) and no planning.riskHeuristics phrase matches the plan text.`,
176
165
  ],
177
166
  };
178
167
  }
@@ -47,13 +47,12 @@ function resolveRiskHeuristics(config = {}) {
47
47
  * - Consolidation: skipped outright when `tickets` is null/absent (the
48
48
  * single-delivery shape authors no draft tickets); otherwise the
49
49
  * deterministic precondition + size/divergence conditions.
50
- * - Pre-mortem: risk verdict overall level high, OR ticket count at least
51
- * half `maxTickets`, OR any `planning.riskHeuristics` phrase matching
52
- * the plan text.
50
+ * - Pre-mortem: ticket count at least half `maxTickets`, OR any
51
+ * `planning.riskHeuristics` phrase matching the plan text. Story #4542
52
+ * retired its authored-risk-level condition with the verdict itself.
53
53
  *
54
54
  * @param {{
55
55
  * techSpecContent: string,
56
- * riskVerdict: { summary?: string },
57
56
  * tickets?: Array<object>|null,
58
57
  * config?: object,
59
58
  * }} args
@@ -64,7 +63,6 @@ function resolveRiskHeuristics(config = {}) {
64
63
  */
65
64
  export function evaluatePlanCritics({
66
65
  techSpecContent,
67
- riskVerdict,
68
66
  tickets = null,
69
67
  config = {},
70
68
  }) {
@@ -84,14 +82,12 @@ export function evaluatePlanCritics({
84
82
  });
85
83
 
86
84
  const premortem = evaluatePremortemDispatch({
87
- riskVerdict,
88
85
  ticketCount: ticketList?.length ?? 0,
89
86
  maxTickets: getLimits(config).maxTickets,
90
87
  riskHeuristics: resolveRiskHeuristics(config),
91
88
  planText: [
92
89
  techSpecContent ?? '',
93
90
  ticketList ? JSON.stringify(ticketList) : '',
94
- riskVerdict?.summary ?? '',
95
91
  ].join('\n'),
96
92
  });
97
93