mandrel 2.60.0 → 2.62.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 (662) hide show
  1. package/.agents/README.md +10 -10
  2. package/.agents/agents/story-worker.md +1 -1
  3. package/.agents/docs/agentrc-reference.json +4 -82
  4. package/.agents/docs/configuration.md +20 -87
  5. package/.agents/docs/workflows.md +1 -1
  6. package/.agents/instructions.md +1 -3
  7. package/.agents/rules/gherkin-standards.md +4 -0
  8. package/.agents/schemas/agentrc.schema.json +11 -486
  9. package/.agents/schemas/story-deliver-terminal.schema.json +10 -0
  10. package/.agents/schemas/validation-evidence.schema.json +2 -1
  11. package/.agents/scripts/README.md +14 -14
  12. package/.agents/scripts/acceptance-eval.js +32 -164
  13. package/.agents/scripts/agents-bootstrap-github.js +27 -129
  14. package/.agents/scripts/apply-quality-bootstrap.js +4 -40
  15. package/.agents/scripts/audit-baselines.js +6 -27
  16. package/.agents/scripts/audit-labels-bootstrap.js +5 -29
  17. package/.agents/scripts/audit-to-stories.js +91 -361
  18. package/.agents/scripts/boot-sweep.js +19 -72
  19. package/.agents/scripts/bootstrap.js +74 -395
  20. package/.agents/scripts/ceremony-derive.js +9 -45
  21. package/.agents/scripts/check-arch-cycles.js +14 -64
  22. package/.agents/scripts/check-baselines.js +8 -48
  23. package/.agents/scripts/check-context-budget.js +46 -172
  24. package/.agents/scripts/check-cyclomatic.js +12 -44
  25. package/.agents/scripts/check-dead-exports.js +13 -64
  26. package/.agents/scripts/check-doc-links.js +38 -186
  27. package/.agents/scripts/check-gherkin-corpus.js +24 -127
  28. package/.agents/scripts/check-test-temp-hygiene.js +28 -169
  29. package/.agents/scripts/coverage-capture.js +32 -99
  30. package/.agents/scripts/deliver-light.js +14 -101
  31. package/.agents/scripts/deliver-recover.js +6 -42
  32. package/.agents/scripts/deliver-run.js +36 -133
  33. package/.agents/scripts/diagnose-friction.js +24 -116
  34. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  35. package/.agents/scripts/evidence-gate.js +23 -85
  36. package/.agents/scripts/file-ci-gap.js +10 -50
  37. package/.agents/scripts/generate-config-docs.js +30 -170
  38. package/.agents/scripts/generate-lens-checklists.js +8 -52
  39. package/.agents/scripts/generate-skills-index.js +14 -112
  40. package/.agents/scripts/generate-workflows-doc.js +10 -70
  41. package/.agents/scripts/git-cleanup.js +2 -34
  42. package/.agents/scripts/lib/Graph.js +24 -81
  43. package/.agents/scripts/lib/ITicketingProvider.js +47 -143
  44. package/.agents/scripts/lib/Logger.js +14 -76
  45. package/.agents/scripts/lib/audit-baselines/engine.js +11 -31
  46. package/.agents/scripts/lib/audit-baselines/gate-surface.js +3 -17
  47. package/.agents/scripts/lib/audit-baselines/headroom.js +4 -20
  48. package/.agents/scripts/lib/audit-baselines/hotspots.js +3 -15
  49. package/.agents/scripts/lib/audit-baselines/kinds.js +23 -89
  50. package/.agents/scripts/lib/audit-baselines/outliers.js +6 -23
  51. package/.agents/scripts/lib/audit-baselines/read.js +3 -16
  52. package/.agents/scripts/lib/audit-baselines/staleness.js +7 -26
  53. package/.agents/scripts/lib/audit-baselines/surface-entry.js +7 -28
  54. package/.agents/scripts/lib/audit-baselines/trend.js +7 -20
  55. package/.agents/scripts/lib/audit-baselines/weights.js +9 -37
  56. package/.agents/scripts/lib/audit-suite/audit-rules-reader.js +3 -18
  57. package/.agents/scripts/lib/audit-suite/checklist-threading.js +26 -124
  58. package/.agents/scripts/lib/audit-suite/dispatch-checklist.js +14 -45
  59. package/.agents/scripts/lib/audit-suite/findings.js +12 -52
  60. package/.agents/scripts/lib/audit-suite/frontmatter.js +5 -29
  61. package/.agents/scripts/lib/audit-suite/index.js +1 -15
  62. package/.agents/scripts/lib/audit-suite/lens-checklist.js +10 -45
  63. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +11 -76
  64. package/.agents/scripts/lib/audit-suite/runner.js +13 -43
  65. package/.agents/scripts/lib/audit-suite/selector.js +49 -349
  66. package/.agents/scripts/lib/audit-suite/substitutions.js +12 -40
  67. package/.agents/scripts/lib/audit-suite/workflow-loader.js +3 -15
  68. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +11 -73
  69. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +8 -37
  70. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +25 -143
  71. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +14 -80
  72. package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +4 -18
  73. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +7 -40
  74. package/.agents/scripts/lib/audit-to-stories/group-findings.js +9 -39
  75. package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +10 -66
  76. package/.agents/scripts/lib/audit-to-stories/issue-index.js +6 -30
  77. package/.agents/scripts/lib/audit-to-stories/issues-file.js +6 -36
  78. package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +17 -69
  79. package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +17 -85
  80. package/.agents/scripts/lib/audit-to-stories/ledger-record.js +10 -48
  81. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +38 -146
  82. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +7 -42
  83. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +10 -57
  84. package/.agents/scripts/lib/baseline-loader.js +12 -51
  85. package/.agents/scripts/lib/baseline-schema-registry.js +5 -22
  86. package/.agents/scripts/lib/baselines/component-matcher.js +3 -12
  87. package/.agents/scripts/lib/baselines/components.js +10 -61
  88. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +7 -36
  89. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +5 -21
  90. package/.agents/scripts/lib/baselines/crap-preview-scan.js +19 -45
  91. package/.agents/scripts/lib/baselines/crap-updater-cli.js +16 -65
  92. package/.agents/scripts/lib/baselines/diff-scope-cli.js +5 -33
  93. package/.agents/scripts/lib/baselines/duplication-scanner.js +11 -60
  94. package/.agents/scripts/lib/baselines/env-overrides.js +12 -67
  95. package/.agents/scripts/lib/baselines/envelope.js +13 -129
  96. package/.agents/scripts/lib/baselines/exit-codes.js +6 -49
  97. package/.agents/scripts/lib/baselines/git-base.js +23 -142
  98. package/.agents/scripts/lib/baselines/kernel.js +11 -111
  99. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +9 -47
  100. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +8 -51
  101. package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +7 -62
  102. package/.agents/scripts/lib/baselines/kinds/bundle-size.js +8 -29
  103. package/.agents/scripts/lib/baselines/kinds/coverage.js +3 -15
  104. package/.agents/scripts/lib/baselines/kinds/crap.js +94 -415
  105. package/.agents/scripts/lib/baselines/kinds/duplication.js +4 -30
  106. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +7 -53
  107. package/.agents/scripts/lib/baselines/kinds/maintainability.js +15 -80
  108. package/.agents/scripts/lib/baselines/kinds/mutation.js +12 -72
  109. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +4 -11
  110. package/.agents/scripts/lib/baselines/merge-envelopes.js +30 -146
  111. package/.agents/scripts/lib/baselines/path-canon.js +19 -128
  112. package/.agents/scripts/lib/baselines/preview-gates.js +8 -37
  113. package/.agents/scripts/lib/baselines/reader.js +14 -107
  114. package/.agents/scripts/lib/baselines/refresh-service.js +35 -252
  115. package/.agents/scripts/lib/baselines/scope.js +11 -135
  116. package/.agents/scripts/lib/baselines/writer.js +16 -149
  117. package/.agents/scripts/lib/bdd-runner-detect.js +18 -113
  118. package/.agents/scripts/lib/bdd-scenario-budget.js +9 -36
  119. package/.agents/scripts/lib/bdd-scenario-scanner.js +11 -73
  120. package/.agents/scripts/lib/bdd-step-index.js +26 -100
  121. package/.agents/scripts/lib/bootstrap/baseline-merge-driver.js +23 -103
  122. package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -5
  123. package/.agents/scripts/lib/bootstrap/commit-push.js +12 -50
  124. package/.agents/scripts/lib/bootstrap/gh-preflight.js +14 -101
  125. package/.agents/scripts/lib/bootstrap/hitl-confirm.js +5 -30
  126. package/.agents/scripts/lib/bootstrap/install-ledger.js +17 -73
  127. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +17 -121
  128. package/.agents/scripts/lib/bootstrap/manifest.js +15 -86
  129. package/.agents/scripts/lib/bootstrap/merge-methods.js +4 -33
  130. package/.agents/scripts/lib/bootstrap/preflight.js +12 -61
  131. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +34 -235
  132. package/.agents/scripts/lib/bootstrap/prompt.js +21 -129
  133. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +27 -141
  134. package/.agents/scripts/lib/bootstrap/summary.js +1 -8
  135. package/.agents/scripts/lib/bootstrap/workflow-audit.js +11 -84
  136. package/.agents/scripts/lib/branch-name-guard.js +4 -23
  137. package/.agents/scripts/lib/changed-files.js +31 -143
  138. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -24
  139. package/.agents/scripts/lib/checks/index.js +18 -101
  140. package/.agents/scripts/lib/checks/loop-health.js +11 -53
  141. package/.agents/scripts/lib/checks/state.js +16 -91
  142. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +10 -61
  143. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +14 -83
  144. package/.agents/scripts/lib/child-exec.js +39 -108
  145. package/.agents/scripts/lib/cli/standard-args.js +9 -116
  146. package/.agents/scripts/lib/cli-args.js +31 -146
  147. package/.agents/scripts/lib/cli-usage.js +5 -27
  148. package/.agents/scripts/lib/cli-utils.js +2 -12
  149. package/.agents/scripts/lib/close-validation/commands.js +13 -76
  150. package/.agents/scripts/lib/close-validation/gates.js +92 -331
  151. package/.agents/scripts/lib/close-validation/process.js +121 -151
  152. package/.agents/scripts/lib/close-validation/projections/advisories.js +5 -31
  153. package/.agents/scripts/lib/close-validation/projections/crap.js +19 -67
  154. package/.agents/scripts/lib/close-validation/projections/head-sha.js +1 -17
  155. package/.agents/scripts/lib/close-validation/projections/inputs.js +4 -34
  156. package/.agents/scripts/lib/close-validation/projections/maintainability.js +11 -58
  157. package/.agents/scripts/lib/close-validation/runner.js +53 -110
  158. package/.agents/scripts/lib/command-header.js +6 -28
  159. package/.agents/scripts/lib/config/acceptance-eval.js +4 -30
  160. package/.agents/scripts/lib/config/baselines.js +3 -14
  161. package/.agents/scripts/lib/config/ci.js +8 -38
  162. package/.agents/scripts/lib/config/commands.js +1 -15
  163. package/.agents/scripts/lib/config/defaults.js +4 -33
  164. package/.agents/scripts/lib/config/delivery-routing.js +6 -35
  165. package/.agents/scripts/lib/config/explain.js +9 -80
  166. package/.agents/scripts/lib/config/gates/coverage.schema.js +0 -12
  167. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +2 -30
  168. package/.agents/scripts/lib/config/gates/crap.schema.js +1 -34
  169. package/.agents/scripts/lib/config/gates/duplication.schema.js +1 -8
  170. package/.agents/scripts/lib/config/gates/index.js +2 -17
  171. package/.agents/scripts/lib/config/gates/maintainability.schema.js +1 -20
  172. package/.agents/scripts/lib/config/gates/mutation.schema.js +1 -7
  173. package/.agents/scripts/lib/config/gates/shared.js +4 -64
  174. package/.agents/scripts/lib/config/github.js +4 -22
  175. package/.agents/scripts/lib/config/limits.js +4 -57
  176. package/.agents/scripts/lib/config/paths.js +2 -21
  177. package/.agents/scripts/lib/config/qa.js +4 -38
  178. package/.agents/scripts/lib/config/quality.js +76 -432
  179. package/.agents/scripts/lib/config/runners.js +12 -56
  180. package/.agents/scripts/lib/config/runtime.js +13 -57
  181. package/.agents/scripts/lib/config/shared.js +2 -16
  182. package/.agents/scripts/lib/config/sync-agentrc.js +9 -50
  183. package/.agents/scripts/lib/config/temp-paths.js +40 -280
  184. package/.agents/scripts/lib/config/validate-orchestration.js +5 -17
  185. package/.agents/scripts/lib/config/worktree-isolation.js +9 -28
  186. package/.agents/scripts/lib/config-resolver.js +10 -52
  187. package/.agents/scripts/lib/config-schema-shared.js +1 -4
  188. package/.agents/scripts/lib/config-settings-schema-delivery.js +12 -294
  189. package/.agents/scripts/lib/config-settings-schema-quality.js +7 -219
  190. package/.agents/scripts/lib/config-settings-schema.js +37 -274
  191. package/.agents/scripts/lib/coverage-baseline.js +20 -110
  192. package/.agents/scripts/lib/coverage-capture-fullscope.js +12 -37
  193. package/.agents/scripts/lib/coverage-capture-incremental.js +12 -43
  194. package/.agents/scripts/lib/coverage-capture-usage.js +4 -24
  195. package/.agents/scripts/lib/coverage-capture.js +99 -224
  196. package/.agents/scripts/lib/coverage-utils.js +14 -79
  197. package/.agents/scripts/lib/cpu-pool.js +17 -123
  198. package/.agents/scripts/lib/crap-baseline-join.js +16 -88
  199. package/.agents/scripts/lib/crap-coordinates.js +6 -25
  200. package/.agents/scripts/lib/crap-engine.js +40 -153
  201. package/.agents/scripts/lib/crap-method-identity.js +16 -78
  202. package/.agents/scripts/lib/crap-utils.js +31 -155
  203. package/.agents/scripts/lib/cyclomatic-ceiling.js +14 -87
  204. package/.agents/scripts/lib/cyclomatic-scope.js +10 -50
  205. package/.agents/scripts/lib/dead-exports-knip.js +18 -56
  206. package/.agents/scripts/lib/dead-exports-mode.js +5 -24
  207. package/.agents/scripts/lib/degraded-mode.js +3 -26
  208. package/.agents/scripts/lib/dependency-parser.js +8 -40
  209. package/.agents/scripts/lib/dependency-version.js +13 -35
  210. package/.agents/scripts/lib/detect-package-manager.js +6 -36
  211. package/.agents/scripts/lib/doc-tiers.js +22 -126
  212. package/.agents/scripts/lib/duplicate-search.js +8 -72
  213. package/.agents/scripts/lib/env-loader.js +5 -27
  214. package/.agents/scripts/lib/error-redactor.js +6 -31
  215. package/.agents/scripts/lib/errors/index.js +2 -22
  216. package/.agents/scripts/lib/escomplex-ast-compat.js +22 -163
  217. package/.agents/scripts/lib/escomplex-kernel.js +21 -123
  218. package/.agents/scripts/lib/feedback-loop/graduator-core.js +93 -419
  219. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +20 -98
  220. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +27 -151
  221. package/.agents/scripts/lib/findings/audit-ledger.js +23 -97
  222. package/.agents/scripts/lib/findings/classify-finding.js +14 -69
  223. package/.agents/scripts/lib/findings/promote-finding.js +14 -112
  224. package/.agents/scripts/lib/findings/provenance-field.js +11 -52
  225. package/.agents/scripts/lib/findings/route-finding.js +37 -255
  226. package/.agents/scripts/lib/findings/semantic-issue-search.js +14 -59
  227. package/.agents/scripts/lib/findings/severity.js +18 -104
  228. package/.agents/scripts/lib/format-generated-json.js +12 -44
  229. package/.agents/scripts/lib/full-suite-lock.js +160 -429
  230. package/.agents/scripts/lib/full-suite-queue.js +213 -0
  231. package/.agents/scripts/lib/gates/baseline-store.js +5 -9
  232. package/.agents/scripts/lib/gates/friction.js +1 -3
  233. package/.agents/scripts/lib/generated/agentrc-validator.js +2 -2
  234. package/.agents/scripts/lib/gh-exec.js +31 -259
  235. package/.agents/scripts/lib/git/cached-fetch.js +9 -52
  236. package/.agents/scripts/lib/git/sync-from-base.js +15 -107
  237. package/.agents/scripts/lib/git-branch-cleanup.js +14 -61
  238. package/.agents/scripts/lib/git-branch-lifecycle.js +16 -87
  239. package/.agents/scripts/lib/git-utils.js +37 -161
  240. package/.agents/scripts/lib/github/framework-repo.js +8 -71
  241. package/.agents/scripts/lib/github-url.js +2 -17
  242. package/.agents/scripts/lib/import-graph.js +11 -38
  243. package/.agents/scripts/lib/install-cmd-parser.js +4 -13
  244. package/.agents/scripts/lib/json-utils.js +3 -18
  245. package/.agents/scripts/lib/label-constants.js +16 -111
  246. package/.agents/scripts/lib/label-taxonomy.js +4 -30
  247. package/.agents/scripts/lib/maintainability-engine.js +12 -80
  248. package/.agents/scripts/lib/maintainability-unscorable.js +5 -25
  249. package/.agents/scripts/lib/maintainability-utils.js +16 -92
  250. package/.agents/scripts/lib/mandrel-catalog.js +12 -70
  251. package/.agents/scripts/lib/notifications/notifier.js +13 -38
  252. package/.agents/scripts/lib/npm-scripts.js +4 -23
  253. package/.agents/scripts/lib/observability/metrics-ledger.js +16 -76
  254. package/.agents/scripts/lib/observability/runtime-friction.js +44 -286
  255. package/.agents/scripts/lib/observability/signal-validator.js +5 -37
  256. package/.agents/scripts/lib/observability/signals-writer.js +11 -121
  257. package/.agents/scripts/lib/observability/source-classifier.js +21 -203
  258. package/.agents/scripts/lib/observability/terse-result.js +14 -47
  259. package/.agents/scripts/lib/onboard/init-tail.js +13 -74
  260. package/.agents/scripts/lib/onboard/scaffold-docs.js +11 -34
  261. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +25 -139
  262. package/.agents/scripts/lib/orchestration/auto-merge-cwd.js +11 -66
  263. package/.agents/scripts/lib/orchestration/behind-recovery.js +10 -55
  264. package/.agents/scripts/lib/orchestration/ceremony-routing.js +5 -75
  265. package/.agents/scripts/lib/orchestration/change-set.js +7 -36
  266. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +9 -46
  267. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +11 -44
  268. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +5 -24
  269. package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +3 -12
  270. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +5 -38
  271. package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +3 -8
  272. package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +28 -136
  273. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +3 -13
  274. package/.agents/scripts/lib/orchestration/check-state.js +92 -0
  275. package/.agents/scripts/lib/orchestration/ci-gap-intake.js +37 -144
  276. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +42 -189
  277. package/.agents/scripts/lib/orchestration/code-review.js +24 -128
  278. package/.agents/scripts/lib/orchestration/column-sync.js +23 -111
  279. package/.agents/scripts/lib/orchestration/complexity-gate.js +42 -214
  280. package/.agents/scripts/lib/orchestration/deliver-recover.js +36 -168
  281. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +4 -35
  282. package/.agents/scripts/lib/orchestration/dependency-candidates.js +9 -41
  283. package/.agents/scripts/lib/orchestration/diff-magnitude.js +25 -115
  284. package/.agents/scripts/lib/orchestration/doc-reader.js +2 -6
  285. package/.agents/scripts/lib/orchestration/docs-digest.js +13 -51
  286. package/.agents/scripts/lib/orchestration/epic-candidates.js +13 -53
  287. package/.agents/scripts/lib/orchestration/epic-checklist.js +9 -33
  288. package/.agents/scripts/lib/orchestration/epic-container.js +41 -167
  289. package/.agents/scripts/lib/orchestration/epic-expansion.js +10 -40
  290. package/.agents/scripts/lib/orchestration/epic-rollup.js +55 -207
  291. package/.agents/scripts/lib/orchestration/file-assumption-enum.js +2 -22
  292. package/.agents/scripts/lib/orchestration/file-assumptions.js +45 -253
  293. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-detect.js +9 -46
  294. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +7 -57
  295. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +21 -126
  296. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +2 -6
  297. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +1 -4
  298. package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +1 -5
  299. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +9 -34
  300. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +41 -190
  301. package/.agents/scripts/lib/orchestration/git-cleanup/phases/merged-tip.js +9 -45
  302. package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +1 -4
  303. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +19 -85
  304. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +3 -17
  305. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +1 -5
  306. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +16 -100
  307. package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +1 -4
  308. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +16 -67
  309. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +9 -33
  310. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +8 -29
  311. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +15 -68
  312. package/.agents/scripts/lib/orchestration/light-backstop.js +11 -49
  313. package/.agents/scripts/lib/orchestration/light-escalation.js +21 -89
  314. package/.agents/scripts/lib/orchestration/light-suitability.js +26 -231
  315. package/.agents/scripts/lib/orchestration/merge-block-class.js +39 -216
  316. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -377
  317. package/.agents/scripts/lib/orchestration/merge-queue.js +158 -0
  318. package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +17 -57
  319. package/.agents/scripts/lib/orchestration/plan-context.js +52 -303
  320. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +22 -79
  321. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +5 -31
  322. package/.agents/scripts/lib/orchestration/plan-metrics.js +29 -108
  323. package/.agents/scripts/lib/orchestration/plan-navigation.js +8 -27
  324. package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +12 -49
  325. package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +18 -73
  326. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +14 -78
  327. package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +6 -30
  328. package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +14 -65
  329. package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +21 -76
  330. package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +6 -43
  331. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +14 -68
  332. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +9 -41
  333. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +49 -248
  334. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +2 -12
  335. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +85 -368
  336. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +8 -38
  337. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +30 -190
  338. package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +9 -49
  339. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +7 -28
  340. package/.agents/scripts/lib/orchestration/plan-reachability.js +13 -42
  341. package/.agents/scripts/lib/orchestration/plan-run-labels/reap.js +16 -91
  342. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +16 -54
  343. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +10 -52
  344. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +14 -109
  345. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +11 -83
  346. package/.agents/scripts/lib/orchestration/pr-watch.js +48 -306
  347. package/.agents/scripts/lib/orchestration/project-meta-cache.js +15 -80
  348. package/.agents/scripts/lib/orchestration/project-meta-resolver.js +10 -51
  349. package/.agents/scripts/lib/orchestration/reassert-status-column.js +12 -76
  350. package/.agents/scripts/lib/orchestration/remote-verifier.js +12 -36
  351. package/.agents/scripts/lib/orchestration/resolve-stories.js +33 -164
  352. package/.agents/scripts/lib/orchestration/retro-proposals.js +66 -361
  353. package/.agents/scripts/lib/orchestration/review-base-ref.js +9 -43
  354. package/.agents/scripts/lib/orchestration/review-depth.js +12 -94
  355. package/.agents/scripts/lib/orchestration/review-providers/codex.js +10 -111
  356. package/.agents/scripts/lib/orchestration/review-providers/degraded-gates.js +13 -71
  357. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +7 -51
  358. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +8 -53
  359. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -214
  360. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +7 -52
  361. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +4 -33
  362. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +11 -71
  363. package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +19 -131
  364. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +6 -89
  365. package/.agents/scripts/lib/orchestration/review-providers/types.js +26 -56
  366. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +3 -46
  367. package/.agents/scripts/lib/orchestration/run-epilogue.js +40 -199
  368. package/.agents/scripts/lib/orchestration/run-scoped-config.js +13 -81
  369. package/.agents/scripts/lib/orchestration/single-story-close/close-note.js +7 -34
  370. package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +16 -89
  371. package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +18 -109
  372. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +190 -280
  373. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +18 -80
  374. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +103 -97
  375. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -64
  376. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +183 -485
  377. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +30 -134
  378. package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +9 -50
  379. package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +49 -0
  380. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +13 -86
  381. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +20 -95
  382. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +46 -203
  383. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +8 -50
  384. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +12 -83
  385. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -39
  386. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -6
  387. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-outcome.js +4 -28
  388. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +6 -51
  389. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +6 -34
  390. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +23 -121
  391. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +110 -290
  392. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +15 -69
  393. package/.agents/scripts/lib/orchestration/story-body-gate.js +4 -23
  394. package/.agents/scripts/lib/orchestration/story-close/baseline-upward-writeback.js +50 -179
  395. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +11 -41
  396. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +23 -311
  397. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +22 -147
  398. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +16 -67
  399. package/.agents/scripts/lib/orchestration/story-deliver-terminal-schema.js +12 -68
  400. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +42 -201
  401. package/.agents/scripts/lib/orchestration/story-follow-ups.js +43 -245
  402. package/.agents/scripts/lib/orchestration/story-init-envelope.js +4 -23
  403. package/.agents/scripts/lib/orchestration/story-init-remote.js +2 -6
  404. package/.agents/scripts/lib/orchestration/story-reachability.js +3 -23
  405. package/.agents/scripts/lib/orchestration/task-body-validator.js +12 -126
  406. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -131
  407. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +15 -149
  408. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +5 -33
  409. package/.agents/scripts/lib/orchestration/ticket-validator.js +39 -181
  410. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +51 -244
  411. package/.agents/scripts/lib/orchestration/ticketing/reads.js +39 -250
  412. package/.agents/scripts/lib/orchestration/ticketing/state.js +14 -68
  413. package/.agents/scripts/lib/orchestration/ticketing/transition.js +46 -263
  414. package/.agents/scripts/lib/orchestration/ticketing.js +2 -26
  415. package/.agents/scripts/lib/orchestration/verify-credit.js +18 -79
  416. package/.agents/scripts/lib/orchestration/worktree-dirty.js +5 -30
  417. package/.agents/scripts/lib/path-security.js +3 -6
  418. package/.agents/scripts/lib/plan-phase-cleanup.js +9 -49
  419. package/.agents/scripts/lib/preflight-runner.js +13 -69
  420. package/.agents/scripts/lib/process-group.js +143 -0
  421. package/.agents/scripts/lib/project-root.js +2 -8
  422. package/.agents/scripts/lib/provider-factory.js +2 -25
  423. package/.agents/scripts/lib/qa/console-allowlist.js +10 -59
  424. package/.agents/scripts/lib/qa/qa-session.js +15 -69
  425. package/.agents/scripts/lib/qa/redact-evidence.js +18 -129
  426. package/.agents/scripts/lib/qa/resolve-qa-contract.js +26 -135
  427. package/.agents/scripts/lib/qa/resolve-selection.js +16 -84
  428. package/.agents/scripts/lib/reserved-test-ids.js +9 -47
  429. package/.agents/scripts/lib/runtime-deps/dep-resolution.js +13 -53
  430. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +10 -44
  431. package/.agents/scripts/lib/runtime-deps/manifest.js +10 -30
  432. package/.agents/scripts/lib/runtime-deps/parser-major.js +15 -51
  433. package/.agents/scripts/lib/runtime-deps/preflight.js +4 -22
  434. package/.agents/scripts/lib/runtime-deps/scan-imports.js +11 -55
  435. package/.agents/scripts/lib/signals/detectors/common.js +12 -38
  436. package/.agents/scripts/lib/signals/index.js +2 -19
  437. package/.agents/scripts/lib/signals/schema.js +14 -109
  438. package/.agents/scripts/lib/signals/write.js +2 -11
  439. package/.agents/scripts/lib/single-story/confirm-merge.js +31 -72
  440. package/.agents/scripts/lib/single-story/story-merged-notify.js +8 -49
  441. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +4 -26
  442. package/.agents/scripts/lib/single-story-sweep/protection.js +13 -100
  443. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +60 -258
  444. package/.agents/scripts/lib/single-story-sweep.js +14 -97
  445. package/.agents/scripts/lib/skills/parse-skill.js +13 -59
  446. package/.agents/scripts/lib/skills/skills-index.js +8 -29
  447. package/.agents/scripts/lib/skills/walk-skill-files.js +17 -78
  448. package/.agents/scripts/lib/source-extensions.js +11 -42
  449. package/.agents/scripts/lib/source-text/strip-js-comments.js +8 -41
  450. package/.agents/scripts/lib/stdio-flush.js +9 -36
  451. package/.agents/scripts/lib/story-adjacency.js +8 -40
  452. package/.agents/scripts/lib/story-body/body-format-lints.js +22 -83
  453. package/.agents/scripts/lib/story-body/footer-block.js +11 -44
  454. package/.agents/scripts/lib/story-body/story-body.js +83 -358
  455. package/.agents/scripts/lib/temp-retention.js +42 -143
  456. package/.agents/scripts/lib/templates/decomposer-prompts.js +11 -65
  457. package/.agents/scripts/lib/test-env.js +12 -65
  458. package/.agents/scripts/lib/test-run-credit.js +10 -65
  459. package/.agents/scripts/lib/test-runner-contract.js +19 -74
  460. package/.agents/scripts/lib/test-temp.js +38 -184
  461. package/.agents/scripts/lib/test-tiers.js +12 -96
  462. package/.agents/scripts/lib/ticket-body-sections.js +21 -96
  463. package/.agents/scripts/lib/transpile.js +15 -74
  464. package/.agents/scripts/lib/util/concurrent-map.js +6 -25
  465. package/.agents/scripts/lib/util/parse-id-list.js +8 -34
  466. package/.agents/scripts/lib/util/poll-loop.js +8 -28
  467. package/.agents/scripts/lib/util/with-timeout.js +2 -11
  468. package/.agents/scripts/lib/validation-evidence.js +21 -96
  469. package/.agents/scripts/lib/wave-runner/footprint.js +17 -70
  470. package/.agents/scripts/lib/wave-runner/live-probe.js +53 -221
  471. package/.agents/scripts/lib/wave-runner/ready-set.js +72 -301
  472. package/.agents/scripts/lib/workers/crap-worker.js +11 -64
  473. package/.agents/scripts/lib/workers/maintainability-report-worker.js +8 -45
  474. package/.agents/scripts/lib/workers/maintainability-worker.js +5 -21
  475. package/.agents/scripts/lib/workers/serve-worker-messages.js +2 -18
  476. package/.agents/scripts/lib/workflow-closure.js +22 -105
  477. package/.agents/scripts/lib/workspace-provisioner.js +16 -54
  478. package/.agents/scripts/lib/worktree/git-hooks.js +15 -60
  479. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +20 -60
  480. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +10 -62
  481. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +22 -86
  482. package/.agents/scripts/lib/worktree/lifecycle/reap.js +24 -126
  483. package/.agents/scripts/lib/worktree/lifecycle-manager.js +2 -20
  484. package/.agents/scripts/lib/worktree/node-modules-strategy.js +46 -180
  485. package/.agents/scripts/lib/worktree-manager.js +15 -40
  486. package/.agents/scripts/lint-issue-body.js +12 -68
  487. package/.agents/scripts/mandrel-update-preflight.js +10 -70
  488. package/.agents/scripts/merge-baseline.js +30 -104
  489. package/.agents/scripts/nav-registry-diff.js +22 -108
  490. package/.agents/scripts/notify.js +12 -69
  491. package/.agents/scripts/plan-context.js +27 -111
  492. package/.agents/scripts/plan-critics.js +12 -66
  493. package/.agents/scripts/plan-persist.js +31 -148
  494. package/.agents/scripts/plan-run-epilogue.js +11 -37
  495. package/.agents/scripts/pr-watch-with-update.js +76 -267
  496. package/.agents/scripts/providers/github/auth.js +3 -8
  497. package/.agents/scripts/providers/github/blocked-by-add.js +14 -61
  498. package/.agents/scripts/providers/github/board-add.js +5 -21
  499. package/.agents/scripts/providers/github/branch-protection.js +11 -43
  500. package/.agents/scripts/providers/github/cache.js +2 -14
  501. package/.agents/scripts/providers/github/comments.js +7 -40
  502. package/.agents/scripts/providers/github/compose.js +3 -19
  503. package/.agents/scripts/providers/github/errors.js +22 -125
  504. package/.agents/scripts/providers/github/issues.js +31 -123
  505. package/.agents/scripts/providers/github/labels.js +24 -124
  506. package/.agents/scripts/providers/github/mappers.js +5 -27
  507. package/.agents/scripts/providers/github/merge-methods.js +3 -22
  508. package/.agents/scripts/providers/github/project-board.js +2 -17
  509. package/.agents/scripts/providers/github/projects-v2-graphql.js +3 -6
  510. package/.agents/scripts/providers/github/request-helpers.js +5 -32
  511. package/.agents/scripts/providers/github/search-budget.js +9 -39
  512. package/.agents/scripts/providers/github/search-query.js +5 -26
  513. package/.agents/scripts/providers/github/sub-issue-add.js +15 -65
  514. package/.agents/scripts/providers/github/sub-issues.js +7 -36
  515. package/.agents/scripts/providers/github/tickets.js +32 -134
  516. package/.agents/scripts/providers/github.js +18 -59
  517. package/.agents/scripts/prune-plan-run-labels.js +12 -46
  518. package/.agents/scripts/quality-preview.js +35 -220
  519. package/.agents/scripts/resolve-doc-tiers.js +3 -26
  520. package/.agents/scripts/resolve-stories.js +16 -67
  521. package/.agents/scripts/resync-status-column.js +6 -25
  522. package/.agents/scripts/run-tests.js +29 -104
  523. package/.agents/scripts/single-story-close.js +23 -135
  524. package/.agents/scripts/single-story-confirm-merge.js +32 -139
  525. package/.agents/scripts/single-story-init.js +47 -209
  526. package/.agents/scripts/stories-wave-tick.js +102 -472
  527. package/.agents/scripts/sync-agentrc.js +3 -21
  528. package/.agents/scripts/sync-claude-agents.js +9 -55
  529. package/.agents/scripts/sync-claude-commands.js +20 -110
  530. package/.agents/scripts/test-wrapper.js +6 -43
  531. package/.agents/scripts/update-coverage-baseline.js +5 -22
  532. package/.agents/scripts/update-crap-baseline.js +7 -30
  533. package/.agents/scripts/update-duplication-baseline.js +16 -92
  534. package/.agents/scripts/update-maintainability-baseline.js +8 -62
  535. package/.agents/scripts/update-ticket-state.js +1 -7
  536. package/.agents/scripts/validate-skills.js +6 -26
  537. package/.agents/templates/agent-protocol.md +2 -2
  538. package/.agents/templates/docs/audit-sweep-runbook.md +3 -4
  539. package/.agents/workflows/audit-accessibility.md +4 -7
  540. package/.agents/workflows/audit-adrs.md +3 -6
  541. package/.agents/workflows/audit-architecture.md +3 -11
  542. package/.agents/workflows/audit-baselines.md +6 -9
  543. package/.agents/workflows/audit-clean-code.md +8 -12
  544. package/.agents/workflows/audit-data-model.md +3 -7
  545. package/.agents/workflows/audit-dependencies.md +4 -7
  546. package/.agents/workflows/audit-devops.md +4 -8
  547. package/.agents/workflows/audit-documentation.md +1 -4
  548. package/.agents/workflows/audit-mobile.md +4 -7
  549. package/.agents/workflows/audit-navigability.md +3 -6
  550. package/.agents/workflows/audit-performance.md +2 -4
  551. package/.agents/workflows/audit-privacy.md +4 -7
  552. package/.agents/workflows/audit-quality.md +4 -7
  553. package/.agents/workflows/audit-security.md +4 -7
  554. package/.agents/workflows/audit-seo.md +4 -7
  555. package/.agents/workflows/audit-sre.md +4 -7
  556. package/.agents/workflows/audit-to-stories.md +17 -30
  557. package/.agents/workflows/audit-ux-ui.md +4 -7
  558. package/.agents/workflows/helpers/audit-lens-core.md +1 -1
  559. package/.agents/workflows/helpers/code-quality-guardrails.md +12 -15
  560. package/.agents/workflows/helpers/deliver-digest.md +6 -4
  561. package/.agents/workflows/helpers/deliver-reference.md +114 -188
  562. package/.agents/workflows/helpers/deliver-story-reference.md +189 -533
  563. package/.agents/workflows/helpers/plan-reference.md +86 -139
  564. package/.agents/workflows/helpers/qa-core.md +12 -0
  565. package/.agents/workflows/memory-consolidate.md +1 -1
  566. package/.agents/workflows/qa-assist.md +27 -71
  567. package/.agents/workflows/qa-explore.md +19 -59
  568. package/.agents/workflows/qa-run.md +11 -33
  569. package/README.md +1 -1
  570. package/bin/mandrel.js +5 -46
  571. package/bin/postinstall.js +18 -107
  572. package/docs/CHANGELOG.md +32 -0
  573. package/lib/cli/doctor.js +11 -71
  574. package/lib/cli/init.js +26 -165
  575. package/lib/cli/migrate.js +5 -64
  576. package/lib/cli/registry.js +84 -467
  577. package/lib/cli/sync-agents.js +4 -64
  578. package/lib/cli/sync-commands.js +11 -64
  579. package/lib/cli/sync.js +34 -213
  580. package/lib/cli/uninstall.js +40 -219
  581. package/lib/cli/update.js +105 -661
  582. package/lib/cli/version-check.js +8 -72
  583. package/lib/cli/version-helpers.js +14 -87
  584. package/lib/migrations/helpers/retire-agentrc-key.js +11 -52
  585. package/lib/migrations/index.js +15 -92
  586. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +2 -15
  587. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +1 -13
  588. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +2 -15
  589. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +9 -38
  590. package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +1 -13
  591. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +2 -27
  592. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +2 -23
  593. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +2 -27
  594. package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +2 -21
  595. package/lib/migrations/steps/strip-removed-agentrc-keys.js +333 -0
  596. package/package.json +16 -14
  597. package/.agents/docs/SDLC.md +0 -590
  598. package/.agents/docs/quality-gates.md +0 -1183
  599. package/.agents/rules/known-tooling-behavior.md +0 -200
  600. package/.agents/rules/orchestration-error-handling.md +0 -61
  601. package/.agents/rules/test-seams.md +0 -59
  602. package/.agents/schemas/baselines/lighthouse.schema.json +0 -59
  603. package/.agents/schemas/baselines/lint.schema.json +0 -47
  604. package/.agents/scripts/check-action-pinning.js +0 -260
  605. package/.agents/scripts/check-audit-attribution.js +0 -302
  606. package/.agents/scripts/check-baseline-drift.js +0 -211
  607. package/.agents/scripts/check-baseline-scope.js +0 -362
  608. package/.agents/scripts/check-generated-validator.js +0 -202
  609. package/.agents/scripts/check-knip-entries.js +0 -159
  610. package/.agents/scripts/check-lifecycle-lint.js +0 -294
  611. package/.agents/scripts/check-pinned-override-notes.js +0 -102
  612. package/.agents/scripts/check-schema-references.js +0 -368
  613. package/.agents/scripts/check-test-portability.js +0 -512
  614. package/.agents/scripts/check-workflow-citations.js +0 -218
  615. package/.agents/scripts/check-workflow-cli-lint.js +0 -299
  616. package/.agents/scripts/check-workflow-timeouts.js +0 -291
  617. package/.agents/scripts/install-matrix-assert.js +0 -326
  618. package/.agents/scripts/lib/audit-advisories.js +0 -195
  619. package/.agents/scripts/lib/audit-attribution.js +0 -134
  620. package/.agents/scripts/lib/baselines/drift-detector.js +0 -351
  621. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +0 -87
  622. package/.agents/scripts/lib/baselines/kinds/lint.js +0 -184
  623. package/.agents/scripts/lib/baselines/orphan-pruner.js +0 -233
  624. package/.agents/scripts/lib/baselines/scope-assert.js +0 -223
  625. package/.agents/scripts/lib/baselines/scope-inventory.js +0 -314
  626. package/.agents/scripts/lib/c8-cli-path.js +0 -21
  627. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +0 -51
  628. package/.agents/scripts/lib/config/gates/lint.schema.js +0 -18
  629. package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +0 -70
  630. package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +0 -284
  631. package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +0 -80
  632. package/.agents/scripts/lib/dynamic-workflow/degraded-coverage.js +0 -81
  633. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +0 -87
  634. package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +0 -74
  635. package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +0 -90
  636. package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +0 -43
  637. package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +0 -83
  638. package/.agents/scripts/lib/fs-walk.js +0 -52
  639. package/.agents/scripts/lib/knip-config-resolver.js +0 -181
  640. package/.agents/scripts/lib/knip-entry-sync.js +0 -452
  641. package/.agents/scripts/lib/pinned-override-notes.js +0 -88
  642. package/.agents/scripts/lib/pinned-override-resolve.js +0 -212
  643. package/.agents/scripts/lib/test-isolate/cli-options.js +0 -93
  644. package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +0 -52
  645. package/.agents/scripts/lib/test-isolate/list-files.js +0 -90
  646. package/.agents/scripts/lib/test-isolate/parse-tap.js +0 -75
  647. package/.agents/scripts/lib/test-isolate/progress-log.js +0 -45
  648. package/.agents/scripts/lib/test-isolate/render-report.js +0 -97
  649. package/.agents/scripts/lib/test-isolate/run-isolate.js +0 -87
  650. package/.agents/scripts/lib/test-isolate/runner.js +0 -483
  651. package/.agents/scripts/lib/test-profile/parse-tap.js +0 -136
  652. package/.agents/scripts/lib/test-profile/render-report.js +0 -45
  653. package/.agents/scripts/lint-label-vocabulary.js +0 -214
  654. package/.agents/scripts/post-structured-comment.js +0 -127
  655. package/.agents/scripts/provision-git-hooks.js +0 -85
  656. package/.agents/scripts/prune-baseline-orphans.js +0 -181
  657. package/.agents/scripts/run-coverage.js +0 -197
  658. package/.agents/scripts/run-lint.js +0 -133
  659. package/.agents/scripts/run-test-profile.js +0 -129
  660. package/.agents/scripts/run-verify.js +0 -125
  661. package/.agents/scripts/test-isolate.js +0 -55
  662. package/.agents/scripts/update-dead-exports-baseline.js +0 -321
