tumwater 0.2.0 → 0.3.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 (446) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +42 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +4 -3
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +1 -2
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +118 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +150 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -0,0 +1,106 @@
1
+ import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
2
+ import { postReviewPath, postReviewWorktreePath, nativeLogPath, piLogPath, reviewRunSessionDir } from "../paths.js";
3
+ import { reviewRunConfig } from "../config/config-views.js";
4
+ import { logEventBestEffort, TUMWATER_SOURCE, tryOrWarn } from "../events/events.js";
5
+ import { isJsonObject } from "../files/json-object.js";
6
+ import { git, gitCouldNotStart, gitTry } from "../git/git-run.js";
7
+ import { ensureDetachedWorktree } from "../git/worktree.js";
8
+ import { useWorktree } from "../git/worktree-use.js";
9
+ import { readPrinciples } from "../prompt/principles.js";
10
+ import { buildReviewPrompt } from "../prompt/gate-prompts.js";
11
+ import { baseRoleOf } from "../roles/runner-ids.js";
12
+ import { shortSha } from "../text/format.js";
13
+ import { errorMessage } from "../text/text.js";
14
+ import { parseVerdict } from "./review-verdict.js";
15
+ /** A commit's review is dropped, with a warning, after this many runs with no verdict. */
16
+ export const POST_REVIEW_FAILURE_LIMIT = 3;
17
+ /** Whether `role`'s changes are reviewed after merging instead of before. */
18
+ export function isAfterMergeRole(config, role) {
19
+ return (config.review.afterMerge ?? []).includes(baseRoleOf(role));
20
+ }
21
+ /** The queued entries. A torn or hand-edited entry (not an object with a string commit, role and
22
+ * summary, or a `rejected` that is not an array) is dropped, and the next write leaves it out. */
23
+ export function readPostReviews(root) {
24
+ const saved = readJsonFile(postReviewPath(root));
25
+ if (!Array.isArray(saved?.pending))
26
+ return [];
27
+ const entries = [];
28
+ for (const raw of saved.pending) {
29
+ if (!isJsonObject(raw))
30
+ continue;
31
+ const { commit, role, summary, body, failures, rejected } = raw;
32
+ if (typeof commit !== "string" || typeof role !== "string" || typeof summary !== "string")
33
+ continue;
34
+ if (rejected !== undefined && !Array.isArray(rejected))
35
+ continue;
36
+ const entry = { commit, role, summary };
37
+ if (typeof body === "string")
38
+ entry.body = body;
39
+ if (typeof failures === "number" && Number.isFinite(failures))
40
+ entry.failures = failures;
41
+ if (Array.isArray(rejected))
42
+ entry.rejected = rejected.filter((r) => typeof r === "string");
43
+ entries.push(entry);
44
+ }
45
+ return entries;
46
+ }
47
+ function writePostReviews(root, pending) {
48
+ tryOrWarn(root, TUMWATER_SOURCE, "write post-review.json", () => writeJsonAtomic(postReviewPath(root), { pending }));
49
+ }
50
+ /** Queue the merged commit for its review. Never throws: a failed write warns and leaves the file
51
+ * as it was, so the commit goes unreviewed. */
52
+ export function addPostReview(root, entry) {
53
+ writePostReviews(root, [...readPostReviews(root).filter((e) => e.commit !== entry.commit), entry]);
54
+ }
55
+ /** Replace the entry for `commit` with `next`, or drop it when `next` is null. Never throws: a
56
+ * failed write warns and leaves the file as it was, so a later poll handles the entry again. */
57
+ export function settlePostReview(root, commit, next) {
58
+ const rest = readPostReviews(root).filter((e) => e.commit !== commit);
59
+ writePostReviews(root, next ? [...rest, next] : rest);
60
+ }
61
+ /** Review the merged commit C: the usual reviewer prompt over `git diff C^ C`, run in a detached
62
+ * checkout of C. Logs `review_start` and `review_verdict` / `review_rejected` with
63
+ * `afterMerge: true`. A diff git fails to produce is a failed review with no reviewer run: read
64
+ * as empty, the reviewer approved a commit it never saw. Throws only when git could not start
65
+ * (gitCouldNotStart), which says nothing about C, so the caller keeps the entry as it was for
66
+ * the next poll instead of counting a failed run toward dropping it. */
67
+ export async function reviewMergedCommit(root, config, entry, runGatePi, signal) {
68
+ const { commit, role } = entry;
69
+ try {
70
+ const reviewCfg = reviewRunConfig(config);
71
+ const startedAt = Date.now();
72
+ const pi = await useWorktree(root, postReviewWorktreePath(root), async () => {
73
+ const wt = await ensureDetachedWorktree(root, postReviewWorktreePath(root), commit);
74
+ await gitTry(wt, "checkout", "--detach", "--force", commit);
75
+ const diff = await git(wt, "diff", `${commit}^`, commit);
76
+ logEventBestEffort(root, { source: role, type: "review_start", head: commit, afterMerge: true });
77
+ return runGatePi({
78
+ cwd: wt,
79
+ kind: "gate",
80
+ prompt: buildReviewPrompt(diff, entry.summary, entry.body, readPrinciples(root), undefined, `${commit}^`),
81
+ config: reviewCfg,
82
+ sessionDir: reviewRunSessionDir(root, role, `post-${shortSha(commit)}`),
83
+ sessionName: `tumwater-review-${role}-post-${shortSha(commit)}`,
84
+ rawLogFile: piLogPath(root, role),
85
+ nativeLogFile: nativeLogPath(root, role, reviewCfg.agent ?? "pi"),
86
+ label: "review",
87
+ signal,
88
+ });
89
+ });
90
+ const verdict = pi.ok && !pi.aborted ? parseVerdict(pi.verdictText ?? "") : null;
91
+ if (!verdict)
92
+ return { kind: "failed", detail: pi.errorMessage ?? "no parseable VERDICT line in the reviewer's reply" };
93
+ const durationMs = Date.now() - startedAt;
94
+ if (verdict.verdict === "reject") {
95
+ logEventBestEffort(root, { source: role, type: "review_rejected", head: commit, reasons: verdict.reasons, durationMs, afterMerge: true });
96
+ return { kind: "reject", reasons: verdict.reasons };
97
+ }
98
+ logEventBestEffort(root, { source: role, type: "review_verdict", head: commit, reason: verdict.reasons[0], durationMs, afterMerge: true });
99
+ return { kind: "approve", reasons: verdict.reasons };
100
+ }
101
+ catch (err) {
102
+ if (gitCouldNotStart(err))
103
+ throw err;
104
+ return { kind: "failed", detail: errorMessage(err) };
105
+ }
106
+ }
@@ -1,16 +1,15 @@
1
- // vocabulary: plain
2
1
  import path from "node:path";
