tumwater 0.4.2 → 0.4.3

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 (305) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-backend.js +6 -6
  4. package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
  5. package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
  6. package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
  7. package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
  8. package/dist/src/agent/backend-traits.js +2 -2
  9. package/dist/src/agent/claude/claude-account.js +1 -8
  10. package/dist/src/agent/claude/claude-backend.js +3 -3
  11. package/dist/src/agent/claude/claude-env.js +27 -31
  12. package/dist/src/agent/claude/claude-stream.js +111 -54
  13. package/dist/src/agent/codex/codex-account.js +9 -0
  14. package/dist/src/agent/codex/codex-backend.js +2 -2
  15. package/dist/src/agent/codex/codex-env.js +28 -15
  16. package/dist/src/agent/codex/codex-hook-trust.js +39 -20
  17. package/dist/src/agent/codex/codex-rollout.js +93 -56
  18. package/dist/src/agent/codex/codex-stream.js +1 -1
  19. package/dist/src/agent/env-scrub.js +18 -0
  20. package/dist/src/agent/image-input.js +68 -11
  21. package/dist/src/agent/notes-shim.js +2 -2
  22. package/dist/src/agent/opencode/opencode-backend.js +2 -2
  23. package/dist/src/agent/opencode/opencode-catalog.js +50 -27
  24. package/dist/src/agent/opencode/opencode-config.js +32 -14
  25. package/dist/src/agent/opencode/opencode-env.js +12 -13
  26. package/dist/src/agent/opencode/opencode-stream.js +47 -25
  27. package/dist/src/agent/session-ledger.js +1 -1
  28. package/dist/src/agent/subscription-account.js +3 -3
  29. package/dist/src/backlog/backlog-cli-view.js +81 -41
  30. package/dist/src/backlog/backlog-cli.js +57 -26
  31. package/dist/src/backlog/backlog-eligibility.js +58 -102
  32. package/dist/src/backlog/backlog-md.js +5 -5
  33. package/dist/src/backlog/backlog-parts.js +65 -27
  34. package/dist/src/backlog/backlog-release.js +12 -12
  35. package/dist/src/backlog/backlog-structure.js +114 -61
  36. package/dist/src/backlog/backlog.js +1 -1
  37. package/dist/src/baseline/main-baseline.js +148 -84
  38. package/dist/src/budget/budget-fallback-view.js +100 -243
  39. package/dist/src/budget/budget-wiring.js +29 -65
  40. package/dist/src/budget/fallback-breaker.js +2 -2
  41. package/dist/src/build/build-check-detect.js +43 -14
  42. package/dist/src/build/build-check-events.js +2 -2
  43. package/dist/src/build/build-check-scoped.js +40 -16
  44. package/dist/src/build/build-check.js +13 -5
  45. package/dist/src/cli/cli-command-args.js +89 -52
  46. package/dist/src/cli/cli-flag-specs.js +42 -22
  47. package/dist/src/cli/cli-init.js +34 -12
  48. package/dist/src/cli/cli-query-commands.js +1 -1
  49. package/dist/src/cli/cli-repo-gate.js +2 -2
  50. package/dist/src/cli/cli-start.js +91 -58
  51. package/dist/src/cli/completion.js +82 -46
  52. package/dist/src/cli/config-commands.js +82 -71
  53. package/dist/src/cli/help.js +4 -6
  54. package/dist/src/cli/log-commands.js +43 -23
  55. package/dist/src/cli/notify-command.js +2 -2
  56. package/dist/src/cli/question-commands.js +86 -60
  57. package/dist/src/cli.js +181 -135
  58. package/dist/src/collections.js +31 -16
  59. package/dist/src/concurrency/check-slots.js +70 -0
  60. package/dist/src/concurrency/lock.js +22 -0
  61. package/dist/src/concurrency/run-permit.js +49 -0
  62. package/dist/src/concurrency/semaphore.js +46 -250
  63. package/dist/src/config/config-editable-keys.js +4 -7
  64. package/dist/src/config/config-fallbacks.js +81 -101
  65. package/dist/src/config/config-field-checks.js +66 -37
  66. package/dist/src/config/config-keys.js +5 -20
  67. package/dist/src/config/config-live.js +4 -28
  68. package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
  69. package/dist/src/config/config-request.js +153 -102
  70. package/dist/src/config/config-schema.js +9 -11
  71. package/dist/src/config/config-selector-checks.js +1 -1
  72. package/dist/src/config/config-validation-agents.js +2 -2
  73. package/dist/src/config/config-validation-roles.js +4 -52
  74. package/dist/src/config/config-validation.js +4 -38
  75. package/dist/src/config/config-views.js +9 -34
  76. package/dist/src/config/config-write.js +6 -23
  77. package/dist/src/config/config.js +4 -5
  78. package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
  79. package/dist/src/doctor/doctor-backlog-plans.js +24 -24
  80. package/dist/src/doctor/doctor-backlog.js +7 -5
  81. package/dist/src/doctor/doctor-check-names.js +3 -6
  82. package/dist/src/doctor/doctor-checks.js +5 -5
  83. package/dist/src/doctor/doctor-claude-checks.js +44 -28
  84. package/dist/src/doctor/doctor-codex-checks.js +6 -6
  85. package/dist/src/doctor/doctor-contention-checks.js +44 -112
  86. package/dist/src/doctor/doctor-fallback-checks.js +2 -2
  87. package/dist/src/doctor/doctor-holds.js +6 -45
  88. package/dist/src/doctor/doctor-install.js +1 -1
  89. package/dist/src/doctor/doctor-launch-services.js +1 -1
  90. package/dist/src/doctor/doctor-model-checks.js +178 -94
  91. package/dist/src/doctor/doctor-notify.js +2 -2
  92. package/dist/src/doctor/doctor-opencode-checks.js +5 -4
  93. package/dist/src/doctor/doctor-orphans.js +120 -20
  94. package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
  95. package/dist/src/doctor/doctor-pi-checks.js +2 -2
  96. package/dist/src/doctor/doctor-principles.js +3 -3
  97. package/dist/src/doctor/doctor-project-checks.js +3 -3
  98. package/dist/src/doctor/doctor-role-notes.js +1 -1
  99. package/dist/src/doctor/doctor.js +8 -11
  100. package/dist/src/events/event-format.js +30 -54
  101. package/dist/src/events/event-labels.js +1 -5
  102. package/dist/src/events/event-legacy.js +1 -3
  103. package/dist/src/events/{notify.js → event-notify.js} +2 -2
  104. package/dist/src/events/event-read.js +1 -1
  105. package/dist/src/failure/failure-cluster.js +6 -6
  106. package/dist/src/failure/failure-data.js +8 -4
  107. package/dist/src/failure/failure-render.js +44 -34
  108. package/dist/src/failure/failure-state-change.js +4 -34
  109. package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
  110. package/dist/src/files/files.js +30 -0
  111. package/dist/src/files/json-object.js +8 -8
  112. package/dist/src/files/stat-cache.js +12 -8
  113. package/dist/src/gates/budget-gates.js +85 -84
  114. package/dist/src/gates/gate-polls.js +27 -68
  115. package/dist/src/gates/pause-gates.js +16 -2
  116. package/dist/src/gates/readiness.js +1 -1
  117. package/dist/src/gates/role-cap-gates.js +44 -35
  118. package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
  119. package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
  120. package/dist/src/git/git.js +75 -45
  121. package/dist/src/git/worktree-pool.js +3 -6
  122. package/dist/src/gui/gui-admin-endpoints.js +3 -3
  123. package/dist/src/gui/gui-backlog-commands.js +4 -4
  124. package/dist/src/gui/gui-backlog-endpoints.js +61 -41
  125. package/dist/src/gui/gui-command.js +43 -24
  126. package/dist/src/gui/gui-history-endpoints.js +100 -52
  127. package/dist/src/gui/gui-operator-commands.js +22 -23
  128. package/dist/src/gui/gui-prompt-commands.js +23 -23
  129. package/dist/src/gui/gui-report-endpoints.js +1 -1
  130. package/dist/src/history/history-data.js +71 -44
  131. package/dist/src/history/history-queued.js +49 -27
  132. package/dist/src/history/history.js +51 -33
  133. package/dist/src/inbox/prompt-commands.js +59 -42
  134. package/dist/src/init/init.js +88 -40
  135. package/dist/src/merge/merge-backlog-conflicts.js +1 -1
  136. package/dist/src/merge/merge-core.js +1 -1
  137. package/dist/src/merge/merge-drain.js +8 -11
  138. package/dist/src/merge/merge-pipeline.js +6 -7
  139. package/dist/src/merge/merge-post-review-drain.js +10 -13
  140. package/dist/src/merge/merge-requeue.js +1 -1
  141. package/dist/src/merge/merge-slot.js +0 -8
  142. package/dist/src/merge/merge-verify.js +8 -18
  143. package/dist/src/notes-cli.js +52 -42
  144. package/dist/src/operator/operator-commands.js +76 -44
  145. package/dist/src/operator/operator-intent.js +3 -3
  146. package/dist/src/orchestrator/orchestrator-launch.js +31 -63
  147. package/dist/src/orchestrator/orchestrator-poll.js +27 -32
  148. package/dist/src/orchestrator/orchestrator.js +8 -21
  149. package/dist/src/orchestrator/scheduling-admit.js +101 -68
  150. package/dist/src/orchestrator/scheduling-scope.js +1 -1
  151. package/dist/src/orchestrator/scheduling-skip.js +1 -1
  152. package/dist/src/orchestrator/startup-migrations.js +1 -1
  153. package/dist/src/paths.js +8 -7
  154. package/dist/src/pi/pi-args.js +5 -2
  155. package/dist/src/pi/pi-backend.js +2 -2
  156. package/dist/src/pi/pi-bin.js +3 -3
  157. package/dist/src/pi/pi-event-line.js +1 -1
  158. package/dist/src/pi/pi-models.js +43 -29
  159. package/dist/src/pi/pi-settings.js +43 -20
  160. package/dist/src/pi/pi-stream.js +32 -6
  161. package/dist/src/pi/pi-watchdogs.js +12 -5
  162. package/dist/src/pi/pi.js +2 -2
  163. package/dist/src/pi-extension/context-shake.js +32 -20
  164. package/dist/src/pi-extension/kill-guard.js +69 -0
  165. package/dist/src/process/process-group.js +49 -5
  166. package/dist/src/process/process.js +8 -8
  167. package/dist/src/process/run-marker.js +31 -13
  168. package/dist/src/process/supervisor.js +40 -28
  169. package/dist/src/prompt/dialect.js +1 -1
  170. package/dist/src/prompt/gate-notes.js +2 -2
  171. package/dist/src/prompt/notes-backlog.js +66 -37
  172. package/dist/src/prompt/principles.js +6 -4
  173. package/dist/src/prompt/prompt-followup.js +12 -4
  174. package/dist/src/prompt/prompt.js +88 -38
  175. package/dist/src/report/report-data.js +53 -37
  176. package/dist/src/report/report-fold.js +16 -9
  177. package/dist/src/report/report-render.js +21 -194
  178. package/dist/src/report/report-since-data.js +4 -3
  179. package/dist/src/report/report-since-render.js +173 -0
  180. package/dist/src/report/report-usage-fold.js +8 -8
  181. package/dist/src/report/report.js +46 -22
  182. package/dist/src/review/post-merge-review-render.js +9 -3
  183. package/dist/src/review/post-merge-review.js +1 -1
  184. package/dist/src/review/review-precheck.js +11 -1
  185. package/dist/src/review/review.js +40 -153
  186. package/dist/src/review/secret-leaks.js +26 -16
  187. package/dist/src/roles/role-catalog.js +11 -3
  188. package/dist/src/roles/role-guidance.js +16 -12
  189. package/dist/src/roles/role-render.js +1 -14
  190. package/dist/src/roles/role-view.js +9 -21
  191. package/dist/src/run/acceptance-greps.js +88 -45
  192. package/dist/src/run/run-apply.js +12 -6
  193. package/dist/src/run/run-detail-data.js +121 -68
  194. package/dist/src/run/run-detail-render.js +92 -35
  195. package/dist/src/run/run-detail.js +72 -41
  196. package/dist/src/run/run-prompt.js +63 -50
  197. package/dist/src/run/run-resume.js +76 -29
  198. package/dist/src/run/run-stage.js +135 -84
  199. package/dist/src/run/run-timing.js +8 -2
  200. package/dist/src/run/run-usage.js +31 -1
  201. package/dist/src/run/run-verdict.js +119 -50
  202. package/dist/src/run/stage-check.js +49 -32
  203. package/dist/src/run/stage-checks-shared.js +28 -0
  204. package/dist/src/run/stage-name-checks.js +111 -92
  205. package/dist/src/run/stage-path-checks.js +69 -42
  206. package/dist/src/run/stage-prose-checks.js +55 -40
  207. package/dist/src/runner/runner-outcome.js +6 -1
  208. package/dist/src/runner/runner-pi.js +3 -10
  209. package/dist/src/runner/runner-recovery.js +1 -1
  210. package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
  211. package/dist/src/runner/runner-run.js +97 -35
  212. package/dist/src/runner/runner-state-load.js +12 -9
  213. package/dist/src/runner/runner.js +41 -25
  214. package/dist/src/scheduling/claims.js +0 -4
  215. package/dist/src/scheduling/scheduling.js +53 -66
  216. package/dist/src/status/status-data.js +2 -2
  217. package/dist/src/status/status-role-notes.js +2 -2
  218. package/dist/src/status/status-roles.js +9 -17
  219. package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
  220. package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
  221. package/dist/src/team/team-hold.js +98 -65
  222. package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
  223. package/dist/src/team/team-polls.js +28 -126
  224. package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
  225. package/dist/src/team/team-state.js +4 -13
  226. package/dist/src/text/markdown.js +1 -1
  227. package/dist/src/text/phrases.js +20 -15
  228. package/dist/src/text/text.js +3 -3
  229. package/dist/src/ui/alert-feeds.js +49 -73
  230. package/dist/src/ui/badges.js +3 -13
  231. package/dist/src/ui/gui/gui-client-boot.js +2 -2
  232. package/dist/src/ui/gui/gui-client-composer.js +1 -1
  233. package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
  234. package/dist/src/ui/gui/gui-client-drawer.js +1 -1
  235. package/dist/src/ui/gui/gui-client-history.js +28 -14
  236. package/dist/src/ui/gui/gui-client-model.js +0 -12
  237. package/dist/src/ui/gui/gui-client-pending.js +7 -6
  238. package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
  239. package/dist/src/ui/gui/gui-client-report.js +1 -1
  240. package/dist/src/ui/gui/gui-client-roles.js +0 -1
  241. package/dist/src/ui/gui/gui-client-settings.js +43 -84
  242. package/dist/src/ui/gui/gui-client-team.js +4 -4
  243. package/dist/src/ui/gui/gui-client.js +7 -1
  244. package/dist/src/ui/progress-data.js +53 -40
  245. package/dist/src/ui/run-progress-model.js +9 -2
  246. package/dist/src/ui/status-model.js +2 -24
  247. package/dist/src/ui/status-payload.js +3 -7
  248. package/dist/src/ui/status-render.js +5 -9
  249. package/dist/src/ui/team-alert-backlog.js +6 -6
  250. package/dist/src/ui/team-alert-status.js +9 -170
  251. package/dist/src/ui/team-alert-tail.js +19 -215
  252. package/dist/src/ui/team-alerts.js +7 -8
  253. package/dist/src/ui/transcript-run.js +27 -16
  254. package/dist/src/ui/tui/tui-editor-answer.js +12 -10
  255. package/dist/src/ui/tui/tui-editor-attach.js +4 -3
  256. package/dist/src/ui/tui/tui-editor-backends.js +4 -3
  257. package/dist/src/ui/tui/tui-editor-brief.js +6 -9
  258. package/dist/src/ui/tui/tui-editor-budget.js +4 -3
  259. package/dist/src/ui/tui/tui-editor-config.js +80 -68
  260. package/dist/src/ui/tui/tui-editor-entry.js +12 -20
  261. package/dist/src/ui/tui/tui-editor-pause.js +51 -57
  262. package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
  263. package/dist/src/ui/tui/tui-editor-queued.js +11 -14
  264. package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
  265. package/dist/src/ui/tui/tui-editor-release.js +4 -3
  266. package/dist/src/ui/tui/tui-editor-retire.js +7 -7
  267. package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
  268. package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
  269. package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
  270. package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
  271. package/dist/src/ui/tui/tui-editor-search.js +4 -3
  272. package/dist/src/ui/tui/tui-editor-session.js +17 -4
  273. package/dist/src/ui/tui/tui-editors.js +1 -7
  274. package/dist/src/ui/tui/tui-frame.js +182 -134
  275. package/dist/src/ui/tui/tui-guard.js +20 -0
  276. package/dist/src/ui/tui/tui-input.js +159 -92
  277. package/dist/src/ui/tui/tui-keymap.js +51 -29
  278. package/dist/src/ui/tui/tui-keys.js +1 -1
  279. package/dist/src/ui/tui/tui-pane-activity.js +3 -3
  280. package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
  281. package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
  282. package/dist/src/ui/tui/tui-pane-health.js +252 -228
  283. package/dist/src/ui/tui/tui-pane-history.js +61 -70
  284. package/dist/src/ui/tui/tui-pane-pending.js +284 -247
  285. package/dist/src/ui/tui/tui-pane-queued.js +25 -27
  286. package/dist/src/ui/tui/tui-pane-report.js +2 -1
  287. package/dist/src/ui/tui/tui-pane-session.js +11 -7
  288. package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
  289. package/dist/src/ui/tui/tui-pane.js +14 -11
  290. package/dist/src/ui/tui/tui-panes.js +29 -32
  291. package/dist/src/ui/tui/tui.js +3 -4
  292. package/dist/src/verdict/fix-claim.js +76 -54
  293. package/dist/src/verdict/refusal.js +1 -1
  294. package/package.json +5 -5
  295. package/dist/src/concurrency/provider-lease.js +0 -105
  296. package/dist/src/runner/model-fallback.js +0 -76
  297. package/dist/src/runner/runner-fallback.js +0 -66
  298. package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
  299. package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
  300. /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
  301. /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
  302. /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
  303. /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
  304. /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
  305. /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
