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
@@ -1,14 +1,17 @@
1
- // vocabulary: plain
2
1
  import { openQuestions } from "../backlog/backlog.js";
3
2
  import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
4
- import { changeBaseRev, commitMessage, headOf } from "../git/git.js";
3
+ import { changeBaseRev, commitMessage, headOf, patchId } from "../git/git.js";
5
4
  import { aheadOfMainFiles, treeDiffFiles } from "../git/git-diff.js";
6
5
  import { resolvedDiffDiverges } from "./merge-diff.js";
7
- import { conflictedFileHunks, conflictedFiles, continueRebase, ffMainToOrWhy, hasConflictMarkers, mainCommitsTouching, rebaseOntoMain, rebaseOntoMainLeaveConflicts, } from "./merge-git.js";
6
+ import { conflictedFileHunks, conflictedFiles, continueRebase, ffMainToOrWhy, overwriteBlockedReason, primaryCheckoutBlockers, hasConflictMarkers, mainCommitsTouching, rebaseOntoMain, rebaseOntoMainLeaveConflicts, } from "./merge-git.js";
8
7
  import { resolveBacklogInsertConflicts } from "./backlog-conflicts.js";
9
8
  import { abortSync } from "../git/worktree.js";
10
9
  import { runScopedBuildCheck } from "../build/build-check-scoped.js";
11
- import { detectBuildCheck, gateCommandOf } from "../build/build-check-detect.js";
10
+ import { detectBuildCheck, gateCommandOf, mergeCommandOf } from "../build/build-check-detect.js";
11
+ import { onlyKnownMainFailures } from "../baseline/main-watch.js";
12
+ import { unverifiedTreeOutcome } from "../build/build-check-events.js";
13
+ import { describeCheck, failureHeadline } from "../build/build-check-report.js";
14
+ import { MERGE_FLAKY_WARNING_PREFIX, quarantineTests } from "../review/known-flakes.js";
12
15
  import { noteGreenBaseline, noteInheritedGreen } from "../baseline/main-baseline.js";
13
16
  import { isExemptDiff } from "../review/exemptions.js";
14
17
  import { falseFixReason } from "../verdict/fix-claim.js";
@@ -24,7 +27,7 @@ import { resolverConfig } from "../config/config-views.js";
24
27
  * history linear), verify the rebased tree when it differs from what was reviewed, and
25
28
  * fast-forward. On conflict, makes one pi-driven resolution attempt
26
29
  * (outside the lock) before giving up. A routine conflict is normal operation, not a warning:
27
- * success merges as an ordinary `merged` event and failure surfaces via the tick's merge_conflict
30
+ * success merges as an ordinary `merged` event and failure surfaces via the run's merge_conflict
28
31
  * result — no separate log line for the hand-off itself. A merged diff that adds entries under
29
32
  * QUESTIONS.md's ## Open also emits one `question_posted` per new heading alongside the `merged`
30
33
  * event, so `tumwater logs` shows what the team is asking for (plans/questions-outbox.md).
