tumwater 0.1.2 → 0.2.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 (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -4,30 +4,31 @@ import { finiteNumber, stringList } from "../files/json-object.js";
4
4
  import { removeQuiet } from "../files/files.js";
5
5
  import { withStateLock } from "../concurrency/lock.js";
6
6
  import { collapseWhitespace, truncate } from "../text/text.js";
7
- import { pausedPath, pausedRolesLockPath, pausedRolesPath } from "../paths.js";
7
+ import { pausedPath, pausedRolesLockPath, pausedRolesPath, scheduledStopPath } from "../paths.js";
8
8
  import { pendingScheduledWakes } from "../operator/operator-requests.js";
9
- /** The operator pause markers: the fleet-wide pause (`tumwater pause`/`resume`) and the
9
+ import { canonicalRoleId } from "../roles/legacy-role-ids.js";
10
+ /** The operator pause markers: the team-wide pause (`tumwater pause`/`resume`) and the
10
11
  * per-role pause set (`pause --role <id>`/`resume --role <id>`), their writers and read-side
11
12
  * wrappers together so producer (CLI, GUI) and every consumer read one module. The running
12
- * orchestrator's info file is the other fleet-state domain and lives in orchestrator-info.ts. */
13
- /** The fleet pause marker read once as the standing pause it represents: non-null while the
13
+ * orchestrator's info file is the other team-state domain and lives in orchestrator-info.ts. */
14
+ /** The team pause marker read once as the standing pause it represents: non-null while the
14
15
  * operator's pause stands, null when absent/unreadable/expired. Snapshot-style callers that
15
- * need both "is the fleet paused" and its deadline read this once instead of paying two
16
- * full marker reads (isFleetPaused + pausedUntil each re-read the same file) per poll —
16
+ * need both "is the team paused" and its deadline read this once instead of paying two
17
+ * full marker reads (isTeamPaused + pausedUntil each re-read the same file) per poll —
17
18
  * the TUI and GUI poll snapshot every second. Never throws (a missing .tumwater/ reads
18
19
  * null). Lives here beside the read-half wrappers below: one module, one marker, one read
19
20
  * per poll per observer. */
20
- export function standingFleetPause(root) {
21
+ export function standingTeamPause(root) {
21
22
  return standingMarker(pausedPath(root));
22
23
  }
23
- /** True while the operator has paused the fleet (`tumwater pause` wrote its marker). The
24
+ /** True while the operator has paused the team (`tumwater pause` wrote its marker). The
24
25
  * marker is persistent state, not a one-shot request: presence means paused until `resume`
25
- * removes it — pausing before startup starts an already-paused fleet. Never throws (a missing
26
+ * removes it — pausing before startup starts an already-paused team. Never throws (a missing
26
27
  * .tumwater/ reads false). Lives here because both the scheduler and every observer must
27
28
  * evaluate it from disk without importing each other's modules — the same "single
28
29
  * definition" rule that put the daily-cost budget in src/budget/budget.ts. */
29
- export function isFleetPaused(root) {
30
- return standingFleetPause(root) !== null;
30
+ export function isTeamPaused(root) {
31
+ return standingTeamPause(root) !== null;
31
32
  }
32
33
  /** The reason cap every writer of a pause reason shares (chars): one constant so the CLI
33
34
  * path and any later GUI path cannot drift on how long an operator note may be — a writer
@@ -38,18 +39,18 @@ export const PAUSE_REASON_MAX = 200;
38
39
  * timed pause: every consumer (scheduler, dashboards, a follow-up `pause`) treats an expired
39
40
  * marker as absent, so the deadline releases the pause with no new scheduler code — the
40
41
  * orchestrator's poll-diff sees an ordinary unpaused transition. Never throws. The reason is
41
- * folded and capped here too — the same shape pauseFleet writes — so the marker's reason
42
+ * folded and capped here too — the same shape pauseTeam writes — so the marker's reason
42
43
  * reads as the operator's one-line why whoever wrote it: a pause standing across the
43
44
  * write-side fold (or a hand edit) must not hand the status header's badge a raw newline
44
45
  * (BUGS.md 2026-09-30). A non-string reason reads as no reason — src/files/json-files.ts's
45
46
  * wrong-shape-reads-as-absent policy — and the type guard is what keeps this function's
46
47
  * never-throws contract intact: normalizePauseReason would throw on a number or object,
47
- * and this read sits on the scheduler's per-cycle isFleetPaused poll and every dashboard
48
+ * and this read sits on the scheduler's per-cycle isTeamPaused poll and every dashboard
48
49
  * snapshot.
49
50
  *
50
51
  * A non-finite `until` (a hand edit's `1e999` parses to Infinity) is the same wrong-shape
51
52
  * read: it is dropped, so the marker stands as an indefinite pause rather than a pause that
52
- * never lapses with its countdown rendered `Infinityd`. The fleet stays stopped — a corrupt
53
+ * never lapses with its countdown rendered `Infinityd`. The team stays stopped — a corrupt
53
54
  * deadline must not resume it — but it no longer claims a deadline no clock can reach. */
54
55
  function standingMarker(path) {
55
56
  const m = readJsonFile(path);
@@ -69,21 +70,21 @@ function standingMarker(path) {
69
70
  delete m.reason;
70
71
  return m;
71
72
  }
72
- /** The fleet marker's standing deadline (ms epoch) while a timed pause holds; undefined when
73
- * the fleet is not timed-paused — no marker, an indefinite `pause` (no `until`), or an expired
73
+ /** The team marker's standing deadline (ms epoch) while a timed pause holds; undefined when
74
+ * the team is not timed-paused — no marker, an indefinite `pause` (no `until`), or an expired
74
75
  * `until` (standingMarker already reads that as unpaused, so the field cannot outlive the
75
- * pause it describes). Fleet-scoped on purpose: the FLEET marker's deadline is what a header
76
- * badge may claim, since only it covers the whole fleet. Lives here beside isFleetPaused —
76
+ * pause it describes). Team-scoped on purpose: the TEAM marker's deadline is what a header
77
+ * badge may claim, since only it covers the whole team. Lives here beside isTeamPaused —
77
78
  * one module for every marker consumer. */
78
79
  export function pausedUntil(root) {
79
- return standingFleetPause(root)?.until;
80
+ return standingTeamPause(root)?.until;
80
81
  }
81
- /** The standing fleet pause's operator reason (`tumwater pause --reason <text>`), undefined
82
- * when the fleet is not paused or the standing pause carries none — same read (and the same
82
+ /** The standing team pause's operator reason (`tumwater pause --reason <text>`), undefined
83
+ * when the team is not paused or the standing pause carries none — same read (and the same
83
84
  * never-throws, expiry-honoring contract) as pausedUntil beside which it lives, so a caller
84
- * wanting both pays one standingFleetPause read, not two. */
85
+ * wanting both pays one standingTeamPause read, not two. */
85
86
  export function pausedReason(root) {
86
- return standingFleetPause(root)?.reason;
87
+ return standingTeamPause(root)?.reason;
87
88
  }
88
89
  /** Fold an operator pause reason to the one line every pause surface renders (the marker
89
90
  * doc's "one-line reason", the status header's badge, the alerts title, the CLI's
@@ -93,13 +94,13 @@ export function pausedReason(root) {
93
94
  * that would split an astral character and marks the cut with an ellipsis), so the CLI
94
95
  * confirmation, the marker, and every read side quote the same bounded line — a cap applied
95
96
  * only at the writer let `pause --reason <over-cap text>` print a note longer than the one
96
- * persisted. One home beside pauseFleet so the CLI path and any later GUI writer cannot
97
+ * persisted. One home beside pauseTeam so the CLI path and any later GUI writer cannot
97
98
  * drift on the shape the marker stores. */
98
99
  export function normalizePauseReason(reason) {
99
100
  const oneLine = reason === undefined ? undefined : collapseWhitespace(reason);
100
101
  return oneLine ? truncate(oneLine, PAUSE_REASON_MAX) : undefined;
101
102
  }
102
- /** Pause the fleet by writing its marker, the writer half of isFleetPaused's contract; the
103
+ /** Pause the team by writing its marker, the writer half of isTeamPaused's contract; the
103
104
  * marker format ({ at: number }, pretty-printed JSON) is the one `tumwater pause` has always
104
105
  * written. `reason` is the operator's one-line why (`tumwater pause --reason <text>`):
105
106
  * folded to one line and capped at PAUSE_REASON_MAX (both normalizePauseReason) — the
@@ -109,8 +110,8 @@ export function normalizePauseReason(reason) {
109
110
  * indefinite pause lands on an already-standing one, so the CLI can report "already paused"
110
111
  * and the dashboard's toggle stays idempotent — a no-op never touches the standing reason
111
112
  * (a timed `--for` on a standing pause overwrites the deadline instead). Lives here beside
112
- * isFleetPaused so the producer (CLI, GUI) and every consumer read the same path. */
113
- export function pauseFleet(root, untilMs, reason) {
113
+ * isTeamPaused so the producer (CLI, GUI) and every consumer read the same path. */
114
+ export function pauseTeam(root, untilMs, reason) {
114
115
  const marker = pausedPath(root);
115
116
  // An already-standing pause is the idempotent no-op it has always been — except under a
116
117
  // fresh `--for`, which overwrites the deadline (extend or shorten): the operator asked for
@@ -126,22 +127,46 @@ export function pauseFleet(root, untilMs, reason) {
126
127
  writeJsonAtomic(marker, body);
127
128
  return true;
128
129
  }
129
- /** Resume the fleet by removing its marker (a no-op if absent); returns whether a marker was
130
- * there to lift, mirroring pauseFleet's changed-state contract. Uses removeQuiet's
130
+ /** Resume the team by removing its marker (a no-op if absent); returns whether a marker was
131
+ * there to lift, mirroring pauseTeam's changed-state contract. Uses removeQuiet's
131
132
  * never-throws delete: a marker vanishing between the check and the unlink is success, not a
132
133
  * failure worth surfacing from a toggle. */
133
- export function resumeFleet(root) {
134
+ export function resumeTeam(root) {
134
135
  const marker = pausedPath(root);
135
136
  if (!fs.existsSync(marker))
136
137
  return false;
137
138
  removeQuiet(marker);
138
139
  return true;
139
140
  }
141
+ /** Schedule a graceful team stop at `untilMs` (epoch ms): `tumwater stop --in`. Overwrites the
142
+ * marker, so the last write wins. */
143
+ export function scheduleStop(root, untilMs) {
144
+ writeJsonAtomic(scheduledStopPath(root), { at: Date.now(), until: untilMs });
145
+ }
146
+ /** The scheduled stop's deadline (epoch ms), or undefined. Never throws: a missing, unreadable,
147
+ * or non-finite marker reads as no stop scheduled. */
148
+ export function scheduledStopAt(root) {
149
+ try {
150
+ const body = readJsonFile(scheduledStopPath(root));
151
+ return typeof body?.until === "number" && Number.isFinite(body.until) ? body.until : undefined;
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ }
157
+ /** Drop the scheduled stop; returns whether a marker was there (resumeTeam's contract). */
158
+ export function cancelScheduledStop(root) {
159
+ const marker = scheduledStopPath(root);
160
+ if (!fs.existsSync(marker))
161
+ return false;
162
+ removeQuiet(marker);
163
+ return true;
164
+ }
140
165
  /** The roles the operator has individually paused (`tumwater pause --role <id>`): a role in
141
166
  * this set starts no new ticks — in-flight ones finish, every other role keeps ticking, the
142
- * director included (the operator named it deliberately). Never throws, like isFleetPaused:
167
+ * director included (the operator named it deliberately). Never throws, like isTeamPaused:
143
168
  * a missing or unreadable file reads as no paused roles, so observers (scheduler, dashboards)
144
- * can poll it every cycle without a guard. Lives here beside pauseFleet for the same
169
+ * can poll it every cycle without a guard. Lives here beside pauseTeam for the same
145
170
  * single-definition reason: producer (CLI, GUI) and every consumer read one module. */
146
171
  export function pausedRoles(root) {
147
172
  return readPausedRoles(pausedRolesPath(root)).roles;
@@ -176,7 +201,7 @@ function readPausedRoles(path) {
176
201
  const until = finiteNumber(state.until, undefined);
177
202
  if (until !== undefined && until <= Date.now())
178
203
  return { roles: [] };
179
- return { roles: stringList(state.roles), until };
204
+ return { roles: stringList(state.roles).map((r) => canonicalRoleId(r)), until };
180
205
  }
181
206
  /** The paused-roles marker is structured state several processes rewrite read-modify-write
182
207
  * (the CLI's pause/resume --role and the dashboard's per-row toggle are separate processes),
@@ -204,7 +229,7 @@ function withPausedRolesLock(root, fn) {
204
229
  * command that named a different role (BUGS.md 2026-09-30). undefined when the file is
205
230
  * missing, unreadable, or carries no `until` — or a non-numeric, non-finite, or already-
206
231
  * expired one, the same wrong-shape-reads-as-absent discipline standingMarker applies to the
207
- * fleet marker. Dropping a non-finite value here matters beyond the read: a rewrite carries
232
+ * team marker. Dropping a non-finite value here matters beyond the read: a rewrite carries
208
233
  * this value into the next marker body, where JSON.stringify turns Infinity into null — which
209
234
  * the expiry rule then reads as a past deadline and resumes every role. */
210
235
  function standingRolesUntil(path) {
@@ -223,7 +248,7 @@ function writePausedRoles(path, roles, until) {
223
248
  writeJsonAtomic(path, until === undefined ? { roles, at: Date.now() } : { roles, at: Date.now(), until });
224
249
  }
225
250
  /** Pause one role by adding it to the paused-roles marker; returns whether this call changed
226
- * state (false when the role was already paused — idempotent like pauseFleet, so the CLI and
251
+ * state (false when the role was already paused — idempotent like pauseTeam, so the CLI and
227
252
  * a dashboard toggle can report "already paused"). Custom-loop ids are stored verbatim: the
228
253
  * marker must survive config edits, which is why callers resolve built-in ids without
229
254
  * touching tumwater.json (namedRole in src/operator/operator-commands.ts). */
@@ -252,8 +277,8 @@ export function pauseRole(root, role, untilMs) {
252
277
  });
253
278
  }
254
279
  /** Resume one role by removing it from the paused-roles marker; returns whether it was there
255
- * to lift (resume's changed-state contract, mirroring resumeFleet). The last removal deletes
256
- * the file outright, so a fully-resumed fleet leaves no marker behind for observers to read. */
280
+ * to lift (resume's changed-state contract, mirroring resumeTeam). The last removal deletes
281
+ * the file outright, so a fully-resumed team leaves no marker behind for observers to read. */
257
282
  export function resumeRole(root, role) {
258
283
  return withPausedRolesLock(root, () => {
259
284
  const current = pausedRoles(root);
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  /** Local date/time formatting and calendar-day arithmetic — the zero-padded clock and
2
3
  * calendar parts, local-midnight day math, and the report-window header every display and
3
4
  * report surface (transcript run separators, the status table's last-tick cell, daily-budget
@@ -14,12 +15,18 @@ import { EVENTS_LOG_BASENAME } from "../paths.js";
14
15
  export function pad2(n) {
15
16
  return String(n).padStart(2, "0");
16
17
  }
18
+ /** The local wall-clock `HH:MM` of an instant, the hours/minutes-only shape the burn-rate
19
+ * forecast, the window badge, and the role view's fallback line share. */
20
+ export function clockHm(ms) {
21
+ const d = new Date(ms);
22
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
23
+ }
17
24
  /** Whole seconds between two epoch-ms instants, rounded to the nearest second: `secondsSince`
18
25
  * clamps at zero (an age never goes negative when the clock skews), `secondsUntil` clamps at
19
26
  * zero the same way for the future direction (a countdown never reads negative once its
20
27
  * deadline has passed). The one home of the clamp + rounding behind every elapsed age and
21
28
  * countdown — status-render.ts's lastTickCell and nextRunCell, the queued-prompt age and
22
- * deliver-in suffix, the fleet pause's auto-resume countdown, and status-model.ts's sleeping
29
+ * deliver-in suffix, the team pause's auto-resume countdown, and status-model.ts's sleeping
23
30
  * phase label (which re-floors at 1s) all render through. The GUI page's browser
24
31
  * script keeps its own one-line twins (gui-client.ts's ageSec and its inline countdown
25
32
  * rounding — a separate runtime that cannot import TypeScript), pinned by its marked regions'
@@ -60,6 +67,15 @@ export function formatTime(d) {
60
67
  export function dayKey(ms) {
61
68
  return formatDate(new Date(ms));
62
69
  }
70
+ /** Whole days from one `YYYY-MM-DD` key to another (negative when `toKey` is earlier). Parsed with
71
+ * Date.UTC so a DST shift cannot move the count by one. */
72
+ export function daysBetween(fromKey, toKey) {
73
+ const utc = (key) => {
74
+ const [y = 0, m = 1, d = 1] = key.split("-").map(Number);
75
+ return Date.UTC(y, m - 1, d);
76
+ };
77
+ return Math.round((utc(toKey) - utc(fromKey)) / 86_400_000);
78
+ }
63
79
  /** The `MM-DD` half of a `YYYY-MM-DD` day key (dayKey/formatDate): the redundant year
64
80
  * dropped for a line whose date lies inside the current window. Shared by the failure
65
81
  * digest's cluster ranges and transition stamps (failure-render.ts) and the usage report's
@@ -98,7 +114,7 @@ export function dayWindow(days, now) {
98
114
  }
99
115
  /** Compact whole-second duration: `45s`, `12m`, `3h`, or `2d` — the phrasing every relative
100
116
  * time renders through (ago labels, sleeping-remaining countdowns, `prompt --list`'s queued
101
- * age, the fleet pause's auto-resume countdown), so the s/m/h/d bucketing and rounding cannot
117
+ * age, the team pause's auto-resume countdown), so the s/m/h/d bucketing and rounding cannot
102
118
  * drift per consumer. A day or more reads days, not an hour count (`pause --for 90d`'s
103
119
  * countdown reads `90d`, not `2160h`). Lives here, beside the other time phrasing, so core
104
120
  * modules (which may not import src/ui) share the same bucketing too. */
@@ -119,6 +135,30 @@ export function humanSeconds(s) {
119
135
  }
120
136
  return `${Math.round(s / 86400)}d`;
121
137
  }
138
+ /** Humanized duration from `now` until a future epoch-ms instant — the "how long until it
139
+ * happens" phrase (a scheduled wake, a timed pause's auto-resume, a hold's re-open):
140
+ * humanSeconds over secondsUntil in one call, collapsing the ten sites that spelled the
141
+ * two-step composition out, so the s/m/h/d bucketing and the past-deadline zero-clamp for
142
+ * the future direction have one home. Reads `0s` once the instant has passed (secondsUntil
143
+ * clamps at zero), matching every countdown's existing behavior. */
144
+ export function humanUntil(atMs, now) {
145
+ return humanSeconds(secondsUntil(atMs, now));
146
+ }
147
+ /** The shared "when may this loop next act" phrase — the `next run` cell (status-render.ts's
148
+ * nextRunCell) and the `Next tick` line (role-render.ts's scheduleLine) render their schedule
149
+ * through it, so the two surfaces cannot drift on the backoff prefix or the humanSeconds
150
+ * bucketing. `remainSeconds` is the seconds until the next run (non-positive = due); `backoff`
151
+ * adds the `backoff ` prefix the wake action clears; `dueWord` names the due case and `withIn`
152
+ * selects the prose `in <duration>` form (the role view) over the cell's bare `<duration>`.
153
+ * Callers keep their own surrounding logic on either side — the status cell's in-flight `-`
154
+ * and its yield-scaled `×N` suffix, the role view's `not scheduled yet`. */
155
+ export function nextRunPhrase(remainSeconds, backoff, dueWord, withIn) {
156
+ if (remainSeconds <= 0)
157
+ return dueWord;
158
+ const label = humanSeconds(remainSeconds);
159
+ const base = withIn ? `in ${label}` : label;
160
+ return backoff ? `sleeping ${base}` : base;
161
+ }
122
162
  /** `N days`, singular at 1 — the window label both report headers render, so a one-day window
123
163
  * reads the same on either surface. Rendered through text/phrases.ts's plural, like every other
124
164
  * count-plus-noun phrase in the harness. */
@@ -11,6 +11,15 @@ import { textOr } from "./text.js";
11
11
  * home of the token display format shared by the status table's gen/peak-ctx columns and the
12
12
  * commit trailer's ctx field — pinned here so those surfaces cannot drift even though they
13
13
  * live in different modules. */
14
+ /** Append a section note to a running prompt, separated from what precedes it by a blank line.
15
+ * The tick and authoring-tick prompt assemblers (tick-prompt.ts, loop-tick.ts) build a prompt by
16
+ * stacking nameless sections — assignment, held-entry, rejected-review, conflict-discard,
17
+ * conflict-handback, revision and cut-off notes — each appended the same way. This is the single
18
+ * append so the `\n\n` section separator stays in one place. The note is appended unconditionally,
19
+ * exactly as the call sites appended the literal, so behavior is unchanged for any note. */
20
+ export function appendPromptNote(prompt, note) {
21
+ return `${prompt}\n\n${note}`;
22
+ }
14
23
  export function compactTokens(n) {
15
24
  if (n >= 1_000_000)
16
25
  return `${(n / 1_000_000).toFixed(1)}M`;
@@ -48,7 +57,7 @@ export function usdCap(n) {
48
57
  }
49
58
  /** A disk size in GB for display: one decimal and the unit (`12.3 GB`) — the single home of
50
59
  * the disk-size format shared by the event feed's disk lines (event-format.ts), the status
51
- * header's disk badge (badges.ts's `diskBadge`), the fleet's low-disk alert (fleet-alerts.ts),
60
+ * header's disk badge (badges.ts's `diskBadge`), the team's low-disk alert (team-alerts.ts),
52
61
  * the doctor's disk check detail (doctor-checks.ts), and `tumwater reclaim`'s freed-size line
53
62
  * (operator-commands.ts), so the decimal width and unit cannot drift per consumer. A caller
54
63
  * reading the value from an untrusted event field applies event-format.ts's `gigabytes`
@@ -22,7 +22,7 @@ export function markdownTable(header, rows, align = []) {
22
22
  * the `map((item, i) => `${i + 1}. ${item}`).join("\n")` step, shared by the review prompt's
23
23
  * prior objections, its stage-fix findings, and the rejected/revision notes
24
24
  * (prompt/gate-prompts.ts) and `tumwater questions`' open-question list
25
- * (cli/question-commands.ts), so the numbering shape cannot drift between the surfaces that
25
+ * (backlog/question-answer.ts), so the numbering shape cannot drift between the surfaces that
26
26
  * read a numbered list. `empty` is returned verbatim when `items` is empty (the reasons
27
27
  * sites pass `(no reasons recorded)`; the others keep the default empty string). */
28
28
  export function numberedList(items, empty = "") {
@@ -1,8 +1,9 @@
1
+ // vocabulary: plain
1
2
  import path from "node:path";
2
3
  import { finiteNumber, isJsonObject, stringList } from "../files/json-object.js";
3
4
  import { squash } from "./text.js";
4
5
  import { shortSha, usd } from "./format.js";
5
- /** The single home of each human-facing PHRASE the fleet renders — the wording fragments (a pause
6
+ /** The single home of each human-facing PHRASE the team renders — the wording fragments (a pause
6
7
  * reason's suffix, a rate-limit hold's "for 60s", a backend hold's kind, a budget transition's "$x
7
8
  * of $y", main's red-tip naming, a tool call's one-line label, the plural helper) shared by the
8
9
  * event feed (event-format.ts), the failure digest (src/failure/failure-render.ts and
@@ -14,11 +15,11 @@ import { shortSha, usd } from "./format.js";
14
15
  const NO_REASONS_GIVEN = "no reasons given";
15
16
  /** The reason an over-long prompt or config field carries: the text rides into a tick's
16
17
  * prefill, so a longer value bloats every tick's context. The single home of that tail,
17
- * shared by init's seed check (init/init.ts) and config-validation's customLoops task and
18
+ * shared by init's seed check (init/init.ts) and config-validation's customRoles task and
18
19
  * roles-instructions caps (config/config-validation.ts), so the three cannot drift on the
19
20
  * wording. inbox-submit's promptLengthProblem renders a role-scoped variant ("it rides into
20
21
  * the <role> tick's prefill") and keeps its own wording. */
21
- export const PREFILL_REASON = "it rides into every tick's prefill";
22
+ export const PREFILL_REASON = "it rides into every run's prefill";
22
23
  /** The reason a review rejection leads with — its first listed reason, or `no reasons given`
23
24
  * when the list is empty or its first entry is not a string. One home so the fallback cannot
24
25
  * drift across the four surfaces that show just that first reason: the review gate's returned
@@ -33,7 +34,7 @@ export function firstReason(reasons) {
33
34
  /** A count and its noun as one phrase (`plural(3, "tick")` → `3 ticks`) — the single home of
34
35
  * the singular/plural selection the CLI's once summary (cli/cli-run.ts), the day window's day label
35
36
  * (datetime.ts), the failure digest's loss-cause lines (src/failure/failure-render.ts), and the
36
- * fleet alerts' banner titles (ui/fleet-alerts.ts, whose local copy this replaces), and the
37
+ * team alerts' banner titles (ui/team-alerts.ts, whose local copy this replaces), and the
37
38
  * build_stale lines' "N commit(s) ahead" (event-format.ts, src/failure/failure-state-change.ts —
38
39
  * the singular/plural wording their tests pin, once "N commit(s)" with the old literal, now "N
39
40
  * commits"/"1 commit"), and the stage self-check and dropped-attachment notes
@@ -48,7 +49,7 @@ export function plural(n, one, many = `${one}s`) {
48
49
  * → `is`, `agree(2, "cause", "causes")` → `causes` — the companion to plural for the slots
49
50
  * plural's count-plus-noun shape cannot fill: a subject or noun whose count is printed elsewhere in
50
51
  * the sentence (a subject phrase such as listRoles's role list, a count named earlier) still needs
51
- * the right number form. The fleet alerts' banner titles (ui/fleet-alerts.ts), the resume
52
+ * the right number form. The team alerts' banner titles (ui/team-alerts.ts), the resume
52
53
  * confirmation's still-paused note (operator/operator-commands.ts), the dropped-attachment note
53
54
  * (inbox-attachments.ts), the failure digest's loss-cause and cluster remainders
54
55
  * (failure/failure-render.ts), the doctor's oh-my-pi and model notes
@@ -59,8 +60,8 @@ export function agree(n, one, many) {
59
60
  return n === 1 ? one : many;
60
61
  }
61
62
  /** The spine of every phrase naming main's tip as red — `main <short-sha> is red` — built here
62
- * once so the fleet-wide red-main warning (main-red.ts), the review gate's attribution detail
63
- * (review.ts), the landing check's attribution error (landing-check-failures.ts), and the redeploy
63
+ * once so the team-wide red-main warning (main-red.ts), the review gate's attribution detail
64
+ * (review.ts), the merge check's attribution error (merge-check-failures.ts), and the redeploy
64
65
  * hold's block reason (src/redeploy/redeployer.ts) cannot drift over how main's red is worded. Each
65
66
  * caller appends its own consequence — the gate's "— not this change's failure", the hold's "
66
67
  * — holding the restart until main is green". */
@@ -68,8 +69,8 @@ export function mainRedPhrase(sha) {
68
69
  return `main ${shortSha(sha)} is red`;
69
70
  }
70
71
  /** The main-red attribution detail — main's redness is not this change's failure — the review
71
- * pre-check's reject detail (review-precheck.ts) and the landing check's lastError
72
- * (landing-check-failures.ts) both build as mainRedPhrase plus the consequence suffix. One
72
+ * pre-check's reject detail (review-precheck.ts) and the merge check's lastError
73
+ * (merge-check-failures.ts) both build as mainRedPhrase plus the consequence suffix. One
73
74
  * home beside mainRedPhrase so the two attribution surfaces cannot drift over the wording
74
75
  * their tests pin. The other mainRedPhrase callers keep their own consequences: main-red.ts
75
76
  * names the failing script and its action, src/redeploy/redeployer.ts holds the restart. */
@@ -78,7 +79,7 @@ export function mainRedNotMine(sha) {
78
79
  }
79
80
  /** The ` — "<reason>"` suffix the operator's pause reason (`pause --reason <text>`) rides on —
80
81
  * one home so the CLI's confirmation line (operator/operator-commands.ts), the status header's
81
- * pause badge (ui/badges.ts), and the paused alert's title (ui/fleet-alerts.ts) cannot drift on the
82
+ * pause badge (ui/badges.ts), and the paused alert's title (ui/team-alerts.ts) cannot drift on the
82
83
  * quoting. No reason, no suffix: every caller's reasonless phrasing keeps today's byte-exact form.
83
84
  * (The GUI keeps its own JS copies in gui-client.ts: a separate runtime that cannot import
84
85
  * TypeScript.) */
@@ -87,48 +88,48 @@ export function pauseReasonSuffix(reason) {
87
88
  }
88
89
  /** The harness warning a loop crossing the error-streak threshold raises — `3 consecutive
89
90
  * tick failures: <reason>` — shared by the tick-side crossing (tick-finalize.ts, an error or
90
- * recovery failure) and the landing-side crossing a review rejection resolves
91
- * (landing-slot.ts), the two sites that build the message, so the operator-facing wording
91
+ * recovery failure) and the merge-side crossing a review rejection resolves
92
+ * (merge-slot.ts), the two sites that build the message, so the operator-facing wording
92
93
  * their tests pin cannot drift between them. */
93
94
  export function consecutiveFailuresWarning(count, reason) {
94
- return `${count} consecutive tick failures: ${reason}`;
95
+ return `${count} runs failed in a row: ${reason}`;
95
96
  }
96
- /** The base line for an operator fleet pause — `fleet paused — role loops stop starting new
97
+ /** The base line for an operator team pause — `team paused — role loops stop starting new
97
98
  * ticks` — built here so the event feed (event-format.ts, which appends its own `(director
98
- * keeps running)` consequence) and the failure digest's Fleet state changes lines
99
+ * keeps running)` consequence) and the failure digest's Team state changes lines
99
100
  * (src/failure/failure-state-change.ts, which keeps the short form) cannot drift on the base
100
101
  * wording. `tumwater pause`'s confirmation (operator/operator-commands.ts) interleaves its
101
102
  * `--for`/`--reason` spans before the dash, so it composes its own line and keeps that wording
102
103
  * inline. */
103
- export function fleetPausePhrase() {
104
- return "fleet paused — role loops stop starting new ticks";
104
+ export function teamPausePhrase() {
105
+ return "team paused — roles start no new runs";
105
106
  }
106
- /** The base line for lifting the fleet pause — `fleet resumed — role loops tick again` —
107
- * shared by the event feed (event-format.ts), the failure digest's Fleet state changes lines
107
+ /** The base line for lifting the team pause — `team resumed — roles run again` —
108
+ * shared by the event feed (event-format.ts), the failure digest's Team state changes lines
108
109
  * (src/failure/failure-state-change.ts), and `tumwater resume`'s confirmation
109
110
  * (operator/operator-commands.ts, which appends its `--for` and still-paused-role spans), so the
110
111
  * three cannot drift. */
111
- export function fleetResumePhrase() {
112
- return "fleet resumed — role loops tick again";
112
+ export function teamResumePhrase() {
113
+ return "team resumed — roles run again";
113
114
  }
114
- /** The base line for a per-role pause — `role <id> paused — it stops starting new ticks` —
115
- * shared by the event feed (event-format.ts, which appends `(the rest of the fleet keeps running)`)
116
- * and the failure digest's Fleet state changes lines (src/failure/failure-state-change.ts, which
115
+ /** The base line for a per-role pause — `role <id> paused — it starts no new runs` —
116
+ * shared by the event feed (event-format.ts, which appends `(the rest of the team keeps running)`)
117
+ * and the failure digest's Team state changes lines (src/failure/failure-state-change.ts, which
117
118
  * deliberately keeps the short form), so the two cannot drift on the base wording. The role is
118
119
  * coerced, so the digest's byte-sliced `field()` output and the feed's raw field both render. */
119
120
  export function rolePausePhrase(role) {
120
- return `role ${String(role)} paused — it stops starting new ticks`;
121
+ return `role ${String(role)} paused — it starts no new runs`;
121
122
  }
122
- /** The base line for a per-role resume — `role <id> resumed — it ticks again` — shared by the
123
- * event feed (event-format.ts) and the failure digest's Fleet state changes lines
123
+ /** The base line for a per-role resume — `role <id> resumed — it runs again` — shared by the
124
+ * event feed (event-format.ts) and the failure digest's Team state changes lines
124
125
  * (src/failure/failure-state-change.ts), so the two cannot drift. The role is coerced, like
125
126
  * rolePausePhrase. */
126
127
  export function roleResumePhrase(role) {
127
- return `role ${String(role)} resumed — it ticks again`;
128
+ return `role ${String(role)} resumed — it runs again`;
128
129
  }
129
130
  /** The line for an orchestrator shutdown — the event feed (event-format.ts) and the failure
130
- * digest's Fleet state changes lines (src/failure/failure-state-change.ts) both render it. */
131
- export const ORCHESTRATOR_STOPPED = "orchestrator stopped";
131
+ * digest's Team state changes lines (src/failure/failure-state-change.ts) both render it. */
132
+ export const ORCHESTRATOR_STOPPED = "tumwater stopped";
132
133
  /** A HarnessEvent's loosely typed `roles` list as a `A, B, C` phrase — the single home of the
133
134
  * array coercion and join, shared by the event feed (event-format.ts: counters_reset's scope,
134
135
  * budget_handback's handed-back list, the rate_limit_hold line) and the failure digest's state-
@@ -145,7 +146,7 @@ export function rolesPhrase(roles, fallback, max, format = String) {
145
146
  return list.length > 0 ? list.join(", ") : fallback;
146
147
  }
147
148
  /** The `$<spent> of $<cap>` fragment every budget-transition event renders — the one home of that
148
- * phrasing, shared by the event feed (event-format.ts) and the failure digest's Fleet state changes
149
+ * phrasing, shared by the event feed (event-format.ts) and the failure digest's Team state changes
149
150
  * lines (src/failure/failure-state-change.ts), so a budget transition reads the same on both
150
151
  * surfaces. Both fields arrive loosely typed on HarnessEvent, so each is coerced through
151
152
  * finiteNumber (a non-number or non-finite value reads as 0, exactly as event-read.ts's eventUsage
@@ -164,7 +165,7 @@ export function shortSpanPhrase(ms) {
164
165
  return seconds < 120 ? `${seconds}s` : `${Math.round(ms / 60_000)}m`;
165
166
  }
166
167
  /** The `for <duration>[ (relapse N)]` fragment the rate_limit_hold event renders — the one home
167
- * of that phrasing, shared by the event feed (event-format.ts) and the failure digest's Fleet state
168
+ * of that phrasing, shared by the event feed (event-format.ts) and the failure digest's Team state
168
169
  * changes lines (src/failure/failure-state-change.ts), like budgetPhrase. The duration is
169
170
  * shortSpanPhrase (seconds under two minutes, so the one-minute base hold reads `60s`); the relapse
170
171
  * count is named only when the storm resumed right after an earlier hold, the one fact that says
@@ -175,8 +176,8 @@ export function holdPhrase(holdMs, escalation) {
175
176
  const relapse = Number(escalation ?? 0);
176
177
  return `for ${span}${relapse > 0 ? ` (relapse ${relapse})` : ""}`;
177
178
  }
178
- /** The human phrase for a fleet hold's backend-failure kind — the one home of that phrasing,
179
- * shared by the event feed (event-format.ts) and the failure digest's Fleet state changes
179
+ /** The human phrase for a team hold's backend-failure kind — the one home of that phrasing,
180
+ * shared by the event feed (event-format.ts) and the failure digest's Team state changes
180
181
  * lines (src/failure/failure-state-change.ts), like holdPhrase above. A "rate-limit" hold (or a
181
182
  * hold with no readable kind — a torn line) never renders through this: those keep the 429
182
183
  * wording, which is the shape every historical event already has. */
@@ -54,8 +54,9 @@ export function didYouMean(suggestion) {
54
54
  /** suggestClosest and didYouMean composed: the " — did you mean `x`?" suffix for a mistyped
55
55
  * `input` against `candidates`, the empty string when nothing is close enough to suggest. The
56
56
  * unknown-X error sites (roles.ts's unknownRoleMessage, config-write.ts's
57
- * unknownConfigKeyError and its role-field error, config-field-checks.ts's checkKnownKeys
58
- * and checkKnownRoleId, gui/gui-args.ts's rejectBadRole, init-templates.ts's
57
+ * unknownConfigKeyError and its role-field error, config-field-checks.ts's checkKnownKeys,
58
+ * checkKnownRoleId and its thinking-level problem, config-validation.ts's backend problem,
59
+ * gui/gui-args.ts's rejectBadRole, init-templates.ts's
59
60
  * unknownTemplateError) render the hint
60
61
  * through this one composition so the two-step pairing cannot drift; callers still print the
61
62
  * full valid list themselves — a suggestion only annotates it. (cli.ts's unknown-command and
@@ -4,7 +4,7 @@
4
4
  * integer parsing. (Number, money, and hash formats — compactTokens, shortSha, usd, usdCap —
5
5
  * live in format.ts; local date/time formatting and calendar-day arithmetic live in
6
6
  * datetime.ts; terminal-column geometry — the wcwidth table and the displayWidth/
7
- * padToWidth/clipToWidth clippers — lives in text-width.ts; the fleet's shared wording
7
+ * padToWidth/clipToWidth clippers — lives in text-width.ts; the team's shared wording
8
8
  * fragments — the red-main phrase, the pause-reason suffix, the hold, budget, and backend
9
9
  * phrasings, tool-call labels — live in text/phrases.ts; the did-you-mean suggestion layer —
10
10
  * suggestClosest, didYouMean, typoSuffix — lives in text/suggest.ts; the Markdown table
@@ -191,7 +191,7 @@ export function parseNonNegativeInt(raw) {
191
191
  * with `tail` naming where the text rides once accepted. The one home of that phrasing, used
192
192
  * by init's seed check (init.ts, "the initial prompt … every tick's prefill"), inbox-submit's
193
193
  * promptLengthProblem ("the prompt … the <role> tick's prefill"), and config-validation's
194
- * customLoops task and roles instructions caps (both "every tick's prefill"), so the four
194
+ * customRoles task and roles instructions caps (both "every tick's prefill"), so the four
195
195
  * too-long messages cannot drift apart in shape. Callers keep their own subject, cap, and
196
196
  * tail; this owns only the sentence around them. */
197
197
  export function tooLongMessage(subject, n, max, tail) {
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
2
3
  import { qaCoveragePath } from "../paths.js";
3
4
  import { isJsonObject } from "../files/json-object.js";
@@ -101,7 +102,7 @@ export function renderCoverageBlock(coverage, now = Date.now()) {
101
102
  return ` ${name} — ${age} ago, ${status}`;
102
103
  });
103
104
  return [
104
- "Flow coverage (from this fleet's own record; least recently exercised first):",
105
+ "Flow coverage (from this team's own record; least recently exercised first):",
105
106
  ...lines,
106
107
  ].join("\n");
107
108
  }