@@ -0,0 +1,173 @@
1
+ import { compactTokens, usd } from "../text/format.js";
2
+ import { durationLabel } from "../cli/cli-args.js";
3
+ import { boundedWindowLine, sinceWindowLine } from "../text/datetime.js";
4
+ import { SPARSE_WINDOW_NOTE } from "../events/event-window.js";
5
+ import { foldBaseRoles } from "./report-fold.js";
6
+ import { rankedRoleMap, rankedRoleLine, promptTokensLine, pushRankedRoleLine, pushPresentRankedLine, pushProviderCostLines, pushReportSummary, costPerCommitPairsFrom, costPerCommitLine, } from "./report-render.js";
7
+ /** Render a trailing-window totals report as Markdown — the aggregate sibling of the
8
+ * `logs --since` raw stream ("how much" here, "what exactly" there). Pure function of
9
+ * SinceReport: no I/O, no clock reads (the window line's local time is rendered from the
10
+ * collector's captured cutoff, so the printed window matches the aggregated one). */
11
+ export function renderSinceReportMarkdown(data) {
12
+ const lines = [];
13
+ pushSinceWindowHeader(lines, data);
14
+ pushSinceSummary(lines, data);
15
+ pushSinceRoleLines(lines, data);
16
+ pushSinceProviderModelLines(lines, data);
17
+ pushSinceMainRedLines(lines, data);
18
+ pushSinceFrictionLines(lines, data);
19
+ pushSinceDeferralLines(lines, data);
20
+ pushSinceCommitLines(lines, data);
21
+ pushSinceCostLines(lines, data);
22
+ pushSinceCoverageLines(lines, data);
23
+ return lines.join("\n");
24
+ }
25
+ /** The window header: the title, the bounded-or-since window line and the role scope line. */
26
+ function pushSinceWindowHeader(lines, data) {
27
+ lines.push("# tumwater usage report");
28
+ lines.push("");
29
+ lines.push(data.untilMs !== undefined && data.toIso !== undefined
30
+ ? boundedWindowLine(durationLabel(data.sinceMs), durationLabel(data.untilMs), data.fromIso, data.toIso)
31
+ : sinceWindowLine(durationLabel(data.sinceMs), data.fromIso));
32
+ if (data.role !== undefined)
33
+ lines.push(`role: ${data.role}`);
34
+ lines.push("");
35
+ }
36
+ /** The shared totals voice (minus features/bugs/filing cells, stated at the tail), gated
37
+ * to the team report like the day sibling's. */
38
+ function pushSinceSummary(lines, data) {
39
+ const t = data.totals;
40
+ // Same totals voice as the day report, minus the features/bugs and filing cells: those tallies
41
+ // come from day-granular backlog-file dates that cannot subdivide a sub-day window, so the
42
+ // final line below states the omission instead of showing a day-rounded number.
43
+ pushReportSummary(lines, t, "", data.role !== undefined);
44
+ lines.push("");
45
+ }
46
+ /** Runs / tokens / prompt-tokens / cost breakdowns by role. */
47
+ function pushSinceRoleLines(lines, data) {
48
+ lines.push(rankedRoleLine("Runs by role", rankedRoleMap(data.runsByRole), String));
49
+ // Same breakdown for token spend: ranked by tokens desc then name asc, zero-token roles
50
+ // omitted — an all-zero window renders "-" like the other lines. Output tokens are the
51
+ // resource the budget charges, so this is the line an operator reads to find which role's
52
+ // prompt loop burned the window's tokens.
53
+ pushRankedRoleLine(lines, "Tokens by role", rankedRoleMap(data.tokensByRole), compactTokens);
54
+ // The prompt side of the same breakdown, straight after the output-token line: how many
55
+ // prompt tokens each role sent and the share of them its backend served from cache — the
56
+ // input side of the bill an operator cannot see from Tokens by role. Zero-prompt roles are
57
+ // omitted, and an all-zero window renders "-" like the sibling lines.
58
+ lines.push(promptTokensLine(rankedRoleMap(data.promptTokensByRole ?? {}), rankedRoleMap(data.cacheReadTokensByRole ?? {})));
59
+ // Same breakdown for spend: ranked by spend desc then name asc, zero-spend roles omitted —
60
+ // an all-zero window renders "-" like the runs line.
61
+ pushRankedRoleLine(lines, "Cost by role", rankedRoleMap(data.costByRole), usd);
62
+ }
63
+ /** The provider cost lines and the per-model line, gated to the team report. */
64
+ function pushSinceProviderModelLines(lines, data) {
65
+ // The same breakdown per PROVIDER (plans: per-provider spend), gated to the team report
66
+ // like its day sibling. SinceReport.costByProvider is always present (empty when none), so
67
+ // the zero-means-absent gate prints no line for a window with no provider spend.
68
+ if (data.role === undefined) {
69
+ pushProviderCostLines(lines, () => rankedRoleMap(data.costByProvider), () => rankedRoleMap(data.notionalByProvider));
70
+ // The same breakdown per MODEL (each run_start's agent-qualified selector — the model a
71
+ // team mixing providers, per-role overrides and fallbacks actually ran on), gated to the
72
+ // team report like the provider lines: a model is not a role, so a role-scoped window
73
+ // omits it. SinceReport.runsByModel is always present, so a window with no modelled
74
+ // start prints no line (keeping a model-less day's output unchanged), exactly as the
75
+ // provider lines stay absent when no provider spent.
76
+ pushPresentRankedLine(lines, "Runs by model", rankedRoleMap(data.runsByModel));
77
+ }
78
+ }
79
+ /** Main's red time and the test/merge culprits behind it, gated like the day sibling. */
80
+ function pushSinceMainRedLines(lines, data) {
81
+ // Main's red wall-clock over the window (main's own, not any role's — a role-scoped
82
+ // collector carries 0, so this never prints for a scoped window). Zero-absent: no line for
83
+ // a window main was never red.
84
+ if (data.mainRedMs > 0)
85
+ lines.push(`**Main red:** ${durationLabel(data.mainRedMs)}`);
86
+ // The same "what kept main red" detail named by failing test — which tests main's own red
87
+ // checks named over the window. Team-only like main's red time, so a role-scoped window
88
+ // omits it; zero-absent: no line when the window's red checks named no test.
89
+ if (data.role === undefined) {
90
+ pushPresentRankedLine(lines, "Main red by test", rankedRoleMap(data.mainRedByTest ?? {}));
91
+ // The same "what kept main red" detail named by merge — the landed merges the watch's
92
+ // own main_culprit events blamed, mirroring the failing-test line right above. Team-only
93
+ // like main's red time and zero-absent: no line when the window blamed no merge.
94
+ pushPresentRankedLine(lines, "Main red by merge", rankedRoleMap(data.mainRedByCulprit ?? {}));
95
+ }
96
+ }
97
+ /** The review-friction and failed-run breakdowns by role. */
98
+ function pushSinceFrictionLines(lines, data) {
99
+ // The same breakdown for review friction: rejections per role from review_rejected events;
100
+ // a window with none renders "-" like the other lines. The post-merge line is the
101
+ // afterMerge: true subset — rejections that reached an already-merged commit.
102
+ pushRankedRoleLine(lines, "Rejections by role", rankedRoleMap(data.rejectionsByRole));
103
+ pushRankedRoleLine(lines, "Post-merge rejections by role", rankedRoleMap(data.postMergeRejectionsByRole));
104
+ pushRankedRoleLine(lines, "Revisions by role", rankedRoleMap(data.revisionsByRole));
105
+ pushRankedRoleLine(lines, "Refusals by role", rankedRoleMap(data.refusalsByRole));
106
+ // The same breakdown for outright failures: error-class run_ends per role under the
107
+ // digest's OUTCOME_CLASS — the runs that burned agent time and merged nothing without
108
+ // going through review, which rejections/revisions would miss entirely. A window with
109
+ // none renders "-" like the other lines.
110
+ pushRankedRoleLine(lines, "Failed runs by role", rankedRoleMap(data.failedRunsByRole));
111
+ pushRankedRoleLine(lines, "Image reroutes by role", rankedRoleMap(data.imageReroutesByRole));
112
+ }
113
+ /** Deferred maintenance runs by reason code, gated to the team report. */
114
+ function pushSinceDeferralLines(lines, data) {
115
+ // Deferred maintenance runs per reason code, the --since twin of the day render's line:
116
+ // how many due maintenance runs stood down and why (backlog / no-work / plans-full).
117
+ // Gated to the team report — a reason is not a role, so a role-scoped window omits it.
118
+ // Zero-means-absent: a window with no deferral prints no line.
119
+ if (data.role === undefined) {
120
+ pushPresentRankedLine(lines, "Deferred runs by reason", rankedRoleMap(data.deferredByReason ?? {}));
121
+ }
122
+ }
123
+ /** Commits by role and the merged/merge-failure split, gated to the team report. */
124
+ function pushSinceCommitLines(lines, data) {
125
+ // The merged-commits split by base role, matching the day render's unfiltered line: the
126
+ // window's totals.commits carries the count; this line says which role's commits it was.
127
+ lines.push(rankedRoleLine("Commits by role", rankedRoleMap(data.commitsByRole), String));
128
+ // The merged-outcomes split by base role, mirroring the day render's line: which roles'
129
+ // changes reach main and which bounce off the gate (merge_failed). Gated to the team
130
+ // report — a role-scoped window omits merge (ROLE_SCOPE_NOTE), so the per-role
131
+ // attribution would contradict it. Zero-means-absent: the failures line prints only when
132
+ // some role has one; a merge-free window renders `-` like the other lines.
133
+ if (data.role === undefined) {
134
+ lines.push(rankedRoleLine("Merged by role", rankedRoleMap(data.mergedByRole), String));
135
+ pushPresentRankedLine(lines, "Merge failures by role", rankedRoleMap(data.mergeFailedByRole));
136
+ // WHY the window's merges failed, from each merge_failed's result code — the day
137
+ // report's reason twin of the role line above, so an operator can tell a review
138
+ // rejection (fix the change) from a conflict/block (rebase) from a shutdown abort
139
+ // (capacity). Ranked the same way (count desc then code asc), reason-free only.
140
+ pushPresentRankedLine(lines, "Merge failures by reason", rankedRoleMap(data.mergeFailedByReason ?? {}));
141
+ }
142
+ }
143
+ /** Cost/estimated per commit and the notional Estimated by role lines. */
144
+ function pushSinceCostLines(lines, data) {
145
+ // Spend per merged commit, the --since twin of the day render's `Cost per commit:` line:
146
+ // costByRole is keyed by role id (instances not folded, so `feature-2` joins `feature`),
147
+ // commitsByRole is already base-role keyed. Prints for a spend-free window as `-` and for a
148
+ // role-scoped window from its scoped maps, matching the day render.
149
+ lines.push(costPerCommitLine("Cost per commit", costPerCommitPairsFrom(foldBaseRoles(data.costByRole), data.commitsByRole)));
150
+ // Subscription spend is notional: its own line, only when the window has any — the same
151
+ // conditional/filtered `usd` form the day render's "Estimated by role" uses.
152
+ if (data.notionalByRole !== undefined) {
153
+ pushRankedRoleLine(lines, "Estimated by role", rankedRoleMap(data.notionalByRole), usd);
154
+ // The day render's `Estimated per commit:` twin, after `Estimated by role`, from the same
155
+ // notionalByRole source folded to base roles and the base-keyed commitsByRole.
156
+ lines.push(costPerCommitLine("Estimated per commit", costPerCommitPairsFrom(foldBaseRoles(data.notionalByRole), data.commitsByRole)));
157
+ }
158
+ }
159
+ /** The trailing blank and the exactly-one coverage/backlog-omission note. */
160
+ function pushSinceCoverageLines(lines, data) {
161
+ lines.push("");
162
+ // Exactly one final line, chosen by the collector's coverage proof: either a hedged note
163
+ // that stays true whenever it prints (the oldest retained event lies inside the window —
164
+ // whether from rotation or a log born inside the window), or the one-time statement of the
165
+ // backlog-tally omission. A flat "rotated out" claim would be false for a log that never
166
+ // had the older events, so the wording names only what is provably the case. A role-scoped
167
+ // report already states the omission in ROLE_SCOPE_NOTE, so it does not repeat the --days
168
+ // pointer, which would imply a scoped day report shows the tallies it too omits.
169
+ if (!data.coversFullWindow)
170
+ lines.push(SPARSE_WINDOW_NOTE);
171
+ else if (data.role === undefined)
172
+ lines.push("backlog tallies (features done / bugs fixed / plans added / bugs filed) need the day report (--days)");
173
+ }
@@ -10,7 +10,7 @@ import { addTo } from "../collections.js";
10
10
  import { isPassingFullCheck, isRedFullCheck } from "../build/build-check-events.js";