@@ -80,6 +83,11 @@ async function tryMerge(ctx, wt, summary, verifiedHead) {
80
83
  // main moved between commit and merge, which under concurrency is the common case (review
81
84
  // runs outside this lock).
82
85
  const target = await headOf(wt, "HEAD");
86
+ const dirty = await primaryCheckoutBlockers(ctx.root, ctx.mainBranch, target);
87
+ if (dirty !== null) {
88
+ warnEventBestEffort(ctx.root, ctx.role, `merge blocked: ${overwriteBlockedReason(dirty)}`);
89
+ return "merge_blocked";
90
+ }
83
91
  if (!(await verifyMerge(ctx, wt, target, verifiedHead)))
84
92
  return "merge_blocked";
85
93
  const mainBefore = await headOf(ctx.root, ctx.mainBranch);
@@ -98,14 +106,28 @@ async function tryMerge(ctx, wt, summary, verifiedHead) {
98
106
  // Best-effort: main has already fast-forwarded, so an unwritable events feed must not turn
99
107
  // a completed merge into a rejected one and skip syncRootInstall below — the same
100
108
  // no-torn-feed policy as the orchestrator's other post-effect logging.
101
- if (commit !== mainBefore)
102
- logEventBestEffort(ctx.root, { source: ctx.role, type: "merged", commit, summary });
109
+ if (commit !== mainBefore) {
110
+ // The merged commit's patch-id (`mainBefore...commit`, src/git/git.ts patchId, -U0 so a
111
+ // re-landing onto a moved main keeps the id): the fast-merge guardrail G2 matches it
112
+ // against a main_revert's to spot a change that came back unchanged (plans/fast-merge.md).
113
+ // Computed here from the commit that actually merged (not the pre-merge pin) so a
114
+ // conflict-resolved merge carries the resolved change's id too. Computed after the ff
115
+ // because mainBefore/commit both live in the shared object store either way.
116
+ const mergedPatch = await patchId(wt, mainBefore, commit).catch(() => null);
117
+ logEventBestEffort(ctx.root, {
118
+ source: ctx.role,
119
+ type: "merged",
120
+ commit,
121
+ summary,
122
+ ...(mergedPatch ? { patchId: mergedPatch } : {}),
123
+ });
124
+ }
103
125
  logNewQuestions(ctx.root, before, ctx.role);
104
126
  // Still under the lock: a merge that moved main's lockfile re-syncs the root install
105
127
  // every worktree resolves through before the next merge's check runs (BUGS.md 2026-10-01).
106
128
  await syncRootInstall(ctx.root, ctx.role);
107
129
  return "changed";
108
- });
130
+ }, ctx.lockTimeoutMs);
109
131
  }
