tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -1,239 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* src/report/report-data.ts
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
1
|
+
/** The windowed event-log fold backing the usage report: read the harness's event log over a
|
|
2
|
+
* local-day window, memoize the day-keyed read per (root, fromKey) so a dashboard polling
|
|
3
|
+
* /api/report only folds appended lines instead of re-reading the window, and group events
|
|
4
|
+
* into per-day `DayFold` accumulators plus the base-role scoping helpers the day and failure
|
|
5
|
+
* reports share. The two window collectors — collectReport (src/report/report-data.ts) and
|
|
6
|
+
* collectReportSince (src/report/report-since-data.ts) — fold through this layer, each parsed
|
|
7
|
+
* event through the shared single-event core in src/report/report-usage-fold.ts
|
|
8
|
+
* (`foldUsageEvent`), so the --since and day windows present cannot drift apart; it lives here
|
|
9
|
+
* matching its own test (test/report-fold-cache.test.ts). Zero incidental runtime
|
|
10
|
+
* dependencies: node built-ins and the app's own modules only. */
|
|
9
11
|
import { sameStatIdentity, statIdentityOrZero, statOrNull } from "../files/files.js";
|
|
10
12
|
import { readWindowEvents } from "../events/event-window.js";
|
|
11
|
-
import { eventDayKey,
|
|
13
|
+
import { eventDayKey, parseEventLine } from "../events/event-read.js";
|
|
12
14
|
import { readCompleteLines } from "../files/tail.js";
|
|
13
15
|
import { eventsLogPath } from "../paths.js";
|
|
14
|
-
import { isPassingFullCheck, isRedFullCheck } from "../build/build-check-events.js";
|
|
15
16
|
import { isPassingMainCheck, isRedMain } from "../baseline/main-guardrails.js";
|
|
16
|
-
import {
|
|
17
|
+
import { memoSetLru } from "../collections.js";
|
|
17
18
|
import { asList } from "../text/text.js";
|
|
18
|
-
import { finiteNumber } from "../files/json-object.js";
|
|
19
|
-
import { OUTCOME_CLASS } from "../failure/time-spend.js";
|
|
20
19
|
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
21
|
-
import {
|
|
22
|
-
/** Fold one event's `providerCost` map (`Record<provider, {costUsd, notionalCostUsd}>` — the
|
|
23
|
-
* shape run-finalize.ts's run_end and merge-slot.ts's merge_done/merge_failed emit) into the
|
|
24
|
-
* per-provider tallies, one addTo per provider per nonzero figure so zero stays absent. A
|
|
25
|
-
* missing, foreign, or empty map folds nothing, so old logs render byte-identically. */
|
|
26
|
-
function foldProviderCost(target, providerCost) {
|
|
27
|
-
if (typeof providerCost !== "object" || providerCost === null)
|
|
28
|
-
return;
|
|
29
|
-
for (const [provider, c] of Object.entries(providerCost)) {
|
|
30
|
-
if (typeof c !== "object" || c === null)
|
|
31
|
-
continue;
|
|
32
|
-
const entry = c;
|
|
33
|
-
const costUsd = finiteNumber(entry.costUsd, 0);
|
|
34
|
-
const notionalCostUsd = finiteNumber(entry.notionalCostUsd, 0);
|
|
35
|
-
if (costUsd !== 0)
|
|
36
|
-
addTo(target.costByProvider, provider, costUsd);
|
|
37
|
-
if (notionalCostUsd !== 0)
|
|
38
|
-
addTo(target.notionalByProvider, provider, notionalCostUsd);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/** Push `ms` into the accumulator list a day median reads, when it is positive: the event
|
|
42
|
-
* fields that feed these medians are missing or 0 for the same event kinds (short runs, build
|
|
43
|
-
* pre-checks, precedent legacy events, probe-seam waits), and those contribute nothing — a
|
|
44
|
-
* median over only real durations/wait times. Every median list folds through here, so the
|
|
45
|
-
* "positive-only" rule cannot drift between fields. */
|
|
46
|
-
function pushPositiveMs(list, ms) {
|
|
47
|
-
if (ms > 0)
|
|
48
|
-
list.push(ms);
|
|
49
|
-
}
|
|
50
|
-
/** Fold one event into a usage accumulator: run_end events add a run (per role and, when
|
|
51
|
-
* the target counts runs, in total), their output tokens, and their cost — zero-cost runs
|
|
52
|
-
* contribute nothing, so they leave no costByRole key, keeping the render's "$0 roles are
|
|
53
|
-
* omitted" rule true on the aggregation itself, not just at display time; merged events add
|
|
54
|
-
* a commit; merge_done/merge_failed events add a merge run and
|
|
55
|
-
* accumulate their tokens/cost into the merge fields only — costByRole stays a runs-only
|
|
56
|
-
* breakdown, and the merge line is how the reviewer's share is named. Both collectors fold
|
|
57
|
-
* through this one place, so the two windows' aggregation rules cannot drift apart. */
|
|
58
|
-
export function foldUsageEvent(target, ev) {
|
|
59
|
-
if (ev.type === "run_end") {
|
|
60
|
-
const role = eventRole(ev);
|
|
61
|
-
addTo(target.runsByRole, role, 1);
|
|
62
|
-
// Error-class runs get their own parallel tally — the day report's ``Failed runs by role``
|
|
63
|
-
// line — reusing the digest's OUTCOME_CLASS so "failed" cannot drift from the failure
|
|
64
|
-
// report's meaning. Never subtracted from runsByRole; the two are independent counts.
|
|
65
|
-
if (OUTCOME_CLASS[ev.result] === "error")
|
|
66
|
-
addTo(target.failedRunsByRole, role, 1);
|
|
67
|
-
// A refusal is its own deliberate outcome (src/verdict/refusal.ts), not error-class, so
|
|
68
|
-
// it needs an explicit result check: the one per-role rejection-without-review count a
|
|
69
|
-
// day report should surface beside rejections, revisions and falls. Never subtracted from
|
|
70
|
-
// runsByRole; a refused run still counts as a run.
|
|
71
|
-
if (ev.result === "refused")
|
|
72
|
-
addTo(target.refusalsByRole, role, 1);
|
|
73
|
-
if (target.runs !== undefined)
|
|
74
|
-
target.runs++;
|
|
75
|
-
const { tokens, costUsd, notionalCostUsd } = eventUsage(ev);
|
|
76
|
-
pushPositiveMs(target.runDurationsMs, finiteNumber(ev.durationMs, 0));
|
|
77
|
-
addTo(target.tokensByRole, role, tokens);
|
|
78
|
-
target.tokensOut += tokens;
|
|
79
|
-
target.costUsd += costUsd;
|
|
80
|
-
target.notionalCostUsd += notionalCostUsd;
|
|
81
|
-
if (costUsd !== 0)
|
|
82
|
-
addTo(target.costByRole, role, costUsd);
|
|
83
|
-
if (notionalCostUsd !== 0)
|
|
84
|
-
addTo(target.notionalByRole, role, notionalCostUsd);
|
|
85
|
-
// The notional spend's backend (plans/codex-backend.md part 3d/9): the subscription seam
|
|
86
|
-
// whose runs made the run's API-equivalent cost, from the run_end's `agent` (run-finalize
|
|
87
|
-
// carries it when the run reported billing/rate windows) — falling back to "claude" when
|
|
88
|
-
// a legacy window event (no agent, subscription billing) attributed its spend, so the old
|
|
89
|
-
// default stays the report's only backend. A run with no backend and no subscription
|
|
90
|
-
// billing is unattributed and adds no key, so the render keeps its generic line for it.
|
|
91
|
-
const notionalBackend = typeof ev.agent === "string" && ev.agent !== ""
|
|
92
|
-
? ev.agent
|
|
93
|
-
: ev.billing === "subscription"
|
|
94
|
-
? "claude"
|
|
95
|
-
: undefined;
|
|
96
|
-
if (notionalBackend !== undefined && notionalCostUsd !== 0)
|
|
97
|
-
addTo(target.notionalByBackend, notionalBackend, notionalCostUsd);
|
|
98
|
-
// The agent runs' per-provider spend (run-finalize.ts emits providerCost on run_end)
|
|
99
|
-
// — folded per provider beside the per-role maps, so a mixed team's `Cost by provider`
|
|
100
|
-
// line names the expensive seam.
|
|
101
|
-
foldProviderCost(target, ev.providerCost);
|
|
102
|
-
}
|
|
103
|
-
else if (ev.type === "review_rejected") {
|
|
104
|
-
// One rejection per event, on the day it happened, per role. Zero-means-absent: a role
|
|
105
|
-
// that never rejected stays absent, like costByRole.
|
|
106
|
-
addTo(target.rejectionsByRole, eventRole(ev), 1);
|
|
107
|
-
// A rejection that carried afterMerge: true is a post-merge one — the commit had already
|
|
108
|
-
// landed, so the reviewer's turn-back reaches the revert path off main. Kept as its own
|
|
109
|
-
// subset of rejectionsByRole because it is a different, costlier kind of rejection.
|
|
110
|
-
if (ev.afterMerge === true)
|
|
111
|
-
addTo(target.postMergeRejectionsByRole, eventRole(ev), 1);
|
|
112
|
-
// A rejection that carried a positive durationMs means a reviewer actually ran the gate
|
|
113
|
-
// and reached a verdict (post-merge rejections do; the build pre-check contributes no
|
|
114
|
-
// duration), so it joins the review-duration median beside approvals. durationMs: 0 and
|
|
115
|
-
// a duration-less event contribute nothing.
|
|
116
|
-
pushPositiveMs(target.reviewDurationsMs, finiteNumber(ev.durationMs, 0));
|
|
117
|
-
}
|
|
118
|
-
else if (ev.type === "review_verdict") {
|
|
119
|
-
// An approval that reached a verdict. Every reviewer run that reached a verdict, approval
|
|
120
|
-
// or rejection, is one entry in the review-duration median; review_failed stays out — it
|
|
121
|
-
// has no verdict. Mirrors the rejection branch above, on the same-day count.
|
|
122
|
-
pushPositiveMs(target.reviewDurationsMs, finiteNumber(ev.durationMs, 0));
|
|
123
|
-
}
|
|
124
|
-
else if (ev.type === "revision") {
|
|
125
|
-
// One rework round per event, on the day it happened, per role. Zero-means-absent: a role
|
|
126
|
-
// that never reworked a rejected change stays absent, like rejectionsByRole.
|
|
127
|
-
addTo(target.revisionsByRole, eventRole(ev), 1);
|
|
128
|
-
}
|
|
129
|
-
else if (isPassingFullCheck(ev)) {
|
|
130
|
-
// The merge slot's passing full-suite checks, for the day report's check-time median.
|
|
131
|
-
pushPositiveMs(target.checkDurationsMs, finiteNumber(ev.durationMs, 0));
|
|
132
|
-
}
|
|
133
|
-
else if (isRedFullCheck(ev)) {
|
|
134
|
-
// The merge slot's red full-suite checks, for the day report's red-check count.
|
|
135
|
-
target.checkFailures++;
|
|
136
|
-
}
|
|
137
|
-
else if (ev.type === "model_fallback_started") {
|
|
138
|
-
// One episode per start event; durations are not summed (an episode can span the window edge).
|
|
139
|
-
addTo(target.fallbacksByRole, eventRole(ev), 1);
|
|
140
|
-
}
|
|
141
|
-
else if (ev.type === "image_reroute") {
|
|
142
|
-
// One reroute per event, preflight or after a blind read.
|
|
143
|
-
addTo(target.imageReroutesByRole, eventRole(ev), 1);
|
|
144
|
-
}
|
|
145
|
-
else if (ev.type === "run_deferred") {
|
|
146
|
-
// One deferral per event, a due maintenance run that stood down. Bucket by the raw
|
|
147
|
-
// reason code ("backlog", "plans-full", else "no-work" for a missing/foreign/torn
|
|
148
|
-
// reason — the same fallback deferredReasonText applies in the feed), so the collector
|
|
149
|
-
// stays free of a presentation import and the render prints the code the event carried.
|
|
150
|
-
// Team-only: a reason is not a role.
|
|
151
|
-
const reason = ev.reason === "backlog" || ev.reason === "plans-full" ? ev.reason : "no-work";
|
|
152
|
-
addTo(target.deferredByReason, reason, 1);
|
|
153
|
-
}
|
|
154
|
-
else if (ev.type === "merged") {
|
|
155
|
-
target.commits++;
|
|
156
|
-
// Work ratio 4/4: split merged commits by base role and by tier. Instance suffixes fold
|
|
157
|
-
// into their role (`feature-2` counts as feature) so the split does not fragment.
|
|
158
|
-
const role = baseRoleOf(eventRole(ev));
|
|
159
|
-
addTo(target.commitsByRole, role, 1);
|
|
160
|
-
const tier = commitTier(role);
|
|
161
|
-
if (tier === "work")
|
|
162
|
-
target.workCommits++;
|
|
163
|
-
else if (tier === "maintenance")
|
|
164
|
-
target.maintenanceCommits++;
|
|
165
|
-
}
|
|
166
|
-
else if (ev.type === "run_start") {
|
|
167
|
-
// One tally per run start on a configured model: the same `model` field the CLI's
|
|
168
|
-
// `run #N started on <model>` line prints, and the only event that carries a model
|
|
169
|
-
// (run_end has none), so a run that started with no configured model contributes no key.
|
|
170
|
-
const model = typeof ev.model === "string" && ev.model.length > 0 ? ev.model : null;
|
|
171
|
-
if (model !== null)
|
|
172
|
-
addTo(target.runsByModel, model, 1);
|
|
173
|
-
}
|
|
174
|
-
else if (ev.type === "provider_wait") {
|
|
175
|
-
// The day report's provider-slot wait median, mirroring the slot_wait push: a wait only
|
|
176
|
-
// event-logged at or above the 30 s threshold counts, so a 0 ms wait (a probe seam) adds
|
|
177
|
-
// nothing and the median reflects real blocked admissions only.
|
|
178
|
-
pushPositiveMs(target.providerWaitsMs, finiteNumber(ev.waitedMs, 0));
|
|
179
|
-
}
|
|
180
|
-
else if (ev.type === "slot_wait") {
|
|
181
|
-
// The day report's workspace-slot wait median, mirroring the provider_wait push: a wait
|
|
182
|
-
// only event-logged at or above the 30 s threshold counts, so a 0 ms wait (a probe seam)
|
|
183
|
-
// or a pre-waitedMs event adds nothing.
|
|
184
|
-
pushPositiveMs(target.slotWaitsMs, finiteNumber(ev.waitedMs, 0));
|
|
185
|
-
}
|
|
186
|
-
else if (ev.type === "check_wait") {
|
|
187
|
-
// The day report's check-slot wait median, mirroring the slot_wait push: only waits logged
|
|
188
|
-
// at or above the 30 s threshold count, so a 0 ms or pre-waitedMs event adds nothing.
|
|
189
|
-
pushPositiveMs(target.checkWaitsMs, finiteNumber(ev.waitedMs, 0));
|
|
190
|
-
}
|
|
191
|
-
else if (ev.type === "merge_done" || ev.type === "merge_failed") {
|
|
192
|
-
target.mergeRuns = (target.mergeRuns ?? 0) + 1;
|
|
193
|
-
if (ev.type === "merge_failed")
|
|
194
|
-
target.mergeFailed = (target.mergeFailed ?? 0) + 1;
|
|
195
|
-
// Attribute the outcome to the authoring role (writeMergeOutcome logs role: entry.role)
|
|
196
|
-
// folded to base role, the same normalization the merged branch uses, so `feature-2`
|
|
197
|
-
// aggregates into `feature`. A parallel tally beside the team run counts, never
|
|
198
|
-
// subtracted from mergeRuns/mergeFailed.
|
|
199
|
-
const role = baseRoleOf(eventRole(ev));
|
|
200
|
-
if (ev.type === "merge_done") {
|
|
201
|
-
addTo(target.mergedByRole, role, 1);
|
|
202
|
-
// Events logged before queuedMs existed carry none: skipped, not counted as zero.
|
|
203
|
-
pushPositiveMs(target.mergeWaitsMs, finiteNumber(ev.queuedMs, 0));
|
|
204
|
-
// How long the merge slot itself held the change, from the verification's start to the
|
|
205
|
-
// outcome. Like mergeWaitsMs, only a landed merge feeds it, and a pre-durationMs event
|
|
206
|
-
// (or 0) contributes nothing.
|
|
207
|
-
pushPositiveMs(target.mergeDurationsMs, finiteNumber(ev.durationMs, 0));
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
addTo(target.mergeFailedByRole, role, 1);
|
|
211
|
-
// The event's result (writeMergeOutcome's RunResult) names WHY the merge bounced
|
|
212
|
-
// off the gate — rejected/merge_conflict/merge_blocked/aborted. A legacy event with
|
|
213
|
-
// no result folds under `unknown`, so the reason buckets always sum to mergeFailed.
|
|
214
|
-
const reason = typeof ev.result === "string" && ev.result.length > 0 ? ev.result : "unknown";
|
|
215
|
-
addTo(target.mergeFailedByReason, reason, 1);
|
|
216
|
-
}
|
|
217
|
-
const { tokens, costUsd, notionalCostUsd } = eventUsage(ev);
|
|
218
|
-
target.mergeTokens = (target.mergeTokens ?? 0) + tokens;
|
|
219
|
-
target.mergeCostUsd = (target.mergeCostUsd ?? 0) + costUsd;
|
|
220
|
-
// A subscription merge's API-equivalent spend joins the notional line, never the
|
|
221
|
-
// budgeted cost totals (plans/agent-backends.md part 6a).
|
|
222
|
-
target.notionalCostUsd += notionalCostUsd;
|
|
223
|
-
// The merge's notional spend per backend (merge-slot.ts emits it on the event, keyed by
|
|
224
|
-
// each subscription run's agent) — folded beside the run-end per-backend map, so a codex
|
|
225
|
-
// merge's subscription spend names codex instead of falling back to the generic line.
|
|
226
|
-
for (const [backend, c] of Object.entries(ev.notionalByBackend ?? {})) {
|
|
227
|
-
const amt = finiteNumber(c, 0);
|
|
228
|
-
if (amt !== 0)
|
|
229
|
-
addTo(target.notionalByBackend, backend, amt);
|
|
230
|
-
}
|
|
231
|
-
// The merge's per-provider spend (merge-slot.ts keys it to its reviewer seam), so a
|
|
232
|
-
// merge's costs appear under the provider that ran the reviewer, matching the author
|
|
233
|
-
// side's foldMergeUsage charge.
|
|
234
|
-
foldProviderCost(target, ev.providerCost);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
20
|
+
import { foldUsageEvent } from "./report-usage-fold.js";
|
|
237
21
|
/** Bounded memo of window folds. Eight entries cover a dashboard flipping among a few day
|
|
238
22
|
* counts plus the midnight rollover's new fromKey; oldest-insertion eviction (Map preserves
|
|
239
23
|
* insertion order) keeps the memory bound to a handful of per-day accumulator maps. */
|
|
@@ -253,6 +37,13 @@ export function zeroCommitCounts() {
|
|
|
253
37
|
export function zeroMergeCounts() {
|
|
254
38
|
return { mergeRuns: 0, mergeTokens: 0, mergeCostUsd: 0 };
|
|
255
39
|
}
|
|
40
|
+
/** Build one object-literal spread cell for a per-slice role/product map only when the map has
|
|
41
|
+
* entries (`map` non-empty), so an entry-free window omits the field instead of carrying an
|
|
42
|
+
* empty map. The single home of that guard for both window collectors — the day report's and
|
|
43
|
+
* the trailing-window totals object-literal spreads all followed it inline before. */
|
|
44
|
+
export function nonemptyObj(key, map) {
|
|
45
|
+
return Object.keys(map).length > 0 ? { [key]: map } : {};
|
|
46
|
+
}
|
|
256
47
|
/** The base roles a report scope names: undefined and an empty list mean no scope (the team
|
|
257
48
|
* report); otherwise each id folded through `baseRoleOf`, deduplicated in first-seen order, so
|
|
258
49
|
* `feature-2,feature` is one base. */
|
|
@@ -333,8 +124,9 @@ function dayFoldFor(entry, dayKey) {
|
|
|
333
124
|
reviewDurationsMs: [],
|
|
334
125
|
failedRunsByRole: {},
|
|
335
126
|
fallbacksByRole: {},
|
|
336
|
-
imageReroutesByRole: {},
|
|
337
127
|
tokensByRole: {},
|
|
128
|
+
promptTokensByRole: {},
|
|
129
|
+
cacheReadTokensByRole: {},
|
|
338
130
|
costByRole: {},
|
|
339
131
|
notionalByRole: {},
|
|
340
132
|
...zeroMergeCounts(),
|
|
@@ -363,7 +155,7 @@ function foldEventInto(entry, ev) {
|
|
|
363
155
|
entry.hadEvents = true;
|
|
364
156
|
entry.firstEventTs = typeof ev.ts === "number" ? ev.ts : undefined;
|
|
365
157
|
}
|
|
366
|
-
if (isRedMain(ev) || isPassingMainCheck(ev))
|
|
158
|
+
if (isRedMain(ev) || isPassingMainCheck(ev) || ev.type === "main_culprit")
|
|
367
159
|
entry.redEvents.push(ev);
|
|
368
160
|
foldUsageEvent(dayFoldFor(entry, dayKey), ev);
|
|
369
161
|
}
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* only on the team scope. */
|
|
7
7
|
import { dayKey } from "../text/datetime.js";
|
|
8
8
|
import { isPassingMainCheck, isRedMain } from "../baseline/main-guardrails.js";
|
|
9
|
+
import { addTo, getOrCreate } from "../collections.js";
|
|
10
|
+
import { shortSha } from "../text/format.js";
|
|
11
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
12
|
+
/** The events whose `ts` is a number in `[fromMs, toMs)`, in log order. The three byDay walks
|
|
13
|
+
* (mainRedMinutesByDay, mainRedFailingTestsByDay, mainRedCulpritsByDay) each iterate these, so
|
|
14
|
+
* they cannot disagree about which events fall in the window; the `typeof e.ts === "number"`
|
|
15
|
+
* guard also drops a torn log line whose stamp never landed on the event. */
|
|
16
|
+
function inWindowEvents(events, fromMs, toMs) {
|
|
17
|
+
return events.filter((e) => typeof e.ts === "number" && e.ts >= fromMs && e.ts < toMs);
|
|
18
|
+
}
|
|
9
19
|
/** Add a spell's ms, split across the local calendar days it spans, to `map` (a Map, so the
|
|
10
20
|
* caller keeps the day-key order the walk produced). */
|
|
11
21
|
function addSpell(map, start, end) {
|
|
@@ -32,9 +42,7 @@ function addSpell(map, start, end) {
|
|
|
32
42
|
* window — the report measures main's own red time within the window it was asked for. */
|
|
33
43
|
export function mainRedMinutesByDay(events, fromMs, toMs) {
|
|
34
44
|
const map = new Map();
|
|
35
|
-
const inWindow = events
|
|
36
|
-
.filter((e) => typeof e.ts === "number" && e.ts >= fromMs && e.ts < toMs)
|
|
37
|
-
.sort((a, b) => a.ts - b.ts);
|
|
45
|
+
const inWindow = inWindowEvents(events, fromMs, toMs).sort((a, b) => a.ts - b.ts);
|
|
38
46
|
let openStart;
|
|
39
47
|
for (const e of inWindow) {
|
|
40
48
|
const ts = e.ts;
|
|
@@ -53,3 +61,62 @@ export function mainRedMinutesByDay(events, fromMs, toMs) {
|
|
|
53
61
|
addSpell(map, openStart, toMs);
|
|
54
62
|
return map;
|
|
55
63
|
}
|
|
64
|
+
/** Count named items into per-calendar-day maps (`YYYY-MM-DD` → key → count) over the window
|
|
65
|
+
* `[fromMs, toMs)`: walk the events `inWindowEvents` admits and, for each one `keysOf` turns
|
|
66
|
+
* into a non-empty list, add each key once into that event's day (`dayKey`). The two byDay
|
|
67
|
+
* count walks (`mainRedFailingTestsByDay`, `mainRedCulpritsByDay`) share this shape so they
|
|
68
|
+
* cannot disagree about which events fall in the window (`inWindowEvents`) or which day an
|
|
69
|
+
* event lands on (`dayKey`); `keysOf` returns `undefined` (or an empty list) for the events a
|
|
70
|
+
* walk ignores, keeping their loose-event-log guards (a non-`failingTests` red, a
|
|
71
|
+
* commit-less `main_culprit`) in the walk where its own rule reads clearest. */
|
|
72
|
+
function countByDayInWindow(events, fromMs, toMs, keysOf) {
|
|
73
|
+
const map = new Map();
|
|
74
|
+
for (const e of inWindowEvents(events, fromMs, toMs)) {
|
|
75
|
+
const keys = keysOf(e);
|
|
76
|
+
if (keys === undefined || keys.length === 0)
|
|
77
|
+
continue;
|
|
78
|
+
const record = getOrCreate(map, dayKey(e.ts), () => ({}));
|
|
79
|
+
for (const key of keys)
|
|
80
|
+
addTo(record, key, 1);
|
|
81
|
+
}
|
|
82
|
+
return map;
|
|
83
|
+
}
|
|
84
|
+
/** Main's red failing tests per local calendar day (`YYYY-MM-DD` → test → count), over the
|
|
85
|
+
* window `[fromMs, toMs)`: for each event `inWindowEvents` admits (numeric `ts` in `[fromMs,
|
|
86
|
+
* toMs)`) that G1 accepts as red (`isRedMain`) — a `main_check` that ran the full
|
|
87
|
+
* check and failed, or a `main_red_hold` — count each `failingTests` element that is a string
|
|
88
|
+
* once into that event's day (`dayKey`). A passing check is not red, and a red event that
|
|
89
|
+
* carries no `failingTests` (a hold whose red named none, or a torn non-array `failingTests`;
|
|
90
|
+
* a non-string element contributes nothing) records nothing, so the loose event log can never
|
|
91
|
+
* emit `undefined` as a key. Shares `isRedMain` and `dayKey` with
|
|
92
|
+
* `mainRedMinutesByDay`, so the two cannot disagree about which events are main's red ones.
|
|
93
|
+
* This is main's own failing tests, not any role's, so the report carries it only on the team
|
|
94
|
+
* scope. */
|
|
95
|
+
export function mainRedFailingTestsByDay(events, fromMs, toMs) {
|
|
96
|
+
return countByDayInWindow(events, fromMs, toMs, (e) => isRedMain(e) && Array.isArray(e.failingTests)
|
|
97
|
+
? e.failingTests.filter((t) => typeof t === "string")
|
|
98
|
+
: undefined);
|
|
99
|
+
}
|
|
100
|
+
/** Main's red culprits per local calendar day (`YYYY-MM-DD` → label → count), over the window
|
|
101
|
+
* `[fromMs, toMs)`: for each event `inWindowEvents` admits (numeric `ts` in `[fromMs, toMs)`)
|
|
102
|
+
* that is a `main_culprit` whose `commit` is a non-empty string, label it
|
|
103
|
+
* `<roleLabel(runner)> <shortSha(commit)>` when `runner` is a non-empty string, else
|
|
104
|
+
* `merge <shortSha(commit)>`, and count it once into that event's day (`dayKey`). This is the
|
|
105
|
+
* live `mainred` alert's own event (`src/status/status-polls.ts`'s `MainCheckStatus.red
|
|
106
|
+
* .culprit`), so the report names the very merge the watch blamed, not just the tests it
|
|
107
|
+
* named. A `main_culprit` with no `commit` or an empty/non-string one contributes nothing, so
|
|
108
|
+
* the loose event log can never emit `undefined` as a key. Shares `dayKey` with the sibling
|
|
109
|
+
* walks (`mainRedMinutesByDay`, `mainRedFailingTestsByDay`), so the three cannot disagree
|
|
110
|
+
* about which day an event lands on. This is main's own blame, not any role's, so the report
|
|
111
|
+
* carries it only on the team scope. */
|
|
112
|
+
export function mainRedCulpritsByDay(events, fromMs, toMs) {
|
|
113
|
+
return countByDayInWindow(events, fromMs, toMs, (e) => e.type === "main_culprit" &&
|
|
114
|
+
typeof e.commit === "string" &&
|
|
115
|
+
e.commit.length > 0
|
|
116
|
+
? [
|
|
117
|
+
typeof e.runner === "string" && e.runner.length > 0
|
|
118
|
+
? `${roleLabel(e.runner)} ${shortSha(e.commit)}`
|
|
119
|
+
: `merge ${shortSha(e.commit)}`,
|
|
120
|
+
]
|
|
121
|
+
: undefined);
|
|
122
|
+
}
|