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,4 +1,3 @@
1
- // vocabulary: plain
2
1
  import { roleLabel } from "../roles/role-labels.js";
3
2
  import { pausedRoles, pausedRolesUntil, standingTeamPause } from "../team/team-state.js";
4
3
  import { MODEL_TIERS } from "../config/config-schema.js";
@@ -9,34 +8,36 @@ import { roleCapVerdicts } from "../gates/role-cap-gates.js";
9
8
  import { readMergeQueue } from "../merge/merge-queue.js";
10
9
  import { MAINTENANCE_DAILY_FLOOR } from "../gates/maintenance-quota.js";
11
10
  import { heldByMainRed, mainCheckForPoll, maintenanceQuotaForPoll } from "../status/status-polls.js";
12
- import { loadLoopState } from "../loop/loop-state.js";
13
- import { baseRoleOf, loopIds, loopPaused } from "../roles/loop-ids.js";
14
- import { ERROR_STREAK_BREAKER, ERROR_STREAK_WARN } from "../tick/tick-apply.js";
11
+ import { loadRunnerState } from "../runner/runner-state-load.js";
12
+ import { baseRoleOf, runnerIds, runnerPaused } from "../roles/runner-ids.js";
13
+ import { ERROR_STREAK_BREAKER, ERROR_STREAK_WARN } from "../run/run-apply.js";
15
14
  import { squash } from "../text/text.js";
16
15
  import { usd } from "../text/format.js";
17
16
  import { parseQuietHours, quietHoursStatus, quietWindowEnd, roleQuietHold } from "../scheduling/quiet-hours.js";
18
- import { humanUntil } from "../text/datetime.js";
17
+ import { DAY_MS, HOUR_MS, humanUntil } from "../text/datetime.js";
19
18
  import { budgetPhrase, pauseReasonSuffix, plural } from "../text/phrases.js";
20
- import { claudeUtilizationPct, claudeWindowEntries, claudeWindowLabel, readClaudeAccount } from "../agent/claude/claude-account.js";
21
- import { claudeWindowObservation } from "../team/team-polls.js";
19
+ import { readAccount, utilizationPct, windowEntries, windowLabel } from "../agent/subscription-account.js";
20
+ import { backendDisplayName } from "../agent/backend-traits.js";
21
+ import { windowObservation } from "../team/team-polls.js";
22
22
  import { readEventsSince } from "../events/event-window.js";
23
23
  import { errorMessage } from "../text/text.js";
24
24
  import { okOutcome, warnOutcome } from "./outcome.js";
