tumwater 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/README.md +27 -13
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +8 -9
  4. package/dist/src/agent/agent-backend.js +8 -6
  5. package/dist/src/agent/backend-traits.js +42 -25
  6. package/dist/src/agent/claude/claude-account.js +71 -204
  7. package/dist/src/agent/claude/claude-args.js +9 -6
  8. package/dist/src/agent/claude/claude-backend.js +10 -10
  9. package/dist/src/agent/claude/claude-env.js +1 -1
  10. package/dist/src/agent/claude/claude-stream.js +4 -3
  11. package/dist/src/agent/claude/claude-write-guard.js +49 -0
  12. package/dist/src/agent/claude/write-guard-hook.js +13 -0
  13. package/dist/src/agent/codex/codex-account.js +119 -0
  14. package/dist/src/agent/codex/codex-args.js +181 -0
  15. package/dist/src/agent/codex/codex-backend.js +279 -0
  16. package/dist/src/agent/codex/codex-catalog.js +97 -0
  17. package/dist/src/agent/codex/codex-env.js +18 -0
  18. package/dist/src/agent/codex/codex-hook-trust.js +246 -0
  19. package/dist/src/agent/codex/codex-patch-paths.js +36 -0
  20. package/dist/src/agent/codex/codex-rollout.js +179 -0
  21. package/dist/src/agent/codex/codex-stream.js +402 -0
  22. package/dist/src/agent/codex/codex-write-guard.js +86 -0
  23. package/dist/src/agent/codex/write-guard-hook.js +12 -0
  24. package/dist/src/agent/image-input.js +29 -24
  25. package/dist/src/agent/notes-shim.js +11 -12
  26. package/dist/src/agent/opencode/opencode-args.js +0 -1
  27. package/dist/src/agent/opencode/opencode-backend.js +12 -9
  28. package/dist/src/agent/opencode/opencode-catalog.js +55 -29
  29. package/dist/src/agent/opencode/opencode-config.js +19 -14
  30. package/dist/src/agent/opencode/opencode-env.js +0 -1
  31. package/dist/src/agent/opencode/opencode-session-id.js +1 -2
  32. package/dist/src/agent/opencode/opencode-stream.js +1 -1
  33. package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
  34. package/dist/src/agent/role-notes-write.js +5 -6
  35. package/dist/src/agent/run-agent.js +465 -448
  36. package/dist/src/agent/run-settle.js +214 -0
  37. package/dist/src/agent/session-ledger.js +86 -16
  38. package/dist/src/agent/subscription-account.js +220 -0
  39. package/dist/src/agent/write-guard.js +148 -0
  40. package/dist/src/backlog/backlog-archive.js +246 -55
  41. package/dist/src/backlog/backlog-cli-view.js +280 -0
  42. package/dist/src/backlog/backlog-cli.js +85 -274
  43. package/dist/src/backlog/backlog-commit.js +4 -5
  44. package/dist/src/backlog/backlog-eligibility.js +175 -242
  45. package/dist/src/backlog/backlog-index.js +149 -0
  46. package/dist/src/backlog/backlog-md.js +54 -6
  47. package/dist/src/backlog/backlog-parts.js +254 -0
  48. package/dist/src/backlog/backlog-release.js +117 -0
  49. package/dist/src/backlog/backlog-render.js +27 -0
  50. package/dist/src/backlog/backlog-structure.js +99 -156
  51. package/dist/src/backlog/backlog-write.js +40 -127
  52. package/dist/src/backlog/backlog.js +21 -6
  53. package/dist/src/backlog/question-answer.js +21 -15
  54. package/dist/src/baseline/main-baseline.js +89 -20
  55. package/dist/src/baseline/main-culprit.js +94 -0
  56. package/dist/src/baseline/main-guardrails.js +209 -0
  57. package/dist/src/baseline/main-red.js +139 -32
  58. package/dist/src/baseline/main-revert.js +133 -0
  59. package/dist/src/baseline/main-watch.js +184 -0
  60. package/dist/src/brief/brief-edit.js +9 -11
  61. package/dist/src/brief/brief.js +31 -51
  62. package/dist/src/budget/budget.js +30 -20
  63. package/dist/src/budget/fallback-breaker.js +26 -27
  64. package/dist/src/build/build-check-detect.js +23 -15
  65. package/dist/src/build/build-check-events.js +25 -6
  66. package/dist/src/build/build-check-report.js +49 -9
  67. package/dist/src/build/build-check-scoped.js +18 -11
  68. package/dist/src/build/build-check.js +2 -1
  69. package/dist/src/build/build-info.js +2 -3
  70. package/dist/src/build/build-stage.js +9 -14
  71. package/dist/src/build/dep-install.js +1 -2
  72. package/dist/src/change/change-data.js +123 -31
  73. package/dist/src/change/change-render.js +46 -5
  74. package/dist/src/cli/brief-command.js +3 -4
  75. package/dist/src/cli/cli-args.js +118 -33
  76. package/dist/src/cli/cli-command-args.js +46 -18
  77. package/dist/src/cli/cli-editor.js +2 -1
  78. package/dist/src/cli/cli-flag-specs.js +150 -45
  79. package/dist/src/cli/cli-init.js +51 -0
  80. package/dist/src/cli/cli-marker-commands.js +4 -4
  81. package/dist/src/cli/cli-query-commands.js +308 -590
  82. package/dist/src/cli/cli-repo-gate.js +11 -1
  83. package/dist/src/cli/cli-role-command.js +420 -0
  84. package/dist/src/cli/cli-start-summary.js +160 -0
  85. package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
  86. package/dist/src/cli/completion.js +149 -22
  87. package/dist/src/cli/config-commands.js +41 -18
  88. package/dist/src/cli/follow-view.js +75 -0
  89. package/dist/src/cli/help.js +189 -73
  90. package/dist/src/cli/log-commands.js +148 -82
  91. package/dist/src/cli/question-commands.js +205 -10
  92. package/dist/src/cli.js +106 -43
  93. package/dist/src/collections.js +26 -10
  94. package/dist/src/concurrency/check-permit.js +20 -3
  95. package/dist/src/concurrency/lock.js +17 -13
  96. package/dist/src/concurrency/provider-lease.js +44 -8
  97. package/dist/src/concurrency/semaphore.js +102 -26
  98. package/dist/src/config/config-aliases.js +0 -1
  99. package/dist/src/config/config-editable-keys.js +13 -1
  100. package/dist/src/config/config-fallbacks.js +93 -56
  101. package/dist/src/config/config-field-checks.js +40 -10
  102. package/dist/src/config/config-keys.js +30 -8
  103. package/dist/src/config/config-live.js +63 -24
  104. package/dist/src/config/config-request.js +137 -0
  105. package/dist/src/config/config-schema.js +31 -24
  106. package/dist/src/config/config-selector-checks.js +123 -0
  107. package/dist/src/config/config-validation-agents.js +80 -0
  108. package/dist/src/config/config-validation-model.js +116 -0
  109. package/dist/src/config/config-validation.js +141 -199
  110. package/dist/src/config/config-views.js +127 -53
  111. package/dist/src/config/config-write-core.js +135 -0
  112. package/dist/src/config/config-write.js +131 -264
  113. package/dist/src/config/config.js +48 -38
  114. package/dist/src/config/model-selector.js +34 -34
  115. package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
  116. package/dist/src/doctor/doctor-backlog-plans.js +263 -0
  117. package/dist/src/doctor/doctor-backlog.js +100 -496
  118. package/dist/src/doctor/doctor-check-names.js +81 -0
  119. package/dist/src/doctor/doctor-checks.js +52 -579
  120. package/dist/src/doctor/doctor-claude-checks.js +145 -0
  121. package/dist/src/doctor/doctor-codex-checks.js +279 -0
  122. package/dist/src/doctor/doctor-contention-checks.js +380 -0
  123. package/dist/src/doctor/doctor-holds.js +82 -66
  124. package/dist/src/doctor/doctor-install.js +2 -1
  125. package/dist/src/doctor/doctor-model-checks.js +236 -402
  126. package/dist/src/doctor/doctor-notify.js +3 -4
  127. package/dist/src/doctor/doctor-opencode-checks.js +162 -0
  128. package/dist/src/doctor/doctor-orphans.js +182 -63
  129. package/dist/src/doctor/doctor-project-checks.js +303 -0
  130. package/dist/src/doctor/doctor.js +121 -77
  131. package/dist/src/doctor/image-probe.js +21 -8
  132. package/dist/src/doctor/outcome.js +3 -2
  133. package/dist/src/doctor/probe.js +7 -11
  134. package/dist/src/events/event-format.js +181 -86
  135. package/dist/src/events/event-labels.js +12 -5
  136. package/dist/src/events/event-legacy.js +39 -15
  137. package/dist/src/events/event-read.js +39 -39
  138. package/dist/src/events/event-tally.js +16 -0
  139. package/dist/src/events/event-window.js +60 -24
  140. package/dist/src/events/events.js +30 -13
  141. package/dist/src/events/notify.js +7 -7
  142. package/dist/src/failure/failure-cluster.js +12 -12
  143. package/dist/src/failure/failure-data.js +56 -56
  144. package/dist/src/failure/failure-render.js +31 -23
  145. package/dist/src/failure/failure-state-change.js +17 -12
  146. package/dist/src/failure/time-spend.js +99 -35
  147. package/dist/src/files/files.js +111 -35
  148. package/dist/src/files/json-files.js +3 -3
  149. package/dist/src/files/json-object.js +28 -7
  150. package/dist/src/files/stat-cache.js +2 -2
  151. package/dist/src/files/tail.js +1 -1
  152. package/dist/src/gates/bootstrap-gates.js +4 -5
  153. package/dist/src/gates/budget-gates.js +177 -84
  154. package/dist/src/gates/disk-gate.js +1 -1
  155. package/dist/src/gates/gate-polls.js +108 -58
  156. package/dist/src/gates/maintenance-quota.js +10 -13
  157. package/dist/src/gates/pause-gates.js +28 -10
  158. package/dist/src/gates/readiness.js +7 -3
  159. package/dist/src/gates/role-cap-gates.js +8 -8
  160. package/dist/src/gates/startup-gate.js +94 -32
  161. package/dist/src/gates/streak-gate.js +54 -30
  162. package/dist/src/git/commit-message.js +77 -46
  163. package/dist/src/git/git-diff.js +16 -10
  164. package/dist/src/git/git-run.js +79 -38
  165. package/dist/src/git/git.js +36 -34
  166. package/dist/src/git/slot-salvage.js +253 -0
  167. package/dist/src/git/slots-state.js +6 -6
  168. package/dist/src/git/worktree-pool.js +132 -38
  169. package/dist/src/git/worktree-use.js +4 -11
  170. package/dist/src/git/worktree.js +94 -37
  171. package/dist/src/gui/gui-admin-endpoints.js +96 -0
  172. package/dist/src/gui/gui-args.js +79 -8
  173. package/dist/src/gui/gui-backlog-commands.js +42 -18
  174. package/dist/src/gui/gui-backlog-endpoints.js +155 -0
  175. package/dist/src/gui/gui-command.js +8 -13
  176. package/dist/src/gui/gui-config-commands.js +94 -72
  177. package/dist/src/gui/gui-endpoint-commands.js +4 -3
  178. package/dist/src/gui/gui-endpoint-common.js +4 -4
  179. package/dist/src/gui/gui-endpoints.js +11 -380
  180. package/dist/src/gui/gui-history-endpoints.js +301 -0
  181. package/dist/src/gui/gui-operator-commands.js +108 -13
  182. package/dist/src/gui/gui-prompt-commands.js +11 -14
  183. package/dist/src/gui/gui-report-endpoints.js +90 -0
  184. package/dist/src/gui/gui-server.js +11 -4
  185. package/dist/src/gui/gui-team-commands.js +7 -7
  186. package/dist/src/gui/http-body.js +1 -1
  187. package/dist/src/history/history-data.js +122 -234
  188. package/dist/src/history/history-queued.js +147 -0
  189. package/dist/src/history/history-render.js +42 -22
  190. package/dist/src/history/history.js +193 -49
  191. package/dist/src/inbox/inbox-attachments.js +29 -30
  192. package/dist/src/inbox/inbox-cancel.js +10 -11
  193. package/dist/src/inbox/inbox-edit.js +9 -9
  194. package/dist/src/inbox/inbox-submit.js +18 -18
  195. package/dist/src/inbox/inbox-top.js +25 -9
  196. package/dist/src/inbox/inbox.js +42 -43
  197. package/dist/src/inbox/pending-prompt.js +26 -26
  198. package/dist/src/inbox/prompt-commands.js +62 -49
  199. package/dist/src/inbox/prompt-not-before.js +2 -2
  200. package/dist/src/init/init-templates.js +1 -2
  201. package/dist/src/init/init.js +28 -23
  202. package/dist/src/merge/backlog-conflicts.js +215 -35
  203. package/dist/src/merge/merge-batch.js +14 -8
  204. package/dist/src/merge/merge-cancel.js +53 -0
  205. package/dist/src/merge/merge-check-failures.js +44 -22
  206. package/dist/src/merge/merge-core.js +81 -32
  207. package/dist/src/merge/merge-diff.js +5 -4
  208. package/dist/src/merge/merge-drain.js +150 -11
  209. package/dist/src/merge/merge-git.js +177 -38
  210. package/dist/src/merge/merge-pipeline.js +15 -3
  211. package/dist/src/merge/merge-post-review.js +90 -0
  212. package/dist/src/merge/merge-queue-render.js +49 -11
  213. package/dist/src/merge/merge-queue.js +31 -15
  214. package/dist/src/merge/merge-requeue.js +102 -0
  215. package/dist/src/merge/merge-slot.js +61 -16
  216. package/dist/src/merge/merge-stack.js +33 -11
  217. package/dist/src/merge/merge-to-main.js +86 -27
  218. package/dist/src/merge/merge-top.js +69 -0
  219. package/dist/src/merge/merge-verify.js +12 -10
  220. package/dist/src/notes-cli.js +30 -15
  221. package/dist/src/operator/operator-commands.js +192 -81
  222. package/dist/src/operator/operator-intent.js +69 -42
  223. package/dist/src/operator/operator-requests.js +35 -40
  224. package/dist/src/operator/retire.js +5 -6
  225. package/dist/src/operator/revert.js +142 -34
  226. package/dist/src/orchestrator/orchestrator-launch.js +60 -52
  227. package/dist/src/orchestrator/orchestrator-poll.js +320 -0
  228. package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
  229. package/dist/src/orchestrator/orchestrator.js +128 -369
  230. package/dist/src/orchestrator/retention.js +42 -8
  231. package/dist/src/orchestrator/scheduling-admit.js +116 -0
  232. package/dist/src/orchestrator/scheduling-scope.js +201 -0
  233. package/dist/src/orchestrator/scheduling-skip.js +185 -0
  234. package/dist/src/orchestrator/startup-migration-steps.js +8 -7
  235. package/dist/src/orchestrator/startup-migrations.js +11 -7
  236. package/dist/src/paths.js +116 -49
  237. package/dist/src/pi/command-shape.js +12 -1
  238. package/dist/src/pi/pi-args.js +6 -4
  239. package/dist/src/pi/pi-backend.js +6 -6
  240. package/dist/src/pi/pi-bin.js +54 -63
  241. package/dist/src/pi/pi-listed-models.js +53 -0
  242. package/dist/src/pi/pi-models.js +63 -30
  243. package/dist/src/pi/pi-run-result.js +1 -1
  244. package/dist/src/pi/pi-settings.js +1 -1
  245. package/dist/src/pi/pi-stream.js +6 -6
  246. package/dist/src/pi/pi-watchdogs.js +18 -19
  247. package/dist/src/pi-extension/bounded-output.js +5 -3
  248. package/dist/src/pi-extension/context-budget.js +2 -2
  249. package/dist/src/pi-extension/context-shake.js +1 -1
  250. package/dist/src/pi-extension/context-window.js +0 -1
  251. package/dist/src/pi-extension/full-output.js +3 -10
  252. package/dist/src/pi-extension/strip-thinking.js +0 -1
  253. package/dist/src/pi-extension/trim-system-prompt.js +3 -4
  254. package/dist/src/pi-extension/write-guard.js +37 -0
  255. package/dist/src/process/launch-services.js +1 -2
  256. package/dist/src/process/node-bin.js +16 -0
  257. package/dist/src/process/process-group.js +8 -18
  258. package/dist/src/process/process.js +41 -2
  259. package/dist/src/process/run-marker.js +18 -6
  260. package/dist/src/process/supervisor.js +22 -21
  261. package/dist/src/prompt/dialect.js +17 -2
  262. package/dist/src/prompt/gate-notes.js +203 -0
  263. package/dist/src/prompt/gate-prompts.js +121 -211
  264. package/dist/src/prompt/notes-backlog.js +181 -0
  265. package/dist/src/prompt/principles.js +1 -2
  266. package/dist/src/prompt/prompt-followup.js +19 -14
  267. package/dist/src/prompt/prompt.js +129 -204
  268. package/dist/src/redeploy/redeploy-policy.js +24 -19
  269. package/dist/src/redeploy/redeploy-probes.js +1 -1
  270. package/dist/src/redeploy/redeploy.js +2 -3
  271. package/dist/src/redeploy/redeployer.js +20 -20
  272. package/dist/src/redeploy/self-reload.js +2 -1
  273. package/dist/src/report/report-data.js +161 -77
  274. package/dist/src/report/report-fold.js +129 -52
  275. package/dist/src/report/report-main-red.js +55 -0
  276. package/dist/src/report/report-render.js +133 -64
  277. package/dist/src/report/report-types.js +1 -0
  278. package/dist/src/report/report.js +26 -9
  279. package/dist/src/request-timeouts.js +2 -2
  280. package/dist/src/review/known-flakes.js +100 -1
  281. package/dist/src/review/post-merge-review.js +106 -0
  282. package/dist/src/review/protected-edits.js +4 -5
  283. package/dist/src/review/review-followup.js +8 -8
  284. package/dist/src/review/review-precheck.js +66 -23
  285. package/dist/src/review/review-run.js +395 -0
  286. package/dist/src/review/review-verdict.js +2 -2
  287. package/dist/src/review/review.js +155 -360
  288. package/dist/src/review/secret-leaks.js +36 -16
  289. package/dist/src/review/suite-rerun.js +0 -1
  290. package/dist/src/roles/legacy-role-migration.js +80 -7
  291. package/dist/src/roles/role-catalog.js +69 -12
  292. package/dist/src/roles/role-guidance.js +55 -20
  293. package/dist/src/roles/role-labels.js +1 -2
  294. package/dist/src/roles/role-render.js +67 -50
  295. package/dist/src/roles/role-view.js +59 -47
  296. package/dist/src/roles/roles.js +31 -31
  297. package/dist/src/roles/runner-ids.js +97 -0
  298. package/dist/src/run/acceptance-greps.js +119 -0
  299. package/dist/src/run/feedback-filings.js +51 -0
  300. package/dist/src/{tick → run}/qa-coverage.js +9 -27
  301. package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
  302. package/dist/src/run/run-detail-data.js +179 -0
  303. package/dist/src/run/run-detail-render.js +98 -0
  304. package/dist/src/run/run-detail.js +168 -0
  305. package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
  306. package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
  307. package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
  308. package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
  309. package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
  310. package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
  311. package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
  312. package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
  313. package/dist/src/run/selfcheck-digest.js +40 -0
  314. package/dist/src/run/stage-backlog-checks.js +249 -0
  315. package/dist/src/run/stage-check.js +172 -0
  316. package/dist/src/run/stage-path-checks.js +730 -0
  317. package/dist/src/{loop → runner}/leftover.js +80 -25
  318. package/dist/src/{loop → runner}/model-fallback.js +12 -12
  319. package/dist/src/runner/revision.js +202 -0
  320. package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
  321. package/dist/src/runner/runner-outcome.js +107 -0
  322. package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
  323. package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
  324. package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
  325. package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
  326. package/dist/src/runner/runner-state.js +102 -0
  327. package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
  328. package/dist/src/scheduling/backoff.js +38 -38
  329. package/dist/src/scheduling/claims.js +35 -22
  330. package/dist/src/scheduling/once-round.js +22 -22
  331. package/dist/src/scheduling/quiet-hours.js +5 -6
  332. package/dist/src/scheduling/scheduling.js +74 -64
  333. package/dist/src/scheduling/work-merged-cache.js +2 -2
  334. package/dist/src/status/status-budget.js +55 -47
  335. package/dist/src/status/status-data.js +43 -16
  336. package/dist/src/status/status-inbox.js +2 -2
  337. package/dist/src/status/status-install.js +0 -1
  338. package/dist/src/status/status-polls.js +28 -28
  339. package/dist/src/status/status-roles.js +39 -13
  340. package/dist/src/team/error-storm.js +8 -8
  341. package/dist/src/team/failure-spread.js +2 -2
  342. package/dist/src/team/orchestrator-info.js +15 -9
  343. package/dist/src/team/reclaim.js +5 -6
  344. package/dist/src/team/team-hold.js +7 -7
  345. package/dist/src/team/team-polls.js +91 -50
  346. package/dist/src/team/team-state.js +8 -2
  347. package/dist/src/text/datetime.js +63 -17
  348. package/dist/src/text/format.js +48 -11
  349. package/dist/src/text/markdown.js +8 -5
  350. package/dist/src/text/phrases.js +57 -23
  351. package/dist/src/text/suggest.js +1 -1
  352. package/dist/src/text/text.js +67 -13
  353. package/dist/src/ui/badges.js +65 -37
  354. package/dist/src/ui/gui/gui-client-backlog.js +292 -0
  355. package/dist/src/ui/gui/gui-client-boot.js +32 -26
  356. package/dist/src/ui/gui/gui-client-composer.js +8 -9
  357. package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
  358. package/dist/src/ui/gui/gui-client-drawer.js +126 -285
  359. package/dist/src/ui/gui/gui-client-feed.js +271 -0
  360. package/dist/src/ui/gui/gui-client-history.js +434 -67
  361. package/dist/src/ui/gui/gui-client-labels.js +1 -2
  362. package/dist/src/ui/gui/gui-client-markdown.js +1 -2
  363. package/dist/src/ui/gui/gui-client-model.js +27 -36
  364. package/dist/src/ui/gui/gui-client-operator.js +55 -51
  365. package/dist/src/ui/gui/gui-client-pending.js +227 -23
  366. package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
  367. package/dist/src/ui/gui/gui-client-report.js +87 -427
  368. package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
  369. package/dist/src/ui/gui/gui-client-settings.js +56 -4
  370. package/dist/src/ui/gui/gui-client-team.js +25 -465
  371. package/dist/src/ui/gui/gui-client.js +27 -25
  372. package/dist/src/ui/gui/gui-icons.js +0 -2
  373. package/dist/src/ui/gui/gui-page.js +9 -7
  374. package/dist/src/ui/gui/gui-styles.js +34 -34
  375. package/dist/src/ui/progress-data.js +9 -11
  376. package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
  377. package/dist/src/ui/status-model.js +133 -125
  378. package/dist/src/ui/status-payload.js +136 -49
  379. package/dist/src/ui/status-render.js +101 -82
  380. package/dist/src/ui/team-alerts.js +248 -57
  381. package/dist/src/ui/tone.js +5 -6
  382. package/dist/src/ui/transcript-run.js +58 -0
  383. package/dist/src/ui/transcript-tail.js +28 -20
  384. package/dist/src/ui/transcript.js +63 -23
  385. package/dist/src/ui/tui/tui-app.js +0 -1
  386. package/dist/src/ui/tui/tui-backlog.js +51 -14
  387. package/dist/src/ui/tui/tui-editor-answer.js +27 -31
  388. package/dist/src/ui/tui/tui-editor-attach.js +3 -5
  389. package/dist/src/ui/tui/tui-editor-backends.js +132 -0
  390. package/dist/src/ui/tui/tui-editor-brief.js +10 -12
  391. package/dist/src/ui/tui/tui-editor-budget.js +35 -31
  392. package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
  393. package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
  394. package/dist/src/ui/tui/tui-editor-config.js +86 -0
  395. package/dist/src/ui/tui/tui-editor-entry.js +36 -47
  396. package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
  397. package/dist/src/ui/tui/tui-editor-instances.js +37 -0
  398. package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
  399. package/dist/src/ui/tui/tui-editor-interval.js +11 -9
  400. package/dist/src/ui/tui/tui-editor-model.js +29 -0
  401. package/dist/src/ui/tui/tui-editor-notes.js +3 -5
  402. package/dist/src/ui/tui/tui-editor-notify.js +25 -0
  403. package/dist/src/ui/tui/tui-editor-pause.js +48 -59
  404. package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
  405. package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
  406. package/dist/src/ui/tui/tui-editor-queued.js +5 -9
  407. package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
  408. package/dist/src/ui/tui/tui-editor-retire.js +4 -6
  409. package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
  410. package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
  411. package/dist/src/ui/tui/tui-editor-search.js +45 -33
  412. package/dist/src/ui/tui/tui-editor-session.js +60 -34
  413. package/dist/src/ui/tui/tui-editor-slots.js +23 -0
  414. package/dist/src/ui/tui/tui-editors.js +35 -2
  415. package/dist/src/ui/tui/tui-frame.js +56 -11
  416. package/dist/src/ui/tui/tui-history.js +8 -9
  417. package/dist/src/ui/tui/tui-input.js +13 -11
  418. package/dist/src/ui/tui/tui-keymap.js +0 -1
  419. package/dist/src/ui/tui/tui-keys.js +24 -9
  420. package/dist/src/ui/tui/tui-pane-activity.js +14 -11
  421. package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
  422. package/dist/src/ui/tui/tui-pane-brief.js +0 -1
  423. package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
  424. package/dist/src/ui/tui/tui-pane-health.js +58 -8
  425. package/dist/src/ui/tui/tui-pane-history.js +402 -82
  426. package/dist/src/ui/tui/tui-pane-pending.js +308 -13
  427. package/dist/src/ui/tui/tui-pane-queued.js +71 -55
  428. package/dist/src/ui/tui/tui-pane-report.js +46 -14
  429. package/dist/src/ui/tui/tui-pane-session.js +25 -6
  430. package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
  431. package/dist/src/ui/tui/tui-pane.js +80 -23
  432. package/dist/src/ui/tui/tui-panes.js +100 -30
  433. package/dist/src/ui/tui/tui.js +14 -14
  434. package/dist/src/verdict/fix-claim.js +82 -32
  435. package/dist/src/verdict/no-change.js +4 -4
  436. package/dist/src/verdict/refusal.js +40 -16
  437. package/dist/src/verdict/reply-contract.js +42 -31
  438. package/dist/src/version.js +0 -1
  439. package/package.json +2 -1
  440. package/dist/src/loop/revision.js +0 -70
  441. package/dist/src/roles/loop-ids.js +0 -77
  442. package/dist/src/tick/stage-check.js +0 -419
  443. package/dist/src/tick/telemetry-digest.js +0 -25
  444. package/dist/src/tick/tick-detail-data.js +0 -108
  445. package/dist/src/tick/tick-detail.js +0 -163
  446. package/dist/src/ui/gui/gui-client-sound.js +0 -53