3
2
  import { promptBlockOf } from "../brief/brief.js";
4
3
  import { readTextOrNull } from "../files/files.js";
5
4
  import { changeBaseRev, fileContentAt } from "../git/git.js";
6
5
  import { briefCandidates } from "../paths.js";
7
- import { baseRoleOf } from "../roles/loop-ids.js";
8
- /** The brief files that may carry the initial prompt block: the basenames of `briefCandidates`. */
6
+ import { baseRoleOf } from "../roles/runner-ids.js";
7
+ /** The brief files that may carry the brief text block: the basenames of `briefCandidates`. */
9
8
  const BRIEF_FILES = briefCandidates("").map((p) => path.basename(p));
10
9
  /** The roles that may edit PRINCIPLES.md. */
11
10
  const PRINCIPLES_ROLES = new Set(["director", "steward"]);
12
- /** The deterministic gate/stage finding for a change that edits what the loop prompts forbid
13
- * editing: the initial prompt block of a brief file (changed, removed, or the file deleted —
11
+ /** The deterministic gate/stage finding for a change that edits what the role prompts forbid
12
+ * editing: the brief text block of a brief file (changed, removed, or the file deleted —
14
13
  * a block that only moves passes), or PRINCIPLES.md from a role other than director or
15
14
  * steward. undefined when neither happened. Reads the worktree and the change's merge-base
16
15
  * like backlogStructureReason; a missing file reads as empty and never throws. */
@@ -1,11 +1,11 @@
1
- import { agentSessionResumable } from "../agent/agent-backend.js";
2
- import { reviewRunConfig, resolvedAgentConfig } from "../config/config-views.js";
1
+ import { backendSessionResumable } from "../agent/agent-backend.js";
2
+ import { reviewRunConfig, resolvedBackendConfig } from "../config/config-views.js";
3
3
  import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
4
4
  import { nativeLogPath, piLogPath } from "../paths.js";
5
5
  import { buildNoRerunPrompt, buildVerdictRequestPrompt } from "../prompt/gate-prompts.js";
6
6
  import { cappedRequestTimeouts } from "../request-timeouts.js";
