tumwater 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (359) hide show
  1. package/README.md +63 -28
  2. package/dist/build-info.json +2 -2
  3. package/dist/src/agent/agent-args.js +24 -0
  4. package/dist/src/agent/agent-backend.js +2 -2
  5. package/dist/src/agent/backend-traits.js +60 -0
  6. package/dist/src/agent/claude/claude-account.js +61 -8
  7. package/dist/src/agent/claude/claude-args.js +12 -15
  8. package/dist/src/agent/claude/claude-backend.js +51 -41
  9. package/dist/src/agent/claude/claude-env.js +7 -3
  10. package/dist/src/agent/claude/claude-stream.js +7 -16
  11. package/dist/src/agent/image-input.js +107 -0
  12. package/dist/src/agent/notes-shim.js +41 -0
  13. package/dist/src/agent/opencode/opencode-args.js +61 -0
  14. package/dist/src/agent/opencode/opencode-backend.js +255 -0
  15. package/dist/src/agent/opencode/opencode-catalog.js +184 -0
  16. package/dist/src/agent/opencode/opencode-config.js +188 -0
  17. package/dist/src/agent/opencode/opencode-env.js +36 -0
  18. package/dist/src/agent/opencode/opencode-session-id.js +37 -0
  19. package/dist/src/agent/opencode/opencode-stream.js +278 -0
  20. package/dist/src/agent/role-notes-write.js +16 -32
  21. package/dist/src/agent/run-agent.js +95 -18
  22. package/dist/src/agent/session-ledger.js +57 -1
  23. package/dist/src/backlog/backlog-archive.js +296 -0
  24. package/dist/src/backlog/backlog-cli.js +573 -0
  25. package/dist/src/backlog/backlog-eligibility.js +332 -43
  26. package/dist/src/backlog/backlog-md.js +79 -17
  27. package/dist/src/backlog/backlog-render.js +62 -11
  28. package/dist/src/backlog/backlog-structure.js +261 -58
  29. package/dist/src/backlog/backlog-write.js +178 -441
  30. package/dist/src/backlog/backlog.js +113 -16
  31. package/dist/src/backlog/question-answer.js +339 -0
  32. package/dist/src/baseline/main-baseline.js +75 -88
  33. package/dist/src/baseline/main-red.js +32 -30
  34. package/dist/src/{brief-edit.js → brief/brief-edit.js} +12 -11
  35. package/dist/src/{brief.js → brief/brief.js} +33 -8
  36. package/dist/src/budget/budget.js +32 -15
  37. package/dist/src/budget/fallback-breaker.js +8 -7
  38. package/dist/src/build/build-check-detect.js +8 -7
  39. package/dist/src/build/build-check-events.js +68 -22
  40. package/dist/src/build/build-check-report.js +22 -23
  41. package/dist/src/build/build-check-scoped.js +51 -62
  42. package/dist/src/build/build-check.js +15 -14
  43. package/dist/src/build/build-info.js +2 -1
  44. package/dist/src/build/build-stage.js +23 -22
  45. package/dist/src/build/dep-install.js +22 -21
  46. package/dist/src/build/host-sleep.js +1 -1
  47. package/dist/src/change/change-data.js +13 -11
  48. package/dist/src/change/change-render.js +11 -10
  49. package/dist/src/cli/brief-command.js +9 -7
  50. package/dist/src/cli/cli-args.js +25 -5
  51. package/dist/src/cli/cli-command-args.js +43 -10
  52. package/dist/src/cli/cli-editor.js +13 -1
  53. package/dist/src/cli/cli-flag-specs.js +31 -6
  54. package/dist/src/cli/cli-marker-commands.js +13 -10
  55. package/dist/src/cli/cli-query-commands.js +439 -56
  56. package/dist/src/cli/cli-repo-gate.js +1 -1
  57. package/dist/src/cli/cli-run.js +124 -68
  58. package/dist/src/cli/completion.js +70 -12
  59. package/dist/src/cli/config-commands.js +14 -11
  60. package/dist/src/cli/help.js +264 -124
  61. package/dist/src/cli/log-commands.js +127 -21
  62. package/dist/src/cli/notify-command.js +33 -18
  63. package/dist/src/cli/question-commands.js +13 -224
  64. package/dist/src/cli.js +78 -36
  65. package/dist/src/collections.js +66 -16
  66. package/dist/src/concurrency/check-permit.js +16 -17
  67. package/dist/src/concurrency/lock.js +6 -5
  68. package/dist/src/concurrency/provider-lease.js +69 -0
  69. package/dist/src/concurrency/semaphore.js +258 -36
  70. package/dist/src/config/config-aliases.js +106 -0
  71. package/dist/src/config/config-editable-keys.js +3 -3
  72. package/dist/src/config/config-fallbacks.js +379 -0
  73. package/dist/src/config/config-field-checks.js +19 -10
  74. package/dist/src/config/config-keys.js +160 -0
  75. package/dist/src/config/config-live.js +64 -12
  76. package/dist/src/config/config-schema.js +34 -22
  77. package/dist/src/config/config-validation.js +132 -48
  78. package/dist/src/config/config-views.js +107 -325
  79. package/dist/src/config/config-write.js +138 -177
  80. package/dist/src/config/config.js +60 -49
  81. package/dist/src/config/model-selector.js +28 -2
  82. package/dist/src/doctor/doctor-backlog.js +494 -68
  83. package/dist/src/doctor/doctor-checks.js +509 -58
  84. package/dist/src/doctor/doctor-holds.js +139 -78
  85. package/dist/src/doctor/doctor-install.js +30 -0
  86. package/dist/src/doctor/doctor-launch-services.js +2 -2
  87. package/dist/src/doctor/doctor-model-checks.js +549 -157
  88. package/dist/src/doctor/doctor-notify.js +2 -3
  89. package/dist/src/doctor/doctor-orphans.js +10 -9
  90. package/dist/src/doctor/doctor-render.js +36 -9
  91. package/dist/src/doctor/doctor.js +76 -25
  92. package/dist/src/doctor/image-probe.js +244 -0
  93. package/dist/src/doctor/probe.js +64 -0
  94. package/dist/src/events/event-format.js +219 -106
  95. package/dist/src/events/event-labels.js +116 -0
  96. package/dist/src/events/event-legacy.js +140 -0
  97. package/dist/src/events/event-read.js +58 -22
  98. package/dist/src/events/event-window.js +27 -7
  99. package/dist/src/events/events.js +18 -14
  100. package/dist/src/events/notify.js +80 -30
  101. package/dist/src/failure/failure-cluster.js +12 -9
  102. package/dist/src/failure/failure-data.js +96 -48
  103. package/dist/src/failure/failure-render.js +69 -58
  104. package/dist/src/failure/failure-state-change.js +33 -26
  105. package/dist/src/failure/time-spend.js +79 -59
  106. package/dist/src/files/file-queue.js +16 -4
  107. package/dist/src/files/files.js +18 -6
  108. package/dist/src/files/json-object.js +18 -3
  109. package/dist/src/files/tail.js +1 -1
  110. package/dist/src/gates/bootstrap-gates.js +5 -4
  111. package/dist/src/gates/budget-gates.js +250 -34
  112. package/dist/src/gates/disk-gate.js +18 -18
  113. package/dist/src/gates/gate-polls.js +117 -69
  114. package/dist/src/gates/maintenance-quota.js +29 -29
  115. package/dist/src/gates/pause-gates.js +11 -11
  116. package/dist/src/gates/readiness.js +2 -1
  117. package/dist/src/gates/role-cap-gates.js +16 -21
  118. package/dist/src/gates/startup-gate.js +75 -31
  119. package/dist/src/gates/streak-gate.js +18 -16
  120. package/dist/src/git/commit-message.js +6 -5
  121. package/dist/src/git/git-diff.js +16 -16
  122. package/dist/src/git/git-run.js +8 -8
  123. package/dist/src/git/git.js +79 -59
  124. package/dist/src/git/slots-state.js +14 -5
  125. package/dist/src/git/worktree-pool.js +25 -18
  126. package/dist/src/git/worktree-use.js +15 -14
  127. package/dist/src/git/worktree.js +18 -18
  128. package/dist/src/gui/gui-args.js +10 -14
  129. package/dist/src/gui/gui-backlog-commands.js +273 -0
  130. package/dist/src/gui/gui-command.js +1 -1
  131. package/dist/src/gui/gui-config-commands.js +440 -0
  132. package/dist/src/gui/gui-endpoint-commands.js +16 -716
  133. package/dist/src/gui/gui-endpoint-common.js +49 -0
  134. package/dist/src/gui/gui-endpoints.js +105 -54
  135. package/dist/src/gui/gui-operator-commands.js +145 -0
  136. package/dist/src/gui/gui-prompt-commands.js +234 -0
  137. package/dist/src/gui/gui-server.js +25 -8
  138. package/dist/src/gui/gui-team-commands.js +164 -0
  139. package/dist/src/gui/http-body.js +17 -1
  140. package/dist/src/history/history-data.js +179 -104
  141. package/dist/src/history/history-render.js +76 -0
  142. package/dist/src/history/history.js +29 -70
  143. package/dist/src/inbox/inbox-attachments.js +66 -10
  144. package/dist/src/inbox/inbox-cancel.js +7 -5
  145. package/dist/src/inbox/inbox-edit.js +1 -1
  146. package/dist/src/inbox/inbox-submit.js +44 -4
  147. package/dist/src/inbox/inbox-top.js +83 -0
  148. package/dist/src/inbox/inbox.js +8 -2
  149. package/dist/src/inbox/pending-prompt.js +1 -1
  150. package/dist/src/inbox/prompt-commands.js +114 -73
  151. package/dist/src/init/init-templates.js +16 -14
  152. package/dist/src/init/init.js +12 -11
  153. package/dist/src/loop/leftover.js +28 -27
  154. package/dist/src/loop/loop-fallback.js +2 -2
  155. package/dist/src/loop/loop-pi.js +104 -50
  156. package/dist/src/loop/loop-recovery.js +15 -15
  157. package/dist/src/loop/loop-state.js +130 -13
  158. package/dist/src/loop/loop-tick.js +190 -88
  159. package/dist/src/loop/loop.js +68 -36
  160. package/dist/src/loop/model-fallback.js +3 -2
  161. package/dist/src/loop/revision.js +16 -7
  162. package/dist/src/merge/backlog-conflicts.js +155 -0
  163. package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
  164. package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
  165. package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
  166. package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
  167. package/dist/src/merge/merge-drain.js +212 -0
  168. package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -20
  169. package/dist/src/merge/merge-pipeline.js +99 -0
  170. package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
  171. package/dist/src/merge/merge-queue-render.js +75 -0
  172. package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
  173. package/dist/src/merge/merge-slot.js +340 -0
  174. package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
  175. package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
  176. package/dist/src/merge/merge-verify.js +212 -0
  177. package/dist/src/notes-cli.js +97 -0
  178. package/dist/src/operator/operator-commands.js +113 -45
  179. package/dist/src/operator/operator-intent.js +74 -53
  180. package/dist/src/operator/operator-requests.js +52 -52
  181. package/dist/src/operator/retire.js +47 -28
  182. package/dist/src/operator/revert.js +71 -0
  183. package/dist/src/orchestrator/orchestrator-launch.js +74 -14
  184. package/dist/src/orchestrator/orchestrator-scheduling.js +74 -43
  185. package/dist/src/orchestrator/orchestrator.js +149 -96
  186. package/dist/src/orchestrator/retention.js +29 -12
  187. package/dist/src/orchestrator/startup-migration-steps.js +51 -0
  188. package/dist/src/orchestrator/startup-migrations.js +40 -0
  189. package/dist/src/paths.js +124 -38
  190. package/dist/src/pi/pi-args.js +26 -3
  191. package/dist/src/pi/pi-backend.js +15 -23
  192. package/dist/src/pi/pi-bin.js +18 -12
  193. package/dist/src/pi/pi-event-line.js +17 -1
  194. package/dist/src/pi/pi-failure.js +87 -0
  195. package/dist/src/pi/pi-models.js +126 -38
  196. package/dist/src/pi/pi-run-result.js +1 -1
  197. package/dist/src/pi/pi-settings.js +86 -0
  198. package/dist/src/pi/pi-stream.js +131 -107
  199. package/dist/src/pi/pi-watchdogs.js +4 -3
  200. package/dist/src/pi-extension/bounded-output.js +20 -5
  201. package/dist/src/pi-extension/context-budget.js +3 -1
  202. package/dist/src/pi-extension/context-shake.js +54 -21
  203. package/dist/src/pi-extension/context-window.js +37 -0
  204. package/dist/src/pi-extension/full-output.js +2 -2
  205. package/dist/src/pi-extension/strip-thinking.js +80 -0
  206. package/dist/src/pi-extension/trim-system-prompt.js +38 -0
  207. package/dist/src/process/launch-services.js +11 -10
  208. package/dist/src/process/process-table.js +1 -1
  209. package/dist/src/process/process.js +7 -7
  210. package/dist/src/process/run-marker.js +29 -6
  211. package/dist/src/process/supervisor.js +36 -25
  212. package/dist/src/prompt/dialect.js +30 -10
  213. package/dist/src/prompt/gate-prompts.js +170 -67
  214. package/dist/src/prompt/principles.js +1 -0
  215. package/dist/src/prompt/prompt-followup.js +29 -20
  216. package/dist/src/prompt/prompt.js +249 -91
  217. package/dist/src/redeploy/redeploy-policy.js +10 -10
  218. package/dist/src/redeploy/redeploy.js +6 -4
  219. package/dist/src/redeploy/redeployer.js +63 -55
  220. package/dist/src/redeploy/self-reload.js +1 -1
  221. package/dist/src/report/report-data.js +259 -254
  222. package/dist/src/report/report-fold.js +353 -0
  223. package/dist/src/report/report-render.js +235 -44
  224. package/dist/src/request-timeouts.js +1 -1
  225. package/dist/src/review/known-flakes.js +34 -14
  226. package/dist/src/review/protected-edits.js +4 -3
  227. package/dist/src/review/review-followup.js +16 -4
  228. package/dist/src/review/review-precheck.js +14 -13
  229. package/dist/src/review/review-verdict.js +14 -0
  230. package/dist/src/review/review.js +55 -34
  231. package/dist/src/review/secret-leaks.js +127 -0
  232. package/dist/src/review/suite-rerun.js +4 -3
  233. package/dist/src/roles/legacy-role-ids.js +21 -0
  234. package/dist/src/roles/legacy-role-migration.js +62 -0
  235. package/dist/src/roles/loop-ids.js +19 -2
  236. package/dist/src/roles/role-catalog.js +109 -131
  237. package/dist/src/roles/role-guidance.js +64 -31
  238. package/dist/src/roles/role-labels.js +10 -0
  239. package/dist/src/roles/role-render.js +194 -18
  240. package/dist/src/roles/role-view.js +216 -17
  241. package/dist/src/roles/roles.js +16 -14
  242. package/dist/src/scheduling/backoff.js +29 -19
  243. package/dist/src/scheduling/claims.js +15 -8
  244. package/dist/src/scheduling/once-round.js +11 -11
  245. package/dist/src/scheduling/quiet-hours.js +25 -32
  246. package/dist/src/scheduling/scheduling.js +28 -25
  247. package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
  248. package/dist/src/status/status-budget.js +115 -0
  249. package/dist/src/status/status-data.js +72 -184
  250. package/dist/src/status/status-inbox.js +47 -0
  251. package/dist/src/status/status-install.js +27 -0
  252. package/dist/src/status/status-polls.js +50 -39
  253. package/dist/src/status/status-roles.js +105 -0
  254. package/dist/src/{fleet → team}/error-storm.js +10 -10
  255. package/dist/src/{fleet → team}/failure-spread.js +8 -8
  256. package/dist/src/{fleet → team}/orchestrator-info.js +84 -18
  257. package/dist/src/{fleet → team}/reclaim.js +5 -4
  258. package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
  259. package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
  260. package/dist/src/{fleet/fleet-state.js → team/team-state.js} +39 -38
  261. package/dist/src/text/datetime.js +42 -2
  262. package/dist/src/text/format.js +10 -1
  263. package/dist/src/text/markdown.js +1 -1
  264. package/dist/src/text/phrases.js +35 -34
  265. package/dist/src/text/suggest.js +3 -2
  266. package/dist/src/text/text.js +2 -2
  267. package/dist/src/tick/qa-coverage.js +2 -1
  268. package/dist/src/tick/stage-check.js +142 -25
  269. package/dist/src/tick/telemetry-digest.js +10 -9
  270. package/dist/src/tick/tick-apply.js +101 -72
  271. package/dist/src/tick/tick-detail-data.js +23 -22
  272. package/dist/src/tick/tick-detail.js +37 -24
  273. package/dist/src/tick/tick-finalize.js +29 -22
  274. package/dist/src/tick/tick-outcome.js +2 -1
  275. package/dist/src/tick/tick-prompt.js +94 -26
  276. package/dist/src/tick/tick-resume.js +29 -13
  277. package/dist/src/tick/tick-stage.js +67 -53
  278. package/dist/src/tick/tick-timing.js +60 -59
  279. package/dist/src/tick/tick-usage.js +53 -23
  280. package/dist/src/tick/tick-verdict.js +75 -10
  281. package/dist/src/ui/badges.js +113 -69
  282. package/dist/src/ui/gui/gui-client-boot.js +202 -29
  283. package/dist/src/ui/gui/gui-client-composer.js +102 -23
  284. package/dist/src/ui/gui/gui-client-drawer.js +330 -75
  285. package/dist/src/ui/gui/gui-client-history.js +94 -37
  286. package/dist/src/ui/gui/gui-client-labels.js +24 -0
  287. package/dist/src/ui/gui/gui-client-loops.js +90 -47
  288. package/dist/src/ui/gui/gui-client-markdown.js +1 -0
  289. package/dist/src/ui/gui/gui-client-model.js +60 -43
  290. package/dist/src/ui/gui/gui-client-operator.js +73 -43
  291. package/dist/src/ui/gui/gui-client-pending.js +34 -31
  292. package/dist/src/ui/gui/gui-client-report.js +330 -47
  293. package/dist/src/ui/gui/gui-client-settings.js +126 -18
  294. package/dist/src/ui/gui/gui-client-sound.js +2 -1
  295. package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +190 -74
  296. package/dist/src/ui/gui/gui-client.js +26 -20
  297. package/dist/src/ui/gui/gui-page.js +18 -19
  298. package/dist/src/ui/gui/gui-styles.js +12 -4
  299. package/dist/src/ui/progress-data.js +5 -3
  300. package/dist/src/ui/status-model.js +176 -102
  301. package/dist/src/ui/status-payload.js +114 -36
  302. package/dist/src/ui/status-render.js +121 -52
  303. package/dist/src/ui/team-alerts.js +341 -0
  304. package/dist/src/ui/tick-progress-model.js +14 -14
  305. package/dist/src/ui/tone.js +9 -8
  306. package/dist/src/ui/transcript-tail.js +1 -0
  307. package/dist/src/ui/transcript.js +23 -16
  308. package/dist/src/ui/tui/tui-app.js +1 -0
  309. package/dist/src/ui/tui/tui-backlog.js +85 -11
  310. package/dist/src/ui/tui/tui-editor-answer.js +97 -0
  311. package/dist/src/ui/tui/tui-editor-attach.js +107 -0
  312. package/dist/src/ui/tui/tui-editor-brief.js +107 -0
  313. package/dist/src/ui/tui/tui-editor-budget.js +155 -0
  314. package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
  315. package/dist/src/ui/tui/tui-editor-entry.js +230 -0
  316. package/dist/src/ui/tui/tui-editor-interval.js +122 -0
  317. package/dist/src/ui/tui/tui-editor-notes.js +114 -0
  318. package/dist/src/ui/tui/tui-editor-pause.js +266 -0
  319. package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
  320. package/dist/src/ui/tui/tui-editor-queued.js +98 -0
  321. package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
  322. package/dist/src/ui/tui/tui-editor-retire.js +83 -0
  323. package/dist/src/ui/tui/tui-editor-search.js +73 -0
  324. package/dist/src/ui/tui/tui-editor-session.js +97 -0
  325. package/dist/src/ui/tui/tui-editors.js +152 -0
  326. package/dist/src/ui/tui/tui-frame.js +61 -57
  327. package/dist/src/ui/tui/tui-history.js +12 -7
  328. package/dist/src/ui/tui/tui-input.js +44 -50
  329. package/dist/src/ui/tui/tui-keymap.js +1 -0
  330. package/dist/src/ui/tui/tui-keys.js +87 -1821
  331. package/dist/src/ui/tui/tui-pane-activity.js +99 -0
  332. package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
  333. package/dist/src/ui/tui/tui-pane-brief.js +30 -0
  334. package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
  335. package/dist/src/ui/tui/tui-pane-health.js +124 -0
  336. package/dist/src/ui/tui/tui-pane-history.js +316 -0
  337. package/dist/src/ui/tui/tui-pane-pending.js +74 -0
  338. package/dist/src/ui/tui/tui-pane-queued.js +143 -0
  339. package/dist/src/ui/tui/tui-pane-report.js +122 -0
  340. package/dist/src/ui/tui/tui-pane-session.js +101 -0
  341. package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
  342. package/dist/src/ui/tui/tui-pane.js +52 -28
  343. package/dist/src/ui/tui/tui-panes.js +294 -0
  344. package/dist/src/ui/tui/tui.js +44 -26
  345. package/dist/src/verdict/fix-claim.js +56 -33
  346. package/dist/src/verdict/no-change.js +19 -0
  347. package/dist/src/verdict/refusal.js +43 -11
  348. package/dist/src/verdict/reply-contract.js +17 -5
  349. package/dist/src/version.js +3 -2
  350. package/package.json +4 -2
  351. package/dist/src/agent/mcp/role-notes-server.js +0 -160
  352. package/dist/src/landing/backlog-conflicts.js +0 -116
  353. package/dist/src/landing/landing-drain.js +0 -204
  354. package/dist/src/landing/landing-pipeline.js +0 -96
  355. package/dist/src/landing/landing-queue-render.js +0 -56
  356. package/dist/src/landing/landing-slot.js +0 -256
  357. package/dist/src/landing/landing-vetting.js +0 -196
  358. package/dist/src/pi-extension/role-notes.js +0 -40
  359. package/dist/src/ui/fleet-alerts.js +0 -174