110
132
  /** The backlog-structure cross-check both verifyMerge paths run (exempt and code diffs
111
133
  * alike): when the tree ahead of main duplicates or drops a `## ` section heading, warn on the
@@ -146,12 +168,16 @@ export async function exemptSkipBlockReason(ctx, wt, files) {
146
168
  * - a no-op rebase whose exact tree the gate verified (`rebasedHead === verifiedHead`): main
147
169
  * did not move under us, so the merge tree is byte-identical to what the gate's pre-check
148
170
  * already ran green. Seed the red-main baseline with the SHA that becomes main: every role's
149
- * next fresh tick then hits the cache instead of re-running the full suite on an
171
+ * next fresh run then hits the cache instead of re-running the full suite on an
150
172
  * already-verified tree. A no-op rebase the gate did NOT verify — its pre-check was skipped
151
173
  * (a timeout, a killed run) — falls through and runs the merge-scope check, which fails
152
174
  * closed on the same timeout; an exempt diff or a project with no declared check falls
153
- * through to its own arm below. Not taken when a `check.gateCommand` is configured: the gate
154
- * then ran only that, not the full check.
175
+ * through to its own arm below. Taken (Fast merge "Defaults") whenever a `check.mergeCommand`
176
+ * resolves — to its own value or, when absent, to `check.gateCommand`. In the fallback case
177
+ * (no separate `mergeCommand`) the in-lock command IS the gate command the gate already ran
178
+ * green on the rebased head, so the in-lock run would add nothing; with a separate
179
+ * `check.mergeCommand` the gate's green was the gate command, not the merge one. Either way
180
+ * it skips and seeds nothing (the main watch decides main's verdict).
155
181
  * - doc-only delta ahead of main (the gate's own exemption test): cannot break the build.
156
182
  * - a code change whose rebased tree differs from the gate-verified one only in exempt paths
157
183
  * (onlyExemptDriftFromVerified): main moved under the review by doc commits alone, so the
@@ -168,10 +194,15 @@ async function verifyMerge(ctx, wt, rebasedHead, verifiedHead) {
168
194
  // re-review sets verifiedHead to the resolved head, and that second rebase is then a no-op).
169
195
  // A no-op rebase the gate never verified — its pre-check timed out or was killed and left
170
196
  // GateResult.verifiedHead undefined — falls through and runs the merge-scope check, which
171
- // fails closed on the same timeout. With a check.gateCommand configured the gate ran only
172
- // that cheaper check, so even a verified no-op rebase must run the full check here: the
173
- // merge scope is what verifies a single change (and each change of an abandoned stack),
174
- // and a gateCommand green must never seed the baseline.
197
+ // fails closed on the same timeout. With a check.gateCommand or check.mergeCommand
198
+ // configured (Fast merge "Defaults": the lock runs the gate command when mergeCommand is
199
+ // absent): in the fallback case (no separate `mergeCommand`) the in-lock command IS the gate
200
+ // command the gate already ran green on the rebased head, so the verified no-op rebase adds
201
+ // nothing — skip it; with a separate `mergeCommand` the gate's green was the gate command.
202
+ // Either way it seeds nothing — a cheaper command's green never seeds the baseline: main's
203
+ // verdicts come from the main watch's full check on the tip.
204
+ if (rebasedHead === verifiedHead && mergeCommandOf(ctx.config) !== undefined)
205
+ return true;
175
206
  if (rebasedHead === verifiedHead && gateCommandOf(ctx.config) === undefined) {
176
207
  noteGreenBaseline(rebasedHead);
177
208
  return true;
@@ -204,19 +235,40 @@ async function verifyMerge(ctx, wt, rebasedHead, verifiedHead) {
204
235
  // runScopedBuildCheck, shared with the review gate's pre-check. The merge cell names the
205
236
  // check (or its wait for a permit) while it runs, then the merge again for the ff.
206
237
  setMergeStage(ctx.root, ctx.role, "build-check");
207
- const check = await runScopedBuildCheck(ctx.root, ctx.role, "merge", wt, ctx.config, undefined, undefined, undefined, checkWaitStage(ctx.root, [ctx.role]));
238
+ const runMergeCheck = () => runScopedBuildCheck(ctx.root, ctx.role, "merge", wt, ctx.config, undefined, undefined, undefined, checkWaitStage(ctx.root, [ctx.role]));
239
+ let check = await runMergeCheck();
240
+ if (check?.outcome.status === "failed" && !unverifiedTreeOutcome(check.outcome) && !onlyKnownMainFailures(ctx.root, check.outcome.failingTests)) {
241
+ // One immediate re-run of the same tree: a pass makes the failed run a flake. The change
242
+ // merges and each test the failed run named is quarantined.
243
+ const failed = check.outcome;
244
+ const retry = await runMergeCheck();
245
+ if (retry?.outcome.status === "passed") {
246
+ warnEventBestEffort(ctx.root, ctx.role, `${MERGE_FLAKY_WARNING_PREFIX}${failureHeadline(failed.outputTail) ?? describeCheck(check.check)}`);
247
+ quarantineTests(ctx.root, failed.failingTests, failed.outputTail);
248
+ check = retry;
249
+ }
250
+ }
208
251
  setMergeStage(ctx.root, ctx.role, "merging");
209
252
  if (!check)
210
253
  return true; // No declared check: nothing to run, exactly like the gate skipping its pre-check.
211
254
  if (check.outcome.status === "failed") {
255
+ // Failing only what main already fails is not a red change: the fast-forward goes ahead, with
256
+ // no strike and no green seeded for a tree that did not pass.
257
+ const known = unverifiedTreeOutcome(check.outcome) ? null : onlyKnownMainFailures(ctx.root, check.outcome.failingTests);
258
+ if (known) {
259
+ warnEventBestEffort(ctx.root, ctx.role, `merge check fails only main's known failures: ${known.join(", ")}`);
260
+ return true;
261
+ }
212
262
  ctx.onMergeCheckRed?.(check.check, check.outcome);
213
263
  return false;
214
264
  }
215
265
  if (check.outcome.status === "skipped")
216
266
  return true; // No npm / broken toolchain — the helper already warned.
217
- // Green on exactly the tree that becomes main: seed it so the next tick's red-main baseline
267
+ // Green on exactly the tree that becomes main: seed it so the next run's red-main baseline
218
268
  // check is a cache hit instead of one redundant full-suite run.
219
- noteGreenBaseline(rebasedHead);
269
+ // A mergeCommand green is not the full check's: main's verdict comes from the main watch.
270
+ if (mergeCommandOf(ctx.config) === undefined)
271
+ noteGreenBaseline(rebasedHead);
220
272
  return true;
221
273
  }
222
274
  /** True, after seeding the red-main baseline, when the tree about to merge differs from the tree
@@ -229,14 +281,20 @@ async function verifyMerge(ctx, wt, rebasedHead, verifiedHead) {
229
281
  * that only rewrote SHAs) is the verified bytes themselves and seeds an observed green. Any
230
282
  * other drift seeds an inherited one, which attribution re-checks before trusting
231
283
  * (main-baseline.ts's observedOnly). Not taken without a verified head (the pre-check was skipped
232
- * or never ran) or with a `check.gateCommand`, for the same reasons the no-op skip above is not.
284
+ * or never ran). With an in-lock command configured (a gate or merge command — Fast merge
285
+ * "Defaults": the lock runs the gate command when mergeCommand is absent) the drift merges
286
+ * without a seed: the main watch decides main's verdict.
233
287
  * A git failure means "not proven" and the check runs. */