11
11
  import { eventRole, eventUsage } from "../events/event-read.js";
12
12
  import { finiteNumber, nonNegativeNumber } from "../files/json-object.js";
13
- import { OUTCOME_CLASS } from "../failure/time-spend.js";
13
+ import { OUTCOME_CLASS } from "../failure/failure-time-spend.js";
14
14
  import { commitTier } from "../roles/roles.js";
15
15
  import { baseRoleOf } from "../roles/runner-ids.js";
16
16
  /** Fold one event's `providerCost` map (`Record<provider, {costUsd, notionalCostUsd}>` — the
@@ -139,10 +139,10 @@ function foldRedCheck(target, _ev) {
139
139
  // The merge slot's red full-suite checks, for the day report's red-check count.
140
140
  target.checkFailures++;
141
141
  }
142
- /** Fold a model_fallback_started: one episode per start event, per role. */
143
- function foldFallbackStarted(target, ev) {
144
- // One episode per start event; durations are not summed (an episode can span the window edge).
145
- addTo(target.fallbacksByRole, eventRole(ev), 1);
142
+ /** Fold an image_reroute: one reroute per event, preflight or after a blind read. */
143
+ function foldImageReroute(target, ev) {
144
+ // One reroute per event, preflight or after a blind read.
145
+ addTo(target.imageReroutesByRole, eventRole(ev), 1);
146
146
  }
147
147
  /** Fold a run_deferred: one deferral per event, bucketed by the raw reason code (team-only). */
148
148
  function foldDeferred(target, ev) {
@@ -245,7 +245,7 @@ function foldMergeOutcome(target, ev) {
245
245
  }
246
246
  /** Fold one event into a usage accumulator by dispatching to a per-family builder —
247
247
  * foldRunEnd, foldReviewRejected, foldReviewVerdict, foldRevision, foldPassingCheck,
248
- * foldRedCheck, foldFallbackStarted, foldDeferred, foldMerged,
248
+ * foldRedCheck, foldImageReroute, foldDeferred, foldMerged,
249
249
  * foldRunStarted, foldProviderWait, foldSlotWait, foldCheckWait or foldMergeOutcome — each
250
250
  * named for the family it folds, so how a family folds is found by name. The dispatch holds
251
251
  * no accumulation of its own. All behavior lives in the builders: run_end events add a run
@@ -269,8 +269,8 @@ export function foldUsageEvent(target, ev) {
269
269
  foldPassingCheck(target, ev);
270
270
  else if (isRedFullCheck(ev))
271
271
  foldRedCheck(target, ev);
272
- else if (ev.type === "model_fallback_started")
273
- foldFallbackStarted(target, ev);
272
+ else if (ev.type === "image_reroute")
273
+ foldImageReroute(target, ev);
274
274
  else if (ev.type === "run_deferred")
275
275
  foldDeferred(target, ev);
276
276
  else if (ev.type === "merged")
@@ -6,7 +6,8 @@ import { collectReport } from "./report-data.js";
6
6
  import { collectReportSince } from "./report-since-data.js";
7
7
  import { collectFailureReport, collectFailureReportSince } from "../failure/failure-data.js";
8
8
  import { renderFailureMarkdown } from "../failure/failure-render.js";
9
- import { renderReportMarkdown, renderSinceReportMarkdown } from "./report-render.js";
9
+ import { renderReportMarkdown } from "./report-render.js";
10
+ import { renderSinceReportMarkdown } from "./report-since-render.js";
10
11
  import { fail, say, sayJson, sayJsonOrRender } from "../cli/cli-output.js";
11
12
  import { followView } from "../cli/follow-view.js";
12
13
  import { failRivalShapes, failUntilShorterThanSince, flagValue, parseCountFlag, parseRoleListScope, parseSinceFlag } from "../cli/cli-args.js";
@@ -26,31 +27,47 @@ export async function cmdReport(root, args, seams = {}) {
26
27
  // digest, whose day and --since collectors take the same optional list of base roles.
27
28
  const role = parseRoleListScope(root, args);
28
29
  const follow = hasFollowFlag(args);
30
+ failOnFollowJson(follow, args);
31
+ if (ms !== null) {
32
+ sinceReportShape(root, args, ms, role, untilMs, follow);
33
+ return;
34
+ }
35
+ await dayReportShape(root, args, reportDays(args), role, follow, seams);
36
+ }
37
+ /** `-f/--follow` with `--json` is refused before either shape dispatches: the live view
38
+ * repaints a document, `--json` prints one — drop one of them. */
39
+ function failOnFollowJson(follow, args) {
29
40
  if (follow && args.includes("--json"))
30
41
  fail("report --json prints one document; -f/--follow repaints a live view — drop one of them");
31
- if (ms !== null) {
32
- // --days is still a rival shape: it counts whole local days, while --since totals a trailing
33
- // window. --failures is not a rival anymore — the digest has a windowed-since collector that
34
- // reuses the day digest's own sections, so `report --failures --since <d>` dispatches it
35
- // through the existing --json / render choice; --until bounds its window's end.
36
- if (follow)
37
- failRivalShapes("report --since", "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
38
- if (args.includes("--days"))
39
- failRivalShapes("report --since", "--days", "--days counts whole local days; --since totals a trailing window");
40
- if (args.includes("--failures")) {
41
- const digest = collectFailureReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
42
- if (args.includes("--json"))
43
- sayJson(digest);
44
- else
45
- say(renderFailureMarkdown(digest));
46
- return;
47
- }
48
- // --json swaps the renderer for the collector's own payload, exactly as status --json
49
- // does: bounds and cap checks above are shared, only the printing differs.
50
- const since = collectReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
51
- sayJsonOrRender(args, since, renderSinceReportMarkdown);
42
+ }
43
+ /** The `--since`/`--until` trailing-window shape: its rival-shape guards against
44
+ * `-f/--follow` and `--days`, then the `--failures` digest or the plain totals, both through
45
+ * the `--json` / render choice. Fatal on any rival flag; returns only after printing. */
46
+ function sinceReportShape(root, args, ms, role, untilMs, follow) {
47
+ // --days is still a rival shape: it counts whole local days, while --since totals a trailing
48
+ // window. --failures is not a rival anymore — the digest has a windowed-since collector that
49
+ // reuses the day digest's own sections, so `report --failures --since <d>` dispatches it
50
+ // through the existing --json / render choice; --until bounds its window's end.
51
+ if (follow)
52
+ failRivalShapes("report --since", "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
53
+ if (args.includes("--days"))
54
+ failRivalShapes("report --since", "--days", "--days counts whole local days; --since totals a trailing window");
55
+ if (args.includes("--failures")) {
56
+ const digest = collectFailureReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
57
+ if (args.includes("--json"))
58
+ sayJson(digest);
59
+ else
60
+ say(renderFailureMarkdown(digest));
52
61
  return;
53
62
  }
63
+ // --json swaps the renderer for the collector's own payload, exactly as status --json
64
+ // does: bounds and cap checks above are shared, only the printing differs.
65
+ const since = collectReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
66
+ sayJsonOrRender(args, since, renderSinceReportMarkdown);
67
+ }
68
+ /** The `--days` parse: the explicit flag's value checked against REPORT_MAX_DAYS, or the
69
+ * default series length when absent. Fatal on a value past the shared bound. */
70
+ function reportDays(args) {
54
71
  const daysRaw = flagValue(args, "--days");
55
72
  let days = REPORT_DEFAULT_DAYS;
56
73
  if (daysRaw !== null) {
@@ -61,6 +78,13 @@ export async function cmdReport(root, args, seams = {}) {
61
78
  // rejected 0, non-decimals, and a missing value.
62
79
  days = parseCountFlag("--days", daysRaw, REPORT_MAX_DAYS);
63
80
  }
81
+ return days;
82
+ }
83
+ /** The whole-day report shape: the `-f/--follow` live repaint, the `--failures --json`
84
+ * digest, the plain `--json` payload, or the day Markdown/failure-digest render. Fatal on a
85
+ * `follow && --json` clash (already refused by cmdReport's failOnFollowJson); returns only
86
+ * after printing. */
87
+ async function dayReportShape(root, args, days, role, follow, seams) {
64
88
  // --failures --json prints the collector's own payload (the FailureReportData object), the
65
89
  // report --json and doctor --json precedent: the time-and-spend fold gave the digest a
66
90
  // stable data shape, so the machine-readable form is the data itself, not a re-parse of the
@@ -33,7 +33,9 @@ export function postReviewState(entry) {
33
33
  export const POST_REVIEW_STATES = ["waiting", "failing", "rejected", "fixed"];
34
34
  /** The bracketed status suffix one post-merge review entry carries on every observer surface
35
35
  * (`tumwater reviews`, the dashboard's Post-merge reviews card, and the TUI Pending pane's
36
- * review block): the reviewer's recorded rejected reasons when `rejected` is set; else the
36
+ * review block): the reviewer's recorded rejected reasons when `rejected` is set — or just
37
+ * `[rejected]` when the recorded list is empty, so a rejection with no reasons never renders
38
+ * a dangling `rejected — `; else the
37
39
  * number of verdict-less review runs when `failures` is positive, naming the
38
40
  * last failure's reason beside the count when `lastFailure` is a non-empty string; else `""`.
39
41
  * Reasons are squashed to 60 as `tumwater reviews` has always printed them, and the failure
@@ -44,8 +46,12 @@ export function postReviewStatusLabel(entry) {
44
46
  switch (postReviewState(entry)) {
45
47
  case "fixed":
46
48
  return `[fixed — the repair ${shortSha(entry.fixed)} already merged on main]`;
47
- case "rejected":
48
- return `[rejected — ${entry.rejected.map((r) => squash(r, 60)).join("; ")}]`;
49
+ case "rejected": {
50
+ const reasons = entry.rejected; // postReviewState only says "rejected" when rejected is an array
51
+ return reasons.length === 0
52
+ ? "[rejected]"
53
+ : `[rejected — ${reasons.map((r) => squash(r, 60)).join("; ")}]`;
54
+ }
49
55
  case "failing": {
50
56
  const failures = entry.failures; // postReviewState only says "failing" when failures > 0
51
57
  if (typeof entry.lastFailure === "string" && entry.lastFailure.length > 0) {
@@ -13,7 +13,7 @@ import { buildReviewPrompt } from "../prompt/gate-prompts.js";
13
13
  import { shortSha } from "../text/format.js";
14
14
  import { errorMessage } from "../text/text.js";
15
15
  import { revisionRoundOf } from "../git/commit-message.js";
16
- import { REVISION_BACKSTOP } from "../runner/revision.js";
16
+ import { REVISION_BACKSTOP } from "../runner/runner-revision.js";
17
17
  import { eventRole } from "../events/event-read.js";
18
18
  import { baseRoleOf } from "../roles/runner-ids.js";
19
19
  import { fixLine } from "../verdict/reply-contract.js";
@@ -56,8 +56,18 @@ export async function gateBuildPrecheck(ctx, state, head, reject) {
56
56
  // The gate's one check invocation — the pre-check and its one flake re-run below are the
57
57
  // same run in the same scope, worktree, and config with the same resolved timeout, so the
58
58
  // scope and timeout resolution live here once and cannot drift between the two calls.
59
- const runGateCheck = () => runScopedBuildCheck(root, role, "gate", wt, config, ctx.buildCheckTimeoutMs ?? BUILD_CHECK_TIMEOUT_MS, ctx.sampleSleep ?? sampleSleepClock, undefined, checkWaitStage(root, [role]));
59
+ const runGateCheck = () => runScopedBuildCheck(root, role, "gate", wt, config, ctx.buildCheckTimeoutMs ?? BUILD_CHECK_TIMEOUT_MS, ctx.sampleSleep ?? sampleSleepClock, undefined, checkWaitStage(root, [role]), undefined, ctx.signal);
60
60
  const preCheck = await runGateCheck();
61
+ // A check the harness stopped on the verify's abort (a user abort or a shutdown) is not the
62
+ // tree's verdict and not a flake: fail closed as aborted before the re-run, the quarantine
63
+ // and the attribution — the signal fires MID-check (runScriptGroup returns an `aborted`
64
+ // skip), and `reviewPinnedChange` settles the verify `aborted` on this result so an abort
65
+ // never has to wait a parked check out. A signal already aborted before the check started is
66
+ // caught by the `aborted` checks in the attribution path below (the check ran and reached
67
+ // its own verdict), so the pre-existing shutdown case keeps its two attempts.
68
+ if (preCheck?.outcome.skipReason === "aborted") {
69
+ return { resolved: { decision: "failed", aborted: true } };
70
+ }
61
71
  if (preCheck) {
62
72
  const { check } = preCheck;
63
73
  let { outcome } = preCheck;