tumwater 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +74 -108
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-commit.js +31 -0
  26. package/dist/src/backlog/backlog-eligibility.js +357 -33
  27. package/dist/src/backlog/backlog-md.js +98 -18
  28. package/dist/src/backlog/backlog-render.js +62 -11
  29. package/dist/src/backlog/backlog-structure.js +261 -58
  30. package/dist/src/backlog/backlog-write.js +197 -319
  31. package/dist/src/backlog/backlog.js +113 -16
  32. package/dist/src/backlog/question-answer.js +339 -0
  33. package/dist/src/baseline/main-baseline.js +75 -88
  34. package/dist/src/baseline/main-red.js +38 -30
  35. package/dist/src/brief/brief-edit.js +43 -0
  36. package/dist/src/{brief.js → brief/brief.js} +66 -10
  37. package/dist/src/budget/budget.js +32 -15
  38. package/dist/src/budget/fallback-breaker.js +8 -7
  39. package/dist/src/build/build-check-detect.js +8 -7
  40. package/dist/src/build/build-check-events.js +68 -22
  41. package/dist/src/build/build-check-report.js +22 -23
  42. package/dist/src/build/build-check-scoped.js +51 -62
  43. package/dist/src/build/build-check.js +15 -14
  44. package/dist/src/build/build-info.js +2 -1
  45. package/dist/src/build/build-stage.js +23 -22
  46. package/dist/src/build/dep-install.js +22 -21
  47. package/dist/src/build/host-sleep.js +1 -1
  48. package/dist/src/change/change-data.js +25 -14
  49. package/dist/src/change/change-render.js +11 -10
  50. package/dist/src/cli/brief-command.js +58 -0
  51. package/dist/src/cli/cli-args.js +25 -5
  52. package/dist/src/cli/cli-command-args.js +46 -14
  53. package/dist/src/cli/cli-editor.js +64 -0
  54. package/dist/src/cli/cli-flag-specs.js +54 -16
  55. package/dist/src/cli/cli-marker-commands.js +13 -10
  56. package/dist/src/cli/cli-output.js +9 -0
  57. package/dist/src/cli/cli-query-commands.js +458 -49
  58. package/dist/src/cli/cli-repo-gate.js +1 -1
  59. package/dist/src/cli/cli-run.js +208 -59
  60. package/dist/src/cli/completion.js +207 -0
  61. package/dist/src/cli/config-commands.js +14 -11
  62. package/dist/src/cli/help.js +305 -110
  63. package/dist/src/cli/log-commands.js +127 -21
  64. package/dist/src/cli/notify-command.js +33 -18
  65. package/dist/src/cli/question-commands.js +29 -224
  66. package/dist/src/cli.js +121 -40
  67. package/dist/src/collections.js +66 -16
  68. package/dist/src/concurrency/check-permit.js +16 -17
  69. package/dist/src/concurrency/lock.js +6 -5
  70. package/dist/src/concurrency/provider-lease.js +69 -0
  71. package/dist/src/concurrency/semaphore.js +258 -36
  72. package/dist/src/config/config-aliases.js +106 -0
  73. package/dist/src/config/config-editable-keys.js +3 -3
  74. package/dist/src/config/config-fallbacks.js +379 -0
  75. package/dist/src/config/config-field-checks.js +19 -10
  76. package/dist/src/config/config-keys.js +160 -0
  77. package/dist/src/config/config-live.js +64 -12
  78. package/dist/src/config/config-schema.js +34 -22
  79. package/dist/src/config/config-validation.js +132 -48
  80. package/dist/src/config/config-views.js +119 -337
  81. package/dist/src/config/config-write.js +138 -177
  82. package/dist/src/config/config.js +60 -49
  83. package/dist/src/config/model-selector.js +28 -2
  84. package/dist/src/doctor/doctor-backlog.js +546 -49
  85. package/dist/src/doctor/doctor-checks.js +534 -56
  86. package/dist/src/doctor/doctor-holds.js +193 -53
  87. package/dist/src/doctor/doctor-install.js +30 -0
  88. package/dist/src/doctor/doctor-launch-services.js +2 -2
  89. package/dist/src/doctor/doctor-model-checks.js +549 -157
  90. package/dist/src/doctor/doctor-notify.js +27 -0
  91. package/dist/src/doctor/doctor-orphans.js +10 -9
  92. package/dist/src/doctor/doctor-render.js +36 -9
  93. package/dist/src/doctor/doctor.js +80 -22
  94. package/dist/src/doctor/image-probe.js +244 -0
  95. package/dist/src/doctor/probe.js +64 -0
  96. package/dist/src/events/event-format.js +226 -102
  97. package/dist/src/events/event-labels.js +116 -0
  98. package/dist/src/events/event-legacy.js +140 -0
  99. package/dist/src/events/event-read.js +58 -22
  100. package/dist/src/events/event-window.js +27 -7
  101. package/dist/src/events/events.js +18 -14
  102. package/dist/src/events/notify.js +137 -38
  103. package/dist/src/failure/failure-cluster.js +12 -9
  104. package/dist/src/failure/failure-data.js +96 -48
  105. package/dist/src/failure/failure-render.js +69 -58
  106. package/dist/src/failure/failure-state-change.js +33 -26
  107. package/dist/src/failure/time-spend.js +79 -59
  108. package/dist/src/files/file-queue.js +16 -4
  109. package/dist/src/files/files.js +18 -6
  110. package/dist/src/files/json-object.js +18 -3
  111. package/dist/src/files/tail.js +1 -1
  112. package/dist/src/gates/bootstrap-gates.js +5 -4
  113. package/dist/src/gates/budget-gates.js +250 -34
  114. package/dist/src/gates/disk-gate.js +18 -18
  115. package/dist/src/gates/gate-polls.js +117 -69
  116. package/dist/src/gates/maintenance-quota.js +29 -29
  117. package/dist/src/gates/pause-gates.js +11 -11
  118. package/dist/src/gates/readiness.js +2 -1
  119. package/dist/src/gates/role-cap-gates.js +16 -21
  120. package/dist/src/gates/startup-gate.js +75 -31
  121. package/dist/src/gates/streak-gate.js +18 -16
  122. package/dist/src/git/commit-message.js +12 -5
  123. package/dist/src/git/git-diff.js +16 -16
  124. package/dist/src/git/git-run.js +8 -8
  125. package/dist/src/git/git.js +86 -55
  126. package/dist/src/git/slots-state.js +14 -5
  127. package/dist/src/git/worktree-pool.js +25 -18
  128. package/dist/src/git/worktree-use.js +15 -14
  129. package/dist/src/git/worktree.js +18 -18
  130. package/dist/src/gui/gui-args.js +10 -14
  131. package/dist/src/gui/gui-backlog-commands.js +273 -0
  132. package/dist/src/gui/gui-command.js +1 -1
  133. package/dist/src/gui/gui-config-commands.js +440 -0
  134. package/dist/src/gui/gui-endpoint-commands.js +16 -669
  135. package/dist/src/gui/gui-endpoint-common.js +49 -0
  136. package/dist/src/gui/gui-endpoints.js +116 -56
  137. package/dist/src/gui/gui-operator-commands.js +145 -0
  138. package/dist/src/gui/gui-prompt-commands.js +234 -0
  139. package/dist/src/gui/gui-server.js +28 -8
  140. package/dist/src/gui/gui-team-commands.js +164 -0
  141. package/dist/src/gui/http-body.js +17 -1
  142. package/dist/src/history/history-data.js +194 -98
  143. package/dist/src/history/history-render.js +76 -0
  144. package/dist/src/history/history.js +37 -58
  145. package/dist/src/inbox/inbox-attachments.js +75 -14
  146. package/dist/src/inbox/inbox-cancel.js +7 -5
  147. package/dist/src/inbox/inbox-edit.js +1 -1
  148. package/dist/src/inbox/inbox-submit.js +44 -4
  149. package/dist/src/inbox/inbox-top.js +83 -0
  150. package/dist/src/inbox/inbox.js +8 -2
  151. package/dist/src/inbox/pending-prompt.js +1 -1
  152. package/dist/src/inbox/prompt-commands.js +170 -80
  153. package/dist/src/init/init-templates.js +16 -14
  154. package/dist/src/init/init.js +12 -11
  155. package/dist/src/loop/leftover.js +28 -27
  156. package/dist/src/loop/loop-fallback.js +2 -2
  157. package/dist/src/loop/loop-pi.js +106 -52
  158. package/dist/src/loop/loop-recovery.js +15 -15
  159. package/dist/src/loop/loop-state.js +139 -13
  160. package/dist/src/loop/loop-tick.js +194 -86
  161. package/dist/src/loop/loop.js +72 -36
  162. package/dist/src/loop/model-fallback.js +3 -2
  163. package/dist/src/loop/revision.js +16 -7
  164. package/dist/src/merge/backlog-conflicts.js +155 -0
  165. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  166. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  167. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  168. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  169. package/dist/src/merge/merge-drain.js +212 -0
  170. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
  171. package/dist/src/merge/merge-pipeline.js +99 -0
  172. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  173. package/dist/src/merge/merge-queue-render.js +75 -0
  174. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  175. package/dist/src/merge/merge-slot.js +340 -0
  176. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  177. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  178. package/dist/src/merge/merge-verify.js +212 -0
  179. package/dist/src/notes-cli.js +97 -0
  180. package/dist/src/operator/operator-commands.js +154 -47
  181. package/dist/src/operator/operator-intent.js +87 -49
  182. package/dist/src/operator/operator-requests.js +52 -52
  183. package/dist/src/operator/retire.js +62 -27
  184. package/dist/src/operator/revert.js +71 -0
  185. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  186. package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
  187. package/dist/src/orchestrator/orchestrator.js +158 -100
  188. package/dist/src/orchestrator/retention.js +29 -12
  189. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  190. package/dist/src/orchestrator/startup-migrations.js +40 -0
  191. package/dist/src/paths.js +128 -37
  192. package/dist/src/pi/pi-args.js +26 -3
  193. package/dist/src/pi/pi-backend.js +15 -23
  194. package/dist/src/pi/pi-bin.js +18 -12
  195. package/dist/src/pi/pi-event-line.js +17 -1
  196. package/dist/src/pi/pi-failure.js +87 -0
  197. package/dist/src/pi/pi-models.js +126 -38
  198. package/dist/src/pi/pi-run-result.js +1 -1
  199. package/dist/src/pi/pi-settings.js +86 -0
  200. package/dist/src/pi/pi-stream.js +131 -107
  201. package/dist/src/pi/pi-watchdogs.js +47 -43
  202. package/dist/src/pi-extension/bounded-output.js +20 -5
  203. package/dist/src/pi-extension/context-budget.js +3 -1
  204. package/dist/src/pi-extension/context-shake.js +54 -21
  205. package/dist/src/pi-extension/context-window.js +37 -0
  206. package/dist/src/pi-extension/full-output.js +2 -2
  207. package/dist/src/pi-extension/strip-thinking.js +80 -0
  208. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  209. package/dist/src/process/launch-services.js +11 -10
  210. package/dist/src/process/process-table.js +1 -1
  211. package/dist/src/process/process.js +7 -7
  212. package/dist/src/process/run-marker.js +29 -6
  213. package/dist/src/process/supervisor.js +47 -25
  214. package/dist/src/prompt/dialect.js +30 -10
  215. package/dist/src/prompt/gate-prompts.js +170 -67
  216. package/dist/src/prompt/principles.js +1 -0
  217. package/dist/src/prompt/prompt-followup.js +35 -20
  218. package/dist/src/prompt/prompt.js +249 -91
  219. package/dist/src/redeploy/redeploy-policy.js +10 -10
  220. package/dist/src/redeploy/redeploy.js +6 -4
  221. package/dist/src/redeploy/redeployer.js +63 -55
  222. package/dist/src/redeploy/self-reload.js +1 -1
  223. package/dist/src/report/report-data.js +264 -255
  224. package/dist/src/report/report-fold.js +353 -0
  225. package/dist/src/report/report-render.js +270 -33
  226. package/dist/src/request-timeouts.js +1 -1
  227. package/dist/src/review/known-flakes.js +34 -14
  228. package/dist/src/review/protected-edits.js +33 -0
  229. package/dist/src/review/review-followup.js +16 -4
  230. package/dist/src/review/review-precheck.js +14 -13
  231. package/dist/src/review/review-verdict.js +14 -0
  232. package/dist/src/review/review.js +81 -39
  233. package/dist/src/review/secret-leaks.js +127 -0
  234. package/dist/src/review/suite-rerun.js +4 -3
  235. package/dist/src/roles/legacy-role-ids.js +21 -0
  236. package/dist/src/roles/legacy-role-migration.js +62 -0
  237. package/dist/src/roles/loop-ids.js +19 -2
  238. package/dist/src/roles/role-catalog.js +125 -135
  239. package/dist/src/roles/role-guidance.js +64 -31
  240. package/dist/src/roles/role-labels.js +10 -0
  241. package/dist/src/roles/role-render.js +194 -18
  242. package/dist/src/roles/role-view.js +216 -17
  243. package/dist/src/roles/roles.js +18 -15
  244. package/dist/src/scheduling/backoff.js +29 -19
  245. package/dist/src/scheduling/claims.js +25 -9
  246. package/dist/src/scheduling/once-round.js +11 -11
  247. package/dist/src/scheduling/quiet-hours.js +25 -32
  248. package/dist/src/scheduling/scheduling.js +43 -24
  249. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  250. package/dist/src/status/status-budget.js +115 -0
  251. package/dist/src/status/status-data.js +77 -184
  252. package/dist/src/status/status-inbox.js +47 -0
  253. package/dist/src/status/status-install.js +27 -0
  254. package/dist/src/status/status-polls.js +68 -33
  255. package/dist/src/status/status-roles.js +105 -0
  256. package/dist/src/{fleet → team}/error-storm.js +10 -10
  257. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  258. package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
  259. package/dist/src/{fleet → team}/reclaim.js +5 -4
  260. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  261. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  262. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
  263. package/dist/src/text/datetime.js +42 -2
  264. package/dist/src/text/format.js +10 -1
  265. package/dist/src/text/markdown.js +1 -1
  266. package/dist/src/text/phrases.js +35 -34
  267. package/dist/src/text/suggest.js +3 -2
  268. package/dist/src/text/text.js +2 -2
  269. package/dist/src/tick/qa-coverage.js +2 -1
  270. package/dist/src/tick/stage-check.js +147 -26
  271. package/dist/src/tick/telemetry-digest.js +10 -9
  272. package/dist/src/tick/tick-apply.js +101 -72
  273. package/dist/src/tick/tick-detail-data.js +32 -18
  274. package/dist/src/tick/tick-detail.js +62 -21
  275. package/dist/src/tick/tick-finalize.js +29 -22
  276. package/dist/src/tick/tick-outcome.js +2 -1
  277. package/dist/src/tick/tick-prompt.js +94 -26
  278. package/dist/src/tick/tick-resume.js +29 -13
  279. package/dist/src/tick/tick-stage.js +91 -47
  280. package/dist/src/tick/tick-timing.js +60 -59
  281. package/dist/src/tick/tick-usage.js +53 -23
  282. package/dist/src/tick/tick-verdict.js +76 -10
  283. package/dist/src/ui/badges.js +118 -67
  284. package/dist/src/ui/gui/gui-client-boot.js +209 -30
  285. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  286. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  287. package/dist/src/ui/gui/gui-client-history.js +96 -38
  288. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  289. package/dist/src/ui/gui/gui-client-loops.js +94 -52
  290. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  291. package/dist/src/ui/gui/gui-client-model.js +63 -43
  292. package/dist/src/ui/gui/gui-client-operator.js +129 -39
  293. package/dist/src/ui/gui/gui-client-pending.js +36 -32
  294. package/dist/src/ui/gui/gui-client-report.js +339 -46
  295. package/dist/src/ui/gui/gui-client-settings.js +148 -14
  296. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  297. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
  298. package/dist/src/ui/gui/gui-client.js +26 -20
  299. package/dist/src/ui/gui/gui-page.js +19 -19
  300. package/dist/src/ui/gui/gui-styles.js +12 -4
  301. package/dist/src/ui/progress-data.js +5 -3
  302. package/dist/src/ui/status-model.js +184 -122
  303. package/dist/src/ui/status-payload.js +119 -32
  304. package/dist/src/ui/status-render.js +122 -52
  305. package/dist/src/ui/team-alerts.js +341 -0
  306. package/dist/src/ui/tick-progress-model.js +14 -14
  307. package/dist/src/ui/tone.js +11 -8
  308. package/dist/src/ui/transcript-tail.js +1 -0
  309. package/dist/src/ui/transcript.js +23 -16
  310. package/dist/src/ui/tui/tui-app.js +1 -0
  311. package/dist/src/ui/tui/tui-backlog.js +85 -11
  312. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  313. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  314. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  315. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  316. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  317. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  318. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  319. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  320. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  321. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  322. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  323. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  324. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  325. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  326. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  327. package/dist/src/ui/tui/tui-editors.js +152 -0
  328. package/dist/src/ui/tui/tui-frame.js +67 -55
  329. package/dist/src/ui/tui/tui-history.js +12 -7
  330. package/dist/src/ui/tui/tui-input.js +49 -44
  331. package/dist/src/ui/tui/tui-keymap.js +1 -0
  332. package/dist/src/ui/tui/tui-keys.js +91 -1704
  333. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  334. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  335. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  336. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  337. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  338. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  339. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  340. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  341. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  342. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  343. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  344. package/dist/src/ui/tui/tui-pane.js +66 -28
  345. package/dist/src/ui/tui/tui-panes.js +294 -0
  346. package/dist/src/ui/tui/tui.js +45 -25
  347. package/dist/src/verdict/fix-claim.js +141 -38
  348. package/dist/src/verdict/no-change.js +19 -0
  349. package/dist/src/verdict/refusal.js +92 -8
  350. package/dist/src/verdict/reply-contract.js +17 -5
  351. package/dist/src/version.js +3 -2
  352. package/package.json +4 -2
  353. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  354. package/dist/src/landing/backlog-conflicts.js +0 -116
  355. package/dist/src/landing/landing-drain.js +0 -204
  356. package/dist/src/landing/landing-pipeline.js +0 -96
  357. package/dist/src/landing/landing-queue-render.js +0 -31
  358. package/dist/src/landing/landing-slot.js +0 -246
  359. package/dist/src/landing/landing-vetting.js +0 -196
  360. package/dist/src/pi-extension/role-notes.js +0 -40
  361. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -1,4 +1,4 @@
