tumwater 0.2.0 → 0.4.0

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