@@ -1,4 +1,3 @@
1
- // vocabulary: plain
2
1
  /** Event-log folding for the usage report: turn raw harness events (run_end/merged/…) into
3
2
  * per-day and whole-window `UsageFold` accumulators, memoizing the day-keyed read per
4
3
  * (root, fromKey) so a dashboard polling /api/report only folds appended lines instead of
@@ -7,19 +6,21 @@
7
6
  * --since and day windows present cannot drift apart; it lives here matching its own test
8
7
  * (test/report-fold-cache.test.ts). Zero incidental runtime dependencies: node built-ins and
9
8
  * the app's own modules only. */
10
- import { sameStatIdentity, statOrNull } from "../files/files.js";
9
+ import { sameStatIdentity, statIdentityOrZero, statOrNull } from "../files/files.js";
11
10
  import { readWindowEvents } from "../events/event-window.js";
12
11
  import { eventDayKey, eventRole, eventUsage, parseEventLine } from "../events/event-read.js";
13
12
  import { readCompleteLines } from "../files/tail.js";
14
13
  import { eventsLogPath } from "../paths.js";
15
14
  import { isPassingFullCheck, isRedFullCheck } from "../build/build-check-events.js";
15
+ import { isPassingMainCheck, isRedMain } from "../baseline/main-guardrails.js";
16
16
  import { addTo, memoSetLru } from "../collections.js";