1
- /** The daily-cost budget: a loop's and the fleet's local-day spend and the gate those figures
1
+ /** The daily-cost budget: a loop's and the team's local-day spend and the gate those figures
2
2
  * drive (plans/daily-cost-budget.md, plans/fallback-model.md). The circuit breaker that judges
3
3
  * whether the gate's fallback can actually serve lives beside it in fallback-breaker.ts. Split
4
4
  * out of loop-state.ts, which owns
@@ -17,7 +17,7 @@ export function todayStamp(now = Date.now()) {
17
17
  }
18
18
  /** A spend figure is finite and non-negative (json-object.ts's nonNegativeNumber); a string,
19
19
  * NaN, ±Infinity, or a negative hand-edited value reads as $0. Negative spend is as poisonous
20
- * to the cap as NaN: it makes every `>= cap` comparison false — and drags the fleet sum down —
20
+ * to the cap as NaN: it makes every `>= cap` comparison false — and drags the team sum down —
21
21
  * silently defeating the budget's safety valve. */
22
22
  function spendNumber(value) {
23
23
  return nonNegativeNumber(value, 0);
@@ -28,7 +28,7 @@ function spendNumber(value) {
28
28
  * non-finite, or negative value reads as $0 too: `loadLoopState` heals the field on read, but
29
29
  * this is the budget gate's own guard, and a hand-edited or torn `dayCostUsd` (or an in-memory
30
30
  * state) would otherwise concatenate into
31
- * fleetDailyCost's sum (or make every `>= cap` comparison false) and silently defeat the cap,
31
+ * teamDailyCost's sum (or make every `>= cap` comparison false) and silently defeat the cap,
32
32
  * the budget's safety valve. Reads never mutate. See plans/daily-cost-budget.md. */
33
33
  export function dailyCost(s, now = Date.now()) {
34
34
  return s.dayStamp === todayStamp(now) ? spendNumber(s.dayCostUsd) : 0;
@@ -40,16 +40,33 @@ export function dailyCost(s, now = Date.now()) {
40
40
  * authoritative across an in-flight tick; the value persists at tick end with the rest of the
41
41
  * state. Both operands are guarded through spendNumber so a corrupt stored value or a
42
42
  * non-finite/negative run cost cannot poison the window with a string/NaN/negative sum. */
43
- export function recordDailyCost(s, usd, now = Date.now()) {
43
+ export function recordDailyCost(s, usd, now = Date.now(), pair) {
44
44
  const stamp = todayStamp(now);
45
45
  if (s.dayStamp !== stamp) {
46
46
  s.dayStamp = stamp;
47
47
  s.dayCostUsd = 0;
48
+ s.dayCostByPair = {};
48
49
  }
49
50
  s.dayCostUsd = spendNumber(s.dayCostUsd) + spendNumber(usd);
51
+ // The per-step split (a priced fallback step's own cap): folded under the run's pair name,
52
+ // an own property so a `__proto__` pair cannot reach the prototype.
53
+ if (pair !== undefined) {
54
+ const by = (s.dayCostByPair ??= {});
55
+ const sum = spendNumber(Object.hasOwn(by, pair) ? by[pair] : 0) + spendNumber(usd);
56
+ Object.defineProperty(by, pair, { value: sum, enumerable: true, writable: true, configurable: true });
57
+ }
58
+ }
59
+ /** One model pair's spend for the local day, summed over every loop's per-pair window
60
+ * (`dayCostByPair`): $0 for a stale day stamp or a pair no run has charged. */
61
+ export function pairDailyCost(states, pair, now = Date.now()) {
62
+ const today = todayStamp(now);
63
+ return states.reduce((sum, s) => {
64
+ const by = s.dayStamp === today ? s.dayCostByPair : undefined;
65
+ return sum + (by !== undefined && Object.hasOwn(by, pair) ? spendNumber(by[pair]) : 0);
66
+ }, 0);
50
67
  }
51
- /** The fleet's spend for the local day: every loop's daily window summed. */
52
- export function fleetDailyCost(states, now = Date.now()) {
68
+ /** The team's spend for the local day: every loop's daily window summed. */
69
+ export function teamDailyCost(states, now = Date.now()) {
53
70
  return states.reduce((sum, s) => sum + dailyCost(s, now), 0);
54
71
  }
55
72
  /** True when today's spend has reached the daily cost budget — the view is non-null, its cap is
@@ -57,23 +74,23 @@ export function fleetDailyCost(states, now = Date.now()) {
57
74
  * above it. The single definition of "the budget gate is on": the orchestrator evaluates it from
58
75
  * live loop states via budgetPaused, and both dashboards evaluate it from the snapshot's
59
76
  * materialized budget field (status/status-data.ts) — which carries the object even while
60
- * disabled, so the capUsd > 0 term is what keeps a disabled fleet out of "budget paused". The
77
+ * disabled, so the capUsd > 0 term is what keeps a disabled team out of "budget paused". The
61
78
  * comparison cannot drift between what the scheduler enforces and what users see. */
62
79
  export function budgetReached(budget) {
63
80
  return budget !== null && budget.capUsd > 0 && budget.spentUsd >= budget.capUsd;
64
81
  }
65
- /** The fixed fraction of the daily cap at which the fleet pages the operator that spend is
82
+ /** The fixed fraction of the daily cap at which the team pages the operator that spend is
66
83
  * closing in on it (PLANS.md 2026-09-30): opinionated default, no config knob. */
67
84
  export const BUDGET_WARNING_FRACTION = 0.8;
68
85
  /** True when today's spend has crossed `BUDGET_WARNING_FRACTION` of the daily cap while the
69
86
  * budget gate is still open — the early heads-up beside budgetReached, which fires only once
70
- * the cap itself is reached and the fleet has already stopped (or demoted) its ticks. Same
87
+ * the cap itself is reached and the team has already stopped (or demoted) its ticks. Same
71
88
  * guard terms as budgetReached (non-null view, a cap of 0 disables the budget), scaled by the
72
89
  * warning fraction, so the two views can never disagree about whether the budget is on. */
73
90
  export function budgetWarning(budget) {
74
91
  return (budget !== null && budget.capUsd > 0 && budget.spentUsd >= budget.capUsd * BUDGET_WARNING_FRACTION);
75
92
  }
76
- /** True while the fleet's spend for the local day has reached `maxDailyCostUsd` (a cap of 0
93
+ /** True while the team's spend for the local day has reached `maxDailyCostUsd` (a cap of 0
77
94
  * disables the budget). The orchestrator re-evaluates this every poll from its runners' live
78
95
  * states and the freshly reloaded config — resume is stateless, so raising/disabling the cap
79
96
  * or crossing midnight flips it on the next cycle and nothing can get stuck. Lives here (not
@@ -81,13 +98,13 @@ export function budgetWarning(budget) {
81
98
  export function budgetPaused(states, config, now = Date.now()) {
82
99
  return budgetReached(budgetSpend(states, config, now));
83
100
  }
84
- /** The fleet's daily-budget figures from live loop states — the exact pair the gate evaluates:
101
+ /** The team's daily-budget figures from live loop states — the exact pair the gate evaluates:
85
102
  * today's summed spend and the configured cap (0 when disabled). budgetReached's own capUsd > 0
86
103
  * term makes a disabled cap read unreached, so the pair needs no null ceremony. Returning the
87
104
  * figures (not just the verdict) lets the scheduler publish WHAT it tripped on, not only THAT it
88
105
  * tripped — the one number every observer must show. */
89
106
  export function budgetSpend(states, config, now = Date.now()) {
90
- return { spentUsd: fleetDailyCost(states, now), capUsd: config.maxDailyCostUsd };
107
+ return { spentUsd: teamDailyCost(states, now), capUsd: config.maxDailyCostUsd };
91
108
  }
92
109
  /** When today's burn will reach the cap, as a local-day epoch-ms instant — the budget badge's
93
110
  * `~cap at HH:MM` forecast — or null when no forecast may be stated: no cap (`capUsd <= 0`, the
@@ -123,8 +140,8 @@ export function modelPairName(p) {
123
140
  * (budgetReached for observers, budgetPaused for the scheduler), and whether the `default` and
124
141
  * `strong` tiers resolved to a usable free pair (resolveTierFallbacks over the full usable
125
142
  * predicate — price AND breaker serving) — plus whether the review gate is on. The gate is
126
- * `paused` when `default` resolves to pause (nothing for the fleet's bulk to run on), or when
127
- * review is on and `strong` does (nothing could land, so every authoring tick is wasted work);
143
+ * `paused` when `default` resolves to pause (nothing for the team's bulk to run on), or when
144
+ * review is on and `strong` does (nothing could merge, so every authoring tick is wasted work);
128
145
  * `fallback` otherwise, with the tiers that did resolve running their pairs and the rest held
129
146
  * per role. The single definition of the gate: the orchestrator enforces it and both dashboards
130
147
  * display it, so what an operator sees is what the scheduler is doing. */
@@ -140,7 +157,7 @@ export function budgetGate(reached, tiers, reviewOn) {
140
157
  /** The configured fallback tier map as display entries: one `tier: pair` per set tier, in
141
158
  * MODEL_TIERS order (small, default, strong). The single home of the tier-list rendering
142
159
  * shared by the budget badge (src/ui/badges.ts), the fallback alert's detail
143
- * (src/ui/fleet-alerts.ts), and the budget_fallback event line
160
+ * (src/ui/team-alerts.ts), and the budget_fallback event line
144
161
  * (src/events/event-format.ts), so the pair shape and the tier order cannot drift between
145
162
  * the three surfaces; each caller joins or length-tests the entries itself. A non-string
146
163
  * value is not a pair (the event reads a loosely typed field) and is left out; absent or
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  /** The fallback's circuit breaker (BUGS.md 2026-09-20): whether the engaged free fallback can
2
3
  * actually SERVE. Split out of budget.ts, which keeps the daily-cost figures and the gate they
3
4
  * drive; the breaker is a self-contained pure state machine — no scheduler, no pi model
@@ -5,13 +6,13 @@
5
6
  * orchestrator can hold it in memory and fold tick outcomes into it without importing the
6
7
  * budget. The gate reads its verdict through fallbackServing (budget.ts's budgetGate). */
7
8
  /** failureLimit 3 is a single loop's failing-streak threshold (tick-apply.ts ERROR_STREAK_WARN)
8
- * applied fleet-wide: consecutive across every role, so any tick that served in between resets
9
+ * applied team-wide: consecutive across every role, so any tick that served in between resets
9
10
  * it — a healthy backend with one flaky tick never trips it, while a dead one trips on its
10
11
  * first three ticks (on 2026-09-19 the third failure landed 24 minutes into the hour; the 30
11
12
  * ticks after it were the waste). A 5-minute first cool-down because a failed probe costs one
12
13
  * tick — about half of that hour's failures came back in under 10 s, the rest ran minutes
13
14
  * before the 400 — which is cheap next to idling until midnight; doubling to at most 30 minutes
14
- * bounds how long an operator who fixed the backend waits for the fleet to notice (editing the
15
+ * bounds how long an operator who fixed the backend waits for the team to notice (editing the
15
16
  * cap or the pair re-trusts it at once — see rekeyFallbackBreaker). */
16
17
  export const FALLBACK_BREAKER_POLICY = {
17
18
  failureLimit: 3,
@@ -64,14 +65,14 @@ export function abandonFallbackProbe(b) {
64
65
  * alone: every way a backend cannot serve — an HTTP 4xx/5xx like oMLX's prefill guard, a
65
66
  * refused connection, a request that never returns (the tick timeout) — ends a tick as `error`,
66
67
  * and 33 of 33 did on 2026-09-19. (An `error` can also be local, e.g. a worktree that will not
67
- * reset; failureLimit of those in a row fleet-wide is still a fleet that cannot work, and the
68
+ * reset; failureLimit of those in a row team-wide is still a team that cannot work, and the
68
69
  * probe wins the pause back.) `served` is a result only a model reply produces: a change bound
69
- * for landing (`queued`, `changed`), an explicit decision (`no_change`, `refused`), or a
70
+ * for merge (`queued`, `changed`), an explicit decision (`no_change`, `refused`), or a
70
71
  * reviewer's rejection. Everything else is no evidence: harness and operator kills (`aborted`,
71
72
  * `user_aborted`), ticks that never reached the model (`skipped`, `main_red`), a quiet kill
72
73
  * (the model had answered before the stall — a tool call or the stream stopped), and the
73
- * landing results (`merge_conflict`, `merge_blocked`, `review_error`) — a tick no longer
74
- * returns them since leftover recovery queues its pin instead of landing it, and they would
74
+ * merge results (`merge_conflict`, `merge_blocked`, `review_error`) — a tick no longer
75
+ * returns them since leftover recovery queues its pin instead of merging it, and they would
75
76
  * not say whether the backend was involved. */
76
77
  export function fallbackEvidence(result) {
77
78
  switch (result) {
@@ -114,7 +115,7 @@ export function recordFallbackTick(b, ranOn, result, probe, now, policy = FALLBA
114
115
  return { ...b, failures, probeAt: now + policy.cooldownMs, cooldownMs: policy.cooldownMs, probing: false };
115
116
  }
116
117
  /** What orchestrator.json publishes while the breaker holds the fallback demoted
117
- * (fleet/orchestrator-info.ts's OrchestratorInfo.fallbackDemoted). Observers need it because the
118
+ * (team/orchestrator-info.ts's OrchestratorInfo.fallbackDemoted). Observers need it because the
118
119
  * price check alone would advertise the demoted pair as a working fallback —
119
120
  * status/status-data.ts nulls the snapshot's `fallback` from it and `tumwater doctor` names it.
120
121
  * Undefined while the fallback is trusted or not engaged. */
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { isJsonObject } from "../files/json-object.js";
@@ -79,7 +80,7 @@ function isCheckScript(s, key) {
79
80
  export function detectBuildCheck(startDir, config, maxLevels = WALK_UP_LEVELS) {
80
81
  const c = config?.check;
81
82
  // check.timeoutSeconds bounds the npm walk-up's check too: it is how a repo whose suite
82
- // outgrows 300 s under load says so (the fleet's own gate runs were timing out there).
83
+ // outgrows 300 s under load says so (the team's own gate runs were timing out there).
83
84
  const configuredMs = typeof c?.timeoutSeconds === "number" && c.timeoutSeconds > 0 ? c.timeoutSeconds * 1000 : undefined;
84
85
  if (c && isNonBlankString(c.command)) {
85
86
  return {
@@ -95,10 +96,10 @@ export function detectBuildCheck(startDir, config, maxLevels = WALK_UP_LEVELS) {
95
96
  }
96
97
  /** The configured per-change gate command (`check.gateCommand`, PLANS.md Land-queue speed 3e) when
97
98
  * it is a non-blank string, else undefined — off. Opt-in: a repo that sets it trades a cheaper
98
- * review-gate pre-check for a full check.command run that happens once per stack at the
99
- * batch/landing scope instead. One reading shared by runScopedBuildCheck (which runs it at scope
100
- * "gate") and landing-merge.ts's verifyLanding (which must then never treat the gate's green as the
101
- * full check's), so the two cannot disagree about whether it is on. */
99
+ * review-gate pre-check for a full check.command run that happens once per stack at the batch/merge
100
+ * scope instead. One reading shared by runScopedBuildCheck (which runs it at scope "gate") and
101
+ * merge-to-main.ts's verifyMerge (which must then never treat the gate's green as the full
102
+ * check's), so the two cannot disagree about whether it is on. */
102
103
  export function gateCommandOf(config) {
103
104
  const g = config?.check?.gateCommand;
104
105
  return isNonBlankString(g) ? g : undefined;
@@ -108,9 +109,9 @@ export function gateCommandOf(config) {
108
109
  * of its own (node_modules is gitignored, so it exists only where someone ran npm install), and
109
110
  * neither does a project nested under an installed root. Returns the first existing path, or
110
111
  * null when no ancestor within `maxLevels` has it. Assuming a local install instead is what
111
- * broke the fleet's own redeploy on 2026-09-08: compileStaged looked only at
112
+ * broke the team's own redeploy on 2026-09-08: compileStaged looked only at
112
113
  * `<root>/node_modules/typescript`, so its test could not pass in a bare worktree, the failing
113
- * suite made main read red, and the blocked restart stranded the fleet on a stale build
114
+ * suite made main read red, and the blocked restart stranded the team on a stale build
114
115
  * (BUGS.md). Never throws. */
115
116
  export function resolveFromNodeModules(startDir, rel, maxLevels = WALK_UP_LEVELS) {
116
117
  return walkUp(startDir, maxLevels, (dir) => {
@@ -1,3 +1,4 @@
1
+ // vocabulary: plain
1
2
  /** The presentation half of the build check: the scope vocabulary, the environmental-skip
2
3
  * warning's wording, and the build_check event's shape. Split out of build-check.ts — which
3
4
  * keeps the deterministic execution and classification (detect → spawn → classify) — because
@@ -7,6 +8,30 @@
7
8
  * running a check. This module imports only types from build-check.js, so no runtime cycle.
8
9
  * runScopedBuildCheck (build-check-scoped.ts) is the only caller that both runs a check and
9
10
  * uses this wording; the baseline surfaces use the event/warning helpers alone. */
11
+ /** Does this build_check scope stand for a full-suite verdict — merge, batch, or the red-main
12
+ * baseline? A gate check can run a subset of the tree, so a scope outside this set is never a
13
+ * full-suite check. The one vocabulary the three predicates that recognize a full-suite check
14
+ * select on — isPassingFullCheck, isRedFullCheck, and the status polls' newest-merge-check
15
+ * scan (status-polls.ts) — so they cannot drift on which scopes count. The deadline-killed
16
+ * failure costing in time-spend.ts is a narrower pair (merge/batch only, with a status and a
17
+ * timeout required), so it keeps its own check. */
18
+ export function isFullCheckScope(scope) {
19
+ return scope === "merge" || scope === "batch" || scope === "baseline";
20
+ }
21
+ /** Is this a passing build_check over the full suite? The scopes doctor's check row counts —
22
+ * merge, batch and the red-main baseline — are isFullCheckScope's set; a gate check can run a
23
+ * subset, so it is left out. */
24
+ export function isPassingFullCheck(ev) {
25
+ return ev.type === "build_check" && ev.status === "passed" && isFullCheckScope(ev.scope);
26
+ }
27
+ /** The red sibling of isPassingFullCheck: a failed build_check over the same three scopes
28
+ * (isFullCheckScope's set). A `skipped` check made no verdict about the tree, and a merge-scope
29
+ * timeout is already remapped to `failed` by runScopedBuildCheck (`MERGE_SCOPES`), so `failed`
30
+ * alone is the rule. The two predicates are mutually exclusive on `status`, so a fold branch on
31
+ * them never sees the other's events. */
32
+ export function isRedFullCheck(ev) {
33
+ return ev.type === "build_check" && ev.status === "failed" && isFullCheckScope(ev.scope);
34
+ }
10
35
  /** How late a check's deadline timer may fire before the timeout's wording stops naming the
11
36
  * configured bound alone. Normal timer lag is milliseconds; past this the warning and the
12
37
  * merge-scope reason name the wall-clock time the deadline actually fired and how late (see
@@ -25,7 +50,7 @@ export const SLEEP_SPAN_TOLERANCE_MS = 5_000;
25
50
  * says nothing about the tree: a skip from a signal the harness did not send, a timeout whose
26
51
  * deadline demonstrably fired late, and a failure the host slept through. The one rule for every
27
52
  * scope — runScopedBuildCheck and the red-main baseline (checkMainBaseline) both ask it. */
28
- export function owesCleanRetry(first) {
53
+ function owesCleanRetry(first) {
29
54
  if (first.status === "failed")
30
55
  return (first.run?.sleptMs ?? 0) > SLEEP_SPAN_TOLERANCE_MS;
31
56
  if (first.status !== "skipped")
@@ -33,22 +58,43 @@ export function owesCleanRetry(first) {
33
58
  return (first.skipReason === "killed" ||
34
59
  (first.skipReason === "timeout" && (first.run?.deadlineLateMs ?? 0) > DEADLINE_LATE_TOLERANCE_MS));
35
60
  }
61
+ /** Run one build-check attempt, priced, and give a verdict-less first attempt one clean retry
62
+ * — the timing-and-counting discipline shared by runScopedBuildCheck (build-check-scoped.ts)
63
+ * and the red-main baseline (checkMainBaseline, main-baseline.ts), so the two surfaces cannot
64
+ * drift on how long one check "took" or on how many attempts a weather-killed run owes
65
+ * (BUGS.md 2026-09-30). `run` starts one attempt; the returned `durationMs` prices the attempt
66
+ * whose verdict stands — the first, unless owesCleanRetry, in which case the retry's. `onFirst`
67
+ * prices the verdict-less first attempt the way the caller's surface reports runs
68
+ * (runScopedBuildCheck logs a build_check event; checkMainBaseline feeds its onRun hook), so a
69
+ * retried attempt's cost is still recorded even though its verdict is discarded.` */
70
+ export async function runWithCleanRetry(run, onFirst) {
71
+ const startedAt = Date.now();
72
+ const first = await run();
73
+ let durationMs = Date.now() - startedAt;
74
+ if (!owesCleanRetry(first))
75
+ return { outcome: first, durationMs };
76
+ onFirst?.(first, durationMs);
77
+ const retryStart = Date.now();
78
+ const retry = await run();
79
+ durationMs = Date.now() - retryStart;
80
+ return { outcome: retry, durationMs };
81
+ }
36
82
  /** Per-scope wording for the environmental-skip warning. The call sites' current messages
37
83
  * are identical apart from these words, so keying them on the scope keeps each surface's feed
38
84
  * line byte-for-byte what it is today. */
39
85
  export const SCOPE_WORDS = {
40
- gate: { label: "build check", proceeding: "proceeding to model review" },
41
- landing: { label: "landing build check", proceeding: "proceeding to merge" },
42
- // The batch's next step after the check is the fast-forward — the same phrase the landing
43
- // scope uses (gate says "proceeding to model review" because its next step is the reviewer).
44
- batch: { label: "batch build check", proceeding: "proceeding to merge" },
86
+ gate: { label: "check", proceeding: "proceeding to model review" },
87
+ merge: { label: "merge check", proceeding: "proceeding to merge" },
88
+ // The batch's next step after the check is the fast-forward — the same phrase the merge scope
89
+ // uses (gate says "proceeding to model review" because its next step is the reviewer).
90
+ batch: { label: "batch check", proceeding: "proceeding to merge" },
45
91
  };
46
- /** Scopes whose outcome gates a merge to main: runScopedBuildCheck remaps a timeout or an
47
- * external signal kill at these scopes to a deterministic "failed" — the tree is unverified,
48
- * and these are the last checks before main. The gate scope's pre-check stays fail-open
49
- * because the model reviewer and the landing path's own check still stand behind it
50
- * (BUGS.md: a landing build check that times out must not merge unverified). */
51
- export const MERGE_SCOPES = new Set(["landing", "batch"]);
92
+ /** Scopes whose outcome gates a merge to main: runScopedBuildCheck remaps a timeout or an external
93
+ * signal kill at these scopes to a deterministic "failed" — the tree is unverified, and these are
94
+ * the last checks before main. The gate scope's pre-check stays fail-open because the model
95
+ * reviewer and the merge path's own check still stand behind it (BUGS.md: a merge build check that
96
+ * times out must not merge unverified). */
97
+ export const MERGE_SCOPES = new Set(["merge", "batch"]);
52
98
  /** The proceeding reason a merge-scope check that made no verdict about the tree carries: the
53
99
  * run was killed, timed out, or spanned a host sleep, so nothing it saw is the change's
54
100
  * failure. The single home of that wording, shared by runScopedBuildCheck's four merge-scope
@@ -79,7 +125,7 @@ export function timedOutPhrase(timeoutMs, run) {
79
125
  // swap-thrashing host, a harness stall), so it is reported as such rather than as a sleep.
80
126
  const cause = (run?.sleptMs ?? 0) > SLEEP_SPAN_TOLERANCE_MS
81
127
  ? "the host was asleep"
82
- : "no host sleep measured: the host was starved or the harness stalled";
128
+ : "no host sleep measured: the host was starved or tumwater stalled";
83
129
  return (`timed out after ${oneDecimalSeconds(bound + late)}s (its ${bound / 1000}s deadline fired ` +
84
130
  `${oneDecimalSeconds(late)}s late: ${cause})`);
85
131
  }
@@ -89,7 +135,7 @@ const STARVED_DEADLINE_MAX_FACTOR = 2;
89
135
  /** The deadline a merge-scope check runs under: `baseMs` stretched by the host's load-to-cores
90
136
  * ratio (at most STARVED_DEADLINE_MAX_FACTOR) when the load average exceeds the core count,
91
137
  * else `baseMs` itself. 46 of 50 merge-scope rejections in one window were the 300 s deadline
92
- * firing on time under the fleet's own load, with a passing tail at 299.97 s — the check was
138
+ * firing on time under the team's own load, with a passing tail at 299.97 s — the check was
93
139
  * measuring the load, not the tree (BUGS.md 2026-10-12). */
94
140
  export function starvedDeadlineMs(baseMs, load) {
95
141
  if (!(load.cpus > 0) || !(load.load1 > load.cpus))
@@ -97,7 +143,7 @@ export function starvedDeadlineMs(baseMs, load) {
97
143
  return Math.round(baseMs * Math.min(load.load1 / load.cpus, STARVED_DEADLINE_MAX_FACTOR));
98
144
  }
99
145
  /** The note an on-time deadline kill carries when the host was starved: the load average
100
- * beyond the core count means the check shared the machine with the rest of the fleet, so the
146
+ * beyond the core count means the check shared the machine with the rest of the team, so the
101
147
  * 300 s kill is a statement about the deadline under load, not a measured verdict on the
102
148
  * change (BUGS.md 2026-10-12). Empty when the host had a core to spare. */
103
149
  export function loadedHostPhrase(load) {
@@ -136,7 +182,7 @@ export function installFailedPhrase(label, install, proceeding) {
136
182
  /** Did this outcome make no verdict about the tree? runScopedBuildCheck records that verdict-
137
183
  * less shape explicitly (`unverified: true`) at the merge scopes; a gate-scope failure carries
138
184
  * only the run's own sleep evidence (BuildCheckRun.sleptMs), so both spellings count. The
139
- * consumers that attribute a red — review.ts's gate, landing-check-failures.ts's attribution, the
185
+ * consumers that attribute a red — review.ts's gate, merge-check-failures.ts's attribution, the
140
186
  * red-main baseline — ask this instead of re-deriving the evidence, so the attribution policy
141
187
  * cannot drift between them (BUGS.md 2026-09-30). */
142
188
  export function unverifiedTreeOutcome(outcome) {
@@ -184,15 +230,15 @@ function buildCheckRunFields(outcome) {
184
230
  ...(run.sleptMs === undefined ? {} : { sleptMs: run.sleptMs }),
185
231
  };
186
232
  }
187
- /** The one home of the build_check event's shape — `{ loop, type: "build_check", scope,
233
+ /** The one home of the build_check event's shape — `{ source, type: "build_check", scope,
188
234
  * status, script, durationMs }` plus the run-timing fields (buildCheckRunFields above) —
189
235
  * so the feed's most expensive event type cannot drift a field between its four surfaces:
190
236
  * runScopedBuildCheck's two priced events in build-check-scoped.ts (the killed retry and the
191
237
  * final verdict) and the red-main baseline loggers (main-red.ts's baselineCheckLogger,
192
238
  * redeploy.ts's redeploy mirror), which all paid the run whose timings the event carries. */
193
- export function buildCheckEvent(loop, scope, outcome, durationMs) {
239
+ export function buildCheckEvent(source, scope, outcome, durationMs) {
194
240
  return {
195
- loop,
241
+ source,
196
242
  type: "build_check",
197
243
  scope,
198
244
  status: outcome.status,
@@ -213,11 +259,11 @@ export function buildCheckEvent(loop, scope, outcome, durationMs) {
213
259
  : {}),
214
260
  };
215
261
  }
216
- /** The onRun hook that logs one baseline check's build_check event through `log` under `loop`:
262
+ /** The onRun hook that logs one baseline check's build_check event through `log` under `source`:
217
263
  * main-red.ts's gate logger (logEventBestEffort) and redeploy.ts's green-check and witness hooks
218
264
  * all turn the same run shape into the same baseline event, so that mapping lives here once
219
265
  * instead of as three hand-rolled closures. `log` receives the event alone; a caller binds its
220
266
  * root (logEventBestEffort) or its RedeployEvent sink. */
221
- export function baselineCheckEventLogger(loop, log) {
222
- return ({ outcome, durationMs }) => log(buildCheckEvent(loop, "baseline", outcome, durationMs));
267
+ export function baselineCheckEventLogger(source, log) {
268
+ return ({ outcome, durationMs }) => log(buildCheckEvent(source, "baseline", outcome, durationMs));
223
269
  }
@@ -1,15 +1,15 @@
1
- /** Pure formatting and description of a build check's failure output — split out of
2
- * build-check.ts, which keeps the execution side (detect → run → classify, the toolchain
3
- * probe, the process-group runner). This module turns a check's raw combined output and a
4
- * BuildCheckOutcome into the machine text that is persisted and shown: the per-line clip cap
5
- * (clipReason/MAX_REASON_CHARS, shared with review-verdict.ts's parseVerdict), the clipped
6
- * output tail (clipBuildTail), the one-line headline a warning names the failure by
7
- * (failureHeadline), the check's human-facing command name (describeCheck), and the reasons a
8
- * red check hands its author (checkFailureReasons). Every consumer of a check's tail — the
9
- * review gate, the landing path, the red-main gate, the tick prompt — imports from here, so
10
- * "which line is the headline" and "how much output survives" have one answer each. Type-only
11
- * back-reference to build-check.ts for BuildCheckOutcome (no runtime cycle); BuildCheck comes
12
- * from build-check-detect.ts. */
1
+ // vocabulary: plain
2
+ /** Pure formatting and description of a build check's failure output — split out of build-check.ts,
3
+ * which keeps the execution side (detect → run → classify, the toolchain probe, the process-group
4
+ * runner). This module turns a check's raw combined output and a BuildCheckOutcome into the machine
5
+ * text that is persisted and shown: the per-line clip cap (clipReason/MAX_REASON_CHARS, shared with
6
+ * review-verdict.ts's parseVerdict), the clipped output tail (clipBuildTail), the one-line headline
7
+ * a warning names the failure by (failureHeadline), the check's human-facing command name
8
+ * (describeCheck), and the reasons a red check hands its author (checkFailureReasons). Every
9
+ * consumer of a check's tail — the review gate, the merge path, the red-main gate, the tick prompt
10
+ * — imports from here, so "which line is the headline" and "how much output survives" have one
11
+ * answer each. Type-only back-reference to build-check.ts for BuildCheckOutcome (no runtime cycle);
12
+ * BuildCheck comes from build-check-detect.ts. */
13
13
  import { truncate } from "../text/text.js";
14
14
  /** Per-reason length cap with ellipsis — bounds one line of machine-generated or reviewer
15
15
  * text so it cannot bloat persisted state (shared by clipBuildTail here and parseVerdict in
@@ -52,7 +52,7 @@ export function clipBuildTail(output) {
52
52
  * window when it would otherwise be cut), so a check that died on an unhandled rejection ends
53
53
  * mid-stack and the tail's FIRST line is a frame: every red-main warning logged before
54
54
  * 2026-09-18 read "main <sha> is red (test: at process.processTicksAndRejections
55
- * (node:internal/...))" — where, never what, which is why a false red that blocked the fleet
55
+ * (node:internal/...))" — where, never what, which is why a false red that blocked the team
56
56
  * for hours could not be diagnosed from the event feed at all (BUGS.md). Prefer the first line
57
57
  * that is not framing — a stack frame, or node:test's summary/framing lines (below) — and fall
58
58
  * back to the tail's first line when every line is framing, so a caller always has something
@@ -95,13 +95,12 @@ export function failureHeadline(tail) {
95
95
  export function describeCheck(check) {
96
96
  return check.kind === "npm" ? `\`npm run ${check.script}\`` : `\`${check.command}\``;
97
97
  }
98
- /** The machine-generated reasons a red check hands its author: the headline joined to the rest
99
- * of the clipped tail, so the compiler error sits right after it in the injected next-tick note.
100
- * The headline is failureHeadline's — the first line that is not a stack frame — not
101
- * outputTail[0]: a suite that dies on an unhandled rejection opens mid-stack, and naming the
102
- * frame tells the author where it broke, never what (BUGS.md 2026-09-19). One definition for
103
- * every red that rejects a change: the gate's pre-check, a batch's single-change red, and a
104
- * single landing's in-lock check. */
98
+ /** The machine-generated reasons a red check hands its author: the headline joined to the rest of
99
+ * the clipped tail, so the compiler error sits right after it in the injected next-tick note. The
100
+ * headline is failureHeadline's — the first line that is not a stack frame — not outputTail[0]: a
101
+ * suite that dies on an unhandled rejection opens mid-stack, and naming the frame tells the author
102
+ * where it broke, never what (BUGS.md 2026-09-19). One definition for every red that rejects a
103
+ * change: the gate's pre-check, a batch's single-change red, and a single merge's in-lock check. */
105
104
  export function checkFailureReasons(check, outcome) {
106
105
  const tail = outcome.outputTail ?? [];
107
106
  // An unverified rejection already says what happened — the check never finished, the tree is
@@ -110,11 +109,11 @@ export function checkFailureReasons(check, outcome) {
110
109
  // failure that never happened (BUGS.md 2026-09-28). The empty-tail fallback keeps the safe
111
110
  // default should a remap ever lose its reason line.
112
111
  if (outcome.unverified) {
113
- return tail.length > 0 ? tail : [`build check failed (${describeCheck(check)})`];
112
+ return tail.length > 0 ? tail : [`check failed (${describeCheck(check)})`];
114
113
  }
115
114
  const headline = failureHeadline(tail);
116
115
  const what = describeCheck(check);
117
116
  return headline !== undefined
118
- ? [`build check failed (${what}): ${headline}`, ...tail.filter((l) => l !== headline)]
119
- : [`build check failed (${what})`];
117
+ ? [`check failed (${what}): ${headline}`, ...tail.filter((l) => l !== headline)]
118
+ : [`check failed (${what})`];
120
119
  }