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
@@ -1,21 +1,23 @@
1
- // vocabulary: plain
2
1
  import crypto from "node:crypto";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { cancelScheduledStop, isTeamPaused, scheduleStop } from "../team/team-state.js";
6
5
  import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
7
6
  import { durationLabel } from "../cli/cli-args.js";
8
- import { formatDate, formatTime, humanUntil } from "../text/datetime.js";
7
+ import { formatDate, formatTime, humanUntil, DAY_MS } from "../text/datetime.js";
9
8
  import { submitRolePrompt } from "../inbox/inbox-submit.js";
10
9
  import { questionAsker } from "../backlog/question-answer.js";
11
- import { knownRoleIdsCached } from "../config/config.js";
10
+ import { knownRoleIdsCached, loadConfigCached } from "../config/config.js";
11
+ import { baseRoleOf } from "../roles/runner-ids.js";
12
12
  import { DIRECTOR_ROLE } from "../roles/roles.js";
13
- import { loadLoopState, saveLoopState, zeroCounters } from "../loop/loop-state.js";
13
+ import { saveRunnerState, zeroCounters } from "../runner/runner-state.js";
14
+ import { loadRunnerState } from "../runner/runner-state-load.js";
14
15
  import { clearBackoff } from "../scheduling/backoff.js";
15
16
  import { writeJsonAtomic } from "../files/json-files.js";
16
17
  import { reclaimPass } from "../team/reclaim.js";
17
18
  import { formatGB } from "../text/format.js";
18
19
  import { plural } from "../text/phrases.js";
20
+ import { numberedList } from "../text/markdown.js";
19
21
  import { cancelScheduledWakes, pendingScheduledWakes } from "./operator-requests.js";
20
22
  import { abortRequestPath, reclaimRequestPath, resetRequestPath, restartRequestPath, scheduledWakeDir, wakeRequestPath, } from "../paths.js";
21
23
  /** The marker-writing cores of the operator-intent protocol, shared by every surface that writes
@@ -60,13 +62,13 @@ export function requestResetCounters(root, roles) {
60
62
  // the affected runners' in-memory copies and the runners save them, and writes the file for a
61
63
  // requested role that has no runner (a disabled role). Rewriting the files here too would be
62
64
  // a second writer racing those whole-file saves — an overwrite from a snapshot the
63
- // orchestrator has since moved past (a just-ended tick's result, a claim, a schedule), which
65
+ // orchestrator has since moved past (a just-ended run's result, a claim, a schedule), which
64
66
  // the lost update discards. So with a live team only the marker is written; with none there
65
67
  // is no concurrent writer and the direct write is what carries the reset to the next run.
66
68
  const { live, when } = markerApplyNote(root);
67
69
  if (!live)
68
70
  for (const r of roles)
69
- saveLoopState(root, zeroCounters(loadLoopState(root, r)));
71
+ saveRunnerState(root, zeroCounters(loadRunnerState(root, r)));
70
72
  // Atomic: a concurrent poll reading the marker mid-write would see a torn file, which
71
73
  // readJsonFile folds to null — the corrupt-marker superset that wakes/resets EVERY runner.
72
74
  // For a scheduled wake (`--in`) that superset drops notBeforeMs, firing the wake hours early.
@@ -78,32 +80,28 @@ export function requestResetCounters(root, roles) {
78
80
  }
79
81
  /** The empty-list wording both wake-list renderers share — `cancelWakes`'s no-op arm and
80
82
  * `renderWakeList`'s empty list: one literal so the two surfaces cannot drift on what an
81
- * operator sees when nothing is pending, plus a ` for <role>` suffix when the view was
83
+ * operator sees when nothing is pending, plus a ` for <roles>` suffix when the view was
82
84
  * role-scoped. */