234
288
  async function onlyExemptDriftFromVerified(ctx, wt, rebasedHead, verifiedHead) {
235
- if (verifiedHead === undefined || gateCommandOf(ctx.config) !== undefined)
289
+ if (verifiedHead === undefined)
290
+ return false;
291
+ if (gateCommandOf(ctx.config) !== undefined && mergeCommandOf(ctx.config) === undefined)
236
292
  return false;
237
293
  const drift = await treeDiffFiles(wt, verifiedHead, rebasedHead);
238
294
  if (drift === null || !isExemptDiff(drift, ctx.exemptPaths))
239
295
  return false;
296
+ if (mergeCommandOf(ctx.config) !== undefined)
297
+ return true; // no seed: the watch decides main's verdict
240
298
  if (drift.length === 0)
241
299
  noteGreenBaseline(rebasedHead);
242
300
  else
@@ -269,12 +327,13 @@ async function resolveConflict(ctx, wt, preMergeHead) {
269
327
  const files = await conflictedFiles(wt);
270
328
  // Deterministic first pass (plans/parallel-work-instances.md, part 3/7): insert-only
271
329
  // conflicts in the backlog markdown — two merges pasting different entries as the first
272
- // under ## Done — carry no authored bytes, so they are settled without a model run. Only the
273
- // files it could not resolve (code, or a same-entry edit) reach the resolver below.
330
+ // under ## Done, or new entries above an entry the other side deleted — carry no authored
331
+ // bytes, so they are settled without a model run. Only the files it could not resolve (code,
332
+ // or a same-entry edit) reach the resolver below.
274
333
  const remaining = await resolveBacklogInsertConflicts(wt, files);
275
334
  if (remaining.length === 0) {
276
335
  // A second stop is only possible when the branch holds more than the one insert-only
277
- // commit. One attempt per tick.
336
+ // commit. One attempt per run.
278
337
  return continueOrAbortRebase(wt);
279
338
  }
280
339
  // The prompt names the project's own check, detected the way the in-lock re-check detects it,
@@ -299,10 +358,10 @@ async function resolveConflict(ctx, wt, preMergeHead) {
299
358
  change,
300
359
  main: commits,
301
360
  mainOmitted: omitted,
302
- }), `tumwater-${ctx.role}-${ctx.tick}-conflict`,
361
+ }), `tumwater-${ctx.role}-${ctx.runNumber}-conflict`,
303
362
  // The resolver rides the strong tier (plans/model-tiers.md part 4/8): resolution is rare,
304
363
  // tolerant of latency, and edits code inside merge. Its spend still folds into the
305
- // authoring role's usage — the fold belongs to the loop's wiring, not to the config.
364
+ // authoring role's usage — the fold belongs to the runner's wiring, not to the config.
306
365
  resolverConfig(ctx.config));
307
366
  }
308
367
  finally {
@@ -312,7 +371,7 @@ async function resolveConflict(ctx, wt, preMergeHead) {
312
371
  await abortSync(wt);
313
372
  return false;
314
373
  }
315
- // A second stop is only possible when pi itself authored extra commits during the tick.
316
- // One resolution attempt per tick.
374
+ // A second stop is only possible when pi itself authored extra commits during the run.
375
+ // One resolution attempt per run.
317
376
  return continueOrAbortRebase(wt);
318
377
  }
