tumwater 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (464) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +55 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +6 -4
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +404 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +281 -55
  41. package/dist/src/backlog/backlog-cli-view.js +255 -0
  42. package/dist/src/backlog/backlog-cli.js +91 -335
  43. package/dist/src/backlog/backlog-commit.js +20 -13
  44. package/dist/src/backlog/backlog-eligibility.js +193 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +264 -0
  48. package/dist/src/backlog/backlog-release.js +134 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-guardrails.js +136 -0
  56. package/dist/src/baseline/main-red.js +153 -36
  57. package/dist/src/baseline/main-watch.js +232 -0
  58. package/dist/src/brief/brief-edit.js +12 -12
  59. package/dist/src/brief/brief.js +31 -51
  60. package/dist/src/budget/budget-fallback-view.js +288 -0
  61. package/dist/src/budget/budget-wiring.js +158 -0
  62. package/dist/src/budget/budget.js +31 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +65 -3
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +181 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +183 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +291 -598
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-review-commands.js +113 -0
  84. package/dist/src/cli/cli-role-command.js +525 -0
  85. package/dist/src/cli/cli-start-summary.js +167 -0
  86. package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
  87. package/dist/src/cli/completion.js +169 -22
  88. package/dist/src/cli/config-commands.js +58 -26
  89. package/dist/src/cli/follow-view.js +75 -0
  90. package/dist/src/cli/help.js +258 -82
  91. package/dist/src/cli/log-commands.js +164 -85
  92. package/dist/src/cli/question-commands.js +211 -10
  93. package/dist/src/cli.js +173 -45
  94. package/dist/src/collections.js +56 -13
  95. package/dist/src/concurrency/check-permit.js +20 -3
  96. package/dist/src/concurrency/lock.js +17 -13
  97. package/dist/src/concurrency/provider-lease.js +44 -8
  98. package/dist/src/concurrency/semaphore.js +102 -26
  99. package/dist/src/config/config-aliases.js +0 -1
  100. package/dist/src/config/config-editable-keys.js +16 -1
  101. package/dist/src/config/config-fallbacks.js +94 -56
  102. package/dist/src/config/config-field-checks.js +40 -10
  103. package/dist/src/config/config-keys.js +30 -8
  104. package/dist/src/config/config-live.js +63 -24
  105. package/dist/src/config/config-request.js +137 -0
  106. package/dist/src/config/config-schema.js +31 -24
  107. package/dist/src/config/config-selector-checks.js +123 -0
  108. package/dist/src/config/config-validation-agents.js +80 -0
  109. package/dist/src/config/config-validation-model.js +116 -0
  110. package/dist/src/config/config-validation.js +169 -222
  111. package/dist/src/config/config-views.js +136 -54
  112. package/dist/src/config/config-write-core.js +135 -0
  113. package/dist/src/config/config-write.js +164 -265
  114. package/dist/src/config/config.js +53 -38
  115. package/dist/src/config/model-selector.js +34 -34
  116. package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
  117. package/dist/src/doctor/doctor-backlog-plans.js +398 -0
  118. package/dist/src/doctor/doctor-backlog.js +249 -512
  119. package/dist/src/doctor/doctor-check-names.js +85 -0
  120. package/dist/src/doctor/doctor-checks.js +61 -593
  121. package/dist/src/doctor/doctor-claude-checks.js +142 -0
  122. package/dist/src/doctor/doctor-codex-checks.js +276 -0
  123. package/dist/src/doctor/doctor-contention-checks.js +506 -0
  124. package/dist/src/doctor/doctor-fallback-checks.js +130 -0
  125. package/dist/src/doctor/doctor-holds.js +80 -66
  126. package/dist/src/doctor/doctor-install.js +2 -1
  127. package/dist/src/doctor/doctor-model-checks.js +216 -537
  128. package/dist/src/doctor/doctor-notify.js +9 -5
  129. package/dist/src/doctor/doctor-opencode-checks.js +164 -0
  130. package/dist/src/doctor/doctor-orphans.js +182 -63
  131. package/dist/src/doctor/doctor-pi-checks.js +62 -0
  132. package/dist/src/doctor/doctor-project-checks.js +397 -0
  133. package/dist/src/doctor/doctor.js +126 -77
  134. package/dist/src/doctor/image-probe.js +21 -8
  135. package/dist/src/doctor/outcome.js +3 -2
  136. package/dist/src/doctor/probe.js +21 -12
  137. package/dist/src/events/event-format.js +185 -89
  138. package/dist/src/events/event-labels.js +11 -5
  139. package/dist/src/events/event-legacy.js +39 -15
  140. package/dist/src/events/event-read.js +52 -39
  141. package/dist/src/events/event-tally.js +16 -0
  142. package/dist/src/events/event-window.js +80 -26
  143. package/dist/src/events/events.js +30 -13
  144. package/dist/src/events/notify.js +7 -7
  145. package/dist/src/failure/failure-cluster.js +12 -12
  146. package/dist/src/failure/failure-data.js +65 -60
  147. package/dist/src/failure/failure-render.js +29 -21
  148. package/dist/src/failure/failure-state-change.js +17 -12
  149. package/dist/src/failure/time-spend.js +35 -35
  150. package/dist/src/files/file-queue.js +17 -0
  151. package/dist/src/files/files.js +111 -35
  152. package/dist/src/files/json-files.js +3 -3
  153. package/dist/src/files/json-object.js +28 -7
  154. package/dist/src/files/stat-cache.js +2 -2
  155. package/dist/src/files/tail.js +1 -1
  156. package/dist/src/gates/bootstrap-gates.js +4 -5
  157. package/dist/src/gates/budget-gates.js +88 -225
  158. package/dist/src/gates/disk-gate.js +1 -1
  159. package/dist/src/gates/gate-polls.js +101 -143
  160. package/dist/src/gates/maintenance-quota.js +37 -30
  161. package/dist/src/gates/pause-gates.js +28 -10
  162. package/dist/src/gates/readiness.js +7 -3
  163. package/dist/src/gates/role-cap-gates.js +8 -8
  164. package/dist/src/gates/startup-gate.js +94 -32
  165. package/dist/src/gates/streak-gate.js +54 -30
  166. package/dist/src/git/commit-message.js +89 -46
  167. package/dist/src/git/git-diff.js +16 -10
  168. package/dist/src/git/git-run.js +79 -38
  169. package/dist/src/git/git.js +54 -34
  170. package/dist/src/git/slot-salvage.js +253 -0
  171. package/dist/src/git/slots-state.js +6 -6
  172. package/dist/src/git/worktree-pool.js +132 -38
  173. package/dist/src/git/worktree-use.js +4 -11
  174. package/dist/src/git/worktree.js +94 -37
  175. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  176. package/dist/src/gui/gui-args.js +79 -8
  177. package/dist/src/gui/gui-backlog-commands.js +43 -19
  178. package/dist/src/gui/gui-backlog-endpoints.js +156 -0
  179. package/dist/src/gui/gui-command.js +26 -13
  180. package/dist/src/gui/gui-config-commands.js +168 -80
  181. package/dist/src/gui/gui-endpoint-commands.js +5 -4
  182. package/dist/src/gui/gui-endpoint-common.js +4 -4
  183. package/dist/src/gui/gui-endpoints.js +11 -380
  184. package/dist/src/gui/gui-history-endpoints.js +301 -0
  185. package/dist/src/gui/gui-operator-commands.js +235 -13
  186. package/dist/src/gui/gui-prompt-commands.js +26 -20
  187. package/dist/src/gui/gui-report-endpoints.js +97 -0
  188. package/dist/src/gui/gui-server.js +26 -5
  189. package/dist/src/gui/gui-team-commands.js +7 -7
  190. package/dist/src/gui/http-body.js +1 -1
  191. package/dist/src/history/history-data.js +122 -234
  192. package/dist/src/history/history-queued.js +155 -0
  193. package/dist/src/history/history-render.js +42 -22
  194. package/dist/src/history/history.js +190 -49
  195. package/dist/src/inbox/inbox-attachments.js +29 -30
  196. package/dist/src/inbox/inbox-cancel.js +10 -11
  197. package/dist/src/inbox/inbox-edit.js +9 -9
  198. package/dist/src/inbox/inbox-submit.js +18 -18
  199. package/dist/src/inbox/inbox-top.js +28 -17
  200. package/dist/src/inbox/inbox.js +42 -43
  201. package/dist/src/inbox/pending-prompt.js +26 -26
  202. package/dist/src/inbox/prompt-commands.js +62 -49
  203. package/dist/src/inbox/prompt-not-before.js +2 -2
  204. package/dist/src/init/init-templates.js +1 -2
  205. package/dist/src/init/init.js +28 -23
  206. package/dist/src/merge/backlog-conflicts.js +215 -35
  207. package/dist/src/merge/merge-batch.js +14 -8
  208. package/dist/src/merge/merge-cancel.js +53 -0
  209. package/dist/src/merge/merge-check-failures.js +44 -22
  210. package/dist/src/merge/merge-core.js +58 -32
  211. package/dist/src/merge/merge-diff.js +5 -4
  212. package/dist/src/merge/merge-drain.js +45 -19
  213. package/dist/src/merge/merge-git.js +177 -38
  214. package/dist/src/merge/merge-pipeline.js +15 -3
  215. package/dist/src/merge/merge-post-review.js +214 -0
  216. package/dist/src/merge/merge-queue-render.js +48 -11
  217. package/dist/src/merge/merge-queue.js +52 -15
  218. package/dist/src/merge/merge-requeue.js +103 -0
  219. package/dist/src/merge/merge-slot.js +61 -16
  220. package/dist/src/merge/merge-stack.js +27 -11
  221. package/dist/src/merge/merge-to-main.js +77 -26
  222. package/dist/src/merge/merge-top.js +83 -0
  223. package/dist/src/merge/merge-verify.js +75 -18
  224. package/dist/src/notes-cli.js +35 -15
  225. package/dist/src/operator/operator-commands.js +192 -81
  226. package/dist/src/operator/operator-intent.js +69 -42
  227. package/dist/src/operator/operator-requests.js +35 -40
  228. package/dist/src/operator/retire.js +5 -6
  229. package/dist/src/operator/revert.js +142 -34
  230. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  231. package/dist/src/orchestrator/orchestrator-poll.js +352 -0
  232. package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
  233. package/dist/src/orchestrator/orchestrator.js +129 -369
  234. package/dist/src/orchestrator/retention.js +42 -8
  235. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  236. package/dist/src/orchestrator/scheduling-scope.js +245 -0
  237. package/dist/src/orchestrator/scheduling-skip.js +195 -0
  238. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  239. package/dist/src/orchestrator/startup-migrations.js +11 -7
  240. package/dist/src/paths.js +126 -49
  241. package/dist/src/pi/command-shape.js +12 -1
  242. package/dist/src/pi/pi-args.js +6 -4
  243. package/dist/src/pi/pi-backend.js +6 -6
  244. package/dist/src/pi/pi-bin.js +54 -63
  245. package/dist/src/pi/pi-listed-models.js +53 -0
  246. package/dist/src/pi/pi-models.js +63 -30
  247. package/dist/src/pi/pi-run-result.js +1 -1
  248. package/dist/src/pi/pi-settings.js +1 -1
  249. package/dist/src/pi/pi-stream.js +6 -6
  250. package/dist/src/pi/pi-watchdogs.js +18 -19
  251. package/dist/src/pi-extension/bounded-output.js +5 -3
  252. package/dist/src/pi-extension/context-budget.js +2 -2
  253. package/dist/src/pi-extension/context-shake.js +1 -1
  254. package/dist/src/pi-extension/context-window.js +0 -1
  255. package/dist/src/pi-extension/full-output.js +3 -10
  256. package/dist/src/pi-extension/strip-thinking.js +0 -1
  257. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  258. package/dist/src/pi-extension/write-guard.js +37 -0
  259. package/dist/src/process/launch-services.js +1 -2
  260. package/dist/src/process/node-bin.js +16 -0
  261. package/dist/src/process/process-group.js +8 -18
  262. package/dist/src/process/process.js +41 -2
  263. package/dist/src/process/run-marker.js +26 -6
  264. package/dist/src/process/supervisor.js +22 -21
  265. package/dist/src/prompt/dialect.js +17 -2
  266. package/dist/src/prompt/gate-notes.js +203 -0
  267. package/dist/src/prompt/gate-prompts.js +150 -217
  268. package/dist/src/prompt/notes-backlog.js +184 -0
  269. package/dist/src/prompt/principles.js +1 -2
  270. package/dist/src/prompt/prompt-followup.js +19 -14
  271. package/dist/src/prompt/prompt.js +131 -204
  272. package/dist/src/redeploy/redeploy-policy.js +24 -19
  273. package/dist/src/redeploy/redeploy-probes.js +1 -1
  274. package/dist/src/redeploy/redeploy.js +2 -3
  275. package/dist/src/redeploy/redeployer.js +20 -20
  276. package/dist/src/redeploy/self-reload.js +2 -1
  277. package/dist/src/report/report-data.js +188 -124
  278. package/dist/src/report/report-fold.js +130 -54
  279. package/dist/src/report/report-main-red.js +55 -0
  280. package/dist/src/report/report-render.js +136 -65
  281. package/dist/src/report/report-types.js +1 -0
  282. package/dist/src/report/report.js +23 -9
  283. package/dist/src/request-timeouts.js +2 -2
  284. package/dist/src/review/diff-rules.js +53 -0
  285. package/dist/src/review/known-flakes.js +112 -1
  286. package/dist/src/review/post-merge-review-accept.js +51 -0
  287. package/dist/src/review/post-merge-review-render.js +112 -0
  288. package/dist/src/review/post-merge-review.js +239 -0
  289. package/dist/src/review/protected-edits.js +24 -10
  290. package/dist/src/review/quarantine-render.js +65 -0
  291. package/dist/src/review/review-followup.js +8 -8
  292. package/dist/src/review/review-precheck.js +66 -23
  293. package/dist/src/review/review-run.js +395 -0
  294. package/dist/src/review/review-verdict.js +2 -2
  295. package/dist/src/review/review.js +180 -401
  296. package/dist/src/review/secret-leaks.js +36 -16
  297. package/dist/src/review/suite-rerun.js +0 -1
  298. package/dist/src/roles/legacy-role-migration.js +80 -7
  299. package/dist/src/roles/role-catalog.js +127 -12
  300. package/dist/src/roles/role-guidance.js +65 -20
  301. package/dist/src/roles/role-labels.js +7 -5
  302. package/dist/src/roles/role-render.js +108 -56
  303. package/dist/src/roles/role-view.js +95 -56
  304. package/dist/src/roles/roles.js +33 -33
  305. package/dist/src/roles/runner-ids.js +97 -0
  306. package/dist/src/run/acceptance-greps.js +119 -0
  307. package/dist/src/run/ci-parity.js +121 -0
  308. package/dist/src/run/feedback-filings.js +51 -0
  309. package/dist/src/run/qa-coverage.js +125 -0
  310. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  311. package/dist/src/run/run-detail-data.js +175 -0
  312. package/dist/src/run/run-detail-render.js +91 -0
  313. package/dist/src/run/run-detail.js +168 -0
  314. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
  315. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  316. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
  317. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  318. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
  319. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  320. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  321. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
  322. package/dist/src/run/selfcheck-digest.js +40 -0
  323. package/dist/src/run/stage-backlog-checks.js +249 -0
  324. package/dist/src/run/stage-check.js +178 -0
  325. package/dist/src/run/stage-checks-shared.js +65 -0
  326. package/dist/src/run/stage-name-checks.js +204 -0
  327. package/dist/src/run/stage-path-checks.js +231 -0
  328. package/dist/src/run/stage-prose-checks.js +261 -0
  329. package/dist/src/runner/leftover.js +216 -0
  330. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  331. package/dist/src/runner/revision.js +191 -0
  332. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  333. package/dist/src/runner/runner-outcome.js +109 -0
  334. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  335. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
  336. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
  337. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  338. package/dist/src/runner/runner-state.js +103 -0
  339. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  340. package/dist/src/scheduling/backoff.js +38 -38
  341. package/dist/src/scheduling/claim-entries.js +49 -0
  342. package/dist/src/scheduling/claims.js +35 -22
  343. package/dist/src/scheduling/once-round.js +22 -22
  344. package/dist/src/scheduling/quiet-hours.js +5 -6
  345. package/dist/src/scheduling/scheduling.js +74 -64
  346. package/dist/src/scheduling/work-merged-cache.js +2 -2
  347. package/dist/src/status/status-budget.js +55 -47
  348. package/dist/src/status/status-data.js +49 -17
  349. package/dist/src/status/status-inbox.js +2 -2
  350. package/dist/src/status/status-install.js +0 -1
  351. package/dist/src/status/status-polls.js +43 -28
  352. package/dist/src/status/status-roles.js +50 -13
  353. package/dist/src/team/error-storm.js +8 -8
  354. package/dist/src/team/failure-spread.js +2 -2
  355. package/dist/src/team/orchestrator-info.js +15 -9
  356. package/dist/src/team/reclaim.js +7 -8
  357. package/dist/src/team/team-hold.js +7 -7
  358. package/dist/src/team/team-polls.js +91 -50
  359. package/dist/src/team/team-state.js +8 -2
  360. package/dist/src/text/datetime.js +65 -18
  361. package/dist/src/text/format.js +48 -11
  362. package/dist/src/text/markdown.js +8 -5
  363. package/dist/src/text/phrases.js +60 -23
  364. package/dist/src/text/suggest.js +1 -1
  365. package/dist/src/text/text.js +78 -13
  366. package/dist/src/ui/badges.js +99 -38
  367. package/dist/src/ui/gui/gui-client-backlog.js +314 -0
  368. package/dist/src/ui/gui/gui-client-boot.js +89 -61
  369. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  370. package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
  371. package/dist/src/ui/gui/gui-client-drawer.js +155 -289
  372. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  373. package/dist/src/ui/gui/gui-client-history.js +437 -68
  374. package/dist/src/ui/gui/gui-client-labels.js +6 -3
  375. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  376. package/dist/src/ui/gui/gui-client-model.js +27 -36
  377. package/dist/src/ui/gui/gui-client-operator.js +60 -52
  378. package/dist/src/ui/gui/gui-client-pending.js +405 -24
  379. package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
  380. package/dist/src/ui/gui/gui-client-report.js +105 -430
  381. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  382. package/dist/src/ui/gui/gui-client-settings.js +163 -5
  383. package/dist/src/ui/gui/gui-client-team.js +36 -465
  384. package/dist/src/ui/gui/gui-client.js +38 -26
  385. package/dist/src/ui/gui/gui-icons.js +0 -2
  386. package/dist/src/ui/gui/gui-page.js +10 -7
  387. package/dist/src/ui/gui/gui-styles.js +38 -34
  388. package/dist/src/ui/progress-data.js +9 -11
  389. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
  390. package/dist/src/ui/status-model.js +133 -125
  391. package/dist/src/ui/status-payload.js +364 -49
  392. package/dist/src/ui/status-render.js +109 -82
  393. package/dist/src/ui/team-alerts.js +728 -62
  394. package/dist/src/ui/tone.js +12 -6
  395. package/dist/src/ui/transcript-run.js +58 -0
  396. package/dist/src/ui/transcript-tail.js +28 -20
  397. package/dist/src/ui/transcript.js +63 -23
  398. package/dist/src/ui/tui/tui-app.js +0 -1
  399. package/dist/src/ui/tui/tui-backlog.js +53 -14
  400. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  401. package/dist/src/ui/tui/tui-editor-attach.js +4 -12
  402. package/dist/src/ui/tui/tui-editor-backends.js +127 -0
  403. package/dist/src/ui/tui/tui-editor-brief.js +14 -36
  404. package/dist/src/ui/tui/tui-editor-budget.js +36 -39
  405. package/dist/src/ui/tui/tui-editor-check.js +29 -0
  406. package/dist/src/ui/tui/tui-editor-checks.js +28 -0
  407. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  408. package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
  409. package/dist/src/ui/tui/tui-editor-config.js +99 -0
  410. package/dist/src/ui/tui/tui-editor-entry.js +39 -64
  411. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  412. package/dist/src/ui/tui/tui-editor-instances.js +46 -0
  413. package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
  414. package/dist/src/ui/tui/tui-editor-interval.js +29 -16
  415. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  416. package/dist/src/ui/tui/tui-editor-notes.js +9 -29
  417. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  418. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  419. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  420. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  421. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  422. package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
  423. package/dist/src/ui/tui/tui-editor-retire.js +5 -13
  424. package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
  425. package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
  426. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  427. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  428. package/dist/src/ui/tui/tui-editor-session.js +101 -34
  429. package/dist/src/ui/tui/tui-editor-slots.js +24 -0
  430. package/dist/src/ui/tui/tui-editors.js +44 -2
  431. package/dist/src/ui/tui/tui-frame.js +68 -11
  432. package/dist/src/ui/tui/tui-history.js +8 -9
  433. package/dist/src/ui/tui/tui-input.js +13 -11
  434. package/dist/src/ui/tui/tui-keymap.js +0 -1
  435. package/dist/src/ui/tui/tui-keys.js +33 -9
  436. package/dist/src/ui/tui/tui-pane-activity.js +35 -19
  437. package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
  438. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  439. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  440. package/dist/src/ui/tui/tui-pane-health.js +168 -8
  441. package/dist/src/ui/tui/tui-pane-history.js +446 -82
  442. package/dist/src/ui/tui/tui-pane-pending.js +539 -15
  443. package/dist/src/ui/tui/tui-pane-queued.js +78 -57
  444. package/dist/src/ui/tui/tui-pane-report.js +54 -16
  445. package/dist/src/ui/tui/tui-pane-session.js +34 -6
  446. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  447. package/dist/src/ui/tui/tui-pane.js +91 -28
  448. package/dist/src/ui/tui/tui-panes.js +153 -34
  449. package/dist/src/ui/tui/tui.js +18 -15
  450. package/dist/src/verdict/fix-claim.js +129 -33
  451. package/dist/src/verdict/no-change.js +4 -4
  452. package/dist/src/verdict/refusal.js +40 -16
  453. package/dist/src/verdict/reply-contract.js +77 -31
  454. package/dist/src/version.js +0 -1
  455. package/package.json +2 -1
  456. package/dist/src/loop/leftover.js +0 -130
  457. package/dist/src/loop/revision.js +0 -70
  458. package/dist/src/roles/loop-ids.js +0 -77
  459. package/dist/src/tick/qa-coverage.js +0 -108
  460. package/dist/src/tick/stage-check.js +0 -419
  461. package/dist/src/tick/telemetry-digest.js +0 -25
  462. package/dist/src/tick/tick-detail-data.js +0 -108
  463. package/dist/src/tick/tick-detail.js +0 -163
  464. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,43 +1,65 @@
1
- // vocabulary: plain
2
1
  import { loadConfigSafe } from "../config/config.js";
3
2
  import { envPath } from "../files/files.js";
4
3
  import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
5
4
  import { systemProcessProbe } from "../process/process-table.js";
6
5
  import { checkOrphans } from "./doctor-orphans.js";
7
- import { checkBudget, checkClaudeWindow, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours } from "./doctor-holds.js";
6
+ import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours, checkSubscriptionWindow } from "./doctor-holds.js";
8
7
  import { checkRootInstall } from "./doctor-install.js";
9
8
  import { checkNotify } from "./doctor-notify.js";
10
9
  import { checkLaunchServicesPorts } from "./doctor-launch-services.js";
11
10
  import { plural } from "../text/phrases.js";
12
11
  import { shortSha } from "../text/format.js";
13
- import { checkAgentBinary, checkBrief, checkBootstrap, checkBuild, checkBuildCheck, checkClaudeTranscripts, checkFunes, checkGitBinary, checkConfigKeyNames, checkInit, checkDiskSpace, checkEventLog, checkFlakyChecks, checkOpencodeVersion, checkMergeLock, checkNodeVersion, checkPiExtensions, checkProviderConcurrency, checkProviderContention, checkRepo, checkStateDir, checkWorktreePool, checkLeftoverRoleBranches, checkWorkInstances, } from "./doctor-checks.js";
14
- import { checkClaudeAuth, checkClaudeVersion, checkAnthroApiKey, checkFallbackChain, checkFallbackModel, checkImageInput, checkOpencode, checkTierModels } from "./doctor-model-checks.js";
15
- import { agentQualifiedSelector } from "../config/config-views.js";
12
+ import { failOutcome } from "./outcome.js";
13
+ import { errorMessage } from "../text/text.js";
14
+ import { checkBackendBinary, checkBrief, checkBootstrap, checkClaudeTranscripts, checkExecPath, checkFunes, checkGitBinary, checkConfigKeyNames, checkInit, checkOpencodeVersion, checkNodeVersion, checkPiExtensions, checkRepo, } from "./doctor-checks.js";
15
+ import { checkBuild, checkBuildCheck, checkCiParity, checkDiskSpace, checkEventLog, checkFlakyChecks, checkPostMergeChurn, checkQaCoverage, checkStateDir, } from "./doctor-project-checks.js";
16
+ import { sampleFreeBytes } from "../gates/disk-gate.js";
17
+ import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock, checkProviderConcurrency, checkProviderContention, checkTumwaterPins, checkWorkInstances, checkWorktreePool, } from "./doctor-contention-checks.js";
18
+ import { checkFeedbackFiling, checkImageInput, checkTierModels } from "./doctor-model-checks.js";
19
+ import { checkFallbackChain, checkFallbackModel } from "./doctor-fallback-checks.js";
20
+ import { checkAnthroApiKey, checkClaudeAuth, checkClaudeVersion } from "./doctor-claude-checks.js";
21
+ import { checkOpencode } from "./doctor-opencode-checks.js";
22
+ import { checkCodexAccount, checkCodexAuth, checkCodexHome, checkCodexLeaks, checkCodexModels, checkCodexSessions, checkCodexWriteGuard, checkCodexVersion, } from "./doctor-codex-checks.js";
23
+ import { backendQualifiedSelector } from "../config/config-views.js";
16
24
  import { seamsInUse } from "../config/config-fallbacks.js";
