tumwater 0.2.0 → 0.3.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 (446) 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 +42 -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 +4 -3
  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 +402 -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 +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -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 +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -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-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -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 +1 -2
  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 +118 -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 +150 -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 +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -0,0 +1,145 @@
1
+ import os from "node:os";
2
+ import { childEnv } from "../process/process.js";
3
+ import { boundedProbe } from "./probe.js";
4
+ import { claudeBackend, claudeConfigDir } from "../agent/claude/claude-backend.js";
5
+ import { claudeEnv } from "../agent/claude/claude-env.js";
6
+ import { claudeAuthUsesSubscription, parseClaudeAuthStatus } from "../agent/claude/claude-account.js";
7
+ import { resolvedBackendConfig } from "../config/config-views.js";
8
+ import { envPath } from "../files/files.js";
9
+ import { belowNodeFloor } from "../version.js";
10
+ import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
11
+ import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, claudeSeamsInUse, loadConfigForCheck, seamedModels, } from "./doctor-model-checks.js";
12
+ /** The doctor's claude credential and version readiness checks, split out of
13
+ * doctor-model-checks.ts (whose shared seam/model framework and the pi, tier-models and
14
+ * image-input checks stay there): checkClaudeAuth, checkAnthroApiKey and checkClaudeVersion.
15
+ * These are the doctor's only checks that spawn the `claude` binary and read its auth — a
16
+ * distinct concern from the filesystem/git checks, kept together so a change to how doctor
17
+ * spawns or scrubs claude does not touch the other model checks. The shared report contract
18
+ * (CheckOutcome) and the seam/model framework (loadConfigForCheck, claudeSeamsInUse,
19
+ * seamPairs, the AUTH_CHECK_* budgets) come from doctor-model-checks.ts and outcome.ts,
20
+ * exactly as the other doctor-* modules share them. */
21
+ /** One bounded probe of the resolved `claude` binary under the scrubbed run environment a real
22
+ * run gets: resolve the binary through the claude adapter that would spawn it and run `args`
23
+ * with a short deadline. Null when the probe could not run, the child errored, or the deadline
24
+ * passed. The one home of the resolveBin + claudeEnv pairing the auth and version checks share,
25
+ * so neither probes a different binary or environment than the other. */
26
+ function claudeProbe(config, args, pathEnv, timeoutMs) {
27
+ const resolved = claudeBackend.resolveBin(config);
28
+ // The configured team config dir must reach the probe's env the way a real run's does, or
29
+ // the auth/version rows would look in claude's default dir (usually ~/.claude) while a run
30
+ // with backends.claude.configDir writes and reads elsewhere — the very mismatch doctor exists
31
+ // to surface. Mirror backends.run's expansion: only claude accepts configDir, and unset leaves
32
+ // today's env byte-identical.
33
+ const configDir = resolvedBackendConfig(config, "claude").configDir;
34
+ const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
35
+ const env = claudeEnv(childEnv({ PATH: pathEnv }), {
36
+ quietTimeoutSeconds: 0,
37
+ ...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
38
+ });
39
+ return boundedProbe(resolved.bin, args, env, timeoutMs, AUTH_CHECK_MAX_OUTPUT_BYTES);
40
+ }
41
+ /** Run `claude auth status` under the same scrubbed environment a real run gets (claudeEnv),
42
+ * or null when the probe could not run or its reply was unreadable. */
43
+ async function claudeAuthProbe(config, pathEnv, timeoutMs) {
44
+ const result = await claudeProbe(config, ["auth", "status"], pathEnv, timeoutMs);
45
+ if (result?.code !== 0)
46
+ return null;
47
+ return parseClaudeAuthStatus(result.stdout);
48
+ }
49
+ /** claude credential readiness (plans/agent-backends.md "Every seam, checked"): a claude seam
50
+ * whose login is gone fails every run before it can work, so doctor runs `claude auth status`
51
+ * under the scrubbed run environment and fails when it reports `loggedIn: false`. An ok detail
52
+ * names the billing the reply implies — the subscription type, or an API key — so an operator
53
+ * telling subscription spend from API spend does not have to run the command themselves. A
54
+ * probe that could not run or a reply this cannot read warns rather than guesses. Stands down
55
+ * (ok) when no seam routes to claude, so a pi-only team spawns nothing. */
56
+ export async function checkClaudeAuth(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
57
+ const config = loadConfigForCheck(root);
58
+ if ("level" in config)
59
+ return config;
60
+ if (!claudeSeamsInUse(config))
61
+ return okOutcome("no model jobs run on claude");
62
+ const auth = await claudeAuthProbe(config, pathEnv, timeoutMs);
63
+ if (auth === null)
64
+ return warnOutcome("could not check — `claude auth status` did not run or its output was unreadable");
65
+ if (!auth.loggedIn)
66
+ return failOutcome("claude reports logged in: false — run `claude auth login`");
67
+ const subscription = claudeAuthUsesSubscription(auth);
68
+ const billing = subscription
69
+ ? `subscription${auth.subscriptionType !== undefined ? ` (${auth.subscriptionType})` : ""}`
70
+ : "API key";
71
+ return okOutcome(`logged in — billing: ${billing}`);
72
+ }
73
+ /** The subscription-vs-API billing trap (plans/provider-concurrency.md §9 gap 6): an operator
74
+ * who exports `ANTHROPIC_API_KEY` for pi's `anthropic` provider will silently move every claude
75
+ * seam to API billing — claude prefers the key over a claude.ai login — and subscription seams
76
+ * stop counting as free. Doctor warns when the key is set, some seam routes to claude, and
77
+ * `claude auth status` reports a claude.ai login (the subscription the key would override). It
78
+ * stays silent (ok) when no seam runs on claude, when the key is unset, or when claude reports
79
+ * an API-key login (then there is no subscription to lose). `env` is injectable so the check
80
+ * stays hermetic; a probe that cannot run warns rather than guesses like checkClaudeAuth. */
81
+ export async function checkAnthroApiKey(root, env = process.env, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
82
+ const config = loadConfigForCheck(root);
83
+ if ("level" in config)
84
+ return config;
85
+ if (!claudeSeamsInUse(config))
86
+ return okOutcome("no model jobs run on claude");
87
+ const apiKey = env.ANTHROPIC_API_KEY;
88
+ if (apiKey === undefined || apiKey === "")
89
+ return okOutcome("no ANTHROPIC_API_KEY set");
90
+ const auth = await claudeAuthProbe(config, pathEnv, timeoutMs);
91
+ if (auth === null)
92
+ return warnOutcome("could not check — `claude auth status` did not run or its output was unreadable");
93
+ if (!auth.loggedIn || !claudeAuthUsesSubscription(auth))
94
+ return okOutcome("ANTHROPIC_API_KEY is set, but claude reports no claude.ai login");
95
+ return warnOutcome("ANTHROPIC_API_KEY is set while claude reports a claude.ai login — model jobs on claude will bill the API key, not the subscription; unset it or route every anthropic model job on pi explicitly");
96
+ }
97
+ /** The dotted version in `claude --version`'s output, or undefined when it names none. */
98
+ function parseClaudeCliVersion(output) {
99
+ return /(\d+\.\d+\.\d+)/.exec(output)?.[1];
100
+ }
101
+ /** claude CLI-version readiness (plans/agent-backends.md finding 9): an installed CLI below a
102
+ * configured model's minimum fails or misprices that model, and team runs disable the
103
+ * auto-updater, so the version an operator has is the version agent runs use. Fails below the
104
+ * highest floor among the claude-routed models, naming which model needs it; an ok detail
105
+ * prints the installed version and the floor it satisfied (or no floor when none applies).
106
+ * The `off`/`minimal` thinking clamp is noted here too, since this check already has the
107
+ * claude seams in hand. Stands down when no seam routes to claude. */
108
+ export async function checkClaudeVersion(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
109
+ const config = loadConfigForCheck(root);
110
+ if ("level" in config)
111
+ return config;
112
+ const seams = seamedModels(config, "claude");
113
+ if (seams.length === 0)
114
+ return okOutcome("no model jobs run on claude");
115
+ // The floor each configured model needs; a model the adapter does not gate contributes none.
116
+ const floors = new Map();
117
+ for (const seam of seams) {
118
+ const min = claudeBackend.minVersion?.(seam.model);
119
+ if (min !== undefined)
120
+ floors.set(seam.model, min);
121
+ }
122
+ const result = await claudeProbe(config, ["--version"], pathEnv, timeoutMs);
123
+ const found = result !== null && result.code === 0 ? parseClaudeCliVersion(result.stdout) : undefined;
124
+ if (found === undefined)
125
+ return warnOutcome("could not check — `claude --version` did not run or its output was unreadable");
126
+ const clamped = seams.some((seam) => seam.thinking === "off" || seam.thinking === "minimal");
127
+ const clampNote = clamped
128
+ ? "; `off`/`minimal` thinking clamps to `--effort low` (claude has no lower setting)"
129
+ : "";
130
+ if (floors.size === 0)
131
+ return okOutcome(`claude ${found}${clampNote}`);
132
+ // The highest floor decides: whichever model needs the newest CLI. Below it, fail and name
133
+ // that model; at or above, print the version against the floor.
134
+ let required = "";
135
+ let neededBy = "";
136
+ for (const [model, min] of floors) {
137
+ if (required === "" || belowNodeFloor(required, `>=${min}`)) {
138
+ required = min;
139
+ neededBy = model;
140
+ }
141
+ }
142
+ if (belowNodeFloor(found, `>=${required}`))
143
+ return failOutcome(`claude ${found} is older than ${required} required by ${neededBy} — run \`claude update\``);
144
+ return okOutcome(`claude ${found} (>= ${required} for ${neededBy})${clampNote}`);
145
+ }
@@ -0,0 +1,279 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { childEnv } from "../process/process.js";
4
+ import { boundedProbe } from "./probe.js";
5
+ import { codexBackend, CODEX_MIN_VERSION } from "../agent/codex/codex-backend.js";
6
+ import { codexEffort, EFFORT_BY_THINKING } from "../agent/codex/codex-args.js";
7
+ import { codexEnv } from "../agent/codex/codex-env.js";
8
+ import { codexHome, readCodexCatalog } from "../agent/codex/codex-catalog.js";
9
+ import { codexHookTrust } from "../agent/codex/codex-hook-trust.js";
10
+ import { parseCodexLoginStatus } from "../agent/codex/codex-account.js";
11
+ import { codexWriteGuardDefinition } from "../agent/codex/codex-write-guard.js";
12
+ import { resolvedBackendConfig } from "../config/config-views.js";
13
+ import { envPath, isDirectory, readTextOrNull, statOrNull, treeBytes } from "../files/files.js";
14
+ import { formatBytes, tokenWindowLabel } from "../text/format.js";
15
+ import { errorMessage } from "../text/text.js";
16
+ import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
17
+ import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, codexSeamsInUse, loadConfigForCheck, minVersionOutcome, seamedModels, } from "./doctor-model-checks.js";
18
+ /** The doctor's codex rows (part 9a/9, plans/codex-backend.md §4, §7, "Hand steps"): one
19
+ * exported check per row — install/version, login, home, models, account, leaks, sessions
20
+ * size and the write guard. These are the doctor's only checks that spawn the `codex` CLI and
21
+ * read the team's
22
+ * `CODEX_HOME` state (catalog, skills, hooks, sessions, rollouts) — a distinct concern from the
23
+ * filesystem/git checks, kept together so a change to how doctor reads codex does not touch the
24
+ * other model checks. Each returns null (omitting its row) when no seam routes to codex, so a
25
+ * pi-only team shows no codex row and spawns nothing and reads nothing. The shared
26
+ * report contract (CheckOutcome) and the seam/model framework (loadConfigForCheck,
27
+ * codexSeamsInUse, seamPairs, the AUTH_CHECK_* budgets) come from doctor-model-checks.ts and
28
+ * outcome.ts, as the other doctor-* modules share them. */
29
+ /** The config and codex home a codex readiness row reads, or the warn outcome a broken
30
+ * `tumwater.json` earns, or null when no seam routes to codex (the row's stand-down). All
31
+ * eight rows repeat the same three-line guard and the same home lookup
32
+ * (`codexHome(resolvedBackendConfig(config, "codex"))`); this folds them so a change to how
33
+ * doctor reads codex's home touches one spot instead of seven. `home` is always the team's
34
+ * codex home, whether or not the row needs it. */
35
+ function codexCheckBase(root) {
36
+ const config = loadConfigForCheck(root);
37
+ if ("level" in config)
38
+ return config;
39
+ if (!codexSeamsInUse(config))
40
+ return null;
41
+ return { config, home: codexHome(resolvedBackendConfig(config, "codex")) };
42
+ }
43
+ /** One bounded probe of the resolved `codex` binary under the scrubbed run environment a real
44
+ * run gets: resolve the binary through the codex adapter that would spawn it, point it at the
45
+ * run's home (`CODEX_HOME`), and run `args` with a short deadline. Null when the probe could
46
+ * not run, the child errored, or the deadline passed. The one home of the resolveBin + codexEnv
47
+ * pairing the version and login rows share, so neither probes a different binary or environment
48
+ * than the other. */
49
+ function codexProbe(config, args, pathEnv, timeoutMs) {
50
+ const resolved = codexBackend.resolveBin(config);
51
+ const home = codexHome(resolvedBackendConfig(config, "codex"));
52
+ const env = codexEnv(childEnv({ PATH: pathEnv }), { home });
53
+ return boundedProbe(resolved.bin, args, env, timeoutMs, AUTH_CHECK_MAX_OUTPUT_BYTES);
54
+ }
55
+ /** The dotted version in `codex --version`'s output, or undefined when it names none. */
56
+ function parseCodexCliVersion(output) {
57
+ return /(\d+\.\d+\.\d+)/.exec(output)?.[1];
58
+ }
59
+ /** codex CLI-version readiness (plans/codex-backend.md "Hand steps", H1): the team targets
60
+ * `CODEX_MIN_VERSION` or later, the version H1 verified live. An installed CLI below that fails
61
+ * naming the floor and the upgrade; at or above it reads the version. Stands down when no seam
62
+ * routes to codex. */
63
+ export async function checkCodexVersion(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
64
+ const base = codexCheckBase(root);
65
+ if (base === null || "level" in base)
66
+ return base;
67
+ const result = await codexProbe(base.config, ["--version"], pathEnv, timeoutMs);
68
+ const found = result?.code === 0 ? parseCodexCliVersion(result.stdout) : undefined;
69
+ return minVersionOutcome("codex", found, CODEX_MIN_VERSION, "upgrade the codex CLI");
70
+ }
71
+ /** codex login readiness (plans/codex-backend.md §4, §7): a ChatGPT login is subscription
72
+ * billing and passes; an API-key login is refused (decision 5 — Codex reports no cost, so an
73
+ * API key would spend money the cap cannot see) with the startup gate's message; a logged-out
74
+ * home fails naming the login command under the run's `CODEX_HOME`. A probe that could not run
75
+ * or a reply this cannot read (neither a ChatGPT login, an API key, nor an explicit
76
+ * not-logged-in) warns rather than guesses. Stands down when no seam routes to codex. */
77
+ export async function checkCodexAuth(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
78
+ const base = codexCheckBase(root);
79
+ if (base === null || "level" in base)
80
+ return base;
81
+ const { home } = base;
82
+ const result = await codexProbe(base.config, ["login", "status"], pathEnv, timeoutMs);
83
+ if (result?.code !== 0)
84
+ return warnOutcome("could not check — `codex login status` did not run or its output was unreadable");
85
+ const status = parseCodexLoginStatus(result.stdout);
86
+ if (status === "subscription")
87
+ return okOutcome("logged in using ChatGPT — billing: subscription");
88
+ if (status === "api")
89
+ return failOutcome(`Codex is logged in with an API key; tumwater runs Codex on a ChatGPT subscription only — run \`CODEX_HOME=${home} codex login --device-auth\``);
90
+ if (/Not logged in/i.test(result.stdout ?? ""))
91
+ return failOutcome(`codex is not logged in — run \`CODEX_HOME=${home} codex login --device-auth\``);
92
+ return warnOutcome("could not check — `codex login status` reply was unreadable");
93
+ }
94
+ /** codex home readiness (plans/codex-backend.md §4): unset `backends.codex.configDir` warns
95
+ * that team runs share the operator's `~/.codex` — the trust entries, team sessions in the
96
+ * `codex resume` picker, any skills and a global `AGENTS.md`. Set, it reads the team home.
97
+ * Stands down when no seam routes to codex. */
98
+ export async function checkCodexHome(root) {
99
+ const base = codexCheckBase(root);
100
+ if (base === null || "level" in base)
101
+ return base;
102
+ const configDir = resolvedBackendConfig(base.config, "codex").configDir;
103
+ if (configDir === undefined)
104
+ return warnOutcome("backends.codex.configDir is unset — team runs share the user's ~/.codex (trust entries, team sessions in the resume picker, skills, a global AGENTS.md); set backends.codex.configDir to a home of the team's own");
105
+ return okOutcome(`team codex home: ${codexHome({ configDir })}`);
106
+ }
107
+ /** codex model readiness (plans/codex-backend.md §7): every codex-routed model must be in the
108
+ * home's catalog (`models_cache.json`) — a missing model warns that its thinking levels and
109
+ * window are unknown, and a thinking level the catalog does not list warns that it is dropped.
110
+ * The cataloged models are reported with their context window. Stands down when no seam routes
111
+ * to codex. */
112
+ export async function checkCodexModels(root) {
113
+ const base = codexCheckBase(root);
114
+ if (base === null || "level" in base)
115
+ return base;
116
+ const { config, home } = base;
117
+ const catalog = readCodexCatalog(home);
118
+ const models = seamedModels(config, "codex");
119
+ const warns = [];
120
+ const notes = [];
121
+ for (const pair of models) {
122
+ const entry = catalog.get(pair.model);
123
+ if (entry === undefined) {
124
+ warns.push(`${pair.model} is not in the codex catalog (models_cache.json) — thinking levels and the window are unknown`);
125
+ continue;
126
+ }
127
+ const window = tokenWindowLabel(entry.contextWindow);
128
+ notes.push(`${pair.model} — ${window}`);
129
+ // A level is dropped when the run path would send no effort for it: `codexEffort` maps the
130
+ // thinking through EFFORT_BY_THINKING (off/minimal clamp to low) and yields undefined when
131
+ // the model's catalog does not list that effort (part 5/9's codexEffort). A thinking level
132
+ // with no mapping at all (e.g. ultra — never sent) is not a catalog gap, so it is not warned.
133
+ if (pair.thinking !== undefined &&
134
+ pair.thinking !== "" &&
135
+ Object.hasOwn(EFFORT_BY_THINKING, pair.thinking) &&
136
+ codexEffort(pair.thinking, entry.levels) === undefined) {
137
+ warns.push(`${pair.model} has no ${pair.thinking} thinking level in the catalog — that level is dropped`);
138
+ }
139
+ }
140
+ if (warns.length > 0) {
141
+ const extra = notes.length > 0 ? `; ${notes.join("; ")}` : "";
142
+ return warnOutcome(`${warns.join("; ")}${extra}`);
143
+ }
144
+ return okOutcome(notes.length > 0 ? `codex models ready — ${notes.join("; ")}` : "no codex-routed models declared");
145
+ }
146
+ /** A `has_credits` reading from the home's rollout captures, or null when none names it.
147
+ * `has_credits` travels in each run's `token_count.rate_limits.credits` (design §7), so the
148
+ * account row reads the captures a finished run left rather than a live probe — the same
149
+ * source the runtime account is fed from. The newest capture (by file mtime) wins,
150
+ * approximating the account's current state — readdir order is filesystem order, not
151
+ * chronological, so the captures are sorted by mtime first. A missing reading is the safe
152
+ * direction (nothing to warn about). */
153
+ function codexSessionCredits(home) {
154
+ const sessions = path.join(home, "sessions");
155
+ if (!isDirectory(sessions))
156
+ return null;
157
+ const rollouts = [];
158
+ const walk = (dir) => {
159
+ let entries;
160
+ try {
161
+ entries = fs.readdirSync(dir, { withFileTypes: true });
162
+ }
163
+ catch {
164
+ return;
165
+ }
166
+ for (const entry of entries) {
167
+ const p = path.join(dir, entry.name);
168
+ if (entry.isDirectory())
169
+ walk(p);
170
+ else if (entry.isFile() && /^rollout-.*\.jsonl$/.test(entry.name)) {
171
+ const st = statOrNull(p);
172
+ if (st !== null)
173
+ rollouts.push({ file: p, mtimeMs: st.mtimeMs });
174
+ }
175
+ }
176
+ };
177
+ walk(sessions);
178
+ rollouts.sort((a, b) => b.mtimeMs - a.mtimeMs);
179
+ for (const r of rollouts) {
180
+ const text = readTextOrNull(r.file);
181
+ if (text === null)
182
+ continue;
183
+ const m = /"has_credits"\s*:\s*(true|false)/.exec(text);
184
+ if (m !== null)
185
+ return m[1] === "true";
186
+ }
187
+ return null;
188
+ }
189
+ /** codex account readiness (plans/codex-backend.md §7 "Credits"): a Plus account can buy
190
+ * credits that run past the windows, and tumwater never spends them on purpose but cannot see
191
+ * the amount, so doctor warns when the runs' captures report `has_credits: true` — the run that
192
+ * crosses a limit may draw on them. Otherwise it reads ok with the safe direction. Stands down
193
+ * when no seam routes to codex. */
194
+ export async function checkCodexAccount(root) {
195
+ const base = codexCheckBase(root);
196
+ if (base === null || "level" in base)
197
+ return base;
198
+ const { home } = base;
199
+ const hasCredits = codexSessionCredits(home);
200
+ if (hasCredits === true)
201
+ return warnOutcome("the codex account can buy credits (has_credits: true) — a run that crosses a limit may spend credits tumwater cannot count; clear the credit balance or accept the blind spend");
202
+ return okOutcome(hasCredits === false ? "no credits — runs stop at the subscription window" : "no credit reading yet — runs stop at the subscription window");
203
+ }
204
+ /** codex home leaks (plans/codex-backend.md §4): the operator's home `AGENTS.md`, any
205
+ * `skills/` entry beyond `.system`, any `hooks/` entry, and the target repo's
206
+ * `.codex/config.toml` MCP servers each still load into team runs, so each is named as one
207
+ * warning. Stands down when no seam routes to codex. */
208
+ export async function checkCodexLeaks(root) {
209
+ const base = codexCheckBase(root);
210
+ if (base === null || "level" in base)
211
+ return base;
212
+ const { home } = base;
213
+ const warns = [];
214
+ const agentsMd = path.join(home, "AGENTS.md");
215
+ if (statOrNull(agentsMd)?.isFile() === true)
216
+ warns.push(`${agentsMd} still loads into team runs`);
217
+ const skills = path.join(home, "skills");
218
+ if (isDirectory(skills)) {
219
+ for (const entry of fs.readdirSync(skills)) {
220
+ if (entry === ".system")
221
+ continue;
222
+ warns.push(`${path.join(skills, entry)} — a skill beyond .system still loads into team runs`);
223
+ }
224
+ }
225
+ const hooks = path.join(home, "hooks");
226
+ if (isDirectory(hooks)) {
227
+ for (const entry of fs.readdirSync(hooks))
228
+ warns.push(`${path.join(hooks, entry)} — a hook still loads into team runs`);
229
+ }
230
+ // The target repo's own `.codex/config.toml` MCP servers (design §4): a repo-local config
231
+ // with an `[mcp_servers.NAME]` section loads those servers into every team run here.
232
+ const repoConfig = path.join(root, ".codex", "config.toml");
233
+ const text = readTextOrNull(repoConfig);
234
+ if (text !== null) {
235
+ const servers = [...text.matchAll(/^\s*\[mcp_servers\.([^\]]+)\]\s*$/gm)].map((m) => m[1]);
236
+ for (const server of servers)
237
+ warns.push(`${repoConfig} configures MCP server ${server}, which loads into team runs`);
238
+ }
239
+ if (warns.length === 0)
240
+ return okOutcome("no codex home leaks");
241
+ return warnOutcome(warns.join("; "));
242
+ }
243
+ /** The size of the home's `sessions/` (plans/codex-backend.md §4): the retention knob prunes
244
+ * expired sessions, and doctor reports how much the collection holds so the operator can size
245
+ * the knob. Stands down when no seam routes to codex. */
246
+ export async function checkCodexSessions(root) {
247
+ const base = codexCheckBase(root);
248
+ if (base === null || "level" in base)
249
+ return base;
250
+ const { home } = base;
251
+ const sessions = path.join(home, "sessions");
252
+ const size = isDirectory(sessions) ? treeBytes(sessions) : null;
253
+ return okOutcome(size !== null ? `codex sessions: ${formatBytes(size)}` : "codex sessions: no sessions dir");
254
+ }
255
+ /** The bound on the codex write-guard row's trust lookup, matching the group's other calls. */
256
+ const CODEX_HOOK_TRUST_TIMEOUT_MS = 10_000;
257
+ /** codex write-guard readiness (plans/codex-write-guard.md "Visibility (parts 8–9)"): whether
258
+ * codex runs the trusted write-guard hook under this team's home, so the operator sees whether
259
+ * codex edits are guarded and why not when they are not. Asks codex's own `hooks/list` through
260
+ * codexHookTrust for the current build's definition, bounded like the group's other Codex calls;
261
+ * the cache file it writes is the one writable surface Doctor keeps. A trust reading gives the
262
+ * pass row naming the hash's first 12 hex; a failed lookup warns with its reason — codex then
263
+ * runs edits without the worktree check. Stands down when no seam routes to codex. */
264
+ export async function checkCodexWriteGuard(root, pathEnv = envPath(), timeoutMs = CODEX_HOOK_TRUST_TIMEOUT_MS) {
265
+ const base = codexCheckBase(root);
266
+ if (base === null || "level" in base)
267
+ return base;
268
+ const resolved = codexBackend.resolveBin(base.config);
269
+ const { home } = base;
270
+ const env = codexEnv(childEnv({ PATH: pathEnv }), { home });
271
+ const definition = codexWriteGuardDefinition();
272
+ try {
273
+ const trust = await codexHookTrust(root, { bin: resolved.bin, env, cwd: root, definition, timeoutMs });
274
+ return okOutcome(`trusted (sha256:${trust.hash.replace(/^sha256:/, "").slice(0, 12)})`);
275
+ }
276
+ catch (error) {
277
+ return warnOutcome(`off: ${errorMessage(error)} — codex runs edit without the worktree check`);
278
+ }
279
+ }