@@ -0,0 +1,69 @@
1
+ /** Operator reordering of a waiting merge (PLANS.md `tumwater queue --top <n>`): moving the
2
+ * Nth queued merge to the head of the execution order. Split out of merge-queue.ts so the
3
+ * queue stays the pure reader/writer it is: this module owns the reorder half of an
4
+ * unattempted entry, renaming its queue file to a stamp one below the queue's minimum — the
5
+ * merge-queue twin of inbox-top.ts (which does the same for the prompt queue). It mirrors
6
+ * inbox-top.ts's rename-and-event pairing and merge-cancel.ts's stance: no lock, one file per
7
+ * entry, tolerant of a concurrent drop. */
8
+ import path from "node:path";
9
+ import fs from "node:fs";
10
+ import { logEventBestEffort } from "../events/events.js";
11
+ import { errCode } from "../errno.js";
12
+ import { queueFileName, queueFileStamp } from "../files/file-queue.js";
13
+ import { queuedMergeFiles } from "./merge-queue.js";
14
+ /** Move the Nth queued merge — 1-based, as `tumwater queue` numbers it and `--cancel`
15
+ * resolves it — to the head of the execution order, so it is the next due entry the queue
16
+ * takes when ready. Position 1 reports `already-first` and changes nothing; an out-of-range
17
+ * position (below 1 or past the readable depth) reports `out-of-range` with the current
18
+ * depth and changes nothing. The move is one rename: the entry JSON is never rewritten, so
19
+ * every field (`enqueuedAt`, `notBefore`, `retry`, `revisionRound`, `priorReview`) is
20
+ * unchanged; giving the file a stamp one below the queue's readable minimum sorts it first
21
+ * (falling back to `Date.now()` when no stamp parses), stepping one lower while the
22
+ * destination exists (a concurrent writer may have just taken the name we computed). One
23
+ * `merge_topped` event is logged under the entry's role. The rename is guarded: if the
24
+ * target vanished between the list and the rename (a concurrent drain took the change), the
25
+ * move answers `gone` and changes nothing rather than throwing. No lock and no pin write:
26
+ * the module mirrors merge-cancel.ts's "one file per entry, tolerant of a concurrent drop"
27
+ * stance. */
28
+ export function moveQueuedMergeToTop(root, position) {
29
+ const files = queuedMergeFiles(root);
30
+ if (position < 1 || position > files.length)
31
+ return { status: "out-of-range", depth: files.length };
32
+ const target = files[position - 1];
33
+ if (position === 1) {
34
+ return {
35
+ status: "already-first",
36
+ role: target.entry.role,
37
+ sha: target.entry.sha,
38
+ summary: target.entry.summary,
39
+ };
40
+ }
41
+ const stamps = files
42
+ .map((q) => queueFileStamp(path.basename(q.file)))
43
+ .filter((s) => s !== null);
44
+ let stamp = (stamps.length > 0 ? Math.min(...stamps) : Date.now()) - 1;
45
+ const dir = path.dirname(target.file);
46
+ let newFile = path.join(dir, queueFileName(stamp, 0, ".json"));
47
+ while (fs.existsSync(newFile)) {
48
+ stamp--;
49
+ newFile = path.join(dir, queueFileName(stamp, 0, ".json"));
50
+ }
51
+ try {
52
+ fs.renameSync(target.file, newFile);
53
+ }
54
+ catch (err) {
55
+ // The queue drained the target between our list and this rename (an orchestrator took
56
+ // the change): a concurrent drop is a normal race, never an error — the entry has
57
+ // already left the queue, so there is nothing to top. Any other rename failure is real.
58
+ if (errCode(err) === "ENOENT")
59
+ return { status: "gone" };
60
+ throw err;
61
+ }
62
+ logEventBestEffort(root, {
63
+ source: target.entry.role,
64
+ type: "merge_topped",
65
+ commit: target.entry.sha,
66
+ summary: target.entry.summary,
67
+ });
68
+ return { status: "topped", role: target.entry.role, sha: target.entry.sha, summary: target.entry.summary };
69
+ }
@@ -1,11 +1,10 @@
1
- // vocabulary: plain
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { gateRoleConfig } from "../gates/budget-gates.js";
5
4
  import { reviewRunConfig, seamConcurrencyKey } from "../config/config-views.js";
6
5
  import { providerWaitStage } from "./merge-slot.js";
7
6
  import { withProviderLease } from "../concurrency/provider-lease.js";
8
- import { LoopRunner } from "../loop/loop.js";
7
+ import { RoleRunner } from "../runner/runner.js";
9
8
  import { branchHead, isMergedInto } from "../git/git.js";
10
9
  import { addMergeChange, mergeUsage, removeMergeChange, setMergeChangeStatus } from "./merge-slot.js";
11
10
  import { dropMerge, entryRevisionFields, queuedMergeFiles, staleHeadFile } from "./merge-queue.js";