17
- import { checkBacklogHeadings, checkFixClaims, checkLiveFinished, checkPlanDependencies, checkRefusedEntries, checkRegressedFixes, checkStaleQueuedMerges, checkStaleBugs, checkStalePrompts, checkStaleQuestions, checkStalledPlans, checkStrandedPlans, checkUncommittedBacklog, checkValidationGaps, checkWaitingBugs } from "./doctor-backlog.js";
25
+ import { checkBacklogHeadings, checkFixClaims, checkLiveFinished, checkPlanDependencies, checkStalledEntries, checkStrandedPlans, checkValidationGaps } from "./doctor-backlog-plans.js";
26
+ import { checkRefusedEntries, checkRegressedFixes, checkStaleBugs, checkStaleQuestions, checkWaitingBugs } from "./doctor-backlog-bugs.js";
27
+ import { checkMergeQueueFiles, checkPostReviews, checkStaleQueuedMerges, checkStalePrompts, checkUncommittedBacklog } from "./doctor-backlog.js";
18
28
  /** Pre-flight environment check (`tumwater doctor`). The harness's preconditions are
19
29
  * scattered across fail-fast checks that each command re-runs on its own (requireReadyRepo in
20
30
  * cli.ts walks git-binary → repo → tumwater.json → commits and stops at the first failure;
21
- * cmdRun adds pi-on-PATH and orchestrator-alive), so a user facing a partially initialized or
31
+ * cmdStart adds pi-on-PATH and orchestrator-alive), so a user facing a partially initialized or
22
32
  * drifted environment gets one error at a time. doctor runs every check, reports each result
23
33
  * individually instead of stopping at the first failure, and exits 0/1 so it can be scripted —
24
34
  * the pre-flight sibling of `status --json`, which queries live team state while doctor
25
35
  * verdicts on the environment. Every check is read-only against .tumwater/ (works with or
26
36
  * without a running harness) and none runs the project's build/test: that can take minutes and
27
37
  * belongs in the review gate / red-main check, not a pre-flight. The individual checks live in
28
- * doctor-checks.ts and doctor-model-checks.ts; this module composes them into the report. */
29
- /** Wrap a possibly-absent doctor check into the report's `checks` spread: null omits the row
30
- * entirely (the pi-extensions row for a team with no pi seam; the funes row when funes is not
31
- * on PATH); an outcome contributes `{ name, ...outcome }`. */
32
- function appendNamedOutcome(name, outcome) {
33
- return outcome === null ? [] : [{ name, ...outcome }];
38
+ * doctor-checks.ts, doctor-project-checks.ts, doctor-contention-checks.ts, doctor-model-checks.ts,
39
+ * doctor-claude-checks.ts, doctor-opencode-checks.ts, doctor-backlog.ts,
40
+ * doctor-backlog-plans.ts and doctor-backlog-bugs.ts;
41
+ * this module composes them into the report. */
42
+ /** Run one doctor check, turning a throw or rejection into a fail row: a check that crashed
43
+ * verified nothing, so it must not read as a pass or abort the report. */
44
+ export async function runDoctorCheck(run) {
45
+ try {
46
+ return await run();
47
+ }
48
+ catch (err) {
49
+ return failOutcome(`the check threw: ${errorMessage(err)}`);
50
+ }
34
51
  }
35
52
  /** Run every check in order and compose the report. Read-only against .tumwater/ by
36
53
  * construction — no check removes or repairs anything (the state-dir probe writes a temp file
37
54
  * and deletes it again; the orphan check reports processes, never signals them) — so doctor
38
55
  * works identically with or without a running harness. `probe` feeds the orphan and port
39
- * checks, so tests can pin the report without reading the host's process table. */
40
- export async function runDoctor(root, pathEnv = envPath(), probe = systemProcessProbe, probeImages = "never") {
56
+ * checks, so tests can pin the report without reading the host's process table. `diskSample`
57
+ * feeds the disk check the same way, so tests can pin the report without reading the host's
58
+ * real free space (a hosted runner under the 10 GB floor would fail its row). `only` runs
59
+ * just the named checks, in report order; a skipped check is never evaluated, and a named
60
+ * conditional check that returns null leaves no row. A check that throws becomes its own fail
61
+ * row, and every other check still runs. */
62
+ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcessProbe, probeImages = "never", only, diskSample = sampleFreeBytes) {
41
63
  // Loaded once for the checks that read config (repo's baseBranch); a broken file stays
42
64
  // undefined — checkInit reports it verbatim — so doctor still runs every other check.
43
65
  const { config } = loadConfigSafe(root);
@@ -48,7 +70,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
48
70
  const routing = config
49
71
  ? seamsInUse(config).map((s) => ({
50
72
  label: s.label,
51
- selector: s.model !== undefined ? agentQualifiedSelector({ ...s, model: s.model }) : "no model declared",
73
+ selector: s.model !== undefined ? backendQualifiedSelector({ ...s, model: s.model }) : "no model declared",
52
74
  backend: s.agent ?? "pi",
53
75
  }))
54
76
  : [];
@@ -56,80 +78,107 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
56
78
  const header = orchestratorAlive(root, info) && info
57
79
  ? `tumwater doctor — running (pid ${info.pid}${info.build ? `, version ${shortSha(info.build.sha)}${info.build.stale ? " — update available" : ""}${info.build.restartBlocked ? " (restart blocked)" : ""}` : ""})`
58
80
  : "tumwater doctor — not running";
59
- const checks = [
60
- { name: "node", ...checkNodeVersion() },
61
- { name: "git binary", ...checkGitBinary(pathEnv) },
62
- { name: "repo", ...(await checkRepo(root, config)) },
63
- { name: "init", ...checkInit(root) },
64
- { name: "config key names", ...checkConfigKeyNames(root) },
65
- { name: "brief", ...checkBrief(root) },
81
+ const steps = [
82
+ { name: "node", run: () => checkNodeVersion() },
83
+ { name: "node binary", run: () => checkExecPath() },
84
+ { name: "git binary", run: () => checkGitBinary(pathEnv) },
85
+ { name: "repo", run: () => checkRepo(root, config) },
86
+ { name: "init", run: () => checkInit(root) },
87
+ { name: "config key names", run: () => checkConfigKeyNames(root) },
88
+ { name: "brief", run: () => checkBrief(root) },
66
89
  // New-project bootstrap (plans/work-ratio.md, part 2/2): an active hold reports as ok
67
90
  // information, never a warning — a young project is not misconfigured.
68
- { name: "bootstrap", ...checkBootstrap(root, config) },
69
- { name: "pause", ...checkPause(root) },
70
- { name: "quiet hours", ...checkQuietHours(config) },
71
- { name: "budget", ...checkBudget(root, config, (orchestratorAlive(root, info) && info?.budget) || null) },
72
- { name: "claude window", ...checkClaudeWindow(root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
73
- { name: "provider waits", ...checkProviderHolds(root) },
74
- { name: "maintenance allowance", ...checkMaintenanceQuota(root, config, orchestratorAlive(root, info)) },
75
- { name: "provider concurrency", ...checkProviderConcurrency(config, root) },
76
- { name: "provider contention", ...checkProviderContention(root, config) },
77
- { name: "repeated failures", ...checkErrorStreaks(root, config) },
78
- { name: "main's check", ...checkMainRed(root, config) },
79
- { name: "over-cap model", ...checkFallbackModel(root, undefined, (orchestratorAlive(root, info) && info?.fallbackDemoted) || null) },
80
- { name: "fallback chain", ...(await checkFallbackChain(root)) },
81
- { name: "models for each tier", ...(await checkTierModels(root)) },
82
- { name: "image input", ...(await checkImageInput(root, undefined, undefined, undefined, probeImages, pathEnv)) },
83
- { name: "claude auth", ...(await checkClaudeAuth(root, pathEnv)) },
84
- { name: "claude api key", ...(await checkAnthroApiKey(root, undefined, pathEnv)) },
85
- { name: "claude version", ...(await checkClaudeVersion(root, pathEnv)) },
86
- { name: "opencode version", ...(await checkOpencodeVersion(root, pathEnv)) },
87
- { name: "opencode", ...(await checkOpencode(root, pathEnv)) },
88
- { name: "backend binary", ...checkAgentBinary(root, pathEnv) },
91
+ { name: "bootstrap", run: () => checkBootstrap(root, config) },
92
+ { name: "pause", run: () => checkPause(root) },
93
+ { name: "quiet hours", run: () => checkQuietHours(config) },
94
+ { name: "budget", run: () => checkBudget(root, config, (orchestratorAlive(root, info) && info?.budget) || null) },
95
+ { name: "claude window", run: () => checkSubscriptionWindow("claude", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
96
+ { name: "codex window", run: () => checkSubscriptionWindow("codex", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
97
+ { name: "provider waits", run: () => checkProviderHolds(root) },
98
+ { name: "maintenance allowance", run: () => checkMaintenanceQuota(root, config, orchestratorAlive(root, info)) },
99
+ { name: "provider concurrency", run: () => checkProviderConcurrency(config, root) },
100
+ { name: "provider contention", run: () => checkProviderContention(root, config) },
101
+ { name: "check contention", run: () => checkCheckContention(root, config) },
102
+ { name: "repeated failures", run: () => checkErrorStreaks(root, config) },
103
+ { name: "main's check", run: () => checkMainRed(root, config) },
104
+ { name: "over-cap model", run: () => checkFallbackModel(root, undefined, (orchestratorAlive(root, info) && info?.fallbackDemoted) || null) },
105
+ { name: "fallback chain", run: () => checkFallbackChain(root) },
106
+ { name: "models for each tier", run: () => checkTierModels(root) },
107
+ { name: "image input", run: () => checkImageInput(root, undefined, undefined, undefined, probeImages, pathEnv) },
108
+ { name: "claude auth", run: () => checkClaudeAuth(root, pathEnv) },
109
+ { name: "claude api key", run: () => checkAnthroApiKey(root, undefined, pathEnv) },
110
+ { name: "claude version", run: () => checkClaudeVersion(root, pathEnv) },
111
+ { name: "opencode version", run: () => checkOpencodeVersion(root, pathEnv) },
112
+ { name: "opencode", run: () => checkOpencode(root, pathEnv) },
113
+ { name: "codex version", run: () => checkCodexVersion(root, pathEnv) },
114
+ { name: "codex login", run: () => checkCodexAuth(root, pathEnv) },
115
+ { name: "codex home", run: () => checkCodexHome(root) },
116
+ { name: "codex models", run: () => checkCodexModels(root) },
117
+ { name: "codex account", run: () => checkCodexAccount(root) },
118
+ { name: "codex leaks", run: () => checkCodexLeaks(root) },
119
+ { name: "codex sessions", run: () => checkCodexSessions(root) },
120
+ { name: "codex write guard", run: () => checkCodexWriteGuard(root, pathEnv) },
121
+ { name: "backend binary", run: () => checkBackendBinary(root, pathEnv) },
122
+ { name: "feedback filing", run: () => checkFeedbackFiling(root, pathEnv) },
89
123
  // The transcript total is informational and only read when a seam routes to claude.
90
- { name: "claude transcripts", ...checkClaudeTranscripts(config) },
124
+ { name: "claude transcripts", run: () => checkClaudeTranscripts(config) },
91
125
  // The receive-side guards name what a running team skips, so an operator tool meant for the
92
126
  // team (a pi package, an extension file, an mcp.json server, or funes) does not vanish
93
127
  // silently. Each check returns null to omit its row: the pi row for a team with no pi seam,
94
- // the funes row when funes is not on PATH.
95
- ...(appendNamedOutcome("pi extensions", checkPiExtensions(config, undefined, pathEnv))),
96
- ...(appendNamedOutcome("funes", checkFunes(config, pathEnv))),
97
- { name: "state dir", ...checkStateDir(root) },
98
- { name: "event log", ...checkEventLog(root) },
99
- { name: "notify", ...checkNotify(root, config) },
100
- { name: "merge lock", ...checkMergeLock(root) },
101
- { name: "workspaces", ...checkWorktreePool(root) },
102
- { name: "parallel instances", ...checkWorkInstances(root, config) },
103
- { name: "leftover role branches", ...(await checkLeftoverRoleBranches(root, config)) },
128
+ // the funes row when funes is not on PATH, and the claude/codex window rows when that backend
129
+ // has no subscription account.
130
+ { name: "pi extensions", run: () => checkPiExtensions(config, undefined, pathEnv) },
131
+ { name: "funes", run: () => checkFunes(config, pathEnv) },
132
+ { name: "state dir", run: () => checkStateDir(root) },
133
+ { name: "event log", run: () => checkEventLog(root) },
134
+ { name: "notify", run: () => checkNotify(root, config) },
135
+ { name: "merge lock", run: () => checkMergeLock(root) },
136
+ { name: "workspaces", run: () => checkWorktreePool(root) },
137
+ { name: "parallel instances", run: () => checkWorkInstances(root, config) },
138
+ { name: "leftover role branches", run: () => checkLeftoverRoleBranches(root, config) },
139
+ { name: "tumwater pins", run: () => checkTumwaterPins(root) },
104
140
  // The disk floor (plans/disk-floor.md, part 1/4): a broken config stays undefined (checkInit
105
141
  // reports it), so the check falls back to the 10 GB default rather than skipping itself.
106
- { name: "disk space", ...checkDiskSpace(root, config?.diskHoldGB ?? 10, config?.diskReclaimGB ?? 40) },
107
- { name: "check", ...checkBuildCheck(root, config) },
108
- { name: "root install", ...checkRootInstall(root) },
142
+ { name: "disk space", run: () => checkDiskSpace(root, config?.diskHoldGB ?? 10, config?.diskReclaimGB ?? 40, diskSample) },
143
+ { name: "check", run: () => checkBuildCheck(root, config) },
144
+ { name: "ci parity", run: () => checkCiParity(root, config, Date.now()) },
145
+ { name: "qa coverage", run: () => checkQaCoverage(root, Date.now()) },
146
+ { name: "root install", run: () => checkRootInstall(root) },
109
147
  // The review gate's flake memory, surfaced read-only: naming which checks the gate kept
110
148
  // excusing as flakes (ok/warn only, so a flaky suite never fails a scripted pre-flight).
111
- { name: "flaky checks", ...checkFlakyChecks(root) },
112
- { name: "claimed bugs", ...checkFixClaims(root) },
113
- { name: "validation gaps", ...checkValidationGaps(root) },
114
- { name: "plans stuck in Planned", ...checkStrandedPlans(root) },
115
- { name: "backlog headings", ...checkBacklogHeadings(root) },
116
- { name: "backlog archive", ...checkLiveFinished(root) },
117
- { name: "refusals", ...checkRefusedEntries(root) },
118
- { name: "stalled plans", ...checkStalledPlans(root) },
119
- { name: "plan dependencies", ...checkPlanDependencies(root) },
120
- { name: "stale questions", ...checkStaleQuestions(root) },
121
- { name: "stale bugs", ...checkStaleBugs(root) },
122
- { name: "waiting bugs", ...checkWaitingBugs(root) },
123
- { name: "regressed fixes", ...checkRegressedFixes(root) },
124
- { name: "stale merge-queue entries", ...checkStaleQueuedMerges(root, Date.now()) },
125
- { name: "stale prompts", ...checkStalePrompts(root, Date.now()) },
126
- { name: "uncommitted backlog", ...(await checkUncommittedBacklog(root, config)) },
149
+ { name: "flaky checks", run: () => checkFlakyChecks(root) },
150
+ { name: "post-merge churn", run: () => checkPostMergeChurn(root) },
151
+ { name: "claimed bugs", run: () => checkFixClaims(root) },
152
+ { name: "validation gaps", run: () => checkValidationGaps(root) },
153
+ { name: "plans stuck in Planned", run: () => checkStrandedPlans(root) },
154
+ { name: "backlog headings", run: () => checkBacklogHeadings(root) },
155
+ { name: "backlog archive", run: () => checkLiveFinished(root) },
156
+ { name: "refusals", run: () => checkRefusedEntries(root) },
157
+ { name: "stalled entries", run: () => checkStalledEntries(root) },
158
+ { name: "plan dependencies", run: () => checkPlanDependencies(root) },
159
+ { name: "stale questions", run: () => checkStaleQuestions(root) },
160
+ { name: "stale bugs", run: () => checkStaleBugs(root) },
161
+ { name: "waiting bugs", run: () => checkWaitingBugs(root) },
162
+ { name: "regressed fixes", run: () => checkRegressedFixes(root) },
163
+ { name: "stale merge-queue entries", run: () => checkStaleQueuedMerges(root, Date.now()) },
164
+ { name: "merge-queue files", run: () => checkMergeQueueFiles(root) },
165
+ { name: "post-merge reviews", run: () => checkPostReviews(root) },
166
+ { name: "stale prompts", run: () => checkStalePrompts(root, Date.now()) },
167
+ { name: "uncommitted backlog", run: () => checkUncommittedBacklog(root, config) },
127
168
  // The running team's own view of its build (staleness and what auto-restart made of it)
128
169
  // when there is one; without it the check still stands on its own git comparison.
129
- { name: "update", ...(await checkBuild(root, undefined, undefined, (orchestratorAlive(root, info) && info?.build) || null)) },
130
- { name: "orphans", ...(await checkOrphans(root, probe)) },
131
- { name: "macOS port leak", ...(await checkLaunchServicesPorts(probe)) },
170
+ { name: "update", run: () => checkBuild(root, undefined, undefined, (orchestratorAlive(root, info) && info?.build) || null) },
171
+ { name: "orphans", run: () => checkOrphans(root, probe) },
172
+ { name: "macOS port leak", run: () => checkLaunchServicesPorts(probe) },
132
173
  ];
174
+ const checks = [];
175
+ for (const step of steps) {
176
+ if (only && !only.includes(step.name))
177
+ continue;
178
+ const outcome = await runDoctorCheck(step.run);
179
+ if (outcome !== null)
180
+ checks.push({ name: step.name, ...outcome });
181
+ }
133
182
  const problems = checks.filter((c) => c.level === "fail").length;
134
183
  return { header, checks, routing, verdict: problems === 0 ? "ready to run" : plural(problems, "problem") };
135
184
  }
@@ -11,14 +11,17 @@ import os from "node:os";
11
11
  import path from "node:path";
12
12
  import { randomInt } from "node:crypto";
13
13
  import zlib from "node:zlib";
14
+ import { DAY_MS } from "../text/datetime.js";
14
15
  import { PiStreamParser } from "../pi/pi-stream.js";
15
- import { resolveAgentBin } from "../pi/pi-bin.js";
16
+ import { resolveBackendBin } from "../pi/pi-bin.js";
16
17
  import { imageProbesPath } from "../paths.js";
18
+ import { removeTree } from "../files/files.js";
17
19
  import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
20
+ import { isJsonObject, validEntries } from "../files/json-object.js";
18
21
  import { boundedProbe } from "./probe.js";
19
22
  /** How long a cached image-probe verdict stays valid while the claim is unchanged (7 days) —
20
23
  * a model's endpoint can change underneath a stale pass, so the answer is dated. */
21
- export const IMAGE_PROBE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
24
+ export const IMAGE_PROBE_TTL_MS = 7 * DAY_MS;
22
25
  /** The probe's deadline (90 s): a local model may need to load, far beyond the quick credential
23
26
  * probes, but a doctor run is still a pre-flight — a hung backend must lose, not stall the
24
27
  * report. */
@@ -41,10 +44,20 @@ export function imageProbeEntryFresh(entry, now = Date.now()) {
41
44
  entry.claim &&
42
45
  now - entry.at <= IMAGE_PROBE_TTL_MS);
43
46
  }
47
+ /** Whether a parsed cache entry has the shape of an `ImageProbeCacheEntry`. */
48
+ function isImageProbeCacheEntry(value) {
49
+ return (isJsonObject(value) &&
50
+ (value.verdict === "pass" || value.verdict === "fail") &&
51
+ typeof value.detail === "string" &&
52
+ typeof value.at === "number" &&
53
+ Number.isFinite(value.at) &&
54
+ typeof value.claim === "boolean");
55
+ }
44
56
  /** Read the probe cache, or an empty cache when the file is missing or torn (the harness's
45
- * no-data policy: a crashed writer never takes down a reader). */
57
+ * no-data policy: a crashed writer never takes down a reader). An entry of a foreign shape is
58
+ * dropped, so it reads as unprobed, and the doctor's next cache write leaves it out. */
46
59
  export function readImageProbeCache(root) {
47
- return readJsonFile(imageProbesPath(root)) ?? {};
60
+ return validEntries(readJsonFile(imageProbesPath(root)), isImageProbeCacheEntry);
48
61
  }
49
62
  /** Write the probe cache atomically (tmp+rename) so a concurrent reader never sees a torn
50
63
  * file. */
@@ -167,7 +180,7 @@ function isFourDigitNumber(text) {
167
180
  /** Probe one claimed pi seam with a real image (plans/image-input.md "Doctor"). Runs the
168
181
  * resolved pi binary in a run's environment (the caller passes `env`, typically
169
182
  * `childEnv({ PATH: pathEnv })` which inherits `PI_CODING_AGENT_DIR`, so the probe reads the
170
- * same models.json a real tick would):
183
+ * same models.json a real run would):
171
184
  *
172
185
  * `pi --print --mode json --no-session --no-tools --no-mcp --provider P --model M
173
186
  * --thinking <level> @<png> "Reply with only the 4-digit number shown in the image."`
@@ -183,13 +196,13 @@ function isFourDigitNumber(text) {
183
196
  * injectable so tests need no real pi. */
184
197
  export async function runImageProbe(config, seam, env, timeoutMs = PROBE_TIMEOUT_MS, code) {
185
198
  const envBin = env.TUMWATER_PI_BIN?.trim();
186
- // Resolve from the run's own environment (env override → config), exactly as a real tick's
199
+ // Resolve from the run's own environment (env override → config), exactly as a real run's
187
200
  // spawn would rather than only the harness process env — so tests can point a fake pi there.
188
201
  const bin = envBin
189
202
  ? envBin.includes("/")
190
203
  ? path.resolve(envBin)
191
204
  : envBin
192
- : resolveAgentBin(config).bin;
205
+ : resolveBackendBin(config).bin;
193
206
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tumwater-image-probe-"));
194
207
  const wrong = []; // the per-attempt "answered …" detail for a wrong answer
195
208
  const misread = []; // whether each wrong answer was a (wrong) 4-digit number
@@ -239,6 +252,6 @@ export async function runImageProbe(config, seam, env, timeoutMs = PROBE_TIMEOUT
239
252
  : { verdict: "fail", detail: `two wrong answers (${wrong[1]})` };
240
253
  }
241
254
  finally {
242
- fs.rmSync(dir, { recursive: true, force: true });
255
+ removeTree(dir);
243
256
  }
244
257
  }
@@ -1,8 +1,9 @@
1
1
  /** The doctor report's one-line outcome contract, in its own module so every check module
2
2
  * shares the shape and its constructors without a runtime import cycle: doctor-checks.ts
3
3
  * value-imports claudeSeamsInUse from doctor-model-checks.ts, so that pair cannot value-import
4
- * each other. doctor-checks.ts, doctor-model-checks.ts, doctor-orphans.ts, doctor-backlog.ts,
5
- * doctor-holds.ts, and doctor-launch-services.ts all import from here. */
4
+ * each other. doctor-checks.ts, doctor-project-checks.ts, doctor-contention-checks.ts,
5
+ * doctor-model-checks.ts, doctor-orphans.ts, doctor-backlog.ts, doctor-holds.ts, and
6
+ * doctor-launch-services.ts all import from here. */
6
7
  /** Constructors for the one CheckOutcome shape, so each check names its verdict
7
8
  * (`okOutcome(detail)`) instead of repeating the two-field literal. */
8
9
  export function okOutcome(detail) {
@@ -1,5 +1,16 @@
1
1
  import { spawn } from "node:child_process";
2
- import { releaseChildHandles, signalTree } from "../process/process.js";
2
+ import { captureCappedOutput, releaseChildHandles, signalTree } from "../process/process.js";
3
+ /** How long a backend credential/version probe may take before it counts as unknown — a doctor
4
+ * run is a pre-flight, not a place to wait out a hung backend. Shared by the pi, claude, opencode
5
+ * and codex credential probes (doctor-pi-checks.ts, doctor-claude-checks.ts,
6
+ * doctor-opencode-checks.ts, doctor-codex-checks.ts). */
7
+ export const AUTH_CHECK_TIMEOUT_MS = 15_000;
8
+ /** Cap on a credential probe's captured stdout. The auth reply is a tiny JSON object; a binary
9
+ * whose output is a runaway log (or a pipe held open with no EOF) must not grow `out` without
10
+ * bound. Sibling probes share EXEC_MAX_BUFFER, but 1 MiB is already orders of magnitude past any
11
+ * real reply, so it is the tighter bound here. Over it the probe kills the child and reads as
12
+ * unknown. */
13
+ export const AUTH_CHECK_MAX_OUTPUT_BYTES = 1024 * 1024;
3
14
  /** One bounded probe of a resolved binary: `null` when the spawn threw, the child errored,
4
15
  * the deadline passed, or stdout overran `maxOutputBytes` — every case the caller must read as
5
16
  * "could not check", never as a verdict. The child runs detached and at the deadline (or on
@@ -10,7 +21,9 @@ import { releaseChildHandles, signalTree } from "../process/process.js";
10
21
  * full stderr pipe would block the child. `timeoutMs` and `maxOutputBytes` are test seams;
11
22
  * production callers pass their own named constants. One home of the shape, shared by the
12
23
  * model checks' credential/version probes, the tier-model probes, and the image-input probe —
13
- * before the split each doctor module carried its own byte-for-byte copy. */
24
+ * before the split each doctor module carried its own byte-for-byte copy. The credential probes'
25
+ * named budgets (AUTH_CHECK_TIMEOUT_MS, AUTH_CHECK_MAX_OUTPUT_BYTES) live beside it here, so the
26
+ * backend check modules share them without importing them from one another. */
14
27
  export function boundedProbe(bin, args, env, timeoutMs, maxOutputBytes) {
15
28
  return new Promise((resolve) => {
16
29
  let child;
@@ -29,8 +42,6 @@ export function boundedProbe(bin, args, env, timeoutMs, maxOutputBytes) {
29
42
  resolve(null);
30
43
  return;
31
44
  }
32
- let out = "";
33
- let bytes = 0;
34
45
  let settled = false;
35
46
  let timer;
36
47
  const settle = (result) => {
@@ -46,19 +57,17 @@ export function boundedProbe(bin, args, env, timeoutMs, maxOutputBytes) {
46
57
  resolve(result);
47
58
  };
48
59
  // Deadline or runaway output: kill the whole group, tear down the handles, and settle —
49
- // do not wait for a `'close'` the group may never deliver.
60
+ // do not wait for a `'close'` the group may never deliver. Overrun shares the path: it
61
+ // passes `abort` as captureCappedOutput's onOverflow.
50
62
  const abort = () => {
51
63
  signalTree(child, "SIGKILL");
52
64
  settle(null);
53
65
  };
54
66
  timer = setTimeout(abort, timeoutMs);
55
- child.stdout?.on("data", (chunk) => {
56
- out += chunk.toString("utf8");
57
- bytes += chunk.length;
58
- if (bytes > maxOutputBytes)
59
- abort();
60
- });
67
+ // Captured stdout is capped at maxOutputBytes (reads past it abort); stderr was piped to
68
+ // ignore at spawn, so captureCappedOutput attaches nothing to it.
69
+ const captured = captureCappedOutput(child, maxOutputBytes, abort);
61
70
  child.on("error", () => settle(null));
62
- child.on("close", (code) => settle({ code, stdout: out }));
71
+ child.on("close", (code) => settle({ code, stdout: captured.stdout }));
63
72
  });
64
73
  }