@@ -1,93 +1,21 @@
1
1
  /**
2
- * phases/confirm-merge.js — the close-and-land merge wait (Story #4428,
3
- * reworked into a resumable, checks-aware wait by Story #4543).
2
+ * phases/confirm-merge.js — the close-and-land merge wait, the default
3
+ * terminal step of every close (`--no-wait-merge` opts out).
4
4
  *
5
- * This is the **default terminal step for every run** — attended and
6
- * headless alike — because `waitForMerge` defaults from
7
- * `delivery.routing.closeAndLand` (`true`); `--no-wait-merge` is the opt-out,
8
- * and a PR the operator deliberately left un-armed (`--no-auto-merge` /
9
- * `autoMerge: "strict"`) resolves to no-wait and rests at `agent::closing`
10
- * for the human.
5
+ * Two timing domains: `maxWaitSeconds` bounds THIS invocation (it must fit
6
+ * the host's ~10-min tool ceiling after the close gates) and expires to a
7
+ * resumable `pending` with no mutation; `maxBudgetSeconds` bounds the
8
+ * cumulative wait, anchored at the PR's `createdAt` so resumes don't restart
9
+ * it, and exhausting it is the real give-up (classify, emit, block). The
10
+ * poll is stateless and re-entrant. Async mode only shortens the
11
+ * per-invocation window. Checks are probed every iteration so a red required
12
+ * check fails fast instead of burning the budget.
11
13
  *
12
- * ## The timing model (Story #4543 — the load-bearing design decision)
13
- *
14
- * The original wait polled a single budget: `maxBudgetSeconds`, one hour.
15
- * The host caps a single tool invocation at ~10 minutes, and the close gates
16
- * burn minutes of that before the wait even starts. So a close-and-land
17
- * whose CI took longer than roughly eight minutes was **killed mid-poll**
18
- * with no terminal path taken — no `merge.unlanded` event, no `agent::blocked`
19
- * flip, the Story parked at `agent::closing`: precisely the strand the
20
- * must-land contract exists to eliminate.
21
- *
22
- * The fix splits the two timing domains that were conflated:
23
- *
24
- * - **`maxWaitSeconds`** bounds THIS invocation (default 300s, comfortably
25
- * inside the host ceiling). On expiry the wait returns
26
- * `terminal: 'pending'` — **no label mutation, no `merge.unlanded`
27
- * event** — and the caller surfaces a resumable terminal with its own
28
- * exit code. Merely shrinking `maxBudgetSeconds` instead would have been
29
- * wrong: that path conflates slow CI with a hard block, so most runs
30
- * would have been misfiled as blocked.
31
- * - **`maxBudgetSeconds`** bounds the CUMULATIVE wait, anchored at the
32
- * PR's `createdAt` rather than this invocation's start, so resumes do not
33
- * restart the clock. Exhausting it is the genuine give-up: classify,
34
- * emit, block. `agent::blocked` stays reserved for hard blocks.
35
- *
36
- * Backgrounding is not a workaround here and does not need to be: an
37
- * interrupted poll is stateless and re-entrant by construction.
38
- *
39
- * ## Async mode (Story #4698 — a designed short probe window, not an accident)
40
- *
41
- * `maxWaitSeconds` (default 300s) still routinely EXPIRES on a slow-CI
42
- * consumer: the median PR-create→merge time can be minutes, so nearly every
43
- * close burns its whole foreground slot polling and then returns `pending`
44
- * anyway. `delivery.mergeWatch.mode: "async"` makes that async confirm a
45
- * designed mode rather than an expiry accident. It caps the per-invocation
46
- * wait to a short probe window (`ASYNC_PROBE_WINDOW_SECONDS`, ~60s) — long
47
- * enough for the loop's existing checks to catch an instant merge and, via the
48
- * imported {@link decideMergeWaitFailFast} decision (Story #4695/#4710), an
49
- * instantly-red required check — then returns the SAME resumable `pending`
50
- * terminal, whose `nextCommand` the worker launches in the background. Nothing
51
- * else changes: the cumulative `maxBudgetSeconds` anchor is untouched, and
52
- * `sync` mode (the default) is byte-compatible. An explicit `--max-wait-seconds`
53
- * override wins over the async cap so a headless caller can still land in one
54
- * block. The clamp lives entirely in `resolveMergeWaitConfig`; the poll loop is
55
- * mode-agnostic.
56
- *
57
- * ## The wait is not weaker than the watch it displaced
58
- *
59
- * The pre-#4543 poll read only `state` / `mergedAt`. A check that went red
60
- * at minute one therefore burned the full hour and then classified as
61
- * `branch-protection-human-required` (the exhaustion probe sees
62
- * `mergeStateStatus: BLOCKED` with checks settled) — sending the operator to
63
- * diagnose branch protection instead of their red check. This wait probes the
64
- * checks every iteration: it fails fast on `checks-failed`, and runs a
65
- * bounded `gh pr update-branch` on a BEHIND PR instead of waiting out the
66
- * budget behind a base it could have caught up to.
67
- *
68
- * The per-iteration `provider.getTicket` is also gone. It was re-fetched
69
- * every poll for an idempotence check whose answer cannot change mid-poll —
70
- * ~240 reads per Story per hour. The loop now probes the PR only, and calls
71
- * the shared `confirmStoryMerged` exactly once, after a merge is observed.
72
- *
73
- * Terminal outcomes:
74
- * - `{ confirmed: true, action, tail }` — the PR merged; `confirmStoryMerged`
75
- * flipped `agent::closing → agent::done` and closed the issue, and the
76
- * shared post-land tail ran.
77
- * - `{ confirmed: false, terminal: 'pending', waitBudget }` — this
78
- * invocation's bound expired with the PR still in flight. Resumable;
79
- * nothing was mutated.
80
- * - `{ confirmed: false, terminal: 'blocked', blockClass, reason }` — the
81
- * arm failed outright, the PR closed without merging, a required check
82
- * went red, or the cumulative budget was exhausted. Classified via the
83
- * shared `classifyMergeBlock`, emitted as `merge.unlanded`, friction
84
- * posted, Story transitioned to `agent::blocked`.
85
- * - `{ confirmed: false, terminal: 'blocked', blockClass: 'merged-flip-failed' }`
86
- * — the PR merged but the `agent::done` label write failed. Its own
87
- * `merge.flip-failed` event and friction wording (Story #4539): the merge
88
- * landed, so attributing it to an unlanded merge would send the operator
89
- * to diagnose branch protection instead of re-running the idempotent
90
- * confirm.
14
+ * Terminals: `landed` (confirmed, post-land tail ran); `pending` (nothing
15
+ * mutated); `blocked` (classified, `merge.unlanded` emitted, friction posted,
16
+ * Story → `agent::blocked`); `blocked`/`merged-flip-failed` (merged but the
17
+ * `agent::done` write failed — its own event and wording, since the merge
18
+ * is not in question).
91
19
  */