25
+ import { redMainWarning } from "../baseline/main-guardrails.js";
25
26
  /** The standing-hold checks: `pause` (checkPause), `quiet hours` (checkQuietHours), and `budget`
26
- * (checkBudget), `claude window` (checkClaudeWindow), `maintenance quota`
27
- * (checkMaintenanceQuota), and `error streaks` (checkErrorStreaks), all read-only reports of
28
- * holds on ticking. */
29
- export function loadLoopStates(root, config) {
30
- return loopIds(config)
31
- .map((id) => loadLoopState(root, id))
27
+ * (checkBudget), the `claude window`/`codex window` rows (checkSubscriptionWindow),
28
+ * `maintenance quota` (checkMaintenanceQuota), and `error streaks` (checkErrorStreaks), all
29
+ * read-only reports of holds on running. */
30
+ export function loadRunnerStates(root, config) {
31
+ return runnerIds(config)
32
+ .map((id) => loadRunnerState(root, id))
32
33
  .sort((a, b) => a.role.localeCompare(b.role));
33
34
  }
34
35
  /** The `pause` check: names a standing operator pause so doctor does not print "ready to run"
35
- * over a team that starts no tick. Read-only: it reads the same team and per-role pause
36
+ * over a team that starts no run. Read-only: it reads the same team and per-role pause
36
37
  * markers the scheduler reads (standingTeamPause, pausedRoles, pausedRolesUntil — all treat
37
38
  * an expired marker as absent), so its verdict cannot disagree with what the team does. A
38
- * team pause is a warn because it stops every loop; paused roles are ok because the operator
39
- * chose them by name and every other loop keeps ticking. `now` only drives the countdowns. */
39
+ * team pause is a warn because it stops every role; paused roles are ok because the operator
40
+ * chose them by name and every other role keeps running. `now` only drives the countdowns. */
40
41
  export function checkPause(root, now = Date.now()) {
41
42
  const team = standingTeamPause(root);
42
43
  const roles = pausedRoles(root);
@@ -53,10 +54,10 @@ export function checkPause(root, now = Date.now()) {
53
54
  return warnOutcome(`${head}${pauseReasonSuffix(team.reason)}${rolesPhrase ? `; ${rolesPhrase}` : ""}`);
54
55
  }
55
56
  /** The `quiet hours` check: names the team quiet window and any per-role windows holding now,
56
- * so doctor does not print "ready to run" over a team that starts no tick until the window
57
+ * so doctor does not print "ready to run" over a team that starts no run until the window
57
58
  * ends. Read-only: membership comes from the scheduler's own parser and predicate
58
59
  * (quietHoursStatus, roleQuietHold), so the verdict cannot disagree with the gate. A team
59
- * window holding is a warn because it stops every loop (the director is exempt); held per-role
60
+ * window holding is a warn because it stops every role (the director is exempt); held per-role
60
61
  * windows are ok because the operator chose those roles by name. A malformed value reads as
61
62
  * unset here — checkInit owns that error. `now` is injected so tests never read the clock. */
62
63
  export function checkQuietHours(config, now = new Date()) {
@@ -89,13 +90,13 @@ export function checkQuietHours(config, now = new Date()) {
89
90
  * own `maxDailyCostUsdPerRole` cap. Read-only and built on the scheduler's own definitions
90
91
  * (teamDailyCost, budgetReached, roleCapVerdicts), so the verdict cannot disagree with the gate.
91
92
  * `published` is the running scheduler's figure (it includes runs charged since the last save)
92
- * and wins over the persisted sum, as in status. A reached team cap is a warn because role loops
93
+ * and wins over the persisted sum, as in status. A reached team cap is a warn because roles
93
94
  * run on their tier fallback or hold until local midnight (the `fallback` row says which);
94
95
  * per-role caps reached alone are ok because the operator chose them by name. */
95
96
  export function checkBudget(root, config, published, now = Date.now()) {
96
97
  if (config === undefined)
97
98
  return okOutcome("not checked — tumwater.json unreadable");
98
- const states = loadLoopStates(root, config);
99
+ const states = loadRunnerStates(root, config);
99
100
  const spent = published?.spentUsd ?? teamDailyCost(states, now);
100
101
  const caps = config.maxDailyCostUsdPerRole;
101
102
  const capped = [...roleCapVerdicts(states.map((s) => ({ role: s.role, state: s })), caps, now)]
@@ -116,63 +117,65 @@ export function checkBudget(root, config, published, now = Date.now()) {
116
117
  return okOutcome(`${phrase} today${cappedPhrase}`);
117
118
  }
118
119
  /** The `maintenance quota` check: rolling-24 h maintenance merges against the allowance
119
- * (`maintenancePerWorkMerge` per work merge + the daily floor), naming the maintenance loops held
120
+ * (`maintenancePerWorkMerge` per work merge + the daily floor), naming the maintenance roles held
120
121
  * when it is full. Read-only and built on the same verdict the scheduler and status use
121
- * (maintenanceQuotaForPoll). Only a live team's running loops and merge queue count as in flight,
122
+ * (maintenanceQuotaForPoll). Only a live team's running roles and merge queue count as in flight,
122
123
  * since a dead team's leftover `running` flag and queue drain nothing. A held allowance is a warn
123
124
  * because it stops a whole tier. */
124
125
  export function checkMaintenanceQuota(root, config, aliveTeam, now = Date.now()) {
125
126
  if (config === undefined)
126
127
  return okOutcome("not checked — tumwater.json unreadable");
127
- const loops = loadLoopStates(root, config).map((s) => ({ role: s.role, running: aliveTeam && s.running === true }));
128
+ const runners = loadRunnerStates(root, config).map((s) => ({ role: s.role, running: aliveTeam && s.running === true }));
128
129
  const queued = aliveTeam ? new Set(readMergeQueue(root).entries.map((e) => e.role)) : new Set();
129
- const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkMerge, loops, queued, now);
130
+ const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkMerge, runners, queued, now);
130
131
  if (held.length > 0) {
131
132
  return warnOutcome(`allowance reached — ${used}/${allowance} maintenance merges in the last 24h; held until the window rolls under: ${[...held].sort().join(", ")}`);
132
133
  }
133
134
  const base = `${used}/${allowance} maintenance merges in the last 24h (${config.maintenancePerWorkMerge} per work merge + ${MAINTENANCE_DAILY_FLOOR} floor)`;
134
- const anyMaintenance = loops.some((l) => commitTier(l.role) === "maintenance");
135
+ const anyMaintenance = runners.some((l) => commitTier(l.role) === "maintenance");
135
136
  return okOutcome(anyMaintenance ? base : `${base}; no maintenance role enabled`);
136
137
  }
137
- /** The `claude window` check: whether a rejected subscription window holds the subscription claude
138
- * seams. Read-only and built on claudeWindowObservation, the verdict the scheduler's team hold
139
- * uses, so it cannot disagree with the hold. A hold is a warn because it stops every subscription
140
- * claude seam until the window resets; otherwise the most constrained window's state is reported
141
- * as ok (a rejected window spending overage reads `rejected` here: overage spends, it does not
142
- * hold). API-billed claude never holds. */
143
- export function checkClaudeWindow(root, now = Date.now(), chain, config) {
144
- const account = readClaudeAccount(root, now);
138
+ /** The `<backend> window` check, one row per subscription backend in use (claude and codex):
139
+ * whether a rejected subscription window holds the subscription `<backend>` seams. Read-only and
140
+ * built on windowObservation (the backend's own), the verdict the scheduler's team hold uses, so
141
+ * it cannot disagree with the hold. A hold is a warn because it stops every subscription seam on
142
+ * that backend until the window resets; otherwise the most constrained window's state is
143
+ * reported as ok (a rejected window spending overage reads `rejected` here: overage spends, it
144
+ * does not hold). Returns null (omitting the row from doctor) when the backend has no
145
+ * subscription account — the same omit-a-row pattern as `pi extensions` — because an absent or
146
+ * API-billed account never holds. */
147
+ export function checkSubscriptionWindow(backend, root, now = Date.now(), chain, config) {
148
+ const account = readAccount(root, backend, now);
145
149
  if (account?.billing !== "subscription")
146
- return okOutcome("no subscription window reported");
147
- const held = claudeWindowObservation(account, now);
150
+ return null;
151
+ const held = windowObservation(backend, account, now);
148
152
  if (held?.kind === "subscription-window" && held.windowType !== undefined && held.resetsAt !== undefined) {
149
- const label = claudeWindowLabel(held.windowType);
153
+ const label = windowLabel(held.windowType);
150
154
  const resets = humanUntil(held.resetsAt, now);
151
- const moved = MODEL_TIERS.filter((t) => {
152
- const reason = chain?.[t]?.reason;
153
- return reason === "window" || reason === "reserve";
154
- });
155
+ const moved = MODEL_TIERS.filter((t) => (chain?.[t]?.reason === "window" || chain?.[t]?.reason === "reserve") &&
156
+ config !== undefined &&
157
+ withModelOverrides(config, t, {}).agent === backend);
155
158
  if (moved.length > 0) {
156
- const waiting = MODEL_TIERS.filter((t) => !chain?.[t] && config !== undefined && withModelOverrides(config, t, {}).agent === "claude");
159
+ const waiting = MODEL_TIERS.filter((t) => !chain?.[t] && config !== undefined && withModelOverrides(config, t, {}).agent === backend);
157
160
  return warnOutcome(`${label} window rejected — ${moved.join(", ")} moved to fallbacks until it resets in ${resets}${waiting.length > 0 ? `; ${waiting.join(", ")} wait` : ""}`);
158
161
  }
159
- return warnOutcome(`${label} window rejected — subscription claude model jobs wait until it resets in ${resets}`);
162
+ return warnOutcome(`${label} window rejected — subscription ${backendDisplayName(backend)} model jobs wait until it resets in ${resets}`);
160
163
  }
161
- const top = claudeWindowEntries(account)[0];
164
+ const top = windowEntries(account)[0];
162
165
  if (top === undefined)
163
166
  return okOutcome("subscription; no window reported yet");
164
- const pct = typeof top.utilization === "number" ? ` (${claudeUtilizationPct(top.utilization)}% used)` : "";
165
- const label = claudeWindowLabel(top.type);
167
+ const pct = typeof top.utilization === "number" ? ` (${utilizationPct(top.utilization)}% used)` : "";
168
+ const label = windowLabel(top.type);
166
169
  return okOutcome(top.status === "allowed_warning" ? `${label} window nearing its limit${pct}` : `${label} window ${top.status}${pct}`);
167
170
  }
168
171
  /** How far back the `provider hold` check reads for hold/resume crossings. The longest a
169
172
  * hold can stay is HOLD_CAP_MS (team-hold's 15-minute cap that escalation doubles against),
170
173
  * so a window a comfortable multiple of that covers any standing hold's crossing event plus
171
174
  * its resume — an older resumed event must not be missed or a re-opened provider reads held. */
172
- const HOLD_CHECK_WINDOW_MS = 2 * 3600_000;
175
+ const HOLD_CHECK_WINDOW_MS = 2 * HOUR_MS;
173
176
  /** The `provider hold` check: names each provider whose team-wide failure hold
174
177
  * (src/team/team-hold.ts, polled by pollTeamHold) stands now — a 429/backend-failure storm
175
- * that stops the roles on that provider from starting new ticks until it re-opens. Read-only:
178
+ * that stops the roles on that provider from starting new runs until it re-opens. Read-only:
176
179
  * it infers the standing set from the event log's own `rate_limit_hold`/`rate_limit_resumed`
177
180
  * crossings, the events pollTeamHold writes, so it cannot disagree with the hold. A standing
178
181
  * hold is a warn (as with the `claude window` row) because it stops every role on that
@@ -210,16 +213,16 @@ export function checkProviderHolds(root, now = Date.now()) {
210
213
  });
211
214
  return warnOutcome(lines.join("; "));
212
215
  }
213
- /** The `error streaks` check: names each enabled loop whose `consecutiveErrors` is at or past
216
+ /** The `error streaks` check: names each enabled role whose `consecutiveErrors` is at or past
214
217
  * ERROR_STREAK_WARN, with its streak and last error, and whether it is paused. Read-only; it reads
215
218
  * the same `consecutiveErrors` the error-streak breaker and `teamAlerts` read, and the same
216
219
  * `pausedRoles` marker the scheduler reads. The marker does not record who wrote it, so a paused
217
- * loop at ERROR_STREAK_BREAKER's bar is described, not attributed to the breaker. A warn, not a
218
- * fail: a failing loop does not stop the team from running. */
220
+ * role at ERROR_STREAK_BREAKER's bar is described, not attributed to the breaker. A warn, not a
221
+ * fail: a failing role does not stop the team from running. */
219
222
  export function checkErrorStreaks(root, config) {
220
223
  if (config === undefined)
221
224
  return okOutcome("not checked — tumwater.json unreadable");
222
- const failing = loadLoopStates(root, config)
225
+ const failing = loadRunnerStates(root, config)
223
226
  .filter((s) => (s.consecutiveErrors ?? 0) >= ERROR_STREAK_WARN);
224
227
  if (failing.length === 0)
225
228
  return okOutcome("no role failing repeatedly");
@@ -227,7 +230,7 @@ export function checkErrorStreaks(root, config) {
227
230
  const phrases = failing.map((s) => {
228
231
  const n = s.consecutiveErrors ?? 0;
229
232
  let phrase = `${roleLabel(s.role)}: ${n} failed runs`;
230
- if (loopPaused(paused, s.role)) {
233
+ if (runnerPaused(paused, s.role)) {
231
234
  const key = paused.has(s.role) ? s.role : paused.has(baseRoleOf(s.role)) ? baseRoleOf(s.role) : `${s.role}-1`;
232
235
  phrase +=
233
236
  n >= ERROR_STREAK_BREAKER
@@ -240,23 +243,34 @@ export function checkErrorStreaks(root, config) {
240
243
  });
241
244
  return warnOutcome(`failing run after run: ${phrases.join("; ")}`);
242
245
  }
243
- /** The `main red` check: names the code loops the red-main gate (mainRedGate) holds, ending
244
- * each blocked tick `main_red` while main's suite is red. Read-only: it asks heldByMainRed,
245
- * the predicate the status phase's `main red` label reads, so the two cannot disagree. A warn,
246
- * not a fail: a red main does not stop the team from running. */
247
- export function checkMainRed(root, config) {
246
+ /** The `main red` check: names the code roles the red-main gate (mainRedGate) holds, ending
247
+ * each blocked run `main_red` while main's suite is red, and warns when main has been red over
248
+ * RED_MAIN_WARN_MS even with no role held (the red-main gate's guardrail, G1). Read-only: it
249
+ * asks heldByMainRed, the predicate the status phase's `main red` label reads, so the two
250
+ * cannot disagree on the held roles, and redMainWarning over the event trail, so both phrases
251
+ * can appear in one row. A warn, not a fail: a red main does not stop the team from running.
252
+ * `now` is injected so a test can pin a red's age. */
253
+ export function checkMainRed(root, config, now = Date.now()) {
248
254
  if (config === undefined)
249
255
  return okOutcome("not checked — tumwater.json unreadable");
250
256
  const mainCheck = mainCheckForPoll(root, config);
251
- const ids = loopIds(config);
252
- const held = loadLoopStates(root, config)
257
+ const ids = runnerIds(config);
258
+ const held = loadRunnerStates(root, config)
253
259
  .filter((s) => heldByMainRed(s, mainCheck));
254
- if (held.length === 0)
260
+ const phrases = [];
261
+ if (held.length > 0) {
262
+ const err = held.find((s) => s.lastError)?.lastError;
263
+ const cause = err ? ` — ${squash(err, 160)}` : "";
264
+ const healer = ids.some((id) => baseRoleOf(id) === "ci")
265
+ ? "; the ci role is exempt and repairs it"
266
+ : "; no ci role is enabled to repair it — fix main by hand or enable ci";
267
+ phrases.push(`main's check is failing; ${plural(held.length, "code role")} waiting: ${held.map((s) => roleLabel(s.role)).join(", ")}${cause}${healer}`);
268
+ }
269
+ const events = readEventsSince(root, DAY_MS).events;
270
+ const red = redMainWarning(events, now);
271
+ if (red !== null)
272
+ phrases.push(red);
273
+ if (phrases.length === 0)
255
274
  return okOutcome("no role waiting on main's check");
256
- const err = held.find((s) => s.lastError)?.lastError;
257
- const cause = err ? ` — ${squash(err, 160)}` : "";
258
- const healer = ids.some((id) => baseRoleOf(id) === "bugfix")
259
- ? "; the bugfix role is exempt and repairs it"
260
- : "; no bugfix role is enabled to repair it — fix main by hand or enable bugfix";
261
- return warnOutcome(`main's check is failing; ${plural(held.length, "code role")} waiting: ${held.map((s) => roleLabel(s.role)).join(", ")}${cause}${healer}`);
275
+ return warnOutcome(phrases.join("; "));
262
276
  }
@@ -5,11 +5,12 @@ import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { installDrift } from "../build/dep-install.js";
7
7
  import { readEventsSince } from "../events/event-window.js";
8
+ import { DAY_MS } from "../text/datetime.js";
8
9
  import { plural, shortSpanPhrase } from "../text/phrases.js";
9
10
  import { errorMessage, textOr } from "../text/text.js";
10
11
  import { okOutcome, warnOutcome } from "./outcome.js";
11
12
  /** How far back a failed `dep_install` event may sit and still explain the drift. */
12
- const DEP_INSTALL_CHECK_WINDOW_MS = 24 * 3600_000;
13
+ const DEP_INSTALL_CHECK_WINDOW_MS = DAY_MS;
13
14
  export function checkRootInstall(root, now = Date.now()) {
14
15
  if (!fs.existsSync(path.join(root, "package-lock.json")))
15
16
  return okOutcome("no package-lock.json — nothing pinned");