tumwater 0.2.0 → 0.4.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 (464) 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 +55 -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 +6 -4
  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 +404 -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 +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -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 +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -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-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -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 +65 -3
  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 +181 -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 +183 -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 +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -0,0 +1,352 @@
1
+ import { publishInfoFields } from "../team/orchestrator-info.js";
2
+ import { baseRoleOf, runnerEnabled } from "../roles/runner-ids.js";
3
+ import { DIRECTOR_ROLE } from "../roles/roles.js";
4
+ import { reviewHoldKey, pollDeferralInputs, pollRunnerReasons } from "./orchestrator-scheduling.js";
5
+ import { seamConcurrencyKey } from "../config/config-views.js";
6
+ import { launchDueRuns } from "./orchestrator-launch.js";
7
+ import { branchHead, currentBranch } from "../git/git.js";
8
+ import { queuedMergeFiles, queuedMergeRoles } from "../merge/merge-queue.js";
9
+ import { requeueMainRedPin } from "../merge/merge-requeue.js";
10
+ import { drainMergeQueue, settleAbortedApproved } from "../merge/merge-drain.js";
11
+ import { abortableMerges, mergeTasks } from "../merge/merge-pipeline.js";
12
+ import { TUMWATER_SOURCE } from "../events/events.js";
13
+ import { consumeAbortRequests, consumeReclaimRequest, consumeRestartRequest, consumeResetRequest, consumeWakeRequest, } from "../operator/operator-requests.js";
14
+ import { checkPermitSnapshot } from "../concurrency/check-permit.js";
15
+ import { slotPoolSnapshot } from "../git/worktree-pool.js";
16
+ import { redeployTarget } from "../baseline/main-baseline.js";
17
+ import { p75RunDurationMs } from "../run/run-timing.js";
18
+ import { pollTeamGates } from "../gates/gate-polls.js";
19
+ import { heldProviders, windowHeldProviders } from "../team/team-hold.js";
20
+ import { workReserveCount } from "../scheduling/claims.js";
21
+ export function newPollState() {
22
+ return { restart: false, holdForRestart: false, diskHeld: false, warnedBranchDivergence: false, lastReserve: 0 };
23
+ }
24
+ /** The start gate every reserved run passes the moment its permit is granted
25
+ * (runTimedRoleRun's `held`): closed while a restart is pending, and after one is decided so
26
+ * no waiter the shutdown hands a permit to starts on the build being replaced. One predicate,
27
+ * so another team-wide hold on new runs can close the same gate point. */
28
+ function runStartHeld(state) {
29
+ return state.holdForRestart || state.restart || state.diskHeld;
30
+ }
31
+ /** The one start-gate rule for merges: a restart/disk hold's `held` counts against every role,
32
+ * while a hold on the reviewer's provider (`reviewHeld`) parks only the entries a pre-merge
33
+ * review gates — a role on `review.afterMerge` keeps draining (it merges without a reviewer),
34
+ * and no role at all (the conservative team-wide verdict a caller that cannot name a role
35
+ * reads) still holds. Shared by pollMergeDrain's startHeld and the merge tests, so the
36
+ * production wiring and its coverage cannot drift. */
37
+ export function mergeStartHeld(held, reviewHeld, afterMergeRoles, role) {
38
+ return held || (reviewHeld && (role === undefined || !afterMergeRoles.has(baseRoleOf(role))));
39
+ }
40
+ /** The base roles whose review runs after merging — the config's `review.afterMerge` list, mapped
41
+ * through `baseRoleOf` so a listed base id stands for all its parallel instances. The one build of
42
+ * the set pollMergeDrain's start gate and pollSchedule's launch gate both exempt from a
43
+ * reviewer-hold, so the two passes cannot read the standing differently. */
44
+ function afterMergeRoleIds(config) {
45
+ return new Set((config.review.afterMerge ?? []).map(baseRoleOf));
46
+ }
47
+ /** Live-reload, notify/retention/launchservices, CLI request markers, abort settling and the
48
+ * branch-divergence watch. Writes state.warnedBranchDivergence. */
49
+ export async function pollRequestsAndWatch(ctx, state) {
50
+ const { root, mainBranch, runners } = ctx;
51
+ // Live-reload tumwater.json — the single reload point shared by all roles.
52
+ // src/config/config-live.ts owns the last-known-good retention and the edge-triggered
53
+ // warnings/events around it.
54
+ const liveConfig = ctx.liveReload.poll();
55
+ // The notify command rides the same last-known-good reload (src/events/notify.ts): a live
56
+ // `config set notify` edit takes effect on the next poll, no restart.
57
+ ctx.notifier.update(liveConfig);
58
+ // Live session retention (the last restart-only setting): the edge-triggered
59
+ // retention_changed event, the once-per-day gate, and the prune itself live in
60
+ // src/orchestrator/retention.ts. The live config (last-known-good while the file is broken or
61
+ // missing) drives the check, like the budget gate.
62
+ ctx.retentionPruner.poll(root, liveConfig.sessionRetentionDays);
63
+ // A missing or stale opencode catalog is refetched at most once an hour while opencode is routed.
64
+ ctx.opencodeCatalogRefresh.poll(liveConfig);
65
+ // launchservicesd's Mach-port budget (src/process/launch-services.ts owns it):
66
+ // a background sample at most every 15 minutes, and a warning days before the kernel
67
+ // kills the daemon and wedges the Mac's GUI session. Never awaited — a poll does not
68
+ // wait on `top`.
69
+ void ctx.launchServicesWatch?.poll();
70
+ // Consume CLI request markers: a reset-counters request, a wake request, a forced-restart
71
+ // request, and per-role abort requests.
72
+ consumeResetRequest(root, runners);
73
+ consumeWakeRequest(root, runners);
74
+ consumeRestartRequest(root, ctx.redeploy);
75
+ consumeReclaimRequest(root, ctx.reclaim);
76
+ consumeAbortRequests(root, runners, abortableMerges(ctx.merges));
77
+ await settleAbortedApproved(root, ctx.merges);
78
+ // branchHead reads the ref files first (microsecond-scale; this runs every poll) and
79
+ // spawns `git rev-parse` only when they cannot resolve it. "" means main does not exist
80
+ // yet — isEligible treats an empty head as "no wake".
81
+ const mainHead = (await branchHead(root, mainBranch)) ?? "";
82
+ // Branch-divergence watch, once per poll: the team keeps fast-forwarding the branch it
83
+ // resolved at startup — the safe behavior, since role worktrees are based on it — and one
84
+ // warning names the divergence instead of leaving it mysterious. Back on the target branch
85
+ // re-arms the check.
86
+ const liveBranch = await currentBranch(root);
87
+ if (liveBranch !== null && liveBranch !== mainBranch) {
88
+ if (!state.warnedBranchDivergence) {
89
+ state.warnedBranchDivergence = true;
90
+ ctx.warn(`your checkout moved to ${liveBranch} — the team keeps merging into ${mainBranch}`);
91
+ }
92
+ }
93
+ else {
94
+ state.warnedBranchDivergence = false;
95
+ }
96
+ return { liveConfig, mainHead, now: Date.now() };
97
+ }
98
+ /** The team's gates and alarms, one poll of the family (src/gates/gate-polls.ts): the daily
99
+ * budget gate — with its derived-state publish and the fallback handback to the primary — the
100
+ * operator and per-role pause gates, quiet hours, the team-wide failure hold, and the two
101
+ * observational storm alarms. The family's cross-poll memory lives in gateStates, advanced in
102
+ * place. The failure hold is deliberately not among the returned verdicts — the start gate and
103
+ * the merges' startHeld read the LATEST poll's verdict at permit time from gateStates.teamHold,
104
+ * so a waiter granted its permit after later polls ran meets the hold as it stands then.
105
+ * Writes state.diskHeld. */
106
+ export function pollGatesAndHolds(ctx, state, { liveConfig, now }) {
107
+ const { root, runners, gateStates } = ctx;
108
+ const gates = pollTeamGates(gateStates, {
109
+ root,
110
+ runners,
111
+ liveConfig,
112
+ modelsPath: ctx.modelsPath,
113
+ now,
114
+ info: ctx.info,
115
+ reclaim: ctx.reclaim,
116
+ });
117
+ // The latest poll's disk verdict, hoisted for runStartHeld's permit-time reads
118
+ // (plans/disk-floor.md, part 1/4).
119
+ state.diskHeld = gates.diskHeld;
120
+ // Publish the permit pool's live state (plans/provider-concurrency.md §8): observers
121
+ // (status, TUI, GUI) read one file instead of coupling to this Semaphore, and the team
122
+ // line shows per-provider counts only while a cap map is configured. Written only when
123
+ // it changes, like budget and disk; a map-less team publishes `byKey` empty, which
124
+ // renders exactly as before. The check-permit pool rides along, so status can show checks
125
+ // running and waiting while merges stall. The workspace pool's live state rides along too:
126
+ // one small slots.json read per poll, and publishInfoFields rewrites the info file only
127
+ // when a value changed.
128
+ publishInfoFields(root, ctx.info, [
129
+ ["permits", ctx.semaphore.snapshot()],
130
+ ["checkPermits", checkPermitSnapshot()],
131
+ ["slotPool", slotPoolSnapshot(root)],
132
+ ]);
133
+ // The failure hold, keyed per provider (PLANS.md 2026-10-05): a role is held when ITS
134
+ // run model's provider stands under a hold, and the roles a pre-merge review gates are held
135
+ // when the reviewer's provider is (with the review gate on their approved change could not
136
+ // merge without a reviewer — while a role on `review.afterMerge` merges without one, so it is
137
+ // exempt, and the merge queue reads the same scoped reviewHeld verdict). Key presence in the
138
+ // map is relapse memory, not a hold: heldProviders() reads only standing holds (`until`
139
+ // non-null), so a lifted hold never keeps blocking.
140
+ const teamHeldProviders = heldProviders(gateStates.teamHold);
141
+ const teamHeldNow = () => heldProviders(gateStates.teamHold);
142
+ // The reviewer's seam is read from the config merges actually review under — the poll's
143
+ // roleConfig, which a used-up claude window or the cap has already moved to a fallback —
144
+ // so a reviewer that left claude no longer holds the roles a pre-merge review gates on the
145
+ // claude hold.
146
+ const reviewSeamKey = reviewHoldKey(gates.roleConfig);
147
+ return {
148
+ ...gates,
149
+ teamHeldProviders,
150
+ teamHeldNow,
151
+ reviewHeldNow: () => liveConfig.review.enabled && teamHeldNow().has(reviewSeamKey),
152
+ reviewHeld: liveConfig.review.enabled && teamHeldProviders.has(reviewSeamKey),
153
+ roleProviders: new Map(runners.map((r) => [r.role, r.runProvider(now)])),
154
+ };
155
+ }
156
+ /** Self-redeploy (src/redeploy/redeployer.ts): with main's head in hand, let the state
157
+ * machine observe it. `hold` starts no new runs at all — director included; a restart
158
+ * takes effect within the drain's window plus a bounded merge hand-off, and its prompt waits
159
+ * in the inbox for the new build — while the green check/compile/drain run in the
160
+ * background. That covers runs reserved before the hold too: a waiter parked in the
161
+ * semaphore queue that is granted a permit mid-drain meets the closed start gate
162
+ * (runStartHeld), releases the permit, and hands its reservation back so it re-schedules
163
+ * once the hold lifts or on the new build. `restart` means dist/ already holds the new
164
+ * build: stop scheduling, abort whatever role runs the drain gave up waiting for — the
165
+ * permit holders; they resume on the new build — and return true. A director run can never
166
+ * be in flight here — poll only returns `restart` once it has finished. (holdForRestart keeps
167
+ * the previous poll's verdict until this one's is in: resetting it before the await
168
+ * would open the gate for a waiter granted a permit while poll runs.)
169
+ * Main watch (src/baseline/main-watch.ts): records the full check's verdict on main's new
170
+ * tip in the background; nothing here waits for it. Writes state.restart and
171
+ * state.holdForRestart; returns whether the loop should stop. */
172
+ export async function pollRedeploy(ctx, state, { liveConfig, mainHead, now }) {
173
+ const { redeploy } = ctx;
174
+ if (!ctx.onceMode)
175
+ ctx.mainWatch.poll(mainHead, state.holdForRestart || state.diskHeld, liveConfig);
176
+ if (!redeploy)
177
+ return false;
178
+ const action = await redeploy.poll(await redeployTarget(ctx.root, mainHead, redeploy.build.sha), {
179
+ roleInFlight: ctx.rolePermitHolders.size,
180
+ directorInFlight: ctx.directorInFlight.size,
181
+ roleRunP75Ms: p75RunDurationMs(ctx.roleRunDurationsMs),
182
+ }, liveConfig.autoRestart, now);
183
+ publishInfoFields(ctx.root, ctx.info, [["build", redeploy.status(now)]]);
184
+ if (action === "restart") {
185
+ state.restart = true;
186
+ // Only permit holders have a pi run to cut off (the director is guaranteed finished by
187
+ // then): a parked waiter meets the closed start gate whenever the shutdown hands it a
188
+ // permit, so it needs no abort — and aborting for it alone would also cut off an
189
+ // in-flight merge that no permit-holding run put at stake. The role-only controller
190
+ // keeps that promise: the merge, wired to the full signal, runs on into the hand-off
191
+ // window the wait announces (BUGS.md 2026-09-30).
192
+ if (ctx.rolePermitHolders.size > 0)
193
+ ctx.internalRoleStop.abort();
194
+ return true;
195
+ }
196
+ state.holdForRestart = action === "hold";
197
+ return false;
198
+ }
199
+ /** Merge queue 3/5 — drain the durable merge queue while neither a restart nor a disk hold is
200
+ * pending (the scheduler's WHEN; merge-drain.ts owns the HOW — the verification stage, its
201
+ * merge slot, and the dedupe against main). A restart or disk hold stops the whole drain, as it
202
+ * starts no run; what is already in flight carries on, and a verify parked for its permit
203
+ * meets the same start gate as a parked run when the permit comes. A hold on the reviewer's
204
+ * provider parks only the entries a pre-merge review gates, per the role-scoped mergeStartHeld:
205
+ * a `review.afterMerge` role's approved/queued changes keep draining. */
206
+ export async function pollMergeDrain(ctx, state, { liveConfig, mainHead }, gates) {
207
+ if (state.holdForRestart || state.diskHeld)
208
+ return;
209
+ const { root, mainBranch } = ctx;
210
+ // The roles whose review runs after merging (plans post-merge-review role): while the
211
+ // reviewer is held, their approved/queued changes keep draining, so the per-entry start gate
212
+ // exempts them from the reviewer-hold leg below.
213
+ const afterMergeRoles = afterMergeRoleIds(liveConfig);
214
+ // A `main_red` pin the pipeline kept goes back on the queue once main's head has moved.
215
+ for (const r of ctx.runners)
216
+ await requeueMainRedPin(root, mainBranch, r.state, mainHead);
217
+ await drainMergeQueue({
218
+ root,
219
+ mainBranch,
220
+ signal: ctx.signal,
221
+ semaphore: ctx.semaphore,
222
+ runners: ctx.runners,
223
+ liveConfig,
224
+ roleConfig: gates.roleConfig,
225
+ directorConfig: gates.directorConfig,
226
+ piDefault: ctx.piDefault,
227
+ startHeld: (role) => mergeStartHeld(runStartHeld(state), gates.reviewHeldNow(), afterMergeRoles, role),
228
+ }, ctx.merges);
229
+ }
230
+ /** The scheduling pass: the deferral inputs feed pollRunnerReasons, whose reasons map
231
+ * launchDueRuns starts runs from. */
232
+ export async function pollSchedule(ctx, state, { liveConfig, mainHead, now }, gates) {
233
+ const { root, runners, gateStates } = ctx;
234
+ const inputs = pollDeferralInputs(root, runners, liveConfig, gateStates, gates, ctx.breakerNow());
235
+ // The roles whose review runs after merging: while the reviewer is held their runs stay
236
+ // launchable (the pre-merge gate skips their review), so the permit-time start gate exempts
237
+ // them from the reviewer-hold leg below, exactly as the scheduling pass and merge drain do.
238
+ const afterMergeRoles = afterMergeRoleIds(liveConfig);
239
+ const reasons = await pollRunnerReasons({
240
+ root,
241
+ runners,
242
+ mainBranch: ctx.mainBranch,
243
+ merges: ctx.merges,
244
+ once: ctx.once,
245
+ now,
246
+ mainHead,
247
+ userPaused: gates.userPaused,
248
+ quietNow: gates.quietNow,
249
+ pausedRoles: gates.pausedRoles,
250
+ capPaused: gates.capPaused,
251
+ maintenanceQuota: gates.maintenanceQuota,
252
+ bootstrapHeld: gates.bootstrapHeld,
253
+ budgetPausedRoles: gates.budgetPausedRoles,
254
+ probeRoles: inputs.probeRoles,
255
+ reviewStrongPaused: inputs.reviewStrongPaused,
256
+ roleQuietHeld: gates.roleQuietHeld,
257
+ holdForRestart: state.holdForRestart,
258
+ diskHeld: state.diskHeld,
259
+ heldProviders: gates.teamHeldProviders,
260
+ windowHeldProviders: windowHeldProviders(gateStates.teamHold),
261
+ reviewHeld: gates.reviewHeld,
262
+ afterMergeRoles,
263
+ roleProviders: gates.roleProviders,
264
+ openBugsNow: inputs.openBugsNow,
265
+ workBacklogOpen: inputs.workBacklogOpen,
266
+ planBacklogFull: inputs.plansFull,
267
+ deferredDue: ctx.deferredDue,
268
+ workMergedSince: ctx.workMergedSince,
269
+ });
270
+ launchDueRuns({
271
+ root,
272
+ reasons,
273
+ // roleSignal, not signal: the timed run wrapper watches the full signal PLUS the
274
+ // role-only stop, exactly as the pre-extraction inline pass did — the restart drain's
275
+ // give-up abort must also turn away a waiter granted its permit at that moment.
276
+ signal: ctx.roleSignal,
277
+ gateStates,
278
+ breakerPolicy: ctx.breakerPolicy,
279
+ breakerNow: ctx.breakerNow,
280
+ probePair: inputs.probePair,
281
+ probeRoles: inputs.probeRoles,
282
+ budgetActive: gates.budgetActive,
283
+ windowMovedRoles: gates.windowMovedRoles,
284
+ semaphore: ctx.semaphore,
285
+ piDefault: ctx.piDefault,
286
+ rolePermitHolders: ctx.rolePermitHolders,
287
+ roleInFlight: ctx.roleInFlight,
288
+ directorInFlight: ctx.directorInFlight,
289
+ roleRunDurationsMs: ctx.roleRunDurationsMs,
290
+ startHeld: (role) => runStartHeld(state) ||
291
+ (role !== DIRECTOR_ROLE
292
+ ? (!afterMergeRoles.has(baseRoleOf(role)) && gates.reviewHeldNow()) ||
293
+ gates.teamHeldNow().has(gates.roleProviders.get(role))
294
+ : windowHeldProviders(gateStates.teamHold).has(gates.roleProviders.get(role))),
295
+ });
296
+ }
297
+ /** Priority headroom, then once mode's exit; returns whether the loop should stop. Writes
298
+ * state.lastReserve and, in once mode, fires internalStop. */
299
+ export function pollReserveAndExit(ctx, state, { liveConfig, now }) {
300
+ const { root, runners, semaphore } = ctx;
301
+ // Priority headroom (plans/parallel-work-instances.md part 7/7): withhold a few permits
302
+ // from maintenance while a work role has work to take, so a due work run never waits out
303
+ // a whole maintenance run. Sized after the launch pass, so a runner launched this poll
304
+ // already reads as running and reserves nothing for itself.
305
+ const poolSnapshot = semaphore.snapshot();
306
+ // The provider-headroom predicate receives the structural ClaimRunner; the config that
307
+ // names the provider lives on the real RoleRunners, so look the runner up by role.
308
+ const runnerByRole = new Map(runners.map((r) => [r.role, r]));
309
+ const reserve = workReserveCount(root, runners, {
310
+ maxConcurrent: liveConfig.maxConcurrent,
311
+ enabled: new Set(runners.filter((r) => runnerEnabled(r.config, r.role)).map((r) => r.role)),
312
+ queuedMergeRoles: queuedMergeRoles(root),
313
+ instancesOf: (base) => liveConfig.roles[base]?.instances ?? 1,
314
+ // Per-provider headroom (plans/provider-concurrency.md §6): an idle work role reserves a
315
+ // permit only when its own provider could use it — capped and full, it would only idle
316
+ // the slot another provider's maintenance run could take. Read from the pool's live
317
+ // snapshot; an uncapped key (or the team-total-only case) always has headroom.
318
+ providerHasHeadroom: (r) => {
319
+ const runner = runnerByRole.get(r.role);
320
+ const cfg = runner && runner.role !== DIRECTOR_ROLE ? runner.runConfig(now) : null;
321
+ const key = cfg ? seamConcurrencyKey(cfg, ctx.piDefault) : undefined;
322
+ if (key === undefined)
323
+ return true;
324
+ const k = poolSnapshot.byKey[key];
325
+ if (k?.limit === undefined)
326
+ return true;
327
+ return k.inUse < k.limit;
328
+ },
329
+ });
330
+ semaphore.setReserve(reserve);
331
+ if ((state.lastReserve === 0) !== (reserve === 0)) {
332
+ ctx.emit({ source: TUMWATER_SOURCE, type: "permit_reserve", reserve });
333
+ }
334
+ state.lastReserve = reserve;
335
+ // Once mode's exit: every enabled role settled, no run in flight, and the merge queue
336
+ // empty with no merge in flight for one full poll cycle — then fire the internal
337
+ // stop, the same graceful shutdown the restart drain uses (the `finally` awaits
338
+ // in-flight merge tasks to the end). The one-poll-cycle requirement is the guard
339
+ // against dropping a queued-but-not-yet-started merge: a stop that arrives during the
340
+ // shutdown drain drops the queue entry, so the slot gets its poll to pick the entry up
341
+ // before the stop is trusted.
342
+ if (ctx.once.exitReady({
343
+ roleRuns: ctx.roleInFlight.size,
344
+ directorRuns: ctx.directorInFlight.size,
345
+ merges: mergeTasks(ctx.merges).length,
346
+ queuedMerges: queuedMergeFiles(root).length,
347
+ })) {
348
+ ctx.internalStop.abort();
349
+ return true;
350
+ }
351
+ return false;
352
+ }