92
20
 
93
21
  import { getCiDelivery } from '../../../config/ci.js';
@@ -98,6 +26,7 @@ import {
98
26
  } from '../../../single-story/confirm-merge.js';
99
27
  import { pollUntil } from '../../../util/poll-loop.js';
100
28
  import { applyBehindUpdate } from '../../behind-recovery.js';
29
+ import { isRerunPermitted } from '../../check-state.js';
101
30
  import {
102
31
  emitMergeFlipFailed as defaultEmitMergeFlipFailed,
103
32
  MERGED_FLIP_FAILED_BLOCK_CLASS,
@@ -114,11 +43,13 @@ import {
114
43
  deriveChecksStatus,
115
44
  deriveRedHeadRuns,
116
45
  deriveRequiredRunEvidence,
46
+ isPrMerged,
117
47
  MERGE_WAIT_GH_TIMEOUT_MS,
118
48
  parseWorkflowRunId,
119
49
  readRunSummary,
120
50
  resolveAdvisoryGateVerdict,
121
51
  } from '../../merge-poll.js';
52
+ import { readMergeQueueState } from '../../merge-queue.js';
122
53
  import { NEXT_COMMANDS } from '../../story-deliver-terminal.js';
123
54
  import {
124
55
  postStructuredComment,
@@ -128,77 +59,38 @@ import {
128
59
  import { disarmAutoMerge } from './auto-merge.js';
129
60
  import { runPostLandTail as defaultRunPostLandTail } from './post-land.js';
130
61
 
131
- /**
132
- * Per-invocation merge-wait bound. 300s fits inside a single host tool
133
- * invocation (~10 min ceiling) with room for the close gates that precede
134
- * the wait. A headless caller with no such ceiling raises
135
- * `delivery.mergeWatch.maxWaitSeconds` to keep single-block semantics.
136
- */
62
+ /** Per-invocation bound; fits the host's ~10-min tool ceiling after the gates. */
137
63
  export const DEFAULT_MAX_WAIT_SECONDS = 300;
138
64
 
139
- /**
140
- * Async-mode per-invocation probe window (Story #4698). When
141
- * `delivery.mergeWatch.mode` is `"async"`, `resolveMergeWaitConfig` caps the
142
- * per-invocation wait to this many seconds so close returns the resumable
143
- * `pending` terminal fast instead of burning the foreground host slot. Sized
144
- * to catch an instant merge and — via the head-anchored required-check
145
- * predicate — an instantly-red required check, while staying far inside the
146
- * cumulative `maxBudgetSeconds` give-up bound.
147
- */
65
+ /** Async-mode per-invocation cap: long enough to catch an instant merge or red check. */
148
66
  export const ASYNC_PROBE_WINDOW_SECONDS = 60;
149
67
 
150
- /** Bounded `gh pr update-branch` attempts for a BEHIND PR. */
151
68
  export const DEFAULT_UPDATE_ATTEMPTS = 3;
152
69
 
153
70
  /**
154
- * Minimum polls before the CUMULATIVE budget may block.
155
- *
156
- * The cumulative clock is anchored at the PR's `createdAt` so resumes do not
157
- * restart it — but that alone means a PR older than `maxBudgetSeconds` (1h by
158
- * default) is already over budget on its very first probe. Resuming a Story
159
- * the next morning, or landing a long-open PR, would then flip
160
- * `agent::blocked` and emit `merge.unlanded` against a perfectly healthy PR
161
- * that was seconds from merging, without ever having waited.
162
- *
163
- * The floor gives every invocation at least one real poll cycle before the
164
- * cumulative bound can fire. A genuinely stuck PR still blocks within one
165
- * interval (~30s), so the give-up bound keeps its meaning; a PR about to go
166
- * green gets the chance it earned.
71
+ * Minimum polls before the cumulative budget may block: the clock is anchored
72
+ * at `createdAt`, so a long-open PR is over budget on its first probe and
73
+ * would otherwise block without ever having waited.
167
74
  */
168
75
  export const MIN_POLLS_BEFORE_BUDGET_BLOCK = 2;
169
76
 
77
+ /** Verdict for a PR closed without merging, decided by the wait itself. */
78
+ const CLOSED_UNMERGED_BLOCK_CLASS = 'api-race-other';
79
+ const CLOSED_UNMERGED_REASON =
80
+ 'PR probe error: PR closed without merging (state=CLOSED)';
81
+
170
82
  function defaultSleep(ms) {
171
83
  return new Promise((resolve) => setTimeout(resolve, ms));
172
84
  }
173
85
 
174
- /**
175
- * The wait's default `gh` facade, bound to a spawn-level timeout (Story
176
- * #4710): every subprocess the wait launches through it carries
177
- * `MERGE_WAIT_GH_TIMEOUT_MS`, so a wedged `gh` child is killed rather than
178
- * stranding an unattended async-mode wait forever. Callers that inject their
179
- * own `gh` (tests, the resume CLI) are bounded by {@link withGhTimeout} at
180
- * the call sites instead.
181
- */
86
+ /** Spawn-level timeout kills a wedged `gh` child; injected `gh`s are bounded by withGhTimeout. */
182
87
  const defaultGh = createGh(undefined, { timeoutMs: MERGE_WAIT_GH_TIMEOUT_MS });
183
88
 
184
89
  /**
185
- * Bound an arbitrary `gh` call with a wall-clock timeout (Story #4710). The
186
- * spawn-level `timeoutMs` on {@link defaultGh} already kills a wedged real
187
- * subprocess, but an injected `gh` implementation (a test stub, a facade
188
- * built without defaults) can still return a promise that never settles —
189
- * and the merge wait must never hang on any of them. Rejection maps to the
190
- * caller's existing error handling: the probe degrades to its conservative
191
- * pending shape, the update-branch attempt logs and continues.
192
- *
193
- * A late settlement of the losing promise is explicitly absorbed so a
194
- * post-timeout rejection cannot surface as an unhandled rejection.
195
- *
196
- * The timeout timer is deliberately NOT `unref`'d: when the awaited call is a
197
- * promise that never settles (a hung stub, or a real gh child whose I/O has
198
- * gone quiet), the timer is the ONLY handle keeping the event loop alive, so
199
- * unref'ing it would let the process/test exit before the timeout ever fires —
200
- * exactly the hang this guard exists to prevent. It is short-lived and always
201
- * cleared in `finally`, so keeping it referenced costs nothing.
90
+ * Wall-clock bound for any `gh` call, including injected ones that may never
91
+ * settle. The losing promise's late rejection is absorbed. The timer is NOT
92
+ * unref'd: for a never-settling call it is the only handle keeping the event
93
+ * loop alive until the timeout fires.
202
94
  */
203
95
  function withGhTimeout(promise, timeoutMs, label) {
204
96
  let timer;
@@ -215,19 +107,11 @@ function withGhTimeout(promise, timeoutMs, label) {
215
107
  }
216
108
 
217
109
  /**
218
- * One string field off a `gh pr view` payload, or `absent` when the API did
219
- * not return one. Deduplicated out of the probe below (Story #5266): six
220
- * identical `typeof x === 'string'` ternaries put that one function over the
221
- * CRAP ratchet the moment a seventh field was needed.
222
- *
223
- * An empty string counts as absent — `gh` returns `""` for a field it cannot
224
- * read, and every caller treats that exactly as "not there".
110
+ * A string field off a `gh pr view` payload; `""` (gh's unreadable field)
111
+ * counts as absent.
225
112
  *
226
113
  * @param {unknown} value
227
- * @param {null|undefined} [absent] What to report when the field is missing.
228
- * `null` for the three fields the poll loop compares against null; the
229
- * `undefined` default for the ones whose absence must not shadow a
230
- * downstream default.
114
+ * @param {null|undefined} [absent] `undefined` where absence must not shadow a downstream default.
231
115
  * @returns {string|null|undefined}
232
116
  */
233
117
  function readString(value, absent = undefined) {
@@ -235,17 +119,32 @@ function readString(value, absent = undefined) {
235
119
  }
236
120
 
237
121
  /**
238
- * One probe per poll iteration, carrying every field the loop and the
239
- * terminal classifier need: merge state, the checks rollup, the merge-state
240
- * status (for BEHIND recovery and human-required classification), and
241
- * `createdAt` (the cumulative-budget anchor).
242
- *
243
- * Returns a degraded `{ checksStatus: 'pending', error }` probe when the read
244
- * itself fails, preserving the conservative classification on probe errors —
245
- * a flaky API read must not be mistaken for a definitive verdict. A probe
246
- * that exceeds `ghTimeoutMs` (Story #4710) takes the SAME degraded path: a
247
- * hung subprocess must surface as a probe error within the bound, never
248
- * strand the wait.
122
+ * `inMergeQueue` for a green open PR, the only state GitHub enqueues from;
123
+ * `undefined` otherwise, so the common poll pays no extra call. An enqueued
124
+ * PR has no auto-merge request and can read BLOCKED — neither is a disarm.
125
+ */
126
+ async function readInMergeQueue({
127
+ view,
128
+ checksStatus,
129
+ gh,
130
+ ghTimeoutMs,
131
+ readMergeQueueStateFn,
132
+ }) {
133
+ const prNodeId = readString(view?.id);
134
+ if (view?.state !== 'OPEN' || checksStatus !== 'success' || !prNodeId) {
135
+ return undefined;
136
+ }
137
+ const queue = await readMergeQueueStateFn({
138
+ prNodeId,
139
+ gh,
140
+ timeoutMs: ghTimeoutMs,
141
+ });
142
+ return typeof queue?.inQueue === 'boolean' ? queue.inQueue : undefined;
143
+ }
144
+
145
+ /**
146
+ * One probe per poll iteration. A failed or timed-out read degrades to
147
+ * `{ checksStatus: 'pending', error }` so a flaky read is never a verdict.
249
148
  *
250
149
  * @returns {Promise<object>}
251
150
  */
@@ -253,38 +152,41 @@ export async function readPrWaitProbe({
253
152
  prNumber,
254
153
  gh = defaultGh,
255
154
  ghTimeoutMs = MERGE_WAIT_GH_TIMEOUT_MS,
155
+ readMergeQueueStateFn = readMergeQueueState,
256
156
  }) {
257
157
  try {
258
158
  const view = await withGhTimeout(
259
159
  gh.pr.view(prNumber, [
160
+ 'id',
260
161
  'state',
261
162
  'mergedAt',
262
163
  'createdAt',
263
164
  'mergeStateStatus',
264
165
  'reviewDecision',
265
166
  'statusCheckRollup',
266
- // Story #5266 — the head the red advisory runs belong to, so their
267
- // check-run output can be read back and classified.
268
167
  'headRefOid',
269
168
  ]),
270
169
  ghTimeoutMs,
271
170
  `gh pr view ${prNumber}`,
272
171
  );
172
+ const checksStatus = deriveChecksStatus(view?.statusCheckRollup);
273
173
  return {
274
174
  state: readString(view?.state, null),
275
175
  mergedAt: readString(view?.mergedAt, null),
276
176
  createdAt: readString(view?.createdAt, null),
277
177
  mergeStateStatus: readString(view?.mergeStateStatus),
278
178
  reviewDecision: readString(view?.reviewDecision),
279
- checksStatus: deriveChecksStatus(view?.statusCheckRollup),
280
- // Head-anchored per-run evidence (Story #4695): distinguishes a
281
- // genuinely red required run from the superseded / still-pending noise
282
- // the aggregate `checksStatus` folds together. `null` when the rollup is
283
- // absent/empty — the loop's consecutive-probe fallback owns that path.
179
+ checksStatus,
180
+ inMergeQueue: await readInMergeQueue({
181
+ view,
182
+ checksStatus,
183
+ gh,
184
+ ghTimeoutMs,
185
+ readMergeQueueStateFn,
186
+ }),
187
+ // Head-anchored, so superseded/pending runs don't read as red; `null`
188
+ // when the rollup is empty (the consecutive-probe fallback owns that).
284
189
  requiredRunEvidence: deriveRequiredRunEvidence(view?.statusCheckRollup),
285
- // The named red head runs (Story #5096), so an `advisory-gate-red`
286
- // verdict can name the offending job and match the allowlist. Same
287
- // red-ness test as `requiredRunEvidence`, so the two cannot disagree.
288
190
  redHeadRuns: deriveRedHeadRuns(view?.statusCheckRollup),
289
191
  headSha: readString(view?.headRefOid),
290
192
  };
@@ -301,31 +203,11 @@ export async function readPrWaitProbe({
301
203
  }
302
204
 
303
205
  /**
304
- * Resolve the wait cadence and both budgets from `delivery.mergeWatch.*`,
305
- * falling back to the framework defaults when a key is absent or invalid.
306
- *
307
- * `maxWaitSecondsOverride` is the per-run `--max-wait-seconds` flag and wins
308
- * over the config: a headless caller with no host tool-invocation ceiling
309
- * raises the per-invocation bound to keep single-block semantics without
310
- * editing the consumer's config.
311
- *
312
- * `mode` (Story #4698) selects the close-time merge posture. `async` caps the
313
- * per-invocation wait to `ASYNC_PROBE_WINDOW_SECONDS` so close returns the
314
- * resumable `pending` terminal fast; `sync` (the default) is unchanged. An
315
- * explicit `maxWaitSecondsOverride` still wins over the async cap — a headless
316
- * caller with no host ceiling opts back into single-block waiting.
317
- *
318
- * `modeOverride` is the per-invocation `--merge-watch-mode` flag (Story #4949)
319
- * and wins over `delivery.mergeWatch.mode` on exactly the precedence
320
- * `maxWaitSecondsOverride` already uses. It exists because run topology is
321
- * knowable only to the orchestrator: close sees one Story and cannot tell a
322
- * solo delivery (where a foreground wait is the cheapest ending) from the Nth
323
- * close of a wave (where each foreground wait is serialized dead time). The
324
- * config default therefore stays `sync`, and the caller that knows better says
325
- * so per invocation. The two flags remain composable — `--merge-watch-mode
326
- * async --max-wait-seconds 900` selects the async posture and then overrides
327
- * its probe cap, because the cap check below keys on the override's presence,
328
- * not on where the mode came from.
206
+ * Resolve posture and budgets from `delivery.mergeWatch.*`. Both overrides
207
+ * (`--max-wait-seconds`, `--merge-watch-mode`) win over config; the mode flag
208
+ * exists because only the orchestrator knows whether a foreground wait is
209
+ * cheap (solo) or serialized dead time (a wave). An explicit max-wait
210
+ * override also beats the async cap.
329
211
  *
330
212
  * @param {object} [config]
331
213
  * @param {number} [maxWaitSecondsOverride]
@@ -338,37 +220,21 @@ export function resolveMergeWaitConfig(
338
220
  modeOverride,
339
221
  ) {
340
222
  const mergeWatch = config?.delivery?.mergeWatch ?? {};
341
- const int = (value, fallback, min = 1) =>
342
- Number.isInteger(value) && value >= min ? value : fallback;
223
+ const int = (value, fallback) =>
224
+ Number.isInteger(value) && value >= 1 ? value : fallback;
343
225
  const requestedMode = modeOverride ?? mergeWatch.mode;
344
226
  const mode = requestedMode === 'async' ? 'async' : 'sync';
345
227
  const configuredMaxWait = int(
346
228
  maxWaitSecondsOverride,
347
229
  int(mergeWatch.maxWaitSeconds, DEFAULT_MAX_WAIT_SECONDS),
348
230
  );
349
- // Async mode caps the per-invocation wait to a short probe window so close
350
- // returns `pending` fast instead of burning the foreground host slot on a
351
- // merge that lands after the wait would have expired anyway. The window is
352
- // long enough for the loop's existing checks to catch an instant merge and —
353
- // via the imported `decideMergeWaitFailFast` decision — an instantly
354
- // red required check. An explicit `--max-wait-seconds` override still wins so
355
- // a headless caller with no host ceiling opts back into single-block waiting.
356
231
  const maxWaitSeconds =
357
232
  mode === 'async' && maxWaitSecondsOverride == null
358
233
  ? Math.min(configuredMaxWait, ASYNC_PROBE_WINDOW_SECONDS)
359
234
  : configuredMaxWait;
360
- // A poll interval longer than the wait bound is incoherent, and silently
361
- // harmful: the pending check would fire on poll 1 every time, so the wait
362
- // could never sleep, `polls` could never reach
363
- // MIN_POLLS_BEFORE_BUDGET_BLOCK, and the cumulative budget would become
364
- // unreachable across ANY number of resumes — a Story stuck in permanent
365
- // `pending` that never escalates. Clamping the interval to the bound keeps
366
- // at least one real poll cycle possible, which is what both the floor and
367
- // the give-up bound depend on.
368
- const intervalSeconds = Math.min(
369
- int(mergeWatch.intervalSeconds, DEFAULT_INTERVAL_SECONDS),
370
- maxWaitSeconds,
371
- );
235
+ // An interval longer than the bound would expire every invocation on poll
236
+ // 1, so MIN_POLLS_BEFORE_BUDGET_BLOCK and the budget could never be reached.
237
+ const intervalSeconds = Math.min(DEFAULT_INTERVAL_SECONDS, maxWaitSeconds);
372
238
  return {
373
239
  mode,
374
240
  intervalSeconds,
@@ -377,16 +243,13 @@ export function resolveMergeWaitConfig(
377
243
  mergeWatch.maxBudgetSeconds,
378
244
  DEFAULT_MAX_BUDGET_SECONDS,
379
245
  ),
380
- updateAttempts: int(mergeWatch.updateAttempts, DEFAULT_UPDATE_ATTEMPTS, 0),
246
+ updateAttempts: DEFAULT_UPDATE_ATTEMPTS,
381
247
  };
382
248
  }
383
249
 
384
250
  /**
385
- * Anchor the cumulative budget at the PR's `createdAt` so a resumed wait
386
- * does not restart the clock. Falls back to this invocation's start when the
387
- * probe carried no timestamp — a conservative degrade: the worst case is a
388
- * resume getting a fresh cumulative budget, which is exactly the pre-#4543
389
- * behaviour, never a premature block.
251
+ * Budget anchor; without `createdAt` it falls back to this invocation's start
252
+ * (worst case a fresh budget, never a premature block).
390
253
  *
391
254
  * @returns {number} epoch ms
392
255
  */
@@ -397,22 +260,9 @@ export function resolveBudgetAnchorMs({ createdAt, fallbackMs }) {
397
260
  }
398
261
 
399
262
  /**
400
- * The advisory-gate half of {@link unlandedRemedy} (Story #5279).
401
- *
402
- * Both advisory classes used to fall through to the generic remedy, which
403
- * opens by telling the operator to resolve "branch protection, required
404
- * checks, or a manual merge" — a diagnosis of a fault that provably does not
405
- * exist here. An advisory gate blocks precisely BECAUSE GitHub reports the PR
406
- * mergeable (`mergeStateStatus=UNSTABLE`) over a NON-required red check, so
407
- * close refused to let native auto-merge land it. Saying so is the paragraph
408
- * that stops the operator hunting a protection rule that is working fine.
409
- *
410
- * The two classes then part on what the evidence authorises. `inconclusive`
411
- * is a job that failed WITHOUT FINISHING and reported no violation — nothing
412
- * says the change is bad — so the proportionate act is a re-run, named here
413
- * as `--rerun-advisory`; the permanent global exemption is deliberately
414
- * mentioned last. `advisory-gate-red` reported a real violation, so the
415
- * change is implicated and landing over it is a deliberate override.
263
+ * Advisory-gate remedy: the PR is mergeable (UNSTABLE over a non-required
264
+ * red), so branch protection is not the fault. `inconclusive` never finished
265
+ * and implicates nothing (re-run first); `red` reported a real violation.
416
266
  *
417
267
  * @param {{ storyId: number, blockClass: string }} args
418
268
  * @returns {string}
@@ -444,10 +294,6 @@ function advisoryGateRemedy({ storyId, blockClass }) {
444
294
  }
445
295
 
446
296
  /**
447
- * The class-specific remediation paragraph of the unlanded friction comment.
448
- * Split out of {@link formatUnlandedFriction} so each class's wording is one
449
- * named branch rather than a nested ternary.
450
- *
451
297
  * @param {{ storyId: number, prNumber: number|null, blockClass: string }} args
452
298
  * @returns {string}
453
299
  */
@@ -473,10 +319,6 @@ function unlandedRemedy({ storyId, prNumber, blockClass }) {
473
319
  );
474
320
  }
475
321
 
476
- /**
477
- * Format the `friction` comment body posted alongside the `agent::blocked`
478
- * transition when a landing attempt gives up without a confirmed merge.
479
- */
480
322
  function formatUnlandedFriction({
481
323
  storyId,
482
324
  prNumber,
@@ -500,13 +342,7 @@ function formatUnlandedFriction({
500
342
  );
501
343
  }
502
344
 
503
- /**
504
- * Format the `friction` comment for a merge that **landed** while the
505
- * `agent::done` label write failed. Deliberately not the unlanded wording:
506
- * the merge is not in question, so pointing the operator at branch
507
- * protection and required checks would send them to diagnose a fault that
508
- * does not exist. Name the actual remedy instead.
509
- */
345
+ /** Friction for a merge that landed but whose `agent::done` write failed. */
510
346
  function formatFlipFailedFriction({
511
347
  storyId,
512
348
  prNumber,
@@ -535,10 +371,7 @@ function formatFlipFailedFriction({
535
371
  }
536
372
 
537
373
  /**
538
- * Post a friction comment best-effort and return its id when the provider
539
- * surfaces one. The id is the terminal envelope's `frictionCommentId`
540
- * pointer, so a caller can link the operator straight at the remediation
541
- * instead of telling them to go find it.
374
+ * Best-effort; the id becomes the envelope's `frictionCommentId`.
542
375
  *
543
376
  * @returns {Promise<string|null>}
544
377
  */
@@ -561,12 +394,7 @@ async function postFriction({ provider, storyId, body, progress }) {
561
394
  }
562
395
  }
563
396
 
564
- /**
565
- * Terminal for a confirmed merge whose `agent::done` flip failed. Emits
566
- * `merge.flip-failed` (NOT `merge.unlanded` — the merge landed), posts the
567
- * flip-failed friction, and blocks explicitly. Best-effort throughout: the
568
- * caller owns the non-zero exit.
569
- */
397
+ /** Emits `merge.flip-failed` (not `merge.unlanded`); best-effort throughout. */
570
398
  async function blockOnFlipFailed({
571
399
  storyId,
572
400
  prNumber,
@@ -628,25 +456,17 @@ async function blockOnFlipFailed({
628
456
  reason,
629
457
  frictionCommentId,
630
458
  elapsedSeconds,
631
- // The merge is CONFIRMED here — only the label write failed — so the
632
- // envelope must say MERGED even when the probe that got us here was read
633
- // before the merge landed. Reporting the stale OPEN (or null) would tell
634
- // the operator to chase a merge that already happened.
459
+ // The merge is confirmed even if the probe was read before it landed.
635
460
  prProbe: { ...(prProbe ?? {}), state: 'MERGED' },
636
461
  };
637
462
  }
638
463
 
639
464
  /**
640
- * Story #5266 — the per-invocation advisory rerun allowance.
641
- *
642
- * `--rerun-advisory <n>` wins over `delivery.ci.rerunAdvisory` on exactly the
643
- * precedence `--max-wait-seconds` already uses. Both default to **0**: a
644
- * rerun spends CI minutes and mutates GitHub state, so close does neither
645
- * unasked. A non-integer or negative override is not an instruction to guess
646
- * — it degrades to the config value.
465
+ * `--rerun-advisory` beats `delivery.ci.rerunAdvisory`; both default 0
466
+ * because a rerun spends CI minutes. An invalid override falls back to config.
647
467
  *
648
- * @param {object|null} config Resolved config (or any `delivery.ci` bag).
649
- * @param {number} [override] The `--rerun-advisory` value, when supplied.
468
+ * @param {object|null} config
469
+ * @param {number} [override]
650
470
  * @returns {number} allowance ≥ 0
651
471
  */
652
472
  export function resolveAdvisoryRerunAllowance(config, override) {
@@ -655,10 +475,8 @@ export function resolveAdvisoryRerunAllowance(config, override) {
655
475
  }
656
476
 
657
477
  /**
658
- * Identify ONE observation of a red run: the job, the workflow run behind it,
659
- * and when that run finished. A rerun changes `completedAt` (and eventually
660
- * the conclusion), so this is what lets the wait tell a re-run's verdict apart
661
- * from the stale pre-rerun snapshot it will keep seeing for a poll or two.
478
+ * One observation of a red run; a rerun changes `completedAt`, which tells a
479
+ * fresh verdict from the stale pre-rerun snapshot.
662
480
  *
663
481
  * @param {{name?: string|null, runId?: number, completedAt?: string}} run
664
482
  * @returns {string}
@@ -668,17 +486,9 @@ function advisoryRunSignature(run) {
668
486
  }
669
487
 
670
488
  /**
671
- * Story #5266 — read each red advisory run's own account of WHY it failed.
672
- *
673
- * `gh pr view --json statusCheckRollup` has a fixed projection that carries no
674
- * output text for a CheckRun, so on the rollup alone every red advisory run
675
- * looks identical — which is the defect. The check-runs API for the head SHA
676
- * carries `output.title` / `output.summary`, and ONE call for the whole head
677
- * is enough to classify every red run on it.
678
- *
679
- * Called only on the block path (never per poll), and **fails open**: any
680
- * error, timeout, or missing head SHA returns the runs unchanged, which
681
- * classifies them as `advisory-gate-red` — the pre-#5266 verdict.
489
+ * Attach each red run's check-run output (the rollup carries none), in one
490
+ * call per head. Block path only; fails open to the unenriched runs, which
491
+ * classify as `advisory-gate-red`.
682
492
  *
683
493
  * @returns {Promise<Array<object>>} the runs, enriched where output was found
684
494
  */
@@ -720,17 +530,10 @@ async function enrichRedRunsWithOutput({
720
530
  }
721
531
 
722
532
  /**
723
- * Story #5266 — re-run the failed advisory workflow run(s), once, within the
724
- * caller's remaining allowance.
533
+ * `rerun-failed-jobs` once per distinct workflow run (not per job).
725
534
  *
726
- * Requests `rerun-failed-jobs` per distinct workflow run rather than per job:
727
- * one advisory workflow commonly fans out, and re-running the whole failed set
728
- * is both cheaper in API calls and what an operator means by "re-run it".
729
- *
730
- * @returns {Promise<boolean>} whether every rerun request succeeded. `false`
731
- * (including "no run id to re-run") leaves the caller on the block path,
732
- * because an allowance that cannot be spent must not silently suppress the
733
- * gate.
535
+ * @returns {Promise<boolean>} `false` (incl. no run id) keeps the caller on
536
+ * the block path — an unspendable allowance must not suppress the gate.
734
537
  */
735
538
  async function rerunAdvisoryRuns({ blockingRuns, gh, ghTimeoutMs, progress }) {
736
539
  const runIds = [
@@ -775,16 +578,20 @@ async function rerunAdvisoryRuns({ blockingRuns, gh, ghTimeoutMs, progress }) {
775
578
  }
776
579
 
777
580
  /**
778
- * Spend one unit of the rerun allowance, if there is one and the rerun takes.
779
- * Records the observation signature of every run it re-ran, so the stale
780
- * pre-rerun snapshot the next poll reads does not re-block on the same job.
581
+ * Advisory runs are non-required by construction; a red required check
582
+ * fails fast as `checks-failed` and never reaches the rerun path.
781
583
  *
782
- * Deliberately does NOT disarm first, unlike the block path: the whole point
783
- * of a rerun is that a green re-run lands the PR on its own. The cost is an
784
- * armed window in which GitHub could land the PR over the still-red advisory
785
- * if the required contexts go green before the re-run reports — which is
786
- * exactly what opting in to `--rerun-advisory` buys and accepts. At the
787
- * default 0 there is no such window.
584
+ * @param {{ remaining: number }} rerunState
585
+ * @returns {number}
586
+ */
587
+ function advisoryRerunsLeft(rerunState) {
588
+ return isRerunPermitted({ required: false }) ? rerunState.remaining : 0;
589
+ }
590
+
591
+ /**
592
+ * Spend one rerun unit, recording each re-run's signature so the stale
593
+ * snapshot doesn't re-block. Does NOT disarm: a green re-run should land on
594
+ * its own; the armed window is what opting in to `--rerun-advisory` accepts.
788
595
  *
789
596
  * @returns {Promise<boolean>} `true` when the caller should keep polling.
790
597
  */
@@ -795,7 +602,7 @@ async function maybeRerunAdvisory({
795
602
  ghTimeoutMs,
796
603
  progress,
797
604
  }) {
798
- if (rerunState.remaining <= 0) return false;
605
+ if (advisoryRerunsLeft(rerunState) <= 0) return false;
799
606
  const rerun = await rerunAdvisoryRuns({
800
607
  blockingRuns,
801
608
  gh,
@@ -811,22 +618,10 @@ async function maybeRerunAdvisory({
811
618
  }
812
619
 
813
620
  /**
814
- * Story #5096 — resolve the advisory-gate terminal for one poll.
815
- *
816
- * Takes the poll's current `unlanded` and returns it unchanged when a terminal
817
- * is already decided, so the caller is a single assignment with NO added
818
- * branch. `runMergePoll` is already above `check-cyclomatic`'s ceiling; the
819
- * three decision points this would otherwise cost inline are a real gate
820
- * regression, and they belong with the policy either way.
821
- *
822
- * Disarms BEFORE returning the terminal: an armed PR can merge out from under
823
- * the block the caller is about to record.
824
- *
825
- * Story #5266 threads three more steps through the same single assignment:
826
- * runs this invocation already re-ran (and has no fresh verdict for) are
827
- * skipped rather than re-blocked; the survivors are enriched with their own
828
- * check-run output so the class can be `advisory-gate-inconclusive`; and a
829
- * remaining rerun allowance is spent before any block is recorded.
621
+ * Advisory-gate terminal for one poll; passes a decided `unlanded` through so
622
+ * the caller stays branch-free (`runMergePoll` is at the cyclomatic ceiling).
623
+ * Skips runs already re-ran, enriches the rest, spends any rerun allowance,
624
+ * and disarms BEFORE returning a block so the PR cannot merge out from under it.
830
625
  */
831
626
  async function resolveAdvisoryUnlanded({
832
627
  unlanded,
@@ -848,9 +643,6 @@ async function resolveAdvisoryUnlanded({
848
643
  advisoryAllowlist,
849
644
  });
850
645
  if (!advisory) return null;
851
- // Every blocking run is one this invocation already re-ran and has not seen
852
- // a fresh verdict for yet (Story #5266) — keep polling rather than blocking
853
- // on the snapshot the rerun was meant to replace.
854
646
  const pending = advisory.blockingRuns.filter(
855
647
  (run) => !rerunState.issued.has(advisoryRunSignature(run)),
856
648
  );
@@ -888,11 +680,18 @@ async function resolveAdvisoryUnlanded({
888
680
  }
889
681
 
890
682
  /**
891
- * Classify the unlanded merge, emit `merge.unlanded`, post a `friction`
892
- * comment, and transition the Story to `agent::blocked`. Every side effect
893
- * is best-effort logged rather than thrown — the caller owns surfacing the
894
- * non-zero exit once this returns.
683
+ * An enqueued PR reads BLOCKED with every check green, which the classifier
684
+ * would call human-required — but the queue lands it unattended.
895
685
  */
686
+ function queuedExhaustionVerdict(probe, cumulativeMs) {
687
+ if (probe?.inMergeQueue !== true) return {};
688
+ return {
689
+ blockClassOverride: 'checks-pending-timeout',
690
+ reasonOverride: `PR is still in the merge queue after ${Math.round(cumulativeMs / 1000)} seconds — GitHub merges it when the queue's checks pass; a queued base may need a larger delivery.mergeWatch.maxBudgetSeconds`,
691
+ };
692
+ }
693
+
694
+ /** Classify, emit `merge.unlanded`, post friction, block — all best-effort. */
896
695
  async function blockOnUnlanded({
897
696
  storyId,
898
697
  prNumber,
@@ -907,11 +706,9 @@ async function blockOnUnlanded({
907
706
  blockClassOverride,
908
707
  reasonOverride,
909
708
  }) {
910
- // Story #5096 — `advisory-gate-red` is emitted DIRECTLY, never derived.
911
- // `classifyMergeBlock` cannot produce it: by construction GitHub is NOT
912
- // gating this merge (`mergeStateStatus: UNSTABLE`), which is the entire
913
- // condition the class names, so every classifier heuristic reads the PR as
914
- // healthy.
709
+ // A verdict decided at detection is emitted as-is, never re-derived (the
710
+ // classifier reads an advisory-gate PR as healthy); the classifier runs
711
+ // only for arm-failure and budget exhaustion.
915
712
  const { blockClass, reason } = blockClassOverride
916
713
  ? {
917
714
  blockClass: blockClassOverride,
@@ -923,11 +720,7 @@ async function blockOnUnlanded({
923
720
  budget,
924
721
  });
925
722
  const elapsedSeconds = budget?.elapsedSeconds ?? 0;
926
- // Which evidence path produced a `checks-failed` verdict (Story #4695):
927
- // `per-run` (head-anchored required-run evidence) or `consecutive-probe`
928
- // (the evidence-unavailable fallback). Named on the emitted record so the
929
- // `merge.unlanded` telemetry attributes the fail-fast to the path that
930
- // fired it. Absent for every other block class.
723
+ // `per-run` | `consecutive-probe` for `checks-failed`; absent otherwise.
931
724
  const evidencePath = prProbe?.evidencePath;
932
725
 
933
726
  if (Number.isInteger(prNumber) && prNumber > 0) {
@@ -988,27 +781,14 @@ async function blockOnUnlanded({
988
781
  reason,
989
782
  frictionCommentId,
990
783
  elapsedSeconds,
991
- // The probe the classifier just read. The terminal envelope reports
992
- // `pr.state` / `pr.checksStatus` from here; dropping it made every
993
- // blocked envelope claim `null` for facts we had just observed — a
994
- // `checks-failed` envelope reporting `checksStatus: null` contradicts
995
- // itself. Schema wants "live PR facts as observed at terminal time".
784
+ // The envelope's `pr.state` / `pr.checksStatus` come from here.
996
785
  prProbe,
997
786
  };
998
787
  }
999
788
 
1000
789
  /**
1001
- * Bring a BEHIND PR up to date, bounded by `updateAttempts`. Best-effort:
1002
- * a failed update is not itself a terminal — the next poll re-reads the
1003
- * real state and lets the normal classification decide, which is why a
1004
- * failed attempt still counts against the wait's tick.
1005
- *
1006
- * The BEHIND / budget / did-it-land decision itself lives in the shared
1007
- * {@link applyBehindUpdate} (Story #5006) — the CI-watch loop in
1008
- * `lib/orchestration/pr-watch.js` runs the same one. This wrapper supplies
1009
- * the merge wait's probe source, its `gh` facade (bounded by
1010
- * {@link withGhTimeout}, so a wedged child cannot strand an unattended
1011
- * async-mode wait), and its operator wording.
790
+ * Update a BEHIND PR, bounded by `updateAttempts`; a failed update is not a
791
+ * terminal (the next poll re-reads) but still counts as an attempt.
1012
792
  *
1013
793
  * @returns {Promise<boolean>} whether an update was actually attempted.
1014
794
  */
@@ -1050,11 +830,7 @@ async function maybeUpdateBehindPr({
1050
830
  return recovery.attempted;
1051
831
  }
1052
832
 
1053
- /**
1054
- * Handle an observed merge: run the shared `confirmStoryMerged` flip, then
1055
- * the shared post-land tail. Called at most once per wait — the loop probes
1056
- * the PR, not the ticket.
1057
- */
833
+ /** Called at most once per wait: the flip, then the post-land tail. */
1058
834
  async function onMergeObserved({
1059
835
  storyId,
1060
836
  storyBranch,
@@ -1085,15 +861,13 @@ async function onMergeObserved({
1085
861
  injectedGh,
1086
862
  injectedNotify,
1087
863
  readPrMergeStateFn,
864
+ // Saves confirmation a second `gh pr view`.
865
+ prState: prProbe,
1088
866
  });
1089
867
 
1090
868
  if (confirmation.merged && confirmation.action === 'flip-failed') {
1091
- // The PR merged but the agent::closing → agent::done label flip itself
1092
- // threw. Blocking explicitly is right — reporting confirmed:true would
1093
- // strand the Story at agent::closing with no notification. Reporting it
1094
- // as UNLANDED was not (Story #4539): the merge landed, so the
1095
- // merge.unlanded event would be false and its friction would send the
1096
- // operator to branch protection instead of the one-line remedy.
869
+ // Block explicitly (confirmed:true would strand it at agent::closing),
870
+ // but not as unlanded — the merge landed.
1097
871
  progress?.(
1098
872
  'CONFIRM',
1099
873
  `⚠️ Story #${storyId} merge confirmed but the agent::done flip failed — blocking explicitly.`,
@@ -1128,11 +902,7 @@ async function onMergeObserved({
1128
902
  terminal: 'landed',
1129
903
  action: confirmation.action,
1130
904
  tail,
1131
- // Carry the OBSERVED rollup rather than stamping 'success'. A merge landed
1132
- // by admin override, or with non-required checks red, must not be reported
1133
- // as a green run nobody actually saw — that is the same
1134
- // report-an-outcome-you-never-checked shape the land tail's per-step
1135
- // booleans exist to prevent.
905
+ // The observed rollup, not a stamped 'success' (admin merges exist).
1136
906
  prProbe,
1137
907
  };
1138
908
  }
@@ -1152,29 +922,20 @@ async function onMergeObserved({
1152
922
  * @param {string|null} args.autoMergeReason
1153
923
  * @param {object} args.provider
1154
924
  * @param {object} [args.config]
1155
- * @param {'sync'|'async'} [args.mergeWatchMode] Per-invocation
1156
- * `--merge-watch-mode` override (Story #4949); wins over
1157
- * `delivery.mergeWatch.mode`.
925
+ * @param {'sync'|'async'} [args.mergeWatchMode]
1158
926
  * @param {(tag: string, msg: string) => void} [args.progress]
1159
- * @param {number} [args.rerunAdvisory] `--rerun-advisory <n>` — the
1160
- * per-invocation override of `delivery.ci.rerunAdvisory` (both default 0).
927
+ * @param {number} [args.rerunAdvisory]
1161
928
  * @param {object} [args.injectedGh]
1162
929
  * @param {Function} [args.injectedNotify]
1163
- * @param {Function} [args.confirmStoryMergedFn] Test seam — defaults to the
1164
- * SAME `confirmStoryMerged` export `single-story-confirm-merge.js` calls
1165
- * (Story #4428 AC4: one merged/`agent::done` implementation).
1166
- * @param {Function} [args.readPrWaitProbeFn] Test seam for the poll probe.
1167
- * @param {Function} [args.readPrMergeStateFn] Test seam for the PR-state reader.
1168
- * @param {Function} [args.classifyMergeBlockFn] Test seam for the classifier.
1169
- * @param {Function} [args.emitMergeUnlandedFn] Test seam for the emitter.
1170
- * @param {Function} [args.runPostLandTailFn] Test seam for the land tail.
1171
- * @param {(ms: number) => Promise<void>} [args.sleepFn] Test seam so the
1172
- * suite does not actually wait.
1173
- * @param {() => number} [args.nowMsFn] Test seam; returns epoch ms.
1174
- * @param {number} [args.ghTimeoutMs] Wall-clock bound for each `gh` call the
1175
- * wait makes (Story #4710). A framework constant
1176
- * (`MERGE_WAIT_GH_TIMEOUT_MS`), overridable only as a test seam — not
1177
- * config.
930
+ * @param {Function} [args.confirmStoryMergedFn] The one shared merged/`agent::done` implementation.
931
+ * @param {Function} [args.readPrWaitProbeFn]
932
+ * @param {Function} [args.readPrMergeStateFn]
933
+ * @param {Function} [args.classifyMergeBlockFn]
934
+ * @param {Function} [args.emitMergeUnlandedFn]
935
+ * @param {Function} [args.runPostLandTailFn]
936
+ * @param {(ms: number) => Promise<void>} [args.sleepFn]
937
+ * @param {() => number} [args.nowMsFn]
938
+ * @param {number} [args.ghTimeoutMs] Test seam only, not config.
1178
939
  * @returns {Promise<object>}
1179
940
  */
1180
941
  export async function runConfirmMergePhase({
@@ -1207,10 +968,7 @@ export async function runConfirmMergePhase({
1207
968
  nowMsFn = Date.now,
1208
969
  ghTimeoutMs = MERGE_WAIT_GH_TIMEOUT_MS,
1209
970
  }) {
1210
- // The arm itself never succeeded (gh failure, unparseable PR number, or a
1211
- // deliberate disablement) — there is no "armed but unconfirmed" PR to
1212
- // poll. An explicit terminal state is still required, so classify and
1213
- // block immediately rather than resting silently.
971
+ // Never armed: nothing to poll, but a terminal is still required.
1214
972
  if (!autoMergeEnabled) {
1215
973
  progress?.(
1216
974
  'CONFIRM',
@@ -1226,12 +984,8 @@ export async function runConfirmMergePhase({
1226
984
  progress,
1227
985
  classifyMergeBlockFn,
1228
986
  emitMergeUnlandedFn,
1229
- // Story #5096 — the arm phase already refused over a red advisory gate;
1230
- // carry its verdict through instead of letting the classifier read this
1231
- // as a generic `arm-failure`.
1232
- // Story #5266 — carry the arm phase's CLASS too: a pre-arm refusal over
1233
- // a scan that never finished is `advisory-gate-inconclusive`, and
1234
- // hard-coding the red class here would relabel it at the terminal.
987
+ // Carry the arm phase's advisory verdict and class (which may be
988
+ // `inconclusive`) rather than a generic `arm-failure`.
1235
989
  ...(autoMergeReason === 'advisory-gate-red'
1236
990
  ? {
1237
991
  blockClassOverride: advisoryGate?.blockClass ?? 'advisory-gate-red',
@@ -1252,11 +1006,8 @@ export async function runConfirmMergePhase({
1252
1006
  maxWaitSecondsOverride,
1253
1007
  mergeWatchModeOverride,
1254
1008
  );
1255
- // Story #5096 — the advisory-gate knobs, read once for the whole wait.
1256
1009
  const { blockOnAdvisoryFailure, advisoryAllowlist } = getCiDelivery(config);
1257
- // Story #5266 — the rerun allowance and its ledger, spent across the whole
1258
- // wait rather than per poll, so `n` bounds the CI minutes this invocation
1259
- // can cost no matter how many times the gate is observed red.
1010
+ // Spent across the whole wait, not per poll, so `n` bounds CI minutes.
1260
1011
  const rerunAllowance = resolveAdvisoryRerunAllowance(
1261
1012
  config,
1262
1013
  rerunAdvisoryOverride,
@@ -1271,11 +1022,8 @@ export async function runConfirmMergePhase({
1271
1022
  let anchorMs = startedAtMs;
1272
1023
  let updatesUsed = 0;
1273
1024
  let polls = 0;
1274
- // Consecutive failing check probes observed WITHOUT per-run evidence
1275
- // (Story #4695). The evidence-unavailable fallback: a single failing rollup
1276
- // snapshot never fail-fasts — two consecutive failing probes at least one
1277
- // poll interval apart are required. Reset on any non-failing (or genuinely
1278
- // evidenced) probe.
1025
+ // Without per-run evidence, fail fast only after two consecutive failing
1026
+ // probes an interval apart; reset on any other probe.
1279
1027
  let consecutiveRequiredFailSnapshots = 0;
1280
1028
 
1281
1029
  progress?.(
@@ -1285,19 +1033,7 @@ export async function runConfirmMergePhase({
1285
1033
  `cumulative budget=${maxBudgetSeconds}s)...`,
1286
1034
  );
1287
1035
 
1288
- /**
1289
- * One poll iteration. Returns `{ done: false }` to keep polling, or
1290
- * `{ done: true, outcome }` with the phase's terminal. Story #4873 lifted
1291
- * this body out of a bespoke unbounded loop so the cadence is owned by the
1292
- * shared {@link pollUntil} primitive — the loop below sleeps, aborts, and
1293
- * counts ticks in exactly one place for every wait in the codebase. Every
1294
- * budget, floor, and classification decision is unchanged; only who owns the
1295
- * `await sleep(...)` moved.
1296
- *
1297
- * A throw from any of the terminal handlers is captured rather than allowed
1298
- * to escape into `pollUntil` (which treats a throwing `fn` as a non-match
1299
- * and would spin forever on it); the caller re-throws it after the loop.
1300
- */
1036
+ /** One iteration: `{ done: false }` or `{ done: true, outcome }`. */
1301
1037
  async function runMergePoll() {
1302
1038
  const probe = await readPrWaitProbeFn({
1303
1039
  prNumber,
@@ -1306,8 +1042,6 @@ export async function runConfirmMergePhase({
1306
1042
  });
1307
1043
  polls += 1;
1308
1044
 
1309
- // Anchor the cumulative budget at the PR's creation the first time we
1310
- // learn it, so a resumed wait continues the clock instead of restarting.
1311
1045
  anchorMs = resolveBudgetAnchorMs({
1312
1046
  createdAt: probe.createdAt,
1313
1047
  fallbackMs: startedAtMs,
@@ -1322,12 +1056,7 @@ export async function runConfirmMergePhase({
1322
1056
  maxBudgetSeconds,
1323
1057
  };
1324
1058
 
1325
- // Heartbeat (Story #4873). A backgrounded close writes this phase's
1326
- // progress to its own output file, and between the opening banner and the
1327
- // terminal there used to be NOTHING for minutes at a time — so an
1328
- // orchestrator watching that file could not tell a healthy in-flight wait
1329
- // from a wedged process without going back to GitHub itself. One line per
1330
- // poll makes the file's own growth the liveness signal.
1059
+ // Heartbeat: a backgrounded close's output-file growth is its liveness signal.
1331
1060
  progress?.(
1332
1061
  'CONFIRM',
1333
1062
  `⏱ poll ${polls}: PR #${prNumber} state=${probe.state ?? 'unknown'} ` +
@@ -1338,7 +1067,7 @@ export async function runConfirmMergePhase({
1338
1067
  (probe.error ? ` — probe error: ${probe.error}` : ''),
1339
1068
  );
1340
1069
 
1341
- if (probe.state === 'MERGED' || probe.mergedAt) {
1070
+ if (isPrMerged(probe)) {
1342
1071
  return doneWith(
1343
1072
  await onMergeObserved({
1344
1073
  storyId,
@@ -1362,38 +1091,21 @@ export async function runConfirmMergePhase({
1362
1091
  );
1363
1092
  }
1364
1093
 
1365
- // Everything below funnels into ONE terminal exit (Story #4710): each
1366
- // definitive condition fills `unlanded` and the single call site at the
1367
- // bottom classifies, emits, and blocks — the fail-fast tree used to
1368
- // duplicate that block twice inline.
1094
+ // Each definitive condition fills `unlanded`; one call site below blocks.
1369
1095
  let unlanded = null;
1370
1096
 
1371
1097
  if (probe.state === 'CLOSED') {
1372
- // Closed without merging — a definitive terminal, not a "still
1373
- // pending" condition the budget should keep waiting on. checksStatus
1374
- // MUST be a non-pending, non-undefined value here: the classifier's
1375
- // budget-exhausted branch treats an undefined checksStatus as "still
1376
- // pending", which would misclassify this definitive case as
1377
- // checks-pending-timeout instead of reaching the api-race-other
1378
- // reason built from prProbe.error.
1379
1098
  unlanded = {
1380
- prProbe: {
1381
- checksStatus: 'closed',
1382
- error: 'PR closed without merging (state=CLOSED)',
1383
- },
1099
+ prProbe: probe,
1384
1100
  budget: {
1385
1101
  exhausted: true,
1386
1102
  elapsedSeconds: Math.round(waitedMs / 1000),
1387
1103
  },
1104
+ blockClassOverride: CLOSED_UNMERGED_BLOCK_CLASS,
1105
+ reasonOverride: CLOSED_UNMERGED_REASON,
1388
1106
  };
1389
1107
  } else {
1390
- // Fail fast on a GENUINELY red REQUIRED check — head-anchored (Story
1391
- // #4695), decided by the extracted `decideMergeWaitFailFast` (Story
1392
- // #4710): per-run evidence decides on a single probe; without evidence
1393
- // two consecutive failing probes are required. No remaining budget
1394
- // turns a failed check green, and waiting it out is what made the
1395
- // pre-#4543 wait report the operator's red test run as a
1396
- // branch-protection block.
1108
+ // No remaining budget turns a red required check green; fail fast.
1397
1109
  const decision = decideMergeWaitFailFast({
1398
1110
  probe,
1399
1111
  consecutiveRequiredFailSnapshots,
@@ -1413,14 +1125,13 @@ export async function runConfirmMergePhase({
1413
1125
  exhausted: false,
1414
1126
  elapsedSeconds: Math.round(waitedMs / 1000),
1415
1127
  },
1128
+ blockClassOverride: decision.blockClass,
1129
+ reasonOverride: decision.reason,
1416
1130
  };
1417
1131
  }
1418
1132
 
1419
- // Story #5096 — the ADVISORY counterpart, and the half that catches the
1420
- // common shape. Close arms immediately after opening the PR, while the
1421
- // gate is still QUEUED, so the pre-arm refusal in `auto-merge.js` sees
1422
- // nothing; the gate reddens here, mid-wait, and native auto-merge would
1423
- // land the PR the moment the REQUIRED contexts go green.
1133
+ // Advisory gates are usually still QUEUED at arm time, so they redden
1134
+ // here, mid-wait, before auto-merge lands over them.
1424
1135
  unlanded = await resolveAdvisoryUnlanded({
1425
1136
  unlanded,
1426
1137
  probe,
@@ -1451,11 +1162,7 @@ export async function runConfirmMergePhase({
1451
1162
  updatesUsed += 1;
1452
1163
  }
1453
1164
 
1454
- // Cumulative budget exhausted → the genuine give-up. Classify from the
1455
- // probe we already hold. Gated behind the poll floor so an
1456
- // already-over-budget PR (anchored at a createdAt older than the budget
1457
- // — a resume the next day, or a long-open PR) still gets a real poll
1458
- // cycle instead of being blocked before this invocation waited at all.
1165
+ // Cumulative budget exhausted, behind the poll floor.
1459
1166
  if (
1460
1167
  polls >= MIN_POLLS_BEFORE_BUDGET_BLOCK &&
1461
1168
  cumulativeMs + intervalMs > maxBudgetSeconds * 1000
@@ -1466,6 +1173,7 @@ export async function runConfirmMergePhase({
1466
1173
  exhausted: true,
1467
1174
  elapsedSeconds: Math.round(cumulativeMs / 1000),
1468
1175
  },
1176
+ ...queuedExhaustionVerdict(probe, cumulativeMs),
1469
1177
  };
1470
1178
  }
1471
1179
  }
@@ -1485,11 +1193,7 @@ export async function runConfirmMergePhase({
1485
1193
  );
1486
1194
  }
1487
1195
 
1488
- // This invocation's bound expired → PENDING. Deliberately NOT a block:
1489
- // nothing is wrong, the run simply reached the edge of its host slot.
1490
- // No label mutation, no merge.unlanded event — the caller surfaces a
1491
- // resumable terminal and the next invocation continues the cumulative
1492
- // clock from the PR's createdAt.
1196
+ // Invocation bound reached: resumable `pending`, no mutation.
1493
1197
  if (waitedMs + intervalMs > maxWaitSeconds * 1000) {
1494
1198
  progress?.(
1495
1199
  'CONFIRM',
@@ -1515,25 +1219,19 @@ export async function runConfirmMergePhase({
1515
1219
  try {
1516
1220
  return await runMergePoll();
1517
1221
  } catch (err) {
1518
- // A terminal handler threw. `pollUntil` treats a throwing `fn` as a
1519
- // non-match and would poll forever on it, so the throw is carried out
1520
- // as a match and re-raised below.
1222
+ // pollUntil treats a throw as a non-match and would spin; carry it out.
1521
1223
  return { done: true, thrown: err };
1522
1224
  }
1523
1225
  },
1524
1226
  predicate: (result) => result?.done === true,
1525
1227
  intervalMs,
1526
- // The wait owns its own bounds (`maxWaitSeconds` → `pending`,
1527
- // `maxBudgetSeconds` → blocked), and both are decided from the probe
1528
- // inside the tick. A second, cruder wall-clock timeout here would throw
1529
- // past those classifications.
1228
+ // No pollUntil timeout: the tick owns both bounds and their classification.
1530
1229
  sleepFn: (ms) => sleepFn(ms),
1531
1230
  });
1532
1231
  if (tick.thrown) throw tick.thrown;
1533
1232
  return tick.outcome;
1534
1233
  }
1535
1234
 
1536
- /** Wrap a phase terminal as the poll loop's match. */
1537
1235
  function doneWith(outcome) {
1538
1236
  return { done: true, outcome };
1539
1237
  }