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.
- package/README.md +63 -28
- 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-eligibility.js +332 -43
- package/dist/src/backlog/backlog-md.js +79 -17
- 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 +178 -441
- 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 +32 -30
- package/dist/src/{brief-edit.js → brief/brief-edit.js} +12 -11
- package/dist/src/{brief.js → brief/brief.js} +33 -8
- 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 +13 -11
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +9 -7
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +43 -10
- package/dist/src/cli/cli-editor.js +13 -1
- package/dist/src/cli/cli-flag-specs.js +31 -6
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-query-commands.js +439 -56
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +124 -68
- package/dist/src/cli/completion.js +70 -12
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +264 -124
- 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 +13 -224
- package/dist/src/cli.js +78 -36
- 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 +107 -325
- 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 +494 -68
- package/dist/src/doctor/doctor-checks.js +509 -58
- package/dist/src/doctor/doctor-holds.js +139 -78
- 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 +2 -3
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +76 -25
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +219 -106
- 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 +80 -30
- 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 +6 -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 +79 -59
- 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 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +105 -54
- 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 +25 -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 +179 -104
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +29 -70
- package/dist/src/inbox/inbox-attachments.js +66 -10
- 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 +114 -73
- 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 +104 -50
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +130 -13
- package/dist/src/loop/loop-tick.js +190 -88
- package/dist/src/loop/loop.js +68 -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 -20
- 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 +113 -45
- package/dist/src/operator/operator-intent.js +74 -53
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +47 -28
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +74 -43
- package/dist/src/orchestrator/orchestrator.js +149 -96
- 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 +124 -38
- 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 +4 -3
- 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 +36 -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 +29 -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 +259 -254
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +235 -44
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +4 -3
- 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 +55 -34
- 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 +109 -131
- 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 +16 -14
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +15 -8
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +28 -25
- 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 +72 -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 +50 -39
- 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 +84 -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} +39 -38
- 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 +142 -25
- 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 +23 -22
- package/dist/src/tick/tick-detail.js +37 -24
- 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 +67 -53
- 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 +75 -10
- package/dist/src/ui/badges.js +113 -69
- package/dist/src/ui/gui/gui-client-boot.js +202 -29
- 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 +94 -37
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +90 -47
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +60 -43
- package/dist/src/ui/gui/gui-client-operator.js +73 -43
- package/dist/src/ui/gui/gui-client-pending.js +34 -31
- package/dist/src/ui/gui/gui-client-report.js +330 -47
- package/dist/src/ui/gui/gui-client-settings.js +126 -18
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +190 -74
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +18 -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 +176 -102
- package/dist/src/ui/status-payload.js +114 -36
- package/dist/src/ui/status-render.js +121 -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 +9 -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 +61 -57
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +44 -50
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +87 -1821
- 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 +52 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +44 -26
- package/dist/src/verdict/fix-claim.js +56 -33
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +43 -11
- 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 -56
- package/dist/src/landing/landing-slot.js +0 -256
- 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,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
|
-
|
|
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") + "
|
|
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
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
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) =>
|
|
118
|
-
// hasOwn again: an absent
|
|
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,
|
|
121
|
-
return { value, color: colorOf(i), title: d.date + " · " +
|
|
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 =
|
|
127
|
-
? "<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>"
|
|
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, "
|
|
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
|
|
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
|
|
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("
|
|
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) + "
|
|
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
|
|
215
|
-
// 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'>
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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("
|
|
394
|
-
block("Output tokens", chartTokens(d)) + block("Spend by
|
|
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("
|
|
397
|
-
(d.role ? "
|
|
398
|
-
|
|
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
|
|
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,
|