7
7
  /** Hard caps on the VERDICT follow-up turn: it should take one short reply on a warm session,
8
- * so it never gets the review run's own budget (mirrors LoopPi's SUMMARY-request caps). */
8
+ * so it never gets the review run's own budget (mirrors RunnerPi's SUMMARY-request caps). */
9
9
  const VERDICT_REQUEST_TIMEOUT_S = 900;
10
10
  const VERDICT_REQUEST_QUIET_S = 300;
11
11
  /** One bounded follow-up turn on the reviewer's just-finished session: the shared session
@@ -19,7 +19,7 @@ async function runFollowupTurn(ctx, opts) {
19
19
  // backend's own resumable rule keeps a newer line of another kind from being the session a
20
20
  // pi continue would actually reach; claude resumes its recorded id (looked up under the
21
21
  // team's config dir, so a team that set one continues under it).
22
- if (!agentSessionResumable(dir, { kind: "gate", agent: cfg.agent ?? "pi" }, resolvedAgentConfig(ctx.config, "claude").configDir)) {
22
+ if (!backendSessionResumable(dir, { kind: "gate", agent: cfg.agent ?? "pi" }, resolvedBackendConfig(ctx.config, "claude").configDir)) {
23
23
  return null;
24
24
  }
25
25
  return ctx.runGatePi({
@@ -46,7 +46,7 @@ async function runFollowupTurn(ctx, opts) {
46
46
  onBlindRead: (path, action) => logEventBestEffort(ctx.root, {
47
47
  source: ctx.role,
48
48
  type: "image_blind",
49
- run: ctx.tick,
49
+ run: ctx.runNumber,
50
50
  path,
51
51
  model: cfg.model,
52
52
  action,
@@ -55,14 +55,14 @@ async function runFollowupTurn(ctx, opts) {
55
55
  });
56
56
  }
57
57
  /** Ask the reviewer's own session (--continue) for the missing VERDICT line: one tightly
58
- * bounded turn on the just-finished review's session, mirroring LoopPi.requestSummary on the
58
+ * bounded turn on the just-finished review's session, mirroring RunnerPi.requestSummary on the
59
59
  * author side (BUGS.md 2026-09-29). Null when there is no session to continue — the caller
60
60
  * then counts the strike exactly as before. The run is returned even when it failed so the
61
61
  * caller can honor a shutdown abort and fold the spend. */