@@ -28,21 +27,21 @@ export function verifyLimit(maxConcurrent) {
28
27
  /** Resolve a merge entry's authoring runner: the live runner when the role is enabled
29
28
  * (runners are never removed from the array on disable — only a warning event fires); a role
30
29
  * disabled before this process started has no runner, so a throwaway one supplies the same
31
- * wiring (loop-pi.ts, runMergePi, foldMergeUsage) and a disk-loaded state to fold and save
30
+ * wiring (runner-pi.ts, runMergePi, foldMergeUsage) and a disk-loaded state to fold and save
32
31
  * on. Both share the live config, like every runner — the director takes directorConfig (the live
33
32
  * config, its budget-gate exemption, unless a used-up window moved it), every other role takes
34
33
  * roleConfig (gateRoleConfig's single home for the rule; the scheduler assigns the live runners
35
34
  * by it every poll). */
36
- function resolveAuthor(ctx, role) {
35
+ export function resolveAuthor(ctx, role) {
37
36
  const { root, mainBranch, signal, runners, liveConfig, roleConfig, directorConfig } = ctx;
38
37
  return (runners.find((r) => r.role === role) ??
39
- new LoopRunner(root, role, gateRoleConfig(role, liveConfig, roleConfig, directorConfig), mainBranch, signal));
38
+ new RoleRunner(root, role, gateRoleConfig(role, liveConfig, roleConfig, directorConfig), mainBranch, signal));
40
39
  }
41
40
  /** Drop a torn queue head. A torn head (torn or foreign — a partial write an older build left,
42
41
  * a hand-placed file) makes headMerge read null forever — nothing else drops it, stranding
43
- * every live entry behind it and pinning their authors' ticks via the interlock. Drop it with
42
+ * every live entry behind it and pinning their authors' runs via the interlock. Drop it with
44
43
  * one warning; the healthy entries behind it are verified in the same poll. The crashed
45
- * entry's commit, if any, still rides its merge ref into next-tick leftover recovery
44
+ * entry's commit, if any, still rides its merge ref into next-run leftover recovery
46
45
  * (BUGS.md 2026-09-17). */
47
46
  function dropTornHead(root) {
48
47
  const stale = staleHeadFile(root);
@@ -98,6 +97,9 @@ export async function drainVerify(ctx, p) {
98
97
  return;
99
98
  if (liveRoles(p).has(entry.role))
100
99
  continue;
100
+ // A re-queued review_error waits out its delay; the entry stays queued and holds its author.
101
+ if (entry.notBefore !== undefined && Date.now() < entry.notBefore)
102
+ continue;
101
103
  if (await mergedIntoMain(ctx.root, entry.sha, ctx.mainBranch, mainHead)) {
102
104
  dropMerge(file);
103
105
  removeMergeChange(ctx.root, entry.role);
@@ -163,10 +165,10 @@ export function startVerify(ctx, p, entry, file) {
163
165
  // so a review run that lands on a different provider than the verification's review seam
164
166
  // re-keys it there, and a re-key park is shown by providerWaitStage; its slot wait drops
165
167
  // the key (pool-slot rule).
166
- verdict = await withProviderLease(lease, semaphore, () => verifyRequest({ root, mainBranch, config: author.config, signal: () => verify.controller.signal, runGatePi: (opts) => author.runGatePi(opts) }, {
168
+ verdict = await withProviderLease(lease, semaphore, () => verifyRequest({ root, mainBranch, config: author.config, signal: () => verify.controller.signal, runGatePi: (opts) => author.runGatePi(opts), reviewHeld: () => ctx.startHeld() }, {
167
169
  role,
168
170
  sha: entry.sha,
169
- tick: entry.run,
171
+ runNumber: entry.run,
170
172
  summary: entry.summary,
171
173
  body: entry.body,
172
174
  highFriction: entry.highFriction,
@@ -198,7 +200,7 @@ export function startVerify(ctx, p, entry, file) {
198
200
  }
199
201
  else {
200
202
  const result = verdict.kind === "stack" ? "aborted" : verdict.result;
201
- await settleMergeOutcome(root, entry, author.state, result, Date.now() - startedAt, usage, file);
203
+ await settleMergeOutcome(root, entry, author.state, result, Date.now() - startedAt, usage, file, mainBranch);
202
204
  if (verify.userAborted)
203
205
  await discardPinnedRefs(root, [role]);
204
206
  }
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // vocabulary: plain
3
2
  /** The `tumwater-notes` command — the second bin entry (dist/src/notes-cli.js), the one
4
3
  * carrier for writing a role's note on every backend (plans/notes-command.md "The
5
4
  * `tumwater-notes` command"). A run finds it on its PATH (src/agent/notes-shim.ts) and hands
@@ -14,9 +13,12 @@
14
13
  * show print the note (nothing when there is none)
15
14
  * clear empty the note
16
15
  *
17
- * Exits 0 on success, 1 on a refusal (empty input, over-cap note, or no
18
- * TUMWATER_NOTES_PATH), and 2 with usage for anything else. */
16
+ * Exits 0 on success, 1 on a refusal (empty input, over-cap note, a note file that cannot be
17
+ * written or read, or no TUMWATER_NOTES_PATH), and 2 with usage for an unknown subcommand (a
18
+ * known subcommand with bad arguments refuses with usage and exit 1). */
19
19
  import fs from "node:fs";
20
+ import { readTextIfExists } from "./files/files.js";
21
+ import { errorMessage } from "./text/text.js";
20
22
  import { validateRoleNote, writeRoleNote } from "./agent/role-notes-write.js";
21
23
  /** The note's path, read ONLY from the environment — never from an argument. */
22
24
  const NOTES_PATH = process.env.TUMWATER_NOTES_PATH;
@@ -29,14 +31,10 @@ const USAGE = [
29
31
  function readStdin() {
30
32
  return fs.readFileSync(0, "utf8");
31
33
  }
32
- /** Read the note's current text; a missing note reads as empty (show prints nothing). */
34
+ /** Read the note's current text; a missing note reads as empty (show prints nothing). Any
35
+ * other read failure (EACCES, EISDIR) throws, so it is never mistaken for an empty note. */
33
36
  function readNote() {
34
- try {
35
- return fs.readFileSync(NOTES_PATH, "utf8");
36
- }
37
- catch {
38
- return "";
39
- }
37
+ return readTextIfExists(NOTES_PATH) ?? "";
40
38
  }
41
39
  /** Refuse with `message` on stderr and exit 1 — the exit code for every expected refusal. */
42
40
  function refuse(message) {
@@ -51,7 +49,12 @@ function writeNote(text) {
51
49
  const error = validateRoleNote(text);
52
50
  if (error)
53
51
  return refuse(error);
54
- writeRoleNote(NOTES_PATH, text);
52
+ try {
53
+ writeRoleNote(NOTES_PATH, text);
54
+ }
55
+ catch (err) {
56
+ return refuse(`could not save the note: ${errorMessage(err)}`);
57
+ }
55
58
  process.stdout.write(`notes saved (${Buffer.byteLength(text, "utf8")} bytes)\n`);
56
59
  return 0;
57
60
  }
@@ -72,8 +75,8 @@ function main() {
72
75
  try {
73
76
  text = fs.readFileSync(flagArg, "utf8");
74
77
  }
75
- catch {
76
- return refuse(`cannot read ${flagArg}`);
78
+ catch (err) {
79
+ return refuse(`cannot read ${flagArg}: ${errorMessage(err)}`);
77
80
  }
78
81
  return writeNote(text);
79
82
  }
@@ -81,12 +84,24 @@ function main() {
81
84
  case "show":
82
85
  if (flag !== undefined)
83
86
  return refuse(USAGE);
84
- process.stdout.write(readNote());
87
+ let note;
88
+ try {
89
+ note = readNote();
90
+ }
91
+ catch (err) {
92
+ return refuse(`could not read the note: ${errorMessage(err)}`);
93
+ }
94
+ process.stdout.write(note);
85
95
  return 0;
86
96
  case "clear":
87
97
  if (flag !== undefined)
88
98
  return refuse(USAGE);
89
- writeRoleNote(NOTES_PATH, "");
99
+ try {
100
+ writeRoleNote(NOTES_PATH, "");
101
+ }
102
+ catch (err) {
103
+ return refuse(`could not clear the note: ${errorMessage(err)}`);
104
+ }
90
105
  process.stdout.write("notes cleared\n");
91
106
  return 0;
92
107
  default: