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
@@ -1,50 +1,66 @@
1
- import { pausedRoles, pausedRolesUntil, standingFleetPause } from "../fleet/fleet-state.js";
1
+ // vocabulary: plain
2
+ import { roleLabel } from "../roles/role-labels.js";
3
+ import { pausedRoles, pausedRolesUntil, standingTeamPause } from "../team/team-state.js";
4
+ import { MODEL_TIERS } from "../config/config-schema.js";
5
+ import { withModelOverrides } from "../config/config-views.js";
2
6
  import { commitTier, DIRECTOR_ROLE } from "../roles/roles.js";
3
- import { budgetReached, budgetWarning, fleetDailyCost } from "../budget/budget.js";
7
+ import { budgetReached, budgetWarning, teamDailyCost } from "../budget/budget.js";
4
8
  import { roleCapVerdicts } from "../gates/role-cap-gates.js";
5
- import { readLandingQueue } from "../landing/landing-queue.js";
9
+ import { readMergeQueue } from "../merge/merge-queue.js";
6
10
  import { MAINTENANCE_DAILY_FLOOR } from "../gates/maintenance-quota.js";
7
- import { maintenanceQuotaForPoll } from "../status/status-polls.js";
11
+ import { heldByMainRed, mainCheckForPoll, maintenanceQuotaForPoll } from "../status/status-polls.js";
8
12
  import { loadLoopState } from "../loop/loop-state.js";
9
- import { loopIds } from "../roles/loop-ids.js";
13
+ import { baseRoleOf, loopIds, loopPaused } from "../roles/loop-ids.js";
14
+ import { ERROR_STREAK_BREAKER, ERROR_STREAK_WARN } from "../tick/tick-apply.js";
15
+ import { squash } from "../text/text.js";
10
16
  import { usd } from "../text/format.js";
11
17
  import { parseQuietHours, quietHoursStatus, quietWindowEnd, roleQuietHold } from "../scheduling/quiet-hours.js";
12
- import { humanSeconds, secondsUntil } from "../text/datetime.js";
13
- import { budgetPhrase, pauseReasonSuffix } from "../text/phrases.js";
18
+ import { humanUntil } from "../text/datetime.js";
19
+ import { budgetPhrase, pauseReasonSuffix, plural } from "../text/phrases.js";
20
+ import { claudeUtilizationPct, claudeWindowEntries, claudeWindowLabel, readClaudeAccount } from "../agent/claude/claude-account.js";
21
+ import { claudeWindowObservation } from "../team/team-polls.js";
22
+ import { readEventsSince } from "../events/event-window.js";
23
+ import { errorMessage } from "../text/text.js";
14
24
  import { okOutcome, warnOutcome } from "./outcome.js";
15
25
  /** The standing-hold checks: `pause` (checkPause), `quiet hours` (checkQuietHours), and `budget`
16
- * (checkBudget), and `maintenance quota` (checkMaintenanceQuota), all read-only reports of holds on ticking.
17
- *
18
- * The `pause` check: names a standing operator pause so doctor does not print "ready to run"
19
- * over a fleet that starts no tick. Read-only: it reads the same fleet and per-role pause
20
- * markers the scheduler reads (standingFleetPause, pausedRoles, pausedRolesUntil — all treat
21
- * an expired marker as absent), so its verdict cannot disagree with what the fleet does. A
22
- * fleet pause is a warn because it stops every loop; paused roles are ok because the operator
26
+ * (checkBudget), `claude window` (checkClaudeWindow), `maintenance quota`
27
+ * (checkMaintenanceQuota), and `error streaks` (checkErrorStreaks), all read-only reports of
28
+ * holds on ticking. */
29
+ export function loadLoopStates(root, config) {
30
+ return loopIds(config)
31
+ .map((id) => loadLoopState(root, id))
32
+ .sort((a, b) => a.role.localeCompare(b.role));
33
+ }
34
+ /** The `pause` check: names a standing operator pause so doctor does not print "ready to run"
35
+ * over a team that starts no tick. Read-only: it reads the same team and per-role pause
36
+ * markers the scheduler reads (standingTeamPause, pausedRoles, pausedRolesUntil — all treat
37
+ * an expired marker as absent), so its verdict cannot disagree with what the team does. A
38
+ * team pause is a warn because it stops every loop; paused roles are ok because the operator
23
39
  * chose them by name and every other loop keeps ticking. `now` only drives the countdowns. */