@@ -1,8 +1,10 @@
1
+ // vocabulary: plain
1
2
  /** The dashboard's Usage and Failures views, browser-side. Usage renders /api/report as six
2
- * totals (each with its per-day average) over four daily bar charts — landed commits, ticks by
3
- * loop, output tokens, spend by loop — with gridlines, a scale, and one shared color per loop;
4
- * a window picker switches between 7, 14, 30, and 90 days and a Loop picker scopes the report
5
- * to one loop group (`?role=`, the browser twin of `tumwater report --role`). Both pickers also
3
+ * totals (each with its per-day average) over five daily bar charts — merged commits, ticks by
4
+ * loop, output tokens, spend by loop, rejections by loop — with gridlines, a scale, and one
5
+ * shared color per loop; a window picker switches between 7, 14, 30, and 90 days and a
6
+ * Loop picker scopes the report to one loop group (`?role=`, the browser twin of
7
+ * `tumwater report --role`). Both pickers also
6
8
  * carry trailing-window buttons (1h/6h/1d, `?sinceMs=`, the browser twin of `tumwater report
7
9
  * --since`), so the day and sub-day shapes read through the same collectors and renders as the
8
10
  * CLI; the since shape draws the returned Markdown instead of the charts. Failures renders
@@ -21,19 +23,35 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
21
23
  const REPORT_PALETTE = ["#6366f1", "#f59e0b", "#10b981", "#ef4444", "#0ea5e9", "#a855f7", "#f97316", "#14b8a6",
22
24
  "#ec4899", "#84cc16", "#06b6d4", "#eab308", "#64748b", "#d946ef"];
23
25
 
26
+ // One [key, total][] for a day series, ordered by total desc then name asc — the shared
27
+ // shape of the role/provider/model orderings. sumFields are the fields that contribute a
28
+ // running total (runsByRole, costByProvider, runsByModel); seedFields are fields whose
29
+ // PRESENCE alone earns a category a 0 row (a loop that only merged or only spent, a
30
+ // provider that only spent notionally) without adding to its total — the split that lets
31
+ // the real-spend charts read 0 for a notional-only provider while still listing it. Every
32
+ // field's keys also seed a 0 when absent, so a category some day carried keeps its row on
33
+ // days (and in fields) that lack it. Keys are dynamic (roles, providers, models), so reads
34
+ // and seeds use Object.hasOwn as each ordering did.
35
+ function tallyAcross(series, sumFields, seedFields) {
36
+ const by = {};
37
+ for (const d of series) {
38
+ for (const field of sumFields) {
39
+ for (const [k, n] of Object.entries(d[field] || {})) by[k] = (Object.hasOwn(by, k) ? by[k] : 0) + n;
40
+ }
41
+ for (const field of [...sumFields, ...seedFields]) {
42
+ for (const k of Object.keys(d[field] || {})) if (!Object.hasOwn(by, k)) by[k] = 0;
43
+ }
44
+ }
45
+ return Object.entries(by).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
46
+ }
47
+
24
48
  // Window totals per role — ticks desc, then name — so the legend and the stack order match
25
49
  // the Markdown report's "Ticks by role" line. Roles that only spent (no ticks) still count.
26
50
  // Reads and seeds use Object.hasOwn: the custom-loop name charset admits "constructor", an
27
51
  // Object.prototype key whose inherited value would otherwise seed the counter and satisfy
28
52
  // the membership test (the browser twin of addTo's fix; BUGS.md 2026-10-10).
29
53
  function reportRoleOrder(data) {
30
- const byRole = {};
31
- for (const d of data.series) {
32
- for (const [role, n] of Object.entries(d.ticksByRole)) byRole[role] = (Object.hasOwn(byRole, role) ? byRole[role] : 0) + n;
33
- for (const role of Object.keys(d.costByRole || {})) if (!Object.hasOwn(byRole, role)) byRole[role] = 0;
34
- for (const role of Object.keys(d.notionalByRole || {})) if (!Object.hasOwn(byRole, role)) byRole[role] = 0;
35
- }
36
- return Object.entries(byRole).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
54
+ return tallyAcross(data.series, ["runsByRole"], ["costByRole", "notionalByRole", "rejectionsByRole", "revisionsByRole", "refusalsByRole", "fallbacksByRole", "imageReroutesByRole", "failedRunsByRole", "mergeFailedByRole", "commitsByRole", "mergedByRole"]);
37
55
  }
38
56
 
39
57
  // X-axis labels: MM-DD, thinned to at most seven, counted back from the newest day so today
@@ -103,44 +121,290 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
103
121
  }
104
122
 
105
123
  function chartCommits(data) {
106
- return reportSvg(data.series, (d) => [{ value: d.commits, color: "#10b981", title: d.date + ": " + plural(d.commits, "commit") + " landed" }],
124
+ return reportSvg(data.series, (d) => [{ value: d.commits, color: "#10b981", title: d.date + ": " + plural(d.commits, "commit") + " merged" }],
107
125
  countAxis, true);
108
126
  }
109
127
 
110
- // Stacked bars, one color per role in reportRoleOrder's order (the first role at the bottom),
111
- // with a legend in the same order. Role names are dynamic (custom loops): escaped everywhere.
112
- function roleStackChart(data, field, fmt, fmtAxis, integer) {
113
- const roles = reportRoleOrder(data);
128
+ // The day's merged commits per base role, the per-role split of the Merged commits total
129
+ // it sits beside (same roleStackChart shape, palette, legend order and integer count axis
130
+ // as the other role charts). Counts merged events, one per change in a stacked fast-forward.
131
+ function chartCommitsByRole(data) {
132
+ return roleStackChart(data, "commitsByRole", (n) => plural(n, "commit"), countAxis, true);
133
+ }
134
+
135
+ // The day's work vs maintenance commits, from workCommits/maintenanceCommits (the same split
136
+ // foldUsageEvent's totals carry): work (feature/bugfix/director) at the bottom, maintenance
137
+ // (code-maintenance/readme) above. This shows only the tiered counts — plan, steward,
138
+ // observer and custom/legacy roles are untiered and count toward the total commits chart
139
+ // alone, so the two tiers need not sum to chartCommits. The Number(...) || 0 guard covers
140
+ // older or hand-built report rows that omit the fields.
141
+ function chartCommitTiers(data) {
142
+ return reportSvg(data.series, (d) => [
143
+ { value: Number(d.workCommits) || 0, color: "#10b981", title: d.date + ": " + plural(Number(d.workCommits) || 0, "work commit") },
144
+ { value: Number(d.maintenanceCommits) || 0, color: "#94a3b8", title: d.date + ": " + plural(Number(d.maintenanceCommits) || 0, "maintenance commit") },
145
+ ], countAxis, true);
146
+ }
147
+
148
+ // The lower-median bar for a duration field (check/merge-wait/provider-wait/slot-wait): one
149
+ // bar per day from that day's median ms, its hover chip in the bar's color. Shared by the four
150
+ // duration-median charts so the field/color/label triple cannot drift when a fifth median
151
+ // joins.
152
+ function medianBarChart(data, field, color, label) {
153
+ return reportSvg(data.series, (d) => [{ value: Number(d[field]) || 0, color, title: d.date + ": " + fmtSpan(Number(d[field]) || 0) + " median " + label }],
154
+ (v) => fmtSpan(v), false);
155
+ }
156
+
157
+ // The lower median of the day's passing full-suite merge checks, from checkMedianMs.
158
+ function chartCheckMedian(data) {
159
+ return medianBarChart(data, "checkMedianMs", "#0ea5e9", "merge check");
160
+ }
161
+
162
+ // The day's failed full-suite checks, from checkFailures; a day without one keeps its empty
163
+ // slot, as chartCheckMedian does.
164
+ function chartCheckFailures(data) {
165
+ return reportSvg(data.series, (d) => [{ value: Number(d.checkFailures) || 0, color: "#ef4444", title: d.date + ": " + plural(Number(d.checkFailures) || 0, "red merge check") }],
166
+ countAxis, true);
167
+ }
168
+
169
+ // The lower median of the day's merge waits, enqueue to merged on main, from mergeWaitMedianMs.
170
+ function chartMergeWaitMedian(data) {
171
+ return medianBarChart(data, "mergeWaitMedianMs", "#f59e0b", "merge wait");
172
+ }
173
+
174
+ // The lower median of the day's provider-slot waits, from providerWaitMedianMs.
175
+ function chartProviderWaitMedian(data) {
176
+ return medianBarChart(data, "providerWaitMedianMs", "#a855f7", "provider wait");
177
+ }
178
+
179
+ // The lower median of the day's workspace-slot waits, from slotWaitMedianMs.
180
+ function chartSlotWaitMedian(data) {
181
+ return medianBarChart(data, "slotWaitMedianMs", "#22c55e", "workspace wait");
182
+ }
183
+
184
+ // Window totals per provider — spend desc, then name — so both provider charts share one
185
+ // color and one legend position, the dashboard twin of the day report's "Cost by provider".
186
+ // Provider keys are dynamic seams, so reads and seeds use Object.hasOwn as reportRoleOrder
187
+ // does; a provider that only spent notionally still counts.
188
+ function reportProviderOrder(data) {
189
+ return tallyAcross(data.series, ["costByProvider"], ["notionalByProvider"]);
190
+ }
191
+
192
+ // Window tallies per model — run count desc, then name — so the "Runs by model" chart
193
+ // matches the day report's line ordering. Model keys are dynamic selectors, so reads and
194
+ // seeds use Object.hasOwn as reportProviderOrder does; a model a day series never started
195
+ // on still gets a row only if some day carries it.
196
+ function reportModelOrder(data) {
197
+ return tallyAcross(data.series, ["runsByModel"], []);
198
+ }
199
+
200
+ // Window tallies per merge-failure reason code, count desc then code asc — the reason
201
+ // twin of reportModelOrder, so the "Merge failures by reason" chart matches the day
202
+ // report's line ordering. Reason keys are dynamic (result strings), so reads and seeds go
203
+ // through tallyAcross's Object.hasOwn checks as reportModelOrder does; a code a day series
204
+ // never failed with still gets a row only if some day carries it.
205
+ function reportMergeFailureOrder(data) {
206
+ return tallyAcross(data.series, ["mergeFailedByReason"], []);
207
+ }
208
+
209
+ // Window tallies per deferred-reason code, count desc then code asc — the reason twin of
210
+ // reportModelOrder and reportMergeFailureOrder, so the "Deferred runs by reason" chart
211
+ // matches the day report's line ordering, reusing the shared tallyAcross these dynamic-
212
+ // keyed orders are built from.
213
+ function reportDeferredOrder(data) {
214
+ return tallyAcross(data.series, ["deferredByReason"], []);
215
+ }
216
+
217
+ // Stacked bars, one color per entry in the caller's order (the first at the bottom), with a
218
+ // legend in the same order. entries is the ordered [key, total][] list that drives the
219
+ // colors, stack order, and legend; keys are dynamic (roles, providers): escaped everywhere.
220
+ function stackChart(data, entries, field, fmt, fmtAxis, integer) {
114
221
  const colorOf = (i) => REPORT_PALETTE[i % REPORT_PALETTE.length];
115
222
  const svg = reportSvg(
116
223
  data.series,
117
- (d) => roles.map(([role], i) => {
118
- // hasOwn again: an absent role must read 0, not Object.prototype's inherited value.
224
+ (d) => entries.map(([key], i) => {
225
+ // hasOwn again: an absent key must read 0, not Object.prototype's inherited value.
119
226
  const m = d[field] || {};
120
- const value = Object.hasOwn(m, role) ? m[role] : 0;
121
- return { value, color: colorOf(i), title: d.date + " · " + role + ": " + fmt(value) };
227
+ const value = Object.hasOwn(m, key) ? m[key] : 0;
228
+ return { value, color: colorOf(i), title: d.date + " · " + key + ": " + fmt(value) };
122
229
  }),
123
230
  fmtAxis,
124
231
  integer,
125
232
  );
126
- const legend = roles.length
127
- ? "<div class='legend'>" + roles.map(([role], i) => "<span><span class='swatch' style='background:" + colorOf(i) + "'></span>" + esc(role) + "</span>").join("") + "</div>"
233
+ const legend = entries.length
234
+ ? "<div class='legend'>" + entries.map(([key], i) => "<span><span class='swatch' style='background:" + colorOf(i) + "'></span>" + esc(key) + "</span>").join("") + "</div>"
128
235
  : "";
129
236
  return svg + legend;
130
237
  }
131
238
 
239
+ // Stacked bars, one color per role in reportRoleOrder's order (the first role at the
240
+ // bottom), with a legend in the same order. Role names are dynamic (custom loops): escaped
241
+ // everywhere. The role-keyed form of stackChart.
242
+ function roleStackChart(data, field, fmt, fmtAxis, integer) {
243
+ return stackChart(data, reportRoleOrder(data), field, fmt, fmtAxis, integer);
244
+ }
245
+
132
246
  function chartTicksByRole(data) {
133
- return roleStackChart(data, "ticksByRole", (n) => plural(n, "tick"), countAxis, true);
247
+ return roleStackChart(data, "runsByRole", (n) => plural(n, "run"), countAxis, true);
134
248
  }
135
249
 
136
250
  function chartCostByRole(data) {
137
251
  return roleStackChart(data, "costByRole", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
138
252
  }
139
253
 
140
- // The API-equivalent (notional) spend of subscription runs, per loop, on the same axis as the cap spend.
254
+ // The day's output tokens, per loop, the dashboard twin of the CLI "Tokens by role" line.
255
+ function chartTokensByRole(data) {
256
+ return roleStackChart(data, "tokensByRole", (n) => fmtTokens(Number(n) || 0) + " output tokens", (v) => fmtTokens(Math.round(v)), true);
257
+ }
258
+
259
+ // The API-equivalent (notional) spend of subscription runs, per loop, on the same axis as
260
+ // the cap spend.
141
261
  function chartNotionalByRole(data) {
142
262
  return roleStackChart(data, "notionalByRole", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
143
263
  }
264
+
265
+ // The team's real spend, per day, stacked by provider: each segment one (day, provider), on
266
+ // the same axis as the role spend. Both provider maps are sparse, so a day with none keeps
267
+ // its empty slot, as the role charts do.
268
+ function chartCostByProvider(data) {
269
+ return stackChart(data, reportProviderOrder(data), "costByProvider", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
270
+ }
271
+
272
+ // The team's run starts, per day, stacked by model: each segment one (day, model), the
273
+ // dashboard twin of the day report's "Runs by model" line. Sparse like the provider charts.
274
+ function chartRunsByModel(data) {
275
+ return stackChart(data, reportModelOrder(data), "runsByModel", (n) => plural(n, "run"), countAxis, true);
276
+ }
277
+
278
+ // The team's deferred maintenance ticks, per day, stacked by reason: each segment one
279
+ // (day, reason code), the dashboard twin of the day report's "Deferred runs by reason"
280
+ // line. Sparse like the model chart; the reason codes are the legend labels.
281
+ function chartDeferredByReason(data) {
282
+ return stackChart(data, reportDeferredOrder(data), "deferredByReason", (n) => plural(n, "deferral"), countAxis, true);
283
+ }
284
+
285
+ // The API-equivalent (notional) spend of subscription runs, per provider, on the same axis
286
+ // as the estimated role spend; the provider twin of chartNotionalByRole.
287
+ function chartNotionalByProvider(data) {
288
+ return stackChart(data, reportProviderOrder(data), "notionalByProvider", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
289
+ }
290
+
291
+ // The team's review friction, per loop, on a tick-like count axis: one segment per
292
+ // (day, loop) with a rejection, empty when the report has none.
293
+ function chartRejectionsByRole(data) {
294
+ return roleStackChart(data, "rejectionsByRole", (n) => plural(n, "rejection"), countAxis, true);
295
+ }
296
+ // Revisions (rework rounds after a rejection), per loop, drawn the same way.
297
+ function chartRevisionsByRole(data) {
298
+ return roleStackChart(data, "revisionsByRole", (n) => plural(n, "revision"), countAxis, true);
299
+ }
300
+ // Runs that deliberately declined their entry (result "refused"), per loop, drawn the same
301
+ // way as its rejection/revision siblings.
302
+ function chartRefusalsByRole(data) {
303
+ return roleStackChart(data, "refusalsByRole", (n) => plural(n, "refusal"), countAxis, true);
304
+ }
305
+ // The team's model-fallback episodes, per loop, on the same count axis: one segment per
306
+ // (day, loop) with a fallback.
307
+ function chartFallbacksByRole(data) {
308
+ return roleStackChart(data, "fallbacksByRole", (n) => plural(n, "fallback"), countAxis, true);
309
+ }
310
+ // Image reroutes per loop, drawn the same way: one segment per (day, loop) with a reroute.
311
+ function chartImageReroutesByRole(data) {
312
+ return roleStackChart(data, "imageReroutesByRole", (n) => plural(n, "image reroute"), countAxis, true);
313
+ }
314
+ // Runs that ended in an error, per loop, drawn the same way.
315
+ function chartFailedRunsByRole(data) {
316
+ return roleStackChart(data, "failedRunsByRole", (n) => plural(n, "failed run"), countAxis, true);
317
+ }
318
+ // Merges that were blocked or conflicted, per loop, drawn the same way.
319
+ function chartMergeFailuresByRole(data) {
320
+ return roleStackChart(data, "mergeFailedByRole", (n) => plural(n, "merge failure"), countAxis, true);
321
+ }
322
+ // WHY merges failed, per reason code (each merge_failed's result), drawn the same way as
323
+ // the by-role chart so the two read as a pair; the reason codes' own ordering comes from
324
+ // reportMergeFailureOrder.
325
+ function chartMergeFailuresByReason(data) {
326
+ return stackChart(data, reportMergeFailureOrder(data), "mergeFailedByReason", (n) => plural(n, "merge failure"), countAxis, true);
327
+ }
328
+ // Window tallies per fixed-bug Validation gap tag, count desc then tag asc — the tag twin
329
+ // of reportMergeFailureOrder, so the "Validation gaps" chart matches the day report's
330
+ // line ordering. Tag keys are dynamic, so reads and seeds go through tallyAcross's
331
+ // Object.hasOwn checks as its siblings do; a tag no day recorded still gets a row only if
332
+ // some day carries it.
333
+ function reportValidationGapOrder(data) {
334
+ return tallyAcross(data.series, ["validationGapsByTag"], []);
335
+ }
336
+ // The team's fixed-bug Validation gap tags, per day, stacked by tag: each segment one
337
+ // (day, tag), the dashboard twin of the day report's "Validation gaps" line. Sparse like
338
+ // the model chart; tag names are the legend labels.
339
+ function chartValidationGaps(data) {
340
+ return stackChart(data, reportValidationGapOrder(data), "validationGapsByTag", (n) => plural(n, "validation gap"), countAxis, true);
341
+ }
342
+ // The day's merged outcomes per loop, the success sibling of chartMergeFailuresByRole,
343
+ // drawn exactly like it (same roleStackChart shape, palette, legend order and integer count
344
+ // axis) so the two read as one pair.
345
+ function chartMergedByRole(data) {
346
+ return roleStackChart(data, "mergedByRole", (n) => plural(n, "merge"), countAxis, true);
347
+ }
348
+ // The day's backlog items: features done stacked at the bottom and bugs fixed above them,
349
+ // from featuresDone/bugsFixed (day-granular backlog-file done dates, so a role scope zeroes
350
+ // them); a day with neither keeps its empty slot.
351
+ function chartBacklogItems(data) {
352
+ return reportSvg(data.series, (d) => [
353
+ { value: Number(d.featuresDone) || 0, color: "#6366f1", title: d.date + ": " + plural(Number(d.featuresDone) || 0, "feature") + " done" },
354
+ { value: Number(d.bugsFixed) || 0, color: "#ef4444", title: d.date + ": " + plural(Number(d.bugsFixed) || 0, "bug") + " fixed" },
355
+ ], countAxis, true);
356
+ }
357
+ // True when some day carries a map under field with an own value above zero; both counts
358
+ // are rare, so their elements render only then.
359
+ function seriesHasAny(data, field) {
360
+ return data.series.some((d) => Object.values(d[field] || {}).some((n) => n > 0));
361
+ }
362
+ // The per-day filing-stamp twin of chartBacklogItems: plans added and bugs filed, the same
363
+ // two colors as the done chart so the opened and done charts read as a pair. A day with
364
+ // neither carries an empty slot, and zero segments draw no rect.
365
+ function chartBacklogItemsOpened(data) {
366
+ return reportSvg(data.series, (d) => [
367
+ { value: Number(d.plansAdded) || 0, color: "#6366f1", title: d.date + ": " + plural(Number(d.plansAdded) || 0, "plan") + " added" },
368
+ { value: Number(d.bugsFiled) || 0, color: "#ef4444", title: d.date + ": " + plural(Number(d.bugsFiled) || 0, "bug") + " filed" },
369
+ ], countAxis, true);
370
+ }
371
+ // True when some day carries a feature or bug count; the gate for the backlog chart, whose
372
+ // tally a role-scoped report zeroes, so the element stays out exactly where the two tiles are.
373
+ function seriesHasBacklogItems(data) {
374
+ return data.series.some((d) => Number(d.featuresDone) > 0 || Number(d.bugsFixed) > 0);
375
+ }
376
+ // True when some day carries a filing count; the gate for the opened chart, the sibling of
377
+ // seriesHasBacklogItems, hidden the same way on a role-scoped series.
378
+ function seriesHasBacklogAdded(data) {
379
+ return data.series.some((d) => Number(d.plansAdded) > 0 || Number(d.bugsFiled) > 0);
380
+ }
381
+ // True when some day carries a positive value for the named scalar series field; the shared
382
+ // guard behind the per-scalar seriesHas* helpers below, so each chart's element renders only
383
+ // when its series has data. Backlog items/added take two fields and stay separate.
384
+ function seriesHasScalar(data, field) {
385
+ return data.series.some((d) => Number(d[field]) > 0);
386
+ }
387
+ // True when some day carries a passing-check median; the element renders only then.
388
+ function seriesHasCheckMedian(data) {
389
+ return seriesHasScalar(data, "checkMedianMs");
390
+ }
391
+ // True when some day carries a count of failed full-suite checks; the red sibling of
392
+ // seriesHasCheckMedian, the element renders only then.
393
+ function seriesHasCheckFailures(data) {
394
+ return seriesHasScalar(data, "checkFailures");
395
+ }
396
+ // True when some day carries a merge-wait median; the element renders only then.
397
+ function seriesHasMergeWaitMedian(data) {
398
+ return seriesHasScalar(data, "mergeWaitMedianMs");
399
+ }
400
+ // True when some day carries a provider-wait median; the element renders only then.
401
+ function seriesHasProviderWaitMedian(data) {
402
+ return seriesHasScalar(data, "providerWaitMedianMs");
403
+ }
404
+ // True when some day carries a workspace-wait median; the element renders only then.
405
+ function seriesHasSlotWaitMedian(data) {
406
+ return seriesHasScalar(data, "slotWaitMedianMs");
407
+ }
144
408
  // report-chart:end
145
409
 
146
410
  // report-tip:start
@@ -194,7 +458,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
194
458
  const tile = (label, ic, value, avg, extra) => "<div class='stat'><span class='stat-label'>" + icon(ic) + esc(label) +
195
459
  "</span><span class='stat-value'>" + esc(value) + "</span><span class='stat-sub'>" + esc(avg + " a day" + (extra || "")) + "</span></div>";
196
460
  return [
197
- tile("Commits landed", "merge", String(t.commits), perDay(t.commits, (v) => v.toFixed(1)),
461
+ tile("Commits merged", "merge", String(t.commits), perDay(t.commits, (v) => v.toFixed(1)),
198
462
  " · " + workSplit(t)),
199
463
  // A role scope has no backlog tallies: features/bugs come from day-granular backlog-file
200
464
  // dates that carry no role, so the scoped collector zeroes them and the tiles stay out —
@@ -203,27 +467,27 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
203
467
  tile("Features done", "plan", String(t.featuresDone), perDay(t.featuresDone, (v) => v.toFixed(1))),
204
468
  tile("Bugs fixed", "bug", String(t.bugsFixed), perDay(t.bugsFixed, (v) => v.toFixed(1))),
205
469
  ]),
206
- tile("Ticks", "refresh", String(t.ticks), perDay(t.ticks, Math.round)),
470
+ tile("Runs", "refresh", String(t.runs), perDay(t.runs, Math.round)),
207
471
  tile("Output tokens", "bars", fmtTokens(t.tokensOut), perDay(t.tokensOut, (v) => fmtTokens(Math.round(v)))),
208
472
  tile("Spent", "dollar", fmtUsd(t.costUsd), perDay(t.costUsd, fmtUsd),
209
- t.notionalCostUsd > 0 ? " · " + fmtUsd(t.notionalCostUsd) + " notional" : undefined),
473
+ t.notionalCostUsd > 0 ? " · " + fmtUsd(t.notionalCostUsd) + " estimated" : undefined),
210
474
  ].join("");
211
475
  }
212
476
 
213
477
  // report-role:start
214
- // The Loop picker's options: the fleet-wide "All loops" empty option plus one option per
215
- // role, in the fleet's own order. syncRolePicker escapes every role through it, so the three
478
+ // The Loop picker's options: the team-wide "All loops" empty option plus one option per
479
+ // role, in the team's own order. syncRolePicker escapes every role through it, so the three
216
480
  // Loop pickers (the report views', History's, and the Activity feed's) render role names the
217
481
  // same way — role names are dynamic (custom loops) and the custom-loop charset admits HTML
218
482
  // metacharacters. Pure, so a test can drive it without a DOM.
219
483
  function rolePickerOptions(roles) {
220
- return "<option value=''>All loops</option>" +
484
+ return "<option value=''>All roles</option>" +
221
485
  (roles || []).map((r) => "<option value='" + esc(r) + "'>" + esc(r) + "</option>").join("");
222
486
  }
223
- // Repaint one Loop picker from the fleet's role list, returning the scope to keep filtering
487
+ // Repaint one Loop picker from the team's role list, returning the scope to keep filtering
224
488
  // with. Every picker (the report views' syncRoles, History's syncHistoryRoles, the Activity
225
489
  // feed's syncFeedRoles) does the same thing: rebuild only when the roster changes, keep the
226
- // persisted choice while the fleet still lists it, and fall back to All loops — persisting
490
+ // persisted choice while the team still lists it, and fall back to All loops — persisting
227
491
  // that fallback — when it does not, so a dimmed picker never scopes the request to a ghost
228
492
  // loop. The last-painted roster is tracked per select id, so the pickers never share a key.
229
493
  const rolePickerKeys = {};
@@ -247,7 +511,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
247
511
  // GUI twin of the TUI's Ctrl+G scope cycle (src/ui/tui/tui-keys.ts scopeCycle). The status
248
512
  // payload already carries the base role as instanceOf on an instance row (src/status/
249
513
  // status-data.ts), so this borrows the server's own fold rather than re-deriving it. History's
250
- // picker (gui-client-history.ts) filters by exact loop id and keeps the raw fleet list. Pure,
514
+ // picker (gui-client-history.ts) filters by exact loop id and keeps the raw team list. Pure,
251
515
  // so a test can drive it without a DOM.
252
516
  function reportBaseRoles(loops) {
253
517
  const bases = [];
@@ -258,7 +522,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
258
522
  return bases;
259
523
  }
260
524
  // Repaint both report views' Loop pickers from a status payload: each view's persisted
261
- // choice is kept while the fleet still lists it and falls back to All loops otherwise. The
525
+ // choice is kept while the team still lists it and falls back to All loops otherwise. The
262
526
  // views are defined below; the boot poll and each fetch call this once they exist.
263
527
  function syncReportRoles(d) {
264
528
  for (const view of [usage, failures]) view.syncRoles(d);
@@ -270,11 +534,11 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
270
534
  function viewHead(title, blurb, pickerId, choices, current, refreshId, rolePickerId, sinceChoices) {
271
535
  // A dropped blurb renders as no subtitle at all, never the string "undefined". The Loop
272
536
  // picker rides the toolbar when the view has one (the shared Usage/Failures shape); the
273
- // options are filled by syncRoles from the fleet's role list. The trailing-window buttons
537
+ // options are filled by syncRoles from the team's role list. The trailing-window buttons
274
538
  // (a view that configured sinceChoices) sit in the same segmented control after the day
275
539
  // buttons; each carries its length as data-since so windowView can mark exactly one active.
276
540
  const rolePicker = rolePickerId
277
- ? "<label><span class='sr-only'>Loop</span><select class='field' id='" + rolePickerId + "' data-role='1'><option value=''>All loops</option></select></label>"
541
+ ? "<label><span class='sr-only'>Role</span><select class='field' id='" + rolePickerId + "' data-role='1'><option value=''>All roles</option></select></label>"
278
542
  : "";
279
543
  const sinceButtons = (sinceChoices || []).map(([ms, label]) =>
280
544
  "<button type='button' data-since='" + ms + "'>" + esc(String(label)) + "</button>").join("");
@@ -305,14 +569,14 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
305
569
  // fetch still uses it.
306
570
  const sinceChoice = o.sinceChoices ? Number(recall(o.sinceKey)) : NaN;
307
571
  let sinceMs = (o.sinceChoices || []).some(([ms]) => ms === sinceChoice) ? sinceChoice : null;
308
- // The persisted Loop scope for this view: "" is the fleet-wide report. Only a view that
572
+ // The persisted Loop scope for this view: "" is the team-wide report. Only a view that
309
573
  // configured a rolePickerId reads it, so a view without a picker never touches storage.
310
574
  let role = o.roleKey ? recall(o.roleKey) || "" : "";
311
575
  let fetchedAt = 0;
312
- // Keep this view's Loop picker in step with the fleet's role list (custom loops included).
576
+ // Keep this view's Loop picker in step with the team's role list (custom loops included).
313
577
  function syncRoles(d) {
314
578
  if (!o.rolePickerId || !d) return;
315
- role = syncRolePicker(o.rolePickerId, reportBaseRoles(d.loops), role, o.roleKey);
579
+ role = syncRolePicker(o.rolePickerId, reportBaseRoles(d.roles), role, o.roleKey);
316
580
  }
317
581
  async function fetchView(throttled) {
318
582
  if (throttled && Date.now() - fetchedAt < 60000) return;
@@ -370,7 +634,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
370
634
 
371
635
  const usage = windowView({
372
636
  panelId: "report", title: "Usage", bodyId: "usagebody",
373
- blurb: "What the fleet produced and what it cost, per day and per loop.",
637
+ blurb: "What the team produced and what it cost, per day and per role.",
374
638
  pickerId: "usagewindow", choices: [7, 14, 30, 90], refreshId: "usagerefresh",
375
639
  key: "usage-days", defaultDays: 14, roleKey: "usage-role", rolePickerId: "usagerole",
376
640
  sinceKey: "usage-since", sinceChoices: REPORT_SINCE_CHOICES,
@@ -388,14 +652,33 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
388
652
  const block = (title, svg, sub) => "<section class='card chart-card'><header class='card-head'><h2>" + esc(title) +
389
653
  "</h2><span class='muted'>" + esc(sub || "per day") + "</span></header><div class='chart'>" + svg + "</div></section>";
390
654
  // A scoped report drops to the four scoped tiles and says what it left out, mirroring the
391
- // CLI's --role report. The fleet-wide render is unchanged.
655
+ // CLI's --role report. The team-wide render is unchanged.
392
656
  return "<div class='stats" + (d.role ? "" : " six") + "'>" + reportSummary(d) + "</div><div class='grid-2 even'>" +
393
- block("Landed commits", chartCommits(d)) + block("Ticks by loop", chartTicksByRole(d)) +
394
- block("Output tokens", chartTokens(d)) + block("Spend by loop", chartCostByRole(d)) +
657
+ block("Work vs maintenance commits", chartCommitTiers(d), "per day · work + maintenance") + block("Merged commits", chartCommits(d)) + block("Commits by role", chartCommitsByRole(d)) + (seriesHasBacklogAdded(d) ? block("Backlog items opened", chartBacklogItemsOpened(d), "per day · plans added + bugs filed") : "") + (seriesHasBacklogItems(d) ? block("Backlog items done", chartBacklogItems(d), "per day · features done + bugs fixed") : "") + block("Runs by role", chartTicksByRole(d)) +
658
+ block("Output tokens", chartTokens(d)) + block("Tokens by role", chartTokensByRole(d)) + block("Spend by role", chartCostByRole(d)) +
659
+ (!d.role && seriesHasAny(d, "costByProvider") ? block("Spend by provider", chartCostByProvider(d)) : "") +
660
+ block("Rejections by role", chartRejectionsByRole(d)) +
661
+ (seriesHasAny(d, "revisionsByRole") ? block("Revisions by role", chartRevisionsByRole(d)) : "") +
662
+ (seriesHasAny(d, "refusalsByRole") ? block("Refusals by role", chartRefusalsByRole(d)) : "") +
663
+ (seriesHasAny(d, "fallbacksByRole") ? block("Fallbacks by role", chartFallbacksByRole(d)) : "") +
664
+ (seriesHasAny(d, "imageReroutesByRole") ? block("Image reroutes by role", chartImageReroutesByRole(d)) : "") +
665
+ (seriesHasAny(d, "failedRunsByRole") ? block("Failed runs by role", chartFailedRunsByRole(d)) : "") +
666
+ (seriesHasAny(d, "mergedByRole") ? block("Merged by role", chartMergedByRole(d)) : "") +
667
+ (!d.role && seriesHasAny(d, "runsByModel") ? block("Runs by model", chartRunsByModel(d)) : "") +
668
+ (!d.role && seriesHasAny(d, "deferredByReason") ? block("Deferred runs by reason", chartDeferredByReason(d), "per day · backlog, no-work, plans-full") : "") +
669
+ (seriesHasAny(d, "mergeFailedByRole") ? block("Merge failures by role", chartMergeFailuresByRole(d)) : "") +
670
+ (seriesHasAny(d, "mergeFailedByReason") ? block("Merge failures by reason", chartMergeFailuresByReason(d)) : "") +
671
+ (!d.role && seriesHasAny(d, "validationGapsByTag") ? block("Validation gaps", chartValidationGaps(d), "per day · what made fixed bugs hard to confirm") : "") +
672
+ (seriesHasCheckMedian(d) ? block("Merge check median", chartCheckMedian(d), "per day · passing full-suite checks") : "") +
673
+ (seriesHasCheckFailures(d) ? block("Red merge checks", chartCheckFailures(d), "per day · failed full-suite checks") : "") +
674
+ (seriesHasMergeWaitMedian(d) ? block("Merge wait median", chartMergeWaitMedian(d), "per day · enqueue to merged on main") : "") +
675
+ (seriesHasProviderWaitMedian(d) ? block("Provider wait median", chartProviderWaitMedian(d), "per day · waits of 30s or more for a provider slot") : "") +
676
+ (seriesHasSlotWaitMedian(d) ? block("Workspace wait median", chartSlotWaitMedian(d), "per day · waits of 30s or more for a workspace") : "") +
395
677
  (d.totals.notionalCostUsd > 0
396
- ? block("Notional spend by loop", chartNotionalByRole(d), "per day · API-equivalent, not in the cap") : "") + "</div>" +
397
- (d.role ? "<p class='muted scope-note'>Loop scope: " + esc(d.role) +
398
- " · landing runs and backlog tallies (features done / bugs fixed) are fleet-wide and omitted.</p>" : "") +
678
+ ? block("Estimated spend by role", chartNotionalByRole(d), "per day · API-equivalent, not in the cap") : "") +
679
+ (!d.role && seriesHasAny(d, "notionalByProvider") ? block("Estimated spend by provider", chartNotionalByProvider(d), "per day · API-equivalent, not in the cap") : "") + "</div>" +
680
+ (d.role ? "<p class='muted scope-note'>Role scope: " + esc(d.role) +
681
+ " · merge runs and backlog tallies (features done / bugs fixed / plans added / bugs filed) are team-wide and omitted.</p>" : "") +
399
682
  (d.from ? "<p class='muted window-note'>" + esc(d.from + " → " + d.to) +
400
683
  (!d.coversFullWindow ? " · " + ${JSON.stringify(SPARSE_WINDOW_NOTE)} : "") + "</p>" : "");
401
684
  },
@@ -403,7 +686,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
403
686
 
404
687
  const failures = windowView({
405
688
  panelId: "failures", title: "Failures", bodyId: "failbody",
406
- blurb: "What went wrong, where, and how often — the digest the telemetry loop reads.",
689
+ blurb: "What went wrong, where, and how often — the digest the selfcheck role reads.",
407
690
  pickerId: "failwindow", choices: [7, 14, 30], refreshId: "failrefresh",
408
691
  key: "fail-days", defaultDays: 14, roleKey: "fail-role", rolePickerId: "failrole",
409
692
  sinceKey: "fail-since", sinceChoices: REPORT_SINCE_CHOICES,