tumwater 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- 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
|
|
3
|
-
* loop, output tokens, spend by loop — with gridlines, a scale, and one
|
|
4
|
-
* a window picker switches between 7, 14, 30, and 90 days and a
|
|
5
|
-
* to one loop group (`?role=`, the browser twin of
|
|
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,18 +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
|
-
|
|
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
|
-
}
|
|
35
|
-
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"]);
|
|
36
55
|
}
|
|
37
56
|
|
|
38
57
|
// X-axis labels: MM-DD, thinned to at most seven, counted back from the newest day so today
|
|
@@ -102,38 +121,290 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
function chartCommits(data) {
|
|
105
|
-
return reportSvg(data.series, (d) => [{ value: d.commits, color: "#10b981", title: d.date + ": " + plural(d.commits, "commit") + "
|
|
124
|
+
return reportSvg(data.series, (d) => [{ value: d.commits, color: "#10b981", title: d.date + ": " + plural(d.commits, "commit") + " merged" }],
|
|
106
125
|
countAxis, true);
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
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) {
|
|
113
221
|
const colorOf = (i) => REPORT_PALETTE[i % REPORT_PALETTE.length];
|
|
114
222
|
const svg = reportSvg(
|
|
115
223
|
data.series,
|
|
116
|
-
(d) =>
|
|
117
|
-
// hasOwn again: an absent
|
|
118
|
-
const
|
|
119
|
-
|
|
224
|
+
(d) => entries.map(([key], i) => {
|
|
225
|
+
// hasOwn again: an absent key must read 0, not Object.prototype's inherited value.
|
|
226
|
+
const m = d[field] || {};
|
|
227
|
+
const value = Object.hasOwn(m, key) ? m[key] : 0;
|
|
228
|
+
return { value, color: colorOf(i), title: d.date + " · " + key + ": " + fmt(value) };
|
|
120
229
|
}),
|
|
121
230
|
fmtAxis,
|
|
122
231
|
integer,
|
|
123
232
|
);
|
|
124
|
-
const legend =
|
|
125
|
-
? "<div class='legend'>" +
|
|
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>"
|
|
126
235
|
: "";
|
|
127
236
|
return svg + legend;
|
|
128
237
|
}
|
|
129
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
|
+
|
|
130
246
|
function chartTicksByRole(data) {
|
|
131
|
-
return roleStackChart(data, "
|
|
247
|
+
return roleStackChart(data, "runsByRole", (n) => plural(n, "run"), countAxis, true);
|
|
132
248
|
}
|
|
133
249
|
|
|
134
250
|
function chartCostByRole(data) {
|
|
135
251
|
return roleStackChart(data, "costByRole", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
|
|
136
252
|
}
|
|
253
|
+
|
|
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.
|
|
261
|
+
function chartNotionalByRole(data) {
|
|
262
|
+
return roleStackChart(data, "notionalByRole", fmtUsd, (v) => (v === 0 ? "$0" : fmtUsd(v)), false);
|
|
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
|
+
}
|
|
137
408
|
// report-chart:end
|
|
138
409
|
|
|
139
410
|
// report-tip:start
|
|
@@ -187,7 +458,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
187
458
|
const tile = (label, ic, value, avg, extra) => "<div class='stat'><span class='stat-label'>" + icon(ic) + esc(label) +
|
|
188
459
|
"</span><span class='stat-value'>" + esc(value) + "</span><span class='stat-sub'>" + esc(avg + " a day" + (extra || "")) + "</span></div>";
|
|
189
460
|
return [
|
|
190
|
-
tile("Commits
|
|
461
|
+
tile("Commits merged", "merge", String(t.commits), perDay(t.commits, (v) => v.toFixed(1)),
|
|
191
462
|
" · " + workSplit(t)),
|
|
192
463
|
// A role scope has no backlog tallies: features/bugs come from day-granular backlog-file
|
|
193
464
|
// dates that carry no role, so the scoped collector zeroes them and the tiles stay out —
|
|
@@ -196,26 +467,27 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
196
467
|
tile("Features done", "plan", String(t.featuresDone), perDay(t.featuresDone, (v) => v.toFixed(1))),
|
|
197
468
|
tile("Bugs fixed", "bug", String(t.bugsFixed), perDay(t.bugsFixed, (v) => v.toFixed(1))),
|
|
198
469
|
]),
|
|
199
|
-
tile("
|
|
470
|
+
tile("Runs", "refresh", String(t.runs), perDay(t.runs, Math.round)),
|
|
200
471
|
tile("Output tokens", "bars", fmtTokens(t.tokensOut), perDay(t.tokensOut, (v) => fmtTokens(Math.round(v)))),
|
|
201
|
-
tile("Spent", "dollar", fmtUsd(t.costUsd), perDay(t.costUsd, fmtUsd)
|
|
472
|
+
tile("Spent", "dollar", fmtUsd(t.costUsd), perDay(t.costUsd, fmtUsd),
|
|
473
|
+
t.notionalCostUsd > 0 ? " · " + fmtUsd(t.notionalCostUsd) + " estimated" : undefined),
|
|
202
474
|
].join("");
|
|
203
475
|
}
|
|
204
476
|
|
|
205
477
|
// report-role:start
|
|
206
|
-
// The Loop picker's options: the
|
|
207
|
-
// role, in the
|
|
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
|
|
208
480
|
// Loop pickers (the report views', History's, and the Activity feed's) render role names the
|
|
209
481
|
// same way — role names are dynamic (custom loops) and the custom-loop charset admits HTML
|
|
210
482
|
// metacharacters. Pure, so a test can drive it without a DOM.
|
|
211
483
|
function rolePickerOptions(roles) {
|
|
212
|
-
return "<option value=''>All
|
|
484
|
+
return "<option value=''>All roles</option>" +
|
|
213
485
|
(roles || []).map((r) => "<option value='" + esc(r) + "'>" + esc(r) + "</option>").join("");
|
|
214
486
|
}
|
|
215
|
-
// Repaint one Loop picker from the
|
|
487
|
+
// Repaint one Loop picker from the team's role list, returning the scope to keep filtering
|
|
216
488
|
// with. Every picker (the report views' syncRoles, History's syncHistoryRoles, the Activity
|
|
217
489
|
// feed's syncFeedRoles) does the same thing: rebuild only when the roster changes, keep the
|
|
218
|
-
// persisted choice while the
|
|
490
|
+
// persisted choice while the team still lists it, and fall back to All loops — persisting
|
|
219
491
|
// that fallback — when it does not, so a dimmed picker never scopes the request to a ghost
|
|
220
492
|
// loop. The last-painted roster is tracked per select id, so the pickers never share a key.
|
|
221
493
|
const rolePickerKeys = {};
|
|
@@ -239,7 +511,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
239
511
|
// GUI twin of the TUI's Ctrl+G scope cycle (src/ui/tui/tui-keys.ts scopeCycle). The status
|
|
240
512
|
// payload already carries the base role as instanceOf on an instance row (src/status/
|
|
241
513
|
// status-data.ts), so this borrows the server's own fold rather than re-deriving it. History's
|
|
242
|
-
// picker (gui-client-history.ts) filters by exact loop id and keeps the raw
|
|
514
|
+
// picker (gui-client-history.ts) filters by exact loop id and keeps the raw team list. Pure,
|
|
243
515
|
// so a test can drive it without a DOM.
|
|
244
516
|
function reportBaseRoles(loops) {
|
|
245
517
|
const bases = [];
|
|
@@ -250,7 +522,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
250
522
|
return bases;
|
|
251
523
|
}
|
|
252
524
|
// Repaint both report views' Loop pickers from a status payload: each view's persisted
|
|
253
|
-
// choice is kept while the
|
|
525
|
+
// choice is kept while the team still lists it and falls back to All loops otherwise. The
|
|
254
526
|
// views are defined below; the boot poll and each fetch call this once they exist.
|
|
255
527
|
function syncReportRoles(d) {
|
|
256
528
|
for (const view of [usage, failures]) view.syncRoles(d);
|
|
@@ -262,11 +534,11 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
262
534
|
function viewHead(title, blurb, pickerId, choices, current, refreshId, rolePickerId, sinceChoices) {
|
|
263
535
|
// A dropped blurb renders as no subtitle at all, never the string "undefined". The Loop
|
|
264
536
|
// picker rides the toolbar when the view has one (the shared Usage/Failures shape); the
|
|
265
|
-
// options are filled by syncRoles from the
|
|
537
|
+
// options are filled by syncRoles from the team's role list. The trailing-window buttons
|
|
266
538
|
// (a view that configured sinceChoices) sit in the same segmented control after the day
|
|
267
539
|
// buttons; each carries its length as data-since so windowView can mark exactly one active.
|
|
268
540
|
const rolePicker = rolePickerId
|
|
269
|
-
? "<label><span class='sr-only'>
|
|
541
|
+
? "<label><span class='sr-only'>Role</span><select class='field' id='" + rolePickerId + "' data-role='1'><option value=''>All roles</option></select></label>"
|
|
270
542
|
: "";
|
|
271
543
|
const sinceButtons = (sinceChoices || []).map(([ms, label]) =>
|
|
272
544
|
"<button type='button' data-since='" + ms + "'>" + esc(String(label)) + "</button>").join("");
|
|
@@ -297,14 +569,14 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
297
569
|
// fetch still uses it.
|
|
298
570
|
const sinceChoice = o.sinceChoices ? Number(recall(o.sinceKey)) : NaN;
|
|
299
571
|
let sinceMs = (o.sinceChoices || []).some(([ms]) => ms === sinceChoice) ? sinceChoice : null;
|
|
300
|
-
// The persisted Loop scope for this view: "" is the
|
|
572
|
+
// The persisted Loop scope for this view: "" is the team-wide report. Only a view that
|
|
301
573
|
// configured a rolePickerId reads it, so a view without a picker never touches storage.
|
|
302
574
|
let role = o.roleKey ? recall(o.roleKey) || "" : "";
|
|
303
575
|
let fetchedAt = 0;
|
|
304
|
-
// Keep this view's Loop picker in step with the
|
|
576
|
+
// Keep this view's Loop picker in step with the team's role list (custom loops included).
|
|
305
577
|
function syncRoles(d) {
|
|
306
578
|
if (!o.rolePickerId || !d) return;
|
|
307
|
-
role = syncRolePicker(o.rolePickerId, reportBaseRoles(d.
|
|
579
|
+
role = syncRolePicker(o.rolePickerId, reportBaseRoles(d.roles), role, o.roleKey);
|
|
308
580
|
}
|
|
309
581
|
async function fetchView(throttled) {
|
|
310
582
|
if (throttled && Date.now() - fetchedAt < 60000) return;
|
|
@@ -362,7 +634,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
362
634
|
|
|
363
635
|
const usage = windowView({
|
|
364
636
|
panelId: "report", title: "Usage", bodyId: "usagebody",
|
|
365
|
-
blurb: "What the
|
|
637
|
+
blurb: "What the team produced and what it cost, per day and per role.",
|
|
366
638
|
pickerId: "usagewindow", choices: [7, 14, 30, 90], refreshId: "usagerefresh",
|
|
367
639
|
key: "usage-days", defaultDays: 14, roleKey: "usage-role", rolePickerId: "usagerole",
|
|
368
640
|
sinceKey: "usage-since", sinceChoices: REPORT_SINCE_CHOICES,
|
|
@@ -377,15 +649,36 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
377
649
|
const md = String(d.markdown).replace(/^# .*\n+/, "");
|
|
378
650
|
return "<article class='card md'>" + renderMarkdown(md, true) + "</article>";
|
|
379
651
|
}
|
|
380
|
-
const block = (title, svg) => "<section class='card chart-card'><header class='card-head'><h2>" + esc(title) +
|
|
381
|
-
"</h2><span class='muted'>per day</span></header><div class='chart'>" + svg + "</div></section>";
|
|
652
|
+
const block = (title, svg, sub) => "<section class='card chart-card'><header class='card-head'><h2>" + esc(title) +
|
|
653
|
+
"</h2><span class='muted'>" + esc(sub || "per day") + "</span></header><div class='chart'>" + svg + "</div></section>";
|
|
382
654
|
// A scoped report drops to the four scoped tiles and says what it left out, mirroring the
|
|
383
|
-
// CLI's --role report. The
|
|
655
|
+
// CLI's --role report. The team-wide render is unchanged.
|
|
384
656
|
return "<div class='stats" + (d.role ? "" : " six") + "'>" + reportSummary(d) + "</div><div class='grid-2 even'>" +
|
|
385
|
-
block("
|
|
386
|
-
block("Output tokens", chartTokens(d)) + block("
|
|
387
|
-
(d.role
|
|
388
|
-
|
|
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") : "") +
|
|
677
|
+
(d.totals.notionalCostUsd > 0
|
|
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>" : "") +
|
|
389
682
|
(d.from ? "<p class='muted window-note'>" + esc(d.from + " → " + d.to) +
|
|
390
683
|
(!d.coversFullWindow ? " · " + ${JSON.stringify(SPARSE_WINDOW_NOTE)} : "") + "</p>" : "");
|
|
391
684
|
},
|
|
@@ -393,7 +686,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-chart:start
|
|
|
393
686
|
|
|
394
687
|
const failures = windowView({
|
|
395
688
|
panelId: "failures", title: "Failures", bodyId: "failbody",
|
|
396
|
-
blurb: "What went wrong, where, and how often — the digest the
|
|
689
|
+
blurb: "What went wrong, where, and how often — the digest the selfcheck role reads.",
|
|
397
690
|
pickerId: "failwindow", choices: [7, 14, 30], refreshId: "failrefresh",
|
|
398
691
|
key: "fail-days", defaultDays: 14, roleKey: "fail-role", rolePickerId: "failrole",
|
|
399
692
|
sinceKey: "fail-since", sinceChoices: REPORT_SINCE_CHOICES,
|