24
40
  export function checkPause(root, now = Date.now()) {
25
- const fleet = standingFleetPause(root);
41
+ const team = standingTeamPause(root);
26
42
  const roles = pausedRoles(root);
27
43
  let rolesPhrase = "";
28
44
  if (roles.length > 0) {
29
45
  const until = pausedRolesUntil(root);
30
- rolesPhrase = `paused loops: ${roles.join(", ")}${until !== undefined ? ` (auto-resume in ${humanSeconds(secondsUntil(until, now))})` : ""}`;
46
+ rolesPhrase = `paused roles: ${roles.join(", ")}${until !== undefined ? ` (auto-resume in ${humanUntil(until, now)})` : ""}`;
31
47
  }
32
- if (!fleet)
48
+ if (!team)
33
49
  return rolesPhrase ? okOutcome(rolesPhrase) : okOutcome("not paused");
34
- const head = fleet.until !== undefined
35
- ? `fleet paused — auto-resumes in ${humanSeconds(secondsUntil(fleet.until, now))}`
36
- : "fleet paused — no loop starts a tick until `tumwater resume`";
37
- return warnOutcome(`${head}${pauseReasonSuffix(fleet.reason)}${rolesPhrase ? `; ${rolesPhrase}` : ""}`);
50
+ const head = team.until !== undefined
51
+ ? `team paused — auto-resumes in ${humanUntil(team.until, now)}`
52
+ : "team paused — no role starts a run until `tumwater resume`";
53
+ return warnOutcome(`${head}${pauseReasonSuffix(team.reason)}${rolesPhrase ? `; ${rolesPhrase}` : ""}`);
38
54
  }
39
- /** The `quiet hours` check: names the fleet quiet window and any per-role windows holding now,
40
- * so doctor does not print "ready to run" over a fleet that starts no tick until the window
55
+ /** The `quiet hours` check: names the team quiet window and any per-role windows holding now,
56
+ * so doctor does not print "ready to run" over a team that starts no tick until the window
41
57
  * ends. Read-only: membership comes from the scheduler's own parser and predicate
42
- * (quietHoursStatus, roleQuietHold), so the verdict cannot disagree with the gate. A fleet
58
+ * (quietHoursStatus, roleQuietHold), so the verdict cannot disagree with the gate. A team
43
59
  * window holding is a warn because it stops every loop (the director is exempt); held per-role
44
60
  * windows are ok because the operator chose those roles by name. A malformed value reads as
45
61
  * unset here — checkInit owns that error. `now` is injected so tests never read the clock. */
46
62
  export function checkQuietHours(config, now = new Date()) {
47
- const fleet = quietHoursStatus(config?.quietHours, now);
63
+ const team = quietHoursStatus(config?.quietHours, now);
48
64
  const perRole = config?.quietHoursPerRole;
49
65
  const roleKeys = Object.keys(perRole ?? {}).filter((k) => k !== DIRECTOR_ROLE);
50
66
  const held = roleKeys.filter((k) => roleQuietHold(perRole, k, now)).sort();
@@ -53,13 +69,13 @@ export function checkQuietHours(config, now = new Date()) {
53
69
  return parsed.ok && parsed.window !== null;
54
70
  });
55
71
  const phrases = [];