62
62
  export async function requestVerdict(ctx) {
63
63
  return runFollowupTurn(ctx, {
64
64
  prompt: buildVerdictRequestPrompt(),
65
- sessionName: `tumwater-review-${ctx.role}-${ctx.tick}-verdict`,
65
+ sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-verdict`,
66
66
  label: "review-verdict",
67
67
  });
68
68
  }
@@ -76,7 +76,7 @@ export async function requestVerdict(ctx) {
76
76
  export async function requestNoRerun(ctx, rerun, calls) {
77
77
  return runFollowupTurn(ctx, {
78
78
  prompt: buildNoRerunPrompt(rerun),
79
- sessionName: `tumwater-review-${ctx.role}-${ctx.tick}-rerun`,
79
+ sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-rerun`,
80
80
  label: "review-rerun",
81
81
  // Collected unconditionally: a nudge only runs after a detected rerun, so the no-re-run
82
82
  // rule stood and the repeat check below needs every call the nudge started.
@@ -1,22 +1,48 @@
1
- // vocabulary: plain
2
1
  /** The review gate's deterministic build pre-check — split out of review.ts so the gate keeps
3
2
  * one responsibility (running the model reviewer and acting on its verdict) while this module
4
3
  * owns the pre-review verification: the one scoped build-check run, its flake re-run, the
5
- * sleep-unverified and main-red attributions, and the harness's own counts sentence. No model
4
+ * sleep-unverified and main-red attributions. No model
6
5
  * runs here — everything is decided by the project's declared check, so a deterministic
7
6
  * rejection never shows as "reviewing" on the dashboards and consumes no reviewer budget. */
8
7
  import { warnEventBestEffort } from "../events/events.js";
9
8
  import { BUILD_CHECK_TIMEOUT_MS } from "../build/build-check-detect.js";
10
9
  import { runScopedBuildCheck } from "../build/build-check-scoped.js";
11
- import { checkFailureReasons, describeCheck, failureHeadline } from "../build/build-check-report.js";
10
+ import { checkFailureReasons, describeCheck, failureHeadline, FAILING_TESTS_MAX } from "../build/build-check-report.js";
12
11
  import { sleptPhrase, UNVERIFIED_TREE, unverifiedTreeOutcome } from "../build/build-check-events.js";
13
12
  import { sampleSleepClock } from "../build/host-sleep.js";
14
- import { recordReview } from "../tick/tick-apply.js";
13
+ import { recordReview } from "../run/run-apply.js";
15
14
  import { mainRedNotMine } from "../text/phrases.js";
16
15
  import { shortSha } from "../text/format.js";
17
16
  import { checkWaitStage, setMergeStage } from "../merge/merge-slot.js";
18
17
  import { mainTipVerdict } from "../baseline/main-red.js";
19
- import { FLAKY_WARNING_PREFIX, isKnownFlake } from "./known-flakes.js";
18
+ import { onlyKnownMainFailures } from "../baseline/main-watch.js";
19
+ import { watchedBaseline } from "../baseline/main-baseline.js";
20
+ import { changeBaseRev } from "../git/git.js";
21
+ import { FLAKY_WARNING_PREFIX, isKnownFlake, quarantineTests } from "./known-flakes.js";
22
+ /** The tests a gate failure shares with the change's own base, when that base is not main's tip:
23
+ * the main watch recorded the base red with every one of them. A branch behind a fix fails the
24
+ * old test on its base while main's tip is green (2026-10-20, report --role), so the tip verdict
25
+ * alone is the wrong question. Null when the failures are not all the base's, the list may have
26
+ * been cut short (the cap onlyKnownMainFailures uses), or git cannot say. */
27
+ async function staleBaseFailures(wt, mainBranch, head, mainTip, failingTests) {
28
+ if (!failingTests || failingTests.length === 0 || failingTests.length >= FAILING_TESTS_MAX)
29
+ return null;
30
+ try {
31
+ const base = await changeBaseRev(wt, mainBranch, head);
32
+ if (base === mainTip)
33
+ return null;
34
+ const verdict = await watchedBaseline(wt, base);
35
+ if (verdict?.status !== "red")
36
+ return null;
37
+ const baseFailed = verdict.failingTests ?? [];
38
+ if (!failingTests.every((t) => baseFailed.includes(t)))
39
+ return null;
40
+ return { base, tests: [...failingTests] };
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
20
46
  /** Run the gate's deterministic build pre-check. `reject` is the gate's one shared rejection
21
47
  * path (review.ts's reject closure): the pre-check routes a reproduced failure through it so
22
48
  * the bookkeeping (lastReview, branch reset, review_rejected event) cannot drift from the
@@ -41,10 +67,12 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
41
67
  // host were exactly that (BUGS.md 2026-09-23). The re-run is one more check, priced as
42
68
  // its own build_check event; if it passes, the tree is verified exactly like a
43
69
  // first-time pass and the flake is named in a warning (the headline clusters in the
44
- // digest, so telemetry and bugfix can go after the flaky test). A re-run that fails
70
+ // digest, so selfcheck and bugfix can go after the flaky test). A re-run that fails
45
71
  // again goes to attribution below; a skipped one says nothing about the tree, so the
46
72
  // first failure stands.
47
- const retry = await runGateCheck();
73
+ // A failure naming only known tests (main's, or quarantined) is judged below as it stands:
74
+ // a re-run could only repeat it.
75
+ const retry = onlyKnownMainFailures(root, outcome.failingTests) ? undefined : await runGateCheck();
48
76
  if (retry?.outcome.status === "passed") {
49
77
  // When the failed run spanned a host sleep, the pass says the tree is fine and the
50
78
  // sleep is the story — not a flaky test (BUGS.md 2026-09-30): the digest clusters
@@ -55,6 +83,8 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
55
83
  else {
56
84
  const flaky = failureHeadline(outcome.outputTail) ?? describeCheck(check);
57
85
  warnEventBestEffort(root, role, `${FLAKY_WARNING_PREFIX}${flaky}`);
86
+ // Each test the failed run named failed and then passed on this same tree: quarantined.
87
+ quarantineTests(root, outcome.failingTests, outcome.outputTail);
58
88
  }
59
89
  outcome = retry.outcome;
60
90
  }
@@ -78,12 +108,23 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
78
108
  // host-sleep path above does. Before this, the repeat was attributed through main's
79
109
  // per-SHA baseline cache (usually a green seeded by an earlier merge), so a load- or
80
110
  // clock-sensitive test rejected whichever unrelated change hit it (BUGS.md 2026-10-06).
111
+ // The excuse is not a failure of the gate, so the change goes on to the model review
112
+ // instead of ending the merge attempt as review_error and paying a requeue. It carries no
113
+ // verifiedHead — the tree was not verified — so the merge
114
+ // path's own check still judges the tree before it reaches main.
81
115
  const flaky = failureHeadline(outcome.outputTail) ?? describeCheck(check);
116
+ // A failure that names only tests main already fails is main's, not this change's: it goes
117
+ // on to review with no fresh run on main. No verifiedHead — the tree did not pass, so the
118
+ // merge path must not seed a green for it — and the merge check judges it by the same rule.
119
+ const known = onlyKnownMainFailures(root, outcome.failingTests);
120
+ if (known) {
121
+ warnEventBestEffort(root, role, `gate check fails only main's known failures: ${known.join(", ")}`);
122
+ return { knownMainFailures: known };
123
+ }
124
+ // The headline excuse stays for a failure that names no test: quarantine covers named ones.
82
125
  if (isKnownFlake(root, flaky)) {
83
- const detail = `known-flaky failure, merge kept: ${flaky}`;
84
- recordReview(state, "failed", [detail], head);
85
- warnEventBestEffort(root, role, detail);
86
- return { resolved: { decision: "failed", detail, unverified: true } };
126
+ warnEventBestEffort(root, role, `known-flaky failure, merge kept: ${flaky}`);
127
+ return {};
87
128
  }
88
129
  const reasons = checkFailureReasons(check, outcome);
89
130
  // A failure that reproduced is attributed, never fixed here: the one merge slot the
@@ -106,14 +147,24 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
106
147
  // Main fails too: not this change's failure. Leave the commit (and with it the pin) and
107
148
  // do not advance the discard counter — the transport-failure rule (BUGS.md 2026-09-20):
108
149
  // nothing judged this diff. main-red.ts's gate and the bugfix handoff own the repair,
109
- // and the author's next tick re-merges the change once main is green again.
150
+ // and the orchestrator poll (requeueMainRedPin) re-queues the kept pin once main's head
151
+ // moves; leftover recovery holds it until then.
110
152
  const detail = mainRedNotMine(main.sha);
111
153
  recordReview(state, "failed", [detail], head);
112
154
  warnEventBestEffort(root, role, `gate check failed on ${shortSha(head)}, but ${detail}; merge kept`);
113
155
  return { resolved: { decision: "failed", detail, mainRed: true } };
114
156
  }
157
+ if (main.status === "green") {
158
+ const stale = await staleBaseFailures(wt, mainBranch, head, main.sha, outcome.failingTests);
159
+ if (stale) {
160
+ // Same hand-off as the known-failure branch: no verifiedHead, so the merge check on the
161
+ // rebased tree still judges it.
162
+ warnEventBestEffort(root, role, `gate check fails only tests its base ${shortSha(stale.base)} already failed, and main ${shortSha(main.sha)} is green: ${stale.tests.join(", ")}; going on to review`);
163
+ return { knownMainFailures: stale.tests };
164
+ }
165
+ }
115
166
  // Main green: the change broke the check — rejected deterministically through the shared
116
- // reject path, no pi run consumed, reasons injected into the author's next tick. With no
167
+ // reject path, no pi run consumed, reasons injected into the author's next run. With no
117
168
  // verdict for main the author's failure is still the safe default, and the reasons say
118
169
  // the attribution could not be made.
119
170
  return {
@@ -126,17 +177,9 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
126
177
  // Passed: this exact tree just went green under the project's own declared check. Hand
127
178
  // the verdict to the merge path via verifiedHead — it seeds the red-main baseline with
128
179
  // the SHA that actually becomes main (the rebased head, which may differ from `head`),
129
- // so every role's next fresh tick is a cache hit instead of one redundant full-suite
180
+ // so every role's next fresh run is a cache hit instead of one redundant full-suite
130
181
  // re-run on an already-verified tree.
131
- // The counts are the harness's own reading of the runner's summary (parseTestCounts):
132
- // attesting them here means neither the author nor the reviewer states a total, which is
133
- // where most record-claim rejections came from (PLANS.md 2026-09-29).
134
- return {
135
- verifiedHead: head,
136
- verifiedByHarness: outcome.counts
137
- ? `${describeCheck(check)} (the project's declared check) passed — ${outcome.counts.pass} pass, ${outcome.counts.fail} fail, ${outcome.counts.skipped} skipped of ${outcome.counts.tests}`
138
- : `${describeCheck(check)} (the project's declared check) passed`,
139
- };
182
+ return { verifiedHead: head };
140
183
  }
141
184
  }
142
185
  return {};