17
+ import { asList } from "../text/text.js";
17
18
  import { finiteNumber } from "../files/json-object.js";
18
19
  import { OUTCOME_CLASS } from "../failure/time-spend.js";
19
- import { baseRoleOf } from "../roles/loop-ids.js";
20
+ import { baseRoleOf } from "../roles/runner-ids.js";
20
21
  import { commitTier } from "../roles/roles.js";
21
22
  /** Fold one event's `providerCost` map (`Record<provider, {costUsd, notionalCostUsd}>` — the
22
- * shape tick-finalize.ts's run_end and merge-slot.ts's merge_done/merge_failed emit) into the
23
+ * shape run-finalize.ts's run_end and merge-slot.ts's merge_done/merge_failed emit) into the
23
24
  * per-provider tallies, one addTo per provider per nonzero figure so zero stays absent. A
24
25
  * missing, foreign, or empty map folds nothing, so old logs render byte-identically. */
25
26
  function foldProviderCost(target, providerCost) {
@@ -37,28 +38,28 @@ function foldProviderCost(target, providerCost) {
37
38
  addTo(target.notionalByProvider, provider, notionalCostUsd);
38
39
  }
39
40
  }
40
- /** Push a provider- or workspace-slot wait's ms into the accumulator list a median reads,
41
- * when the event carried a positive wait: a wait is only event-logged at or above the 30 s
42
- * threshold, so a 0 ms wait (a probe seam) or a pre-waitedMs event adds nothing and the two
43
- * wait medians reflect real blocked admissions only. */
44
- function pushPositiveWaitedMs(list, ev) {
45
- const waited = finiteNumber(ev.waitedMs, 0);
46
- if (waited > 0)
47
- list.push(waited);
41
+ /** Push `ms` into the accumulator list a day median reads, when it is positive: the event
42
+ * fields that feed these medians are missing or 0 for the same event kinds (short runs, build
43
+ * pre-checks, precedent legacy events, probe-seam waits), and those contribute nothing — a
44
+ * median over only real durations/wait times. Every median list folds through here, so the
45
+ * "positive-only" rule cannot drift between fields. */
46
+ function pushPositiveMs(list, ms) {
47
+ if (ms > 0)
48
+ list.push(ms);
48
49
  }
49
- /** Fold one event into a usage accumulator: run_end events add a tick (per role and, when
50
- * the target counts ticks, in total), their output tokens, and their cost — zero-cost ticks
50
+ /** Fold one event into a usage accumulator: run_end events add a run (per role and, when
51
+ * the target counts runs, in total), their output tokens, and their cost — zero-cost runs
51
52
  * contribute nothing, so they leave no costByRole key, keeping the render's "$0 roles are
52
53
  * omitted" rule true on the aggregation itself, not just at display time; merged events add
53
54
  * a commit; merge_done/merge_failed events add a merge run and
54
- * accumulate their tokens/cost into the merge fields only — costByRole stays a ticks-only
55
+ * accumulate their tokens/cost into the merge fields only — costByRole stays a runs-only
55
56
  * breakdown, and the merge line is how the reviewer's share is named. Both collectors fold
56
57
  * through this one place, so the two windows' aggregation rules cannot drift apart. */
57
58
  export function foldUsageEvent(target, ev) {
58
59
  if (ev.type === "run_end") {
59
60
  const role = eventRole(ev);
60
61
  addTo(target.runsByRole, role, 1);
61
- // Error-class ticks get their own parallel tally — the day report's ``Failed ticks by role``
62
+ // Error-class runs get their own parallel tally — the day report's ``Failed runs by role``
62
63
  // line — reusing the digest's OUTCOME_CLASS so "failed" cannot drift from the failure
63
64
  // report's meaning. Never subtracted from runsByRole; the two are independent counts.
64
65
  if (OUTCOME_CLASS[ev.result] === "error")
@@ -66,12 +67,13 @@ export function foldUsageEvent(target, ev) {
66
67
  // A refusal is its own deliberate outcome (src/verdict/refusal.ts), not error-class, so
67
68
  // it needs an explicit result check: the one per-role rejection-without-review count a
68
69
  // day report should surface beside rejections, revisions and falls. Never subtracted from
69
- // runsByRole; a refused tick still counts as a run.
70
+ // runsByRole; a refused run still counts as a run.
70
71
  if (ev.result === "refused")
71
72
  addTo(target.refusalsByRole, role, 1);
72
73
  if (target.runs !== undefined)
73
74
  target.runs++;
74
75
  const { tokens, costUsd, notionalCostUsd } = eventUsage(ev);
76
+ pushPositiveMs(target.runDurationsMs, finiteNumber(ev.durationMs, 0));
75
77
  addTo(target.tokensByRole, role, tokens);
76
78
  target.tokensOut += tokens;
77
79
  target.costUsd += costUsd;
@@ -80,26 +82,48 @@ export function foldUsageEvent(target, ev) {
80
82
  addTo(target.costByRole, role, costUsd);
81
83
  if (notionalCostUsd !== 0)
82
84
  addTo(target.notionalByRole, role, notionalCostUsd);
83
- // The tick's own runs' per-provider spend (tick-finalize.ts emits providerCost on run_end)
85
+ // The notional spend's backend (plans/codex-backend.md part 3d/9): the subscription seam
86
+ // whose runs made the run's API-equivalent cost, from the run_end's `agent` (run-finalize
87
+ // carries it when the run reported billing/rate windows) — falling back to "claude" when
88
+ // a legacy window event (no agent, subscription billing) attributed its spend, so the old
89
+ // default stays the report's only backend. A run with no backend and no subscription
90
+ // billing is unattributed and adds no key, so the render keeps its generic line for it.
91
+ const notionalBackend = typeof ev.agent === "string" && ev.agent !== ""
92
+ ? ev.agent
93
+ : ev.billing === "subscription"
94
+ ? "claude"
95
+ : undefined;
96
+ if (notionalBackend !== undefined && notionalCostUsd !== 0)
97
+ addTo(target.notionalByBackend, notionalBackend, notionalCostUsd);
98
+ // The agent runs' per-provider spend (run-finalize.ts emits providerCost on run_end)
84
99
  // — folded per provider beside the per-role maps, so a mixed team's `Cost by provider`
85
100
  // line names the expensive seam.
86
101
  foldProviderCost(target, ev.providerCost);
87
102
  }
88
103
  else if (ev.type === "review_rejected") {
89
- // One rejection per event, on the day it happened, per loop. Zero-means-absent: a role
104
+ // One rejection per event, on the day it happened, per role. Zero-means-absent: a role
90
105
  // that never rejected stays absent, like costByRole.
91
106
  addTo(target.rejectionsByRole, eventRole(ev), 1);
107
+ // A rejection that carried a positive durationMs means a reviewer actually ran the gate
108
+ // and reached a verdict (post-merge rejections do; the build pre-check contributes no
109
+ // duration), so it joins the review-duration median beside approvals. durationMs: 0 and
110
+ // a duration-less event contribute nothing.
111
+ pushPositiveMs(target.reviewDurationsMs, finiteNumber(ev.durationMs, 0));
112
+ }
113
+ else if (ev.type === "review_verdict") {
114
+ // An approval that reached a verdict. Every reviewer run that reached a verdict, approval
115
+ // or rejection, is one entry in the review-duration median; review_failed stays out — it
116
+ // has no verdict. Mirrors the rejection branch above, on the same-day count.
117
+ pushPositiveMs(target.reviewDurationsMs, finiteNumber(ev.durationMs, 0));
92
118
  }
93
119
  else if (ev.type === "revision") {
94
- // One rework round per event, on the day it happened, per loop. Zero-means-absent: a role
120
+ // One rework round per event, on the day it happened, per role. Zero-means-absent: a role
95
121
  // that never reworked a rejected change stays absent, like rejectionsByRole.
96
122
  addTo(target.revisionsByRole, eventRole(ev), 1);
97
123
  }
98
124
  else if (isPassingFullCheck(ev)) {
99
125
  // The merge slot's passing full-suite checks, for the day report's check-time median.
100
- const durationMs = finiteNumber(ev.durationMs, 0);
101
- if (durationMs > 0)
102
- target.checkDurationsMs.push(durationMs);
126
+ pushPositiveMs(target.checkDurationsMs, finiteNumber(ev.durationMs, 0));
103
127
  }
104
128
  else if (isRedFullCheck(ev)) {
105
129
  // The merge slot's red full-suite checks, for the day report's red-check count.
@@ -114,7 +138,7 @@ export function foldUsageEvent(target, ev) {
114
138
  addTo(target.imageReroutesByRole, eventRole(ev), 1);
115
139
  }
116
140
  else if (ev.type === "run_deferred") {
117
- // One deferral per event, a due maintenance tick that stood down. Bucket by the raw
141
+ // One deferral per event, a due maintenance run that stood down. Bucket by the raw
118
142
  // reason code ("backlog", "plans-full", else "no-work" for a missing/foreign/torn
119
143
  // reason — the same fallback deferredReasonText applies in the feed), so the collector
120
144
  // stays free of a presentation import and the render prints the code the event carried.
@@ -134,6 +158,15 @@ export function foldUsageEvent(target, ev) {
134
158
  else if (tier === "maintenance")
135
159
  target.maintenanceCommits++;
136
160
  }
161
+ else if (ev.type === "main_revert") {
162
+ // Which role's merged change the main watch took back off main, keyed by the culprit's
163
+ // `runner` — a revert is a merge outcome, so it counts under the role that merged the
164
+ // culprit, folded to base role like commitsByRole/mergedByRole (`feature-2` → `feature`).
165
+ // A revert with no `runner` (a hand-made or director commit) adds nothing — no
166
+ // `(unknown)` key — matching the zero-means-absent rule of the other role maps.
167
+ if (typeof ev.runner === "string" && ev.runner !== "")
168
+ addTo(target.revertsByRole, baseRoleOf(ev.runner), 1);
169
+ }
137
170
  else if (ev.type === "run_start") {
138
171
  // One tally per run start on a configured model: the same `model` field the CLI's
139
172
  // `run #N started on <model>` line prints, and the only event that carries a model
@@ -146,19 +179,24 @@ export function foldUsageEvent(target, ev) {
146
179
  // The day report's provider-slot wait median, mirroring the slot_wait push: a wait only
147
180
  // event-logged at or above the 30 s threshold counts, so a 0 ms wait (a probe seam) adds
148
181
  // nothing and the median reflects real blocked admissions only.
149
- pushPositiveWaitedMs(target.providerWaitsMs, ev);
182
+ pushPositiveMs(target.providerWaitsMs, finiteNumber(ev.waitedMs, 0));
150
183
  }
151
184
  else if (ev.type === "slot_wait") {
152
185
  // The day report's workspace-slot wait median, mirroring the provider_wait push: a wait
153
186
  // only event-logged at or above the 30 s threshold counts, so a 0 ms wait (a probe seam)
154
187
  // or a pre-waitedMs event adds nothing.
155
- pushPositiveWaitedMs(target.slotWaitsMs, ev);
188
+ pushPositiveMs(target.slotWaitsMs, finiteNumber(ev.waitedMs, 0));
189
+ }
190
+ else if (ev.type === "check_wait") {
191
+ // The day report's check-slot wait median, mirroring the slot_wait push: only waits logged
192
+ // at or above the 30 s threshold count, so a 0 ms or pre-waitedMs event adds nothing.
193
+ pushPositiveMs(target.checkWaitsMs, finiteNumber(ev.waitedMs, 0));
156
194
  }
157
195
  else if (ev.type === "merge_done" || ev.type === "merge_failed") {
158
196
  target.mergeRuns = (target.mergeRuns ?? 0) + 1;
159
197
  if (ev.type === "merge_failed")
160
198
  target.mergeFailed = (target.mergeFailed ?? 0) + 1;
161
- // Attribute the outcome to the authoring role (writeMergeOutcome logs loop: entry.role)
199
+ // Attribute the outcome to the authoring role (writeMergeOutcome logs role: entry.role)
162
200
  // folded to base role, the same normalization the merged branch uses, so `feature-2`
163
201
  // aggregates into `feature`. A parallel tally beside the team run counts, never
164
202
  // subtracted from mergeRuns/mergeFailed.
@@ -166,13 +204,15 @@ export function foldUsageEvent(target, ev) {
166
204
  if (ev.type === "merge_done") {
167
205
  addTo(target.mergedByRole, role, 1);
168
206
  // Events logged before queuedMs existed carry none: skipped, not counted as zero.
169
- const waited = finiteNumber(ev.queuedMs, 0);
170
- if (waited > 0)
171
- target.mergeWaitsMs.push(waited);
207
+ pushPositiveMs(target.mergeWaitsMs, finiteNumber(ev.queuedMs, 0));
208
+ // How long the merge slot itself held the change, from the verification's start to the
209
+ // outcome. Like mergeWaitsMs, only a landed merge feeds it, and a pre-durationMs event
210
+ // (or 0) contributes nothing.
211
+ pushPositiveMs(target.mergeDurationsMs, finiteNumber(ev.durationMs, 0));
172
212
  }
173
213
  else {
174
214
  addTo(target.mergeFailedByRole, role, 1);
175
- // The event's result (writeMergeOutcome's TickResult) names WHY the merge bounced
215
+ // The event's result (writeMergeOutcome's RunResult) names WHY the merge bounced
176
216
  // off the gate — rejected/merge_conflict/merge_blocked/aborted. A legacy event with
177
217
  // no result folds under `unknown`, so the reason buckets always sum to mergeFailed.
178
218
  const reason = typeof ev.result === "string" && ev.result.length > 0 ? ev.result : "unknown";
@@ -184,6 +224,14 @@ export function foldUsageEvent(target, ev) {
184
224
  // A subscription merge's API-equivalent spend joins the notional line, never the
185
225
  // budgeted cost totals (plans/agent-backends.md part 6a).
186
226
  target.notionalCostUsd += notionalCostUsd;
227
+ // The merge's notional spend per backend (merge-slot.ts emits it on the event, keyed by
228
+ // each subscription run's agent) — folded beside the run-end per-backend map, so a codex
229
+ // merge's subscription spend names codex instead of falling back to the generic line.
230
+ for (const [backend, c] of Object.entries(ev.notionalByBackend ?? {})) {
231
+ const amt = finiteNumber(c, 0);
232
+ if (amt !== 0)
233
+ addTo(target.notionalByBackend, backend, amt);
234
+ }
187
235
  // The merge's per-provider spend (merge-slot.ts keys it to its reviewer seam), so a
188
236
  // merge's costs appear under the provider that ran the reviewer, matching the author
189
237
  // side's foldMergeUsage charge.
@@ -209,34 +257,58 @@ export function zeroCommitCounts() {
209
257
  export function zeroMergeCounts() {
210
258
  return { mergeRuns: 0, mergeTokens: 0, mergeCostUsd: 0 };
211
259
  }
212
- /** Sum a per-loop-id count/cost map over one base role: instance keys (`feature-2`) fold into
213
- * their base (`feature`), so a scoped report aggregates a whole role group exactly as the
214
- * per-role cap's group does. Ticks/tokens/cost are keyed by exact loop id while commits are
260
+ /** The base roles a report scope names: undefined and an empty list mean no scope (the team
261
+ * report); otherwise each id folded through `baseRoleOf`, deduplicated in first-seen order, so
262
+ * `feature-2,feature` is one base. */
263
+ export function scopeBases(role) {
264
+ if (role === undefined)
265
+ return undefined;
266
+ const ids = asList(role);
267
+ if (ids.length === 0)
268
+ return undefined;
269
+ const out = [];
270
+ for (const id of ids) {
271
+ const base = baseRoleOf(id);
272
+ if (!out.includes(base))
273
+ out.push(base);
274
+ }
275
+ return out;
276
+ }
277
+ /** Sum a per-role-id count/cost map over the scoped base roles: instance keys (`feature-2`)
278
+ * fold into their base (`feature`), so a scoped report aggregates a whole role group exactly
279
+ * as the per-role cap's group does. Runs/tokens/cost are keyed by exact role id while commits are
215
280
  * already base-role keyed; running every key through `baseRoleOf` reads both shapes the same
216
281
  * and returns 0 when nothing matches. */
217
- export function roleSum(record, role) {
282
+ export function roleSum(record, bases) {
283
+ const list = asList(bases);
218
284
  let sum = 0;
219
285
  for (const [key, n] of Object.entries(record)) {
220
- if (baseRoleOf(key) === role)
286
+ if (list.includes(baseRoleOf(key)))
221
287
  sum += n;
222
288
  }
223
289
  return sum;
224
290
  }
225
- /** Narrow a per-loop-id count/cost map to one base role, keyed by the base role; a role absent
226
- * from the map (no ticks, or no nonzero cost) yields an empty map, never a zero entry — the
227
- * same zero-means-absent rule the team render applies. Used by the role-scoped collectors so a
228
- * scoped series derives from the fold's per-role maps without inventing entries. */
229
- export function narrowToRole(record, role) {
230
- const n = roleSum(record, role);
231
- return n === 0 ? {} : { [role]: n };
291
+ /** Narrow a per-role-id count/cost map to the scoped base roles, one entry per base keyed by
292
+ * that base; a role absent from the map (no runs, or no nonzero cost) yields no entry, never
293
+ * a zero one — the same zero-means-absent rule the team render applies. Used by the
294
+ * role-scoped collectors so a scoped series derives from the fold's per-role maps without
295
+ * inventing entries. */
296
+ export function narrowToRole(record, bases) {
297
+ const out = {};
298
+ for (const base of asList(bases)) {
299
+ const n = roleSum(record, base);
300
+ if (n !== 0)
301
+ out[base] = n;
302
+ }
303
+ return out;
232
304
  }
233
- /** A per-loop map scoped to one base role for a report: the full map as-is when no scope is
234
- * set (a team report), else the same map narrowed to `base` — narrowToRole's zero-means-absent
305
+ /** A per-role map scoped to the base roles of a report: the full map as-is when no scope is
306
+ * set (a team report), else the same map narrowed to `bases` — narrowToRole's zero-means-absent
235
307
  * rule, so a scoped report never invents a zero entry. The one home of the "keep everything,
236
308
  * or keep only the named base role" rule: the 13 per-role/pair fields of the `--since` report's
237
309
  * output all shape through it, so the scoping rule cannot drift between them. */
238
- export function scopedByRole(record, base) {
239
- return base === undefined ? record : narrowToRole(record, base);
310
+ export function scopedByRole(record, bases) {
311
+ return bases === undefined ? record : narrowToRole(record, bases);
240
312
  }
241
313
  function dayFoldFor(entry, dayKey) {
242
314
  let fold = entry.days.get(dayKey);
@@ -250,11 +322,16 @@ function dayFoldFor(entry, dayKey) {
250
322
  rejectionsByRole: {},
251
323
  revisionsByRole: {},
252
324
  refusalsByRole: {},
325
+ revertsByRole: {},
253
326
  checkDurationsMs: [],
254
327
  checkFailures: 0,
255
328
  mergeWaitsMs: [],
329
+ mergeDurationsMs: [],
330
+ runDurationsMs: [],
256
331
  providerWaitsMs: [],
257
332
  slotWaitsMs: [],
333
+ checkWaitsMs: [],
334
+ reviewDurationsMs: [],
258
335
  failedRunsByRole: {},
259
336
  fallbacksByRole: {},
260
337
  imageReroutesByRole: {},
@@ -267,6 +344,7 @@ function dayFoldFor(entry, dayKey) {
267
344
  mergeFailedByReason: {},
268
345
  costByProvider: {},
269
346
  notionalByProvider: {},
347
+ notionalByBackend: {},
270
348
  runsByModel: {},
271
349
  deferredByReason: {},
272
350
  };
@@ -286,6 +364,8 @@ function foldEventInto(entry, ev) {
286
364
  entry.hadEvents = true;
287
365
  entry.firstEventTs = typeof ev.ts === "number" ? ev.ts : undefined;
288
366
  }
367
+ if (isRedMain(ev) || isPassingMainCheck(ev))
368
+ entry.redEvents.push(ev);
289
369
  foldUsageEvent(dayFoldFor(entry, dayKey), ev);
290
370
  }
291
371
  /** Parse one appended log line and fold it; a torn or non-event line folds nothing. */
@@ -307,11 +387,7 @@ function foldLineInto(entry, line) {
307
387
  * carrying yesterday's proof forward. */
308
388
  export function foldWindowEvents(root, fromKey) {
309
389
  const key = `${root}\0${fromKey}`;
310
- const liveStat = statOrNull(eventsLogPath(root));
311
- const size = liveStat?.size ?? 0;
312
- const mtimeMs = liveStat?.mtimeMs ?? 0;
313
- const dev = liveStat?.dev ?? 0;
314
- const ino = liveStat?.ino ?? 0;
390
+ const { size, mtimeMs, dev, ino } = statIdentityOrZero(statOrNull(eventsLogPath(root)));
315
391
  const cached = reportFoldCache.get(key);
316
392
  if (cached &&
317
393
  sameStatIdentity({ dev, ino, mtimeMs, size }, { dev: cached.dev, ino: cached.ino, mtimeMs: cached.mtimeMs, size: cached.offset })) {
@@ -345,6 +421,7 @@ export function foldWindowEvents(root, fromKey) {
345
421
  fileCovers: raw.coversFullWindow,
346
422
  hadEvents: false,
347
423
  days: new Map(),
424
+ redEvents: [],
348
425
  };
349
426
  for (const ev of raw.events)
350
427
  foldEventInto(entry, ev);
@@ -0,0 +1,55 @@
1
+ /** Main's red time per local calendar day: how long main had no passing full check, from the
2
+ * same `main_check` / `main_red_hold` event trail G1 uses (src/baseline/main-guardrails.ts).
3
+ * Pure and synchronous over an event list and a `[fromMs, toMs)` window, so the usage
4
+ * report's day and --since collectors both call it without reading the clock or the log
5
+ * again. This is main's own wall-clock red time, not any role's, so the report carries it
6
+ * only on the team scope. */
7
+ import { dayKey } from "../text/datetime.js";
8
+ import { isPassingMainCheck, isRedMain } from "../baseline/main-guardrails.js";
9
+ /** Add a spell's ms, split across the local calendar days it spans, to `map` (a Map, so the
10
+ * caller keeps the day-key order the walk produced). */
11
+ function addSpell(map, start, end) {
12
+ let cur = start;
13
+ while (cur < end) {
14
+ // The Date constructor's month/date fields cross the local midnight (and any DST shift
15
+ // at it) exactly as dayAt does, so an hour cannot leak into the next day.
16
+ const d = new Date(cur);
17
+ const nextMidnight = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1).getTime();
18
+ const segEnd = Math.min(end, nextMidnight);
19
+ const ms = segEnd - cur;
20
+ if (ms > 0)
21
+ map.set(dayKey(cur), (map.get(dayKey(cur)) ?? 0) + ms);
22
+ cur = segEnd;
23
+ }
24
+ }
25
+ /** Main's red wall-clock time per local calendar day (`YYYY-MM-DD` → ms), over the window
26
+ * `[fromMs, toMs)`. A red spell is G1's rule (isRedMain / isPassingMainCheck): it opens at a
27
+ * red event while none is open (a further red while open keeps the earliest start) and closes
28
+ * at the next passing full `main_check`; a spell still open at `toMs` runs to the window's
29
+ * end. Every spell is split across local calendar days (dayKey) with midnight arithmetic, so
30
+ * a DST shift cannot leak an hour into the next day; only nonzero daily entries are returned.
31
+ * A spell already open before `fromMs` is not counted, because its opener lies outside the
32
+ * window — the report measures main's own red time within the window it was asked for. */
33
+ export function mainRedMinutesByDay(events, fromMs, toMs) {
34
+ const map = new Map();
35
+ const inWindow = events
36
+ .filter((e) => typeof e.ts === "number" && e.ts >= fromMs && e.ts < toMs)
37
+ .sort((a, b) => a.ts - b.ts);
38
+ let openStart;
39
+ for (const e of inWindow) {
40
+ const ts = e.ts;
41
+ if (isRedMain(e)) {
42
+ if (openStart === undefined)
43
+ openStart = ts;
44
+ }
45
+ else if (isPassingMainCheck(e)) {
46
+ if (openStart !== undefined) {
47
+ addSpell(map, openStart, ts);
48
+ openStart = undefined;
49
+ }
50
+ }
51
+ }
52
+ if (openStart !== undefined)
53
+ addSpell(map, openStart, toMs);
54
+ return map;
55
+ }