83
- function noWakePending(role) {
84
- return `no scheduled wake pending${role ? ` for ${role}` : ""}`;
85
+ function noWakePending(roles) {
86
+ return `no scheduled wake pending${roles ? ` for ${roles.join(", ")}` : ""}`;
85
87
  }
86
88
  /** Drop pending scheduled wakes (`tumwater wake --cancel`, the dashboard's POST /api/wake-cancel):
87
- * every pending wake, or only `role`'s share of them. Returns the confirmation the CLI prints
88
- * verbatim and the GUI flashes, so the surfaces cannot drift on wording. */
89
- export function cancelWakes(root, role) {
90
- const n = cancelScheduledWakes(root, role);
91
- const forRole = role ? ` for ${role}` : "";
92
- return n > 0 ? `cancelled ${plural(n, "scheduled wake")}${forRole}` : noWakePending(role);
89
+ * every pending wake, or only the named roles' share of them. Returns the confirmation the CLI
90
+ * prints verbatim and the GUI flashes, so the surfaces cannot drift on wording. */
91
+ export function cancelWakes(root, roles) {
92
+ const n = cancelScheduledWakes(root, roles);
93
+ const forRole = roles ? ` for ${roles.join(", ")}` : "";
94
+ return n > 0 ? `cancelled ${plural(n, "scheduled wake")}${forRole}` : noWakePending(roles);
93
95
  }
94
- /** The pending scheduled wakes (`tumwater wake --list`), earliest first; with `role`, only the
95
- * wakes naming that loop. Entries keep their full roles list. Read-only. */
96
- export function listWakes(root, role, now) {
96
+ /** The pending scheduled wakes (`tumwater wake --list`), earliest first; with `roles`, only the
97
+ * wakes naming any of them. Entries keep their full roles list. Read-only. */
98
+ export function listWakes(root, roles, now) {
97
99
  const wakes = pendingScheduledWakes(root, now);
98
- return role ? wakes.filter((w) => w.roles.includes(role)) : wakes;
100
+ return roles ? wakes.filter((w) => w.roles.some((r) => roles.includes(r))) : wakes;
99
101
  }
100
102
  /** `wake --list`'s text: one numbered line per wake, or the empty wording `cancelWakes` uses. */
101
- export function renderWakeList(wakes, now, role) {
102
- if (wakes.length === 0)
103
- return noWakePending(role);
104
- return wakes
105
- .map((w, i) => `${i + 1}. in ${humanUntil(w.atMs, now)} — ${w.roles.join(", ")}`)
106
- .join("\n");
103
+ export function renderWakeList(wakes, now, roles) {
104
+ return numberedList(wakes.map((w) => `in ${humanUntil(w.atMs, now)} — ${w.roles.join(", ")}`), noWakePending(roles));
107
105
  }
108
106
  /** The marker-writing core of `wake [--in <duration>]`, shared with the GUI's POST /api/wake:
109
107
  * clear the named roles' backoff and pull nextRunAt to now (touching ONLY the schedule —
@@ -114,9 +112,9 @@ export function renderWakeList(wakes, now, role) {
114
112
  * team stopped at submit (or restarted before the deadline) does not wake early;
115
113
  * consumeWakeRequest clears the schedules when it consumes the marker at or after the
116
114
  * deadline. The "wakes in" phrase is inMs itself, not the deadline minus a second clock read
117
- * (the rule timedPauseBits follows): a caller-computed deadline let a millisecond tick between
118
- * the two reads print "wakes in 2699999ms" for `--in 45m`. Returns the confirmation the CLI
119
- * prints verbatim and the GUI flashes. */
115
+ * (the rule timedPauseBits follows): a caller-computed deadline let a millisecond of clock
116
+ * time between the two reads print "wakes in 2699999ms" for `--in 45m`. Returns the
117
+ * confirmation the CLI prints verbatim and the GUI flashes. */
120
118
  export function requestWake(root, roles, inMs) {
121
119
  const now = Date.now();
122
120
  // Deferred only for a genuinely positive delay: zero or less reads as immediate, so the
@@ -131,7 +129,7 @@ export function requestWake(root, roles, inMs) {
131
129
  const { live, when } = markerApplyNote(root);
132
130
  if (!deferred && !live)
133
131
  for (const r of roles)
134
- saveLoopState(root, clearBackoff(loadLoopState(root, r), now));
132
+ saveRunnerState(root, clearBackoff(loadRunnerState(root, r), now));
135
133
  // Atomic — see requestResetCounters: a torn wake marker reads as corrupt and wakes every
136
134
  // runner immediately, dropping a scheduled wake's notBeforeMs on the floor. A scheduled wake
137
135
  // gets its own file so a later wake request, immediate or scheduled, cannot overwrite it.
@@ -143,12 +141,35 @@ export function requestWake(root, roles, inMs) {
143
141
  writeJsonAtomic(wakeRequestPath(root), { at: now, roles });
144
142
  // Same liveness contract as reset-counters and pause/resume: only a live team consumes the
145
143
  // marker, so say so instead of promising a poll that will not happen.
144
+ const disabledNote = disabledWakeNote(root, roles);
146
145
  if (deferred)
147
146
  return (`wake scheduled for ${roles.join(", ")} — wakes in ${durationLabel(inMs)} — ` +
148
147
  (live
149
148
  ? "the running team applies it within one poll after the deadline"
150
- : "tumwater is not running — the next `tumwater start` applies it after the deadline"));
151
- return `wake requested for ${roles.join(", ")} — ${applyClause(live, when, "applies it")}`;
149
+ : "tumwater is not running — the next `tumwater start` applies it after the deadline") +
150
+ disabledNote);
151
+ return `wake requested for ${roles.join(", ")} — ${applyClause(live, when, "applies it")}${disabledNote}`;
152
+ }
153
+ /** The ids among `roles` whose config entry is `enabled: false`; empty when the config
154
+ * cannot be read. */
155
+ function disabledOf(root, roles) {
156
+ const { config } = loadConfigCached(root);
157
+ if (!config)
158
+ return [];
159
+ return roles.filter((id) => config.roles[baseRoleOf(id)]?.enabled === false);
160
+ }
161
+ /** The confirmation clause naming the woken roles that are disabled: a disabled role has no
162
+ * runner, so the wake only ends its sleep. Empty when none is disabled or the config
163
+ * cannot be read (the cached read, so a broken tumwater.json never makes a wake throw). */
164
+ function disabledWakeNote(root, roles) {
165
+ const disabled = disabledOf(root, roles);
166
+ if (disabled.length === 0)
167
+ return "";
168
+ const one = disabled.length === 1;
169
+ return (`; ${disabled.join(", ")} ${one ? "is" : "are"} disabled, so the wake only ends ` +
170
+ `${one ? "its" : "their"} sleep — ${one ? "it" : "they"} won't run until you enable ` +
171
+ `${one ? "it" : "them"} with ` +
172
+ `tumwater config set roles.${one ? disabled[0] : "<id>"}.enabled true`);
152
173
  }
153
174
  /** Schedule a graceful team stop `inMs` from now (`tumwater stop --in`, the dashboard's "Stop the
154
175
  * team later"): the marker means nothing without a live team to consume it, so a stopped one
@@ -196,17 +217,17 @@ function requestReclaim(root) {
196
217
  const { live, when } = markerApplyNote(root);
197
218
  return `reclaim requested — ${applyClause(live, when, "reclaims idle worktrees")}`;
198
219
  }
199
- /** Queue a prompt for one loop and wake that loop — the one workflow `tumwater prompt
220
+ /** Queue a prompt for one role and wake that role — the one workflow `tumwater prompt
200
221
  * --role`, the dashboard's POST /api/prompt-role, and the TUI's Ctrl+R submit all share:
201
- * submitRolePrompt enqueues into the loop's own queue (length-capped by the shared rule)
202
- * and logs under the loop, then requestWake's single-role marker brings a live team's
203
- * loop in within one poll instead of whenever its backoff next expires (and is safe with
222
+ * submitRolePrompt enqueues into the role's own queue (length-capped by the shared rule)
223
+ * and logs under the role, then requestWake's single-role marker brings a live team's
224
+ * role in within one poll instead of whenever its backoff next expires (and is safe with
204
225
  * no team running — the same contract as `tumwater wake`). Returns requestWake's
205
226
  * confirmation so a surface can show what the wake did. Keeping enqueue and wake in one
206
227
  * call keeps a surface from ever queueing a prompt without the wake that delivers it
207
228
  * promptly — the pairing is the invariant, not each surface's private discipline. An optional
208
229
  * `notBeforeMs` defers the prompt (PLANS.md "tumwater prompt --at <duration>"); the wake
209
- * still fires — it brings the loop in at its time to find a deliverable queue, and a queue
230
+ * still fires — it brings the role in at its time to find a deliverable queue, and a queue
210
231
  * holding only future prompts keeps it asleep. */
211
232
  export function submitRolePromptAndWake(root, role, text, images, notBeforeMs) {
212
233
  submitRolePrompt(root, role, text, images, notBeforeMs);
@@ -223,7 +244,7 @@ export function requestAbort(root, role) {
223
244
  writeJsonAtomic(abortRequestPath(root, role), { at: Date.now() });
224
245
  let confirmation = `abort requested for ${role} — the running team applies it within ~2s`;
225
246
  if (role === DIRECTOR_ROLE) {
226
- // The director's in-flight prompt was dequeued from the inbox file at tick start and an
247
+ // The director's in-flight prompt was dequeued from the inbox file at run start and an
227
248
  // abort discards it without re-queueing — say so, since the discard is otherwise silent.
228
249
  confirmation +=
229
250
  "; its current in-flight prompt will be discarded (re-submit with `tumwater prompt` if you want it retried)";
@@ -263,13 +284,16 @@ export function rolePauseMessage(root, role, changed, timed, now = Date.now()) {
263
284
  return `role ${role} is already paused`;
264
285
  const { when, tail } = markerApplyNote(root);
265
286
  const { forPhrase, note } = timedPauseBits(timed, now);
266
- return `role ${role} paused${forPhrase} — it starts no new runs${when} (current runs finish; the rest of the team is unaffected)${note}${tail}`;
287
+ const disabledNote = disabledOf(root, [role]).length > 0
288
+ ? `; ${role} is disabled, so it starts no runs either way — the pause holds once you enable it`
289
+ : "";
290
+ return `role ${role} paused${forPhrase} — it starts no new runs${when} (current runs finish; the rest of the team is unaffected)${note}${disabledNote}${tail}`;
267
291
  }
268
292
  /** The per-role resume confirmation, shared between `tumwater resume --role` and the TUI's
269
293
  * Ctrl+P for the same no-drift reason. Carries the team-pause interplay note cmdResume
270
294
  * phrases: the team pause is the stronger gate, so with its marker present a freshly
271
- * resumed role still starts no ticks (the director is exempt from that gate, so its
272
- * resumption is real) — saying nothing would promise ticking the scheduler then deny, the
295
+ * resumed role still starts no runs (the director is exempt from that gate, so its
296
+ * resumption is real) — saying nothing would promise running the scheduler then deny, the
273
297
  * same honest-confirmation contract markerApplyNote's tail serves. */
274
298
  export function roleResumeMessage(root, role, changed) {
275
299
  if (!changed)
@@ -278,7 +302,10 @@ export function roleResumeMessage(root, role, changed) {
278
302
  const teamNote = isTeamPaused(root) && role !== DIRECTOR_ROLE
279
303
  ? " (the team pause is still active — `tumwater resume` lifts it)"
280
304
  : "";
281
- return `role ${role} resumed — it starts new runs again${when}${teamNote}${tail}`;
305
+ const starts = disabledOf(root, [role]).length > 0
306
+ ? `it is disabled, so it still starts no runs — enable it with tumwater config set roles.${role}.enabled true`
307
+ : "it starts new runs again";
308
+ return `role ${role} resumed — ${starts}${when}${teamNote}${tail}`;
282
309
  }
283
310
  /** The `tumwater pause --for <duration>` cap: 90 days. A timed pause is meant to answer "step
284
311
  * away for a while and come back to a resumed team" — the same bound the report's day windows
@@ -288,7 +315,7 @@ export function roleResumeMessage(root, role, changed) {
288
315
  * value whose wall-clock resume time no `Date` can even hold (a `--for 100000000d` once printed
289
316
  * "resumes automatically at NaN:NaN:NaN"). Lives beside the marker contract it caps, which
290
317
  * every surface shares, so no new `--for` consumer can outgrow it unnoticed. */
291
- export const PAUSE_FOR_MAX_MS = 90 * 24 * 60 * 60 * 1000;
318
+ export const PAUSE_FOR_MAX_MS = 90 * DAY_MS;
292
319
  /** One manual reclaim, shared by `tumwater reclaim` and the dashboard's POST /api/reclaim: with
293
320
  * a live team the pass is requested through the marker the orchestrator consumes (so in-use
294
321
  * worktrees are respected); with none running it runs in-process, since nothing is in use.
@@ -1,12 +1,12 @@
1
- // vocabulary: plain
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { logEvent, warnEvent, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
5
- import { removeQuiet } from "../files/files.js";
4
+ import { dirEntryNames, removeQuiet } from "../files/files.js";
6
5
  import { errorMessage } from "../text/text.js";
7
6
  import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
8
7
  import { finiteNumber } from "../files/json-object.js";
9
- import { loadLoopState, saveLoopState, zeroCounters } from "../loop/loop-state.js";
8
+ import { saveRunnerState, zeroCounters } from "../runner/runner-state.js";
9
+ import { loadRunnerState } from "../runner/runner-state-load.js";
10
10
  import { canonicalRoleId } from "../roles/legacy-role-ids.js";
11
11
  import { clearBackoff } from "../scheduling/backoff.js";
12
12
  import { abortRequestPath, reclaimRequestPath, resetRequestPath, restartRequestPath, scheduledWakeDir, statePath, wakeRequestPath, STATE_DIR, } from "../paths.js";
@@ -76,7 +76,7 @@ function warnStatePersistFailed(root, role, err) {
76
76
  }
77
77
  /** Consume a pending reset request from `tumwater reset-counters`, if any: the CLI zeroed the
78
78
  * state files only when no team was running; this zeroes the affected runners' in-memory
79
- * copies (which then re-save), or their next tick's save would resurrect the pre-reset values,
79
+ * copies (which then re-save), or their next run's save would resurrect the pre-reset values,
80
80
  * and writes the zeroed state file directly for requested roles with no runner, which the CLI
81
81
  * left to its marker. */
82
82
  export function consumeResetRequest(root, runners) {
@@ -99,7 +99,7 @@ export function consumeResetRequest(root, runners) {
99
99
  const runnerlessReset = [];
100
100
  for (const role of runnerless) {
101
101
  try {
102
- saveLoopState(root, zeroCounters(loadLoopState(root, role)));
102
+ saveRunnerState(root, zeroCounters(loadRunnerState(root, role)));
103
103
  runnerlessReset.push(role);
104
104
  }
105
105
  catch (err) {
@@ -109,7 +109,7 @@ export function consumeResetRequest(root, runners) {
109
109
  const applied = [...affected.map((r) => r.role), ...runnerlessReset];
110
110
  if (applied.length > 0) {
111
111
  const [only] = applied;
112
- // One role → filed under that loop; several → one harness-level event listing them.
112
+ // One role → filed under that role; several → one harness-level event listing them.
113
113
  if (applied.length === 1 && only)
114
114
  logRequestEvent(root, { source: only, type: "counters_reset" });
115
115
  else
@@ -120,7 +120,7 @@ export function consumeResetRequest(root, runners) {
120
120
  /** Consume a pending wake request from `tumwater wake [--role <id>] [--in <duration>]`, if any.
121
121
  * Immediate wakes find the state files already cleared by the CLI when no team was running; this
122
122
  * clears the affected runners' in-memory schedules (backoffSeconds, nextRunAt), or their next save
123
- * would resurrect the pre-wake sleep window and the loops would keep sleeping until the original
123
+ * would resurrect the pre-wake sleep window and the roles would keep sleeping until the original
124
124
  * backoff expired, and writes the cleared schedule directly for requested roles with no runner,
125
125
  * which the CLI left to its marker. A scheduled wake (`--in`) carries a `notBeforeMs`: before the
126
126
  * deadline the marker is left in place (a later poll retries it — the wake takes effect within one
@@ -129,7 +129,7 @@ export function consumeResetRequest(root, runners) {
129
129
  * deliberately skipped it. A non-numeric or non-finite value (a hand edit's `1e999`) reads as
130
130
  * immediate (defensive; validation happens at the CLI) — leaving Infinity as a deadline would hold
131
131
  * `notBefore > Date.now()` true forever and strand the marker, which nothing else deletes. Each
132
- * woken role logs the existing `wake` event with the operator reason, so the team's early ticks
132
+ * woken role logs the existing `wake` event with the operator reason, so the team's early runs
133
133
  * read in the feed as deliberate. */
134
134
  export function consumeWakeRequest(root, runners) {
135
135
  // A legacy wake.json (the pre-upgrade build wrote scheduled wakes here) keeps its deadline.
@@ -149,25 +149,26 @@ export function consumeWakeRequest(root, runners) {
149
149
  if (!wakeNotDue(file))
150
150
  applyWakeMarker(root, file, runners);
151
151
  }
152
+ /** A scheduled-wake marker's deadline in ms, or undefined when it has none: undefined for a
153
+ * missing or corrupt marker and for a `notBeforeMs` that is absent, non-numeric, or non-finite
154
+ * (the "immediate" reading both the due-check and the pending-list apply — a hand edit's
155
+ * `1e999` must not read as a forever-out deadline). */
156
+ function wakeDeadlineMs(markerFile) {
157
+ const marker = readJsonFile(markerFile);
158
+ return marker === null ? undefined : finiteNumber(marker.notBeforeMs, undefined);
159
+ }
152
160
  /** Whether a wake marker's deadline still lies ahead: a missing, non-numeric, or non-finite
153
- * `notBeforeMs` reads as immediate, and a null (corrupt) marker is due. */
161
+ * `notBeforeMs` reads as immediate (one helper owns the reading), and a null (corrupt) marker
162
+ * is due. */
154
163
  function wakeNotDue(markerFile) {
155
- const marker = readJsonFile(markerFile);
156
- const notBefore = marker === null ? undefined : finiteNumber(marker.notBeforeMs, undefined);
164
+ const notBefore = wakeDeadlineMs(markerFile);
157
165
  return notBefore !== undefined && notBefore > Date.now();
158
166
  }
159
167
  /** The scheduled-wake files in `scheduledWakeDir`, sorted; `.tmp-<pid>` leftovers of an atomic
160
168
  * write are skipped. A missing directory is an empty list. */
161
169
  function scheduledWakeFiles(root) {
162
170
  const dir = scheduledWakeDir(root);
163
- let names;
164
- try {
165
- names = fs.readdirSync(dir);
166
- }
167
- catch {
168
- return [];
169
- }
170
- return names
171
+ return dirEntryNames(dir)
171
172
  .filter((n) => n.endsWith(".json"))
172
173
  .sort()
173
174
  .map((n) => path.join(dir, n));
@@ -179,8 +180,7 @@ export function pendingScheduledWakes(root, now) {
179
180
  const out = [];
180
181
  for (const file of scheduledWakeFiles(root)) {
181
182
  const roles = requestedRoles(file);
182
- const marker = readJsonFile(file);
183
- const atMs = marker === null ? undefined : finiteNumber(marker.notBeforeMs, undefined);
183
+ const atMs = wakeDeadlineMs(file);
184
184
  if (roles && roles.length > 0 && atMs !== undefined && atMs > now)
185
185
  out.push({ roles, atMs });
186
186
  }
@@ -188,26 +188,26 @@ export function pendingScheduledWakes(root, now) {
188
188
  }
189
189
  /** Drop the pending scheduled wakes (`tumwater wake --cancel`), returning how many files it
190
190
  * removed or rewrote. Only files whose deadline still lies ahead are touched; due and corrupt
191
- * files stay the consumer's to apply. Without `role` each pending file is removed. With `role`,
192
- * files not naming it are skipped, a file naming only it is removed, and a file naming others
193
- * too is rewritten without it (same `at` and `notBeforeMs`). */
194
- export function cancelScheduledWakes(root, role) {
191
+ * files stay the consumer's to apply. Without `roles` each pending file is removed. With `roles`,
192
+ * files naming none of them are skipped, a file left with no role is removed, and a file naming
193
+ * others too is rewritten without the listed ones (same `at` and `notBeforeMs`). */
194
+ export function cancelScheduledWakes(root, roles) {
195
195
  let count = 0;
196
196
  for (const file of scheduledWakeFiles(root)) {
197
197
  if (!wakeNotDue(file))
198
198
  continue;
199
- if (role === undefined) {
199
+ if (roles === undefined) {
200
200
  removeQuiet(file);
201
201
  count++;
202
202
  continue;
203
203
  }
204
- const roles = requestedRoles(file);
205
- if (!roles?.includes(role))
204
+ const named = requestedRoles(file);
205
+ if (!named?.some((r) => roles.includes(r)))
206
206
  continue;
207
207
  const marker = readJsonFile(file);
208
208
  if (!marker)
209
209
  continue;
210
- const rest = roles.filter((r) => r !== role);
210
+ const rest = named.filter((r) => !roles.includes(r));
211
211
  if (rest.length === 0)
212
212
  removeQuiet(file);
213
213
  else
@@ -234,7 +234,7 @@ function applyWakeMarker(root, markerFile, runners) {
234
234
  }
235
235
  for (const role of runnerlessTargets(root, markerFile, runners)) {
236
236
  try {
237
- saveLoopState(root, clearBackoff(loadLoopState(root, role), Date.now()));
237
+ saveRunnerState(root, clearBackoff(loadRunnerState(root, role), Date.now()));
238
238
  }
239
239
  catch (err) {
240
240
  warnStatePersistFailed(root, role, err);
@@ -270,10 +270,10 @@ export function consumeReclaimRequest(root, reclaim) {
270
270
  consumeMarkerRequest(reclaimRequestPath(root), () => reclaim?.requestManual());
271
271
  }
272
272
  /** Consume per-role abort requests from `tumwater abort --role <id>`: one marker file per role (no
273
- * parsing needed), so a request for an idle OR disabled loop is still cleaned up. A running tick
273
+ * parsing needed), so a request for an idle OR disabled role is still cleaned up. An in-flight run
274
274
  * gets killed and logs exactly one event; anything else is a silent no-op — the marker's presence
275
275
  * IS the request, removing it acknowledges. Since merge queue 3/5 a role's in-flight work is often
276
- * a MERGE rather than a tick, so the merge's controller is passed in: an abort for the role being
276
+ * a MERGE rather than a run, so the merge's controller is passed in: an abort for the role being
277
277
  * merged right now kills that too (the drain's task sees `userAborted` and discards the pinned ref
278
278
  * when the merge ends). There can be several in-flight units at once (land-queue speed 2c) — one
279
279
  * per change being verified, the merge slot's stack, and each verified change waiting for it
@@ -284,13 +284,8 @@ export function consumeReclaimRequest(root, reclaim) {
284
284
  * refs of the not-yet-processed changes for one-at-a-time recovery, which is already the
285
285
  * fallback), and the merge discards every stacked ref when it ends. */
286
286
  export function consumeAbortRequests(root, runners, merges) {
287
- let markers;
288
- try {
289
- markers = fs.readdirSync(path.join(root, STATE_DIR));
290
- }
291
- catch {
292
- return; // .tumwater/ missing — nothing to consume (a fresh repo before the first tick).
293
- }
287
+ // A missing .tumwater/ (a fresh repo before the first run) reads empty — nothing to consume.
288
+ const markers = dirEntryNames(path.join(root, STATE_DIR));
294
289
  // Best-effort report of one marker's failure: the warning can itself throw when the
295
290
  // unwritable event feed is what failed, and there is nothing left to report on then.
296
291
  const warnFailure = (role, err) => {
@@ -319,7 +314,7 @@ export function consumeAbortRequests(root, runners, merges) {
319
314
  merge.controller.abort();
320
315
  }
321
316
  if (runner?.state.running)
322
- runner.abortTick();
317
+ runner.abortRun();
323
318
  }
324
319
  catch (err) {
325
320
  warnFailure(role, err);
@@ -1,18 +1,17 @@
1
- // vocabulary: plain
2
1
  import fs from "node:fs";
3
2
  import { loadConfigSafe } from "../config/config.js";
4
3
  import { CUSTOM_NAME_RE } from "../config/config-schema.js";
5
4
  import { DIRECTOR_ROLE } from "../roles/roles.js";
6
- import { loopEnabled } from "../roles/loop-ids.js";
5
+ import { runnerEnabled } from "../roles/runner-ids.js";
7
6
  import { branchExists, countCommitsInRange, currentBranch, deleteRef, isDirty, isMergedInto, refSha, targetBranch, } from "../git/git.js";
8
7
  import { errorMessage } from "../text/text.js";
9
- import { loadLoopState } from "../loop/loop-state.js";
8
+ import { loadRunnerState } from "../runner/runner-state-load.js";
10
9
  import { branchName, mergeRefName, legacyLandingRefName, worktreePath } from "../paths.js";
11
10
  import { isUsableWorktree, removeWorktree } from "../git/worktree.js";
12
11
  import { roleWorktreeDir, slotForDir, updateSlotsState } from "../git/slots-state.js";
13
12
  import { pausedRoles, resumeRole } from "../team/team-state.js";
14
13
  import { git } from "../git/git-run.js";
15
- /** `tumwater retire --role <id>`: remove a disabled loop's persistent worktree and branch, plus the
14
+ /** `tumwater retire --role <id>`: remove a disabled role's persistent worktree and branch, plus the
16
15
  * per-role merge ref and paused-state marker that outlive it. The safety rails and the `--force`
17
16
  * override live here, so the CLI layer (operator/operator-commands.ts) stays a thin renderer. */
18
17
  /** The role's merge pin, falling back to the legacy-named one a not-yet-migrated repo holds. */
@@ -66,8 +65,8 @@ export async function collectRetire(root, role) {
66
65
  mergeRefPresent: mergeRef !== null,
67
66
  aheadOfMain: ahead + pinnedUnmerged,
68
67
  dirty,
69
- enabledInConfig: loopEnabled(config.config, role),
70
- midRun: loadLoopState(root, role).running === true,
68
+ enabledInConfig: runnerEnabled(config.config, role),
69
+ midRun: loadRunnerState(root, role).running === true,
71
70
  };
72
71
  }
73
72
  /** The artifacts retire manages, in removal order. */