56
- if (fleet.window !== null) {
57
- phrases.push(fleet.inWindow
58
- ? `quiet now (${fleet.window}) — no role loop starts a tick until ${quietWindowEnd(fleet.window)}; the director is exempt`
59
- : `${fleet.window} (not in the window now)`);
72
+ if (team.window !== null) {
73
+ phrases.push(team.inWindow
74
+ ? `quiet now (${team.window}) — no role starts a run until ${quietWindowEnd(team.window)}; the director is exempt`
75
+ : `${team.window} (not in the window now)`);
60
76
  }
61
77
  if (held.length > 0) {
62
- phrases.push(`quiet loops: ${held.map((k) => `${k} until ${quietWindowEnd(String(perRole?.[k]))}`).join(", ")}`);
78
+ phrases.push(`quiet roles: ${held.map((k) => `${k} until ${quietWindowEnd(String(perRole?.[k]))}`).join(", ")}`);
63
79
  }
64
80
  else if (anyRoleWindow) {
65
81
  phrases.push("per-role windows set, none holding now");
@@ -67,56 +83,180 @@ export function checkQuietHours(config, now = new Date()) {
67
83
  if (phrases.length === 0)
68
84
  return okOutcome("none configured");
69
85
  const detail = phrases.join("; ");
70
- return fleet.inWindow ? warnOutcome(detail) : okOutcome(detail);
86
+ return team.inWindow ? warnOutcome(detail) : okOutcome(detail);
71
87
  }
72
- /** The `budget` check: today's fleet spend against `maxDailyCostUsd`, plus base roles over their
88
+ /** The `budget` check: today's team spend against `maxDailyCostUsd`, plus base roles over their
73
89
  * own `maxDailyCostUsdPerRole` cap. Read-only and built on the scheduler's own definitions
74
- * (fleetDailyCost, budgetReached, roleCapVerdicts), so the verdict cannot disagree with the gate.
90
+ * (teamDailyCost, budgetReached, roleCapVerdicts), so the verdict cannot disagree with the gate.
75
91
  * `published` is the running scheduler's figure (it includes runs charged since the last save)
76
- * and wins over the persisted sum, as in status. A reached fleet cap is a warn because role loops
92
+ * and wins over the persisted sum, as in status. A reached team cap is a warn because role loops
77
93
  * run on their tier fallback or hold until local midnight (the `fallback` row says which);
78
94
  * per-role caps reached alone are ok because the operator chose them by name. */
79
95
  export function checkBudget(root, config, published, now = Date.now()) {
80
96
  if (config === undefined)
81
97
  return okOutcome("not checked — tumwater.json unreadable");
82
- const states = loopIds(config).map((id) => loadLoopState(root, id));
83
- const spent = published?.spentUsd ?? fleetDailyCost(states, now);
98
+ const states = loadLoopStates(root, config);
99
+ const spent = published?.spentUsd ?? teamDailyCost(states, now);
84
100
  const caps = config.maxDailyCostUsdPerRole;
85
101
  const capped = [...roleCapVerdicts(states.map((s) => ({ role: s.role, state: s })), caps, now)]
86
102
  .filter(([, v]) => v.over)
87
103
  .sort(([a], [b]) => a.localeCompare(b))
88
104
  .map(([base, v]) => `${base} (${budgetPhrase(v.spentUsd, caps?.[base])})`);
89
- const cappedPhrase = capped.length > 0 ? `; capped loops: ${capped.join(", ")}` : "";
105
+ const cappedPhrase = capped.length > 0 ? `; capped roles: ${capped.join(", ")}` : "";
90
106
  const cap = config.maxDailyCostUsd;
91
107
  if (!(cap > 0))
92
108
  return okOutcome(`no daily cap; ${usd(spent)} spent today${cappedPhrase}`);
93
109
  const view = { spentUsd: spent, capUsd: cap };
94
110
  const phrase = budgetPhrase(spent, cap);
95
111
  if (budgetReached(view)) {
96
- return warnOutcome(`daily cap reached — ${phrase} today; role loops run on their tier fallback or hold until local midnight (see the fallback row)${cappedPhrase}`);
112
+ return warnOutcome(`daily cap reached — ${phrase} today; roles run on their over-cap model or hold until local midnight (see the over-cap model row)${cappedPhrase}`);
97
113
  }
98
114
  if (budgetWarning(view))
99
115
  return okOutcome(`${phrase} today — past 80% of the cap${cappedPhrase}`);
100
116
  return okOutcome(`${phrase} today${cappedPhrase}`);
101
117
  }
102
- /** The `maintenance quota` check: rolling-24 h maintenance landings against the allowance
103
- * (`maintenancePerWorkLanding` per work landing + the daily floor), naming the maintenance loops
104
- * held when it is full. Read-only and built on the same verdict the scheduler and status use
105
- * (maintenanceQuotaForPoll). Only a live fleet's running loops and landing queue count as in
106
- * flight, since a dead fleet's leftover `running` flag and queue drain nothing. A held allowance
107
- * is a warn because it stops a whole tier. */
108
- export function checkMaintenanceQuota(root, config, aliveFleet, now = Date.now()) {
118
+ /** The `maintenance quota` check: rolling-24 h maintenance merges against the allowance
119
+ * (`maintenancePerWorkMerge` per work merge + the daily floor), naming the maintenance loops held
120
+ * when it is full. Read-only and built on the same verdict the scheduler and status use
121
+ * (maintenanceQuotaForPoll). Only a live team's running loops and merge queue count as in flight,
122
+ * since a dead team's leftover `running` flag and queue drain nothing. A held allowance is a warn
123
+ * because it stops a whole tier. */
124
+ export function checkMaintenanceQuota(root, config, aliveTeam, now = Date.now()) {
109
125
  if (config === undefined)
110
126
  return okOutcome("not checked — tumwater.json unreadable");
111
- const loops = loopIds(config)
112
- .map((id) => loadLoopState(root, id))
113
- .map((s) => ({ role: s.role, running: aliveFleet && s.running === true }));
114
- const queued = aliveFleet ? new Set(readLandingQueue(root).entries.map((e) => e.role)) : new Set();
115
- const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkLanding, loops, queued, now);
127
+ const loops = loadLoopStates(root, config).map((s) => ({ role: s.role, running: aliveTeam && s.running === true }));
128
+ const queued = aliveTeam ? new Set(readMergeQueue(root).entries.map((e) => e.role)) : new Set();
129
+ const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkMerge, loops, queued, now);
116
130
  if (held.length > 0) {
117
- return warnOutcome(`allowance reached — ${used}/${allowance} maintenance landings in the last 24h; held until the window rolls under: ${[...held].sort().join(", ")}`);
131
+ return warnOutcome(`allowance reached — ${used}/${allowance} maintenance merges in the last 24h; held until the window rolls under: ${[...held].sort().join(", ")}`);
118
132
  }
119
- const base = `${used}/${allowance} maintenance landings in the last 24h (${config.maintenancePerWorkLanding} per work landing + ${MAINTENANCE_DAILY_FLOOR} floor)`;
133
+ const base = `${used}/${allowance} maintenance merges in the last 24h (${config.maintenancePerWorkMerge} per work merge + ${MAINTENANCE_DAILY_FLOOR} floor)`;
120
134
  const anyMaintenance = loops.some((l) => commitTier(l.role) === "maintenance");
121
- return okOutcome(anyMaintenance ? base : `${base}; no maintenance loop enabled`);
135
+ return okOutcome(anyMaintenance ? base : `${base}; no maintenance role enabled`);
136
+ }
137
+ /** The `claude window` check: whether a rejected subscription window holds the subscription claude
138
+ * seams. Read-only and built on claudeWindowObservation, the verdict the scheduler's team hold
139
+ * uses, so it cannot disagree with the hold. A hold is a warn because it stops every subscription
140
+ * claude seam until the window resets; otherwise the most constrained window's state is reported
141
+ * as ok (a rejected window spending overage reads `rejected` here: overage spends, it does not
142
+ * hold). API-billed claude never holds. */
143
+ export function checkClaudeWindow(root, now = Date.now(), chain, config) {
144
+ const account = readClaudeAccount(root, now);
145
+ if (account?.billing !== "subscription")
146
+ return okOutcome("no subscription window reported");
147
+ const held = claudeWindowObservation(account, now);
148
+ if (held?.kind === "subscription-window" && held.windowType !== undefined && held.resetsAt !== undefined) {
149
+ const label = claudeWindowLabel(held.windowType);
150
+ const resets = humanUntil(held.resetsAt, now);
151
+ const moved = MODEL_TIERS.filter((t) => {
152
+ const reason = chain?.[t]?.reason;
153
+ return reason === "window" || reason === "reserve";
154
+ });
155
+ if (moved.length > 0) {
156
+ const waiting = MODEL_TIERS.filter((t) => !chain?.[t] && config !== undefined && withModelOverrides(config, t, {}).agent === "claude");
157
+ return warnOutcome(`${label} window rejected — ${moved.join(", ")} moved to fallbacks until it resets in ${resets}${waiting.length > 0 ? `; ${waiting.join(", ")} wait` : ""}`);
158
+ }
159
+ return warnOutcome(`${label} window rejected — subscription claude model jobs wait until it resets in ${resets}`);
160
+ }
161
+ const top = claudeWindowEntries(account)[0];
162
+ if (top === undefined)
163
+ return okOutcome("subscription; no window reported yet");
164
+ const pct = typeof top.utilization === "number" ? ` (${claudeUtilizationPct(top.utilization)}% used)` : "";
165
+ const label = claudeWindowLabel(top.type);
166
+ return okOutcome(top.status === "allowed_warning" ? `${label} window nearing its limit${pct}` : `${label} window ${top.status}${pct}`);
167
+ }
168
+ /** How far back the `provider hold` check reads for hold/resume crossings. The longest a
169
+ * hold can stay is HOLD_CAP_MS (team-hold's 15-minute cap that escalation doubles against),
170
+ * so a window a comfortable multiple of that covers any standing hold's crossing event plus
171
+ * its resume — an older resumed event must not be missed or a re-opened provider reads held. */
172
+ const HOLD_CHECK_WINDOW_MS = 2 * 3600_000;
173
+ /** The `provider hold` check: names each provider whose team-wide failure hold
174
+ * (src/team/team-hold.ts, polled by pollTeamHold) stands now — a 429/backend-failure storm
175
+ * that stops the roles on that provider from starting new ticks until it re-opens. Read-only:
176
+ * it infers the standing set from the event log's own `rate_limit_hold`/`rate_limit_resumed`
177
+ * crossings, the events pollTeamHold writes, so it cannot disagree with the hold. A standing
178
+ * hold is a warn (as with the `claude window` row) because it stops every role on that
179
+ * provider; `now` is injected so a test can pin a hold standing before its deadline and
180
+ * re-opened after. The ended hold's kind rides the resumed event (see pollTeamHold), and any
181
+ * resumed event ends that provider's current hold whatever its kind. */
182
+ export function checkProviderHolds(root, now = Date.now()) {
183
+ let events;
184
+ try {
185
+ events = readEventsSince(root, HOLD_CHECK_WINDOW_MS).events;
186
+ }
187
+ catch (err) {
188
+ return warnOutcome(`could not read the event log for provider holds: ${errorMessage(err)}`);
189
+ }
190
+ const standing = new Map();
191
+ for (const ev of events) {
192
+ if (ev.type === "rate_limit_hold") {
193
+ const provider = typeof ev.provider === "string" ? ev.provider : undefined;
194
+ const end = (typeof ev.ts === "number" ? ev.ts : 0) + (typeof ev.holdMs === "number" ? ev.holdMs : 0);
195
+ standing.set(provider, { end, kind: ev.kind, roles: ev.roles });
196
+ }
197
+ else if (ev.type === "rate_limit_resumed") {
198
+ standing.delete(typeof ev.provider === "string" ? ev.provider : undefined);
199
+ }
200
+ }
201
+ const held = [...standing.entries()]
202
+ .filter(([, h]) => h.end > now)
203
+ .sort(([a], [b]) => (a ?? "").localeCompare(b ?? ""));
204
+ if (held.length === 0)
205
+ return okOutcome("no provider holds");
206
+ const lines = held.map(([provider, h]) => {
207
+ const name = provider ?? "pi's default backend";
208
+ const roles = Array.isArray(h.roles) && h.roles.length > 0 ? `; roles: ${h.roles.join(", ")}` : "";
209
+ return `${name} (${h.kind}) holds until it re-opens in ${humanUntil(h.end, now)}${roles}`;
210
+ });
211
+ return warnOutcome(lines.join("; "));
212
+ }
213
+ /** The `error streaks` check: names each enabled loop whose `consecutiveErrors` is at or past
214
+ * ERROR_STREAK_WARN, with its streak and last error, and whether it is paused. Read-only; it reads
215
+ * the same `consecutiveErrors` the error-streak breaker and `teamAlerts` read, and the same
216
+ * `pausedRoles` marker the scheduler reads. The marker does not record who wrote it, so a paused
217
+ * loop at ERROR_STREAK_BREAKER's bar is described, not attributed to the breaker. A warn, not a
218
+ * fail: a failing loop does not stop the team from running. */
219
+ export function checkErrorStreaks(root, config) {
220
+ if (config === undefined)
221
+ return okOutcome("not checked — tumwater.json unreadable");
222
+ const failing = loadLoopStates(root, config)
223
+ .filter((s) => (s.consecutiveErrors ?? 0) >= ERROR_STREAK_WARN);
224
+ if (failing.length === 0)
225
+ return okOutcome("no role failing repeatedly");
226
+ const paused = new Set(pausedRoles(root));
227
+ const phrases = failing.map((s) => {
228
+ const n = s.consecutiveErrors ?? 0;
229
+ let phrase = `${roleLabel(s.role)}: ${n} failed runs`;
230
+ if (loopPaused(paused, s.role)) {
231
+ const key = paused.has(s.role) ? s.role : paused.has(baseRoleOf(s.role)) ? baseRoleOf(s.role) : `${s.role}-1`;
232
+ phrase +=
233
+ n >= ERROR_STREAK_BREAKER
234
+ ? `, paused at the repeated-failure pause's bar — \`tumwater resume --role ${key}\` once the cause is fixed`
235
+ : ", paused";
236
+ }
237
+ if (s.lastError)
238
+ phrase += ` — last error: ${squash(s.lastError, 120)}`;
239
+ return phrase;
240
+ });
241
+ return warnOutcome(`failing run after run: ${phrases.join("; ")}`);
242
+ }
243
+ /** The `main red` check: names the code loops the red-main gate (mainRedGate) holds, ending
244
+ * each blocked tick `main_red` while main's suite is red. Read-only: it asks heldByMainRed,
245
+ * the predicate the status phase's `main red` label reads, so the two cannot disagree. A warn,
246
+ * not a fail: a red main does not stop the team from running. */
247
+ export function checkMainRed(root, config) {
248
+ if (config === undefined)
249
+ return okOutcome("not checked — tumwater.json unreadable");
250
+ const mainCheck = mainCheckForPoll(root, config);
251
+ const ids = loopIds(config);
252
+ const held = loadLoopStates(root, config)
253
+ .filter((s) => heldByMainRed(s, mainCheck));
254
+ if (held.length === 0)
255
+ return okOutcome("no role waiting on main's check");
256
+ const err = held.find((s) => s.lastError)?.lastError;
257
+ const cause = err ? ` — ${squash(err, 160)}` : "";
258
+ const healer = ids.some((id) => baseRoleOf(id) === "bugfix")
259
+ ? "; the bugfix role is exempt and repairs it"
260
+ : "; no bugfix role is enabled to repair it — fix main by hand or enable bugfix";
261
+ return warnOutcome(`main's check is failing; ${plural(held.length, "code role")} waiting: ${held.map((s) => roleLabel(s.role)).join(", ")}${cause}${healer}`);
122
262
  }
@@ -0,0 +1,30 @@
1
+ /** The doctor's `root install` row: whether the root checkout's node_modules still matches
2
+ * package-lock.json's direct pins. A failed post-merge re-sync (syncRootInstall) leaves the root
3
+ * behind, and every check then installs into its own tree until a later merge retries. */
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { installDrift } from "../build/dep-install.js";
7
+ import { readEventsSince } from "../events/event-window.js";
8
+ import { plural, shortSpanPhrase } from "../text/phrases.js";
9
+ import { errorMessage, textOr } from "../text/text.js";
10
+ import { okOutcome, warnOutcome } from "./outcome.js";
11
+ /** How far back a failed `dep_install` event may sit and still explain the drift. */
12
+ const DEP_INSTALL_CHECK_WINDOW_MS = 24 * 3600_000;
13
+ export function checkRootInstall(root, now = Date.now()) {
14
+ if (!fs.existsSync(path.join(root, "package-lock.json")))
15
+ return okOutcome("no package-lock.json — nothing pinned");
16
+ const drift = installDrift(root);
17
+ if (drift.length === 0)
18
+ return okOutcome("node_modules matches package-lock.json");
19
+ let detail = `${plural(drift.length, "direct dependency", "direct dependencies")} not installed as package-lock.json pins (${drift.join(", ")}) — run npm install in ${root}; until then each check installs into its own tree`;
20
+ try {
21
+ const installs = readEventsSince(root, DEP_INSTALL_CHECK_WINDOW_MS).events.filter((ev) => ev.type === "dep_install");
22
+ const ev = installs[installs.length - 1];
23
+ if (ev?.status === "failed")
24
+ detail += `; the last re-sync failed ${shortSpanPhrase(now - ev.ts)} ago: ${textOr(ev.error)}`;
25
+ }
26
+ catch (err) {
27
+ detail += `; could not read the event log: ${errorMessage(err)}`;
28
+ }
29
+ return warnOutcome(detail);
30
+ }
@@ -1,10 +1,10 @@
1
1
  /** Doctor's `mach ports` line: a warning at or past launchservicesd's port threshold, a warning
2
- * when the count cannot be read on macOS, ok otherwise. Never a failure — the fleet runs fine on
2
+ * when the count cannot be read on macOS, ok otherwise. Never a failure — the team runs fine on
3
3
  * a leaking Mac; it is the Mac that does not. Split from process/launch-services.ts (2026-10-07,
4
4
  * organize) so the doctor's environment checks live together (doctor-orphans.ts reads the process
5
5
  * table the same way) and the process layer no longer imports the doctor's report contract. The
6
6
  * launchservicesd facts this reads — the thresholds and the warning text — stay in
7
- * src/process/launch-services.ts, shared with the running fleet's watch. `platform` is a
7
+ * src/process/launch-services.ts, shared with the running team's watch. `platform` is a
8
8
  * parameter so the macOS branches are testable on any host. */
9
9
  import { okOutcome, warnOutcome } from "./outcome.js";
10
10
  import { systemProcessProbe } from "../process/process-table.js";