tumwater 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- 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 +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -17,7 +17,7 @@ import { buildStaleness, isSelfHosted, readBuildInfo, STALE_INPUTS_LABEL } from
|
|
|
17
17
|
import { dirEntryNames, statOrNull } from "../files/files.js";
|
|
18
18
|
import { refSha } from "../git/git.js";
|
|
19
19
|
import { STATE_DIR, eventsArchivePath, eventsLogPath } from "../paths.js";
|
|
20
|
-
import { BYTES_PER_GB, diskVolumePath, sampleFreeBytes } from "../gates/disk-
|
|
20
|
+
import { BYTES_PER_GB, diskVolumePath, sampleFreeBytes } from "../gates/disk-gates.js";
|
|
21
21
|
import { DAY_MS, dayKey } from "../text/datetime.js";
|
|
22
22
|
import { errorMessage } from "../text/text.js";
|
|
23
23
|
import { lowerMedian } from "../collections.js";
|
|
@@ -33,7 +33,7 @@ import { roleLabel } from "../roles/role-labels.js";
|
|
|
33
33
|
import { quarantineWarning } from "../baseline/main-guardrails.js";
|
|
34
34
|
import { readCiParity, currentCiParityState, parityIsCurrent, ciParityStalled, CI_PARITY_STALE_MS } from "../run/ci-parity.js";
|
|
35
35
|
import { readQaCoverage, qaCoverageStalled, QA_COVERAGE_STALE_MS } from "../run/qa-coverage.js";
|
|
36
|
-
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
36
|
+
import { failOutcome, okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
37
37
|
/** .tumwater writable — absent is fine (created on first run); present, prove it by writing
|
|
38
38
|
* and deleting a temp file inside. The probe leaves the dir's listing unchanged after the run.
|
|
39
39
|
* The write is the property under test: only a write that fails is "not writable", so the
|
|
@@ -251,7 +251,7 @@ export function checkBuildCheck(root, config) {
|
|
|
251
251
|
* once, so a routine one-off flake stays quiet. ok/warn only — never a fail — so a flaky suite
|
|
252
252
|
* cannot fail a scripted doctor pre-flight (the checkStalledEntries precedent; the exit-code
|
|
253
253
|
* rule is
|
|
254
|
-
* outcome.ts's). */
|
|
254
|
+
* doctor-outcome.ts's). */
|
|
255
255
|
export function checkFlakyChecks(root, now = Date.now()) {
|
|
256
256
|
const { events } = readEventsSince(root, KNOWN_FLAKE_WINDOW_MS);
|
|
257
257
|
const flapped = repeatedFlakeCounts(events);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readRoleNotesOverCap } from "../status/status-role-notes.js";
|
|
2
2
|
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
3
3
|
import { agree, plural } from "../text/phrases.js";
|
|
4
|
-
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
4
|
+
import { okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
5
5
|
/** How many oversized notes the `role notes` row names before appending "and N more", the
|
|
6
6
|
* cap the other multi-role observer rows (e.g. workspaces) use so a long tail does not drown
|
|
7
7
|
* the report. */
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { loadConfigSafe } from "../config/config.js";
|
|
2
2
|
import { envPath } from "../files/files.js";
|
|
3
|
-
import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
3
|
+
import { orchestratorAlive, readOrchestratorInfo } from "../team/team-orchestrator-info.js";
|
|
4
4
|
import { systemProcessProbe } from "../process/process-table.js";
|
|
5
5
|
import { checkOrphans } from "./doctor-orphans.js";
|
|
6
|
-
import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours
|
|
6
|
+
import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours } from "./doctor-holds.js";
|
|
7
7
|
import { checkRootInstall } from "./doctor-install.js";
|
|
8
8
|
import { checkNotify } from "./doctor-notify.js";
|
|
9
9
|
import { checkRoleNotes } from "./doctor-role-notes.js";
|
|
10
10
|
import { checkLaunchServicesPorts } from "./doctor-launch-services.js";
|
|
11
11
|
import { plural } from "../text/phrases.js";
|
|
12
12
|
import { shortSha } from "../text/format.js";
|
|
13
|
-
import { failOutcome } from "./outcome.js";
|
|
13
|
+
import { failOutcome } from "./doctor-outcome.js";
|
|
14
14
|
import { errorMessage } from "../text/text.js";
|
|
15
15
|
import { checkBackendBinary, checkBrief, checkBootstrap, checkClaudeTranscripts, checkExecPath, checkFunes, checkGitBinary, checkConfigKeyNames, checkInit, checkOpencodeVersion, checkNodeVersion, checkPiExtensions, checkRepo, } from "./doctor-checks.js";
|
|
16
16
|
import { checkPrinciples } from "./doctor-principles.js";
|
|
17
17
|
import { checkBuild, checkBuildCheck, checkCiParity, checkDiskSpace, checkDroppedPostMergeReviews, checkEventLog, checkFlakyChecks, checkMainDiffRules, checkPostMergeChurn, checkQaCoverage, checkStateDir, } from "./doctor-project-checks.js";
|
|
18
|
-
import { sampleFreeBytes } from "../gates/disk-
|
|
19
|
-
import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock,
|
|
20
|
-
import { checkFeedbackFiling, checkTierModels } from "./doctor-model-checks.js";
|
|
18
|
+
import { sampleFreeBytes } from "../gates/disk-gates.js";
|
|
19
|
+
import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock, checkProviderContention, checkTumwaterPins, checkWorkInstances, checkWorktreePool, } from "./doctor-contention-checks.js";
|
|
20
|
+
import { checkFeedbackFiling, checkImageInput, checkTierModels } from "./doctor-model-checks.js";
|
|
21
21
|
import { checkFallbackChain, checkFallbackModel } from "./doctor-fallback-checks.js";
|
|
22
22
|
import { checkAnthroApiKey, checkClaudeAuth, checkClaudeVersion } from "./doctor-claude-checks.js";
|
|
23
23
|
import { checkOpencode } from "./doctor-opencode-checks.js";
|
|
@@ -95,11 +95,8 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
95
95
|
{ name: "pause", run: () => checkPause(root) },
|
|
96
96
|
{ name: "quiet hours", run: () => checkQuietHours(config) },
|
|
97
97
|
{ name: "budget", run: () => checkBudget(root, config, (orchestratorAlive(root, info) && info?.budget) || null) },
|
|
98
|
-
{ name: "claude window", run: () => checkSubscriptionWindow("claude", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
|
|
99
|
-
{ name: "codex window", run: () => checkSubscriptionWindow("codex", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
|
|
100
98
|
{ name: "provider waits", run: () => checkProviderHolds(root) },
|
|
101
99
|
{ name: "maintenance allowance", run: () => checkMaintenanceQuota(root, config, orchestratorAlive(root, info)) },
|
|
102
|
-
{ name: "provider concurrency", run: () => checkProviderConcurrency(config, root) },
|
|
103
100
|
{ name: "provider contention", run: () => checkProviderContention(root, config) },
|
|
104
101
|
{ name: "check contention", run: () => checkCheckContention(root, config) },
|
|
105
102
|
{ name: "repeated failures", run: () => checkErrorStreaks(root, config) },
|
|
@@ -107,6 +104,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
107
104
|
{ name: "over-cap model", run: () => checkFallbackModel(root, undefined, (orchestratorAlive(root, info) && info?.fallbackDemoted) || null) },
|
|
108
105
|
{ name: "fallback chain", run: () => checkFallbackChain(root) },
|
|
109
106
|
{ name: "models for each tier", run: () => checkTierModels(root) },
|
|
107
|
+
{ name: "image input", run: () => checkImageInput(root) },
|
|
110
108
|
{ name: "claude auth", run: () => checkClaudeAuth(root, pathEnv) },
|
|
111
109
|
{ name: "claude api key", run: () => checkAnthroApiKey(root, undefined, pathEnv) },
|
|
112
110
|
{ name: "claude version", run: () => checkClaudeVersion(root, pathEnv) },
|
|
@@ -127,8 +125,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
127
125
|
// The receive-side guards name what a running team skips, so an operator tool meant for the
|
|
128
126
|
// team (a pi package, an extension file, an mcp.json server, or funes) does not vanish
|
|
129
127
|
// silently. Each check returns null to omit its row: the pi row for a team with no pi seam,
|
|
130
|
-
// the funes row when funes is not on PATH
|
|
131
|
-
// has no subscription account.
|
|
128
|
+
// the funes row when funes is not on PATH.
|
|
132
129
|
{ name: "pi extensions", run: () => checkPiExtensions(config, undefined, pathEnv) },
|
|
133
130
|
{ name: "funes", run: () => checkFunes(config, pathEnv) },
|
|
134
131
|
{ name: "state dir", run: () => checkStateDir(root) },
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { claudeUtilizationPct } from "../agent/claude/claude-account.js";
|
|
2
1
|
import { eventRole, eventRun, eventUsage } from "./event-read.js";
|
|
3
2
|
import { eventTypeLabel } from "./event-labels.js";
|
|
4
3
|
import { roleLabel } from "../roles/role-labels.js";
|
|
@@ -111,9 +110,16 @@ function providerScope(e) {
|
|
|
111
110
|
export function backendLabel(e) {
|
|
112
111
|
return typeof e.backend === "string" && e.backend !== "" ? e.backend : "claude";
|
|
113
112
|
}
|
|
113
|
+
/** A subscription-window event's window, as "<backend> subscription window <type>" — the head
|
|
114
|
+
* every `subscription_window_*` line names (windowType is a torn/older event's only nullable
|
|
115
|
+
* half, so `reset` and the shaped lines differ only in what follows this prefix). One home so
|
|
116
|
+
* the four lines cannot drift on the backend's label or the window-type word. */
|
|
117
|
+
function subscriptionWindowPhrase(e) {
|
|
118
|
+
return `${backendLabel(e)} subscription window ${textOr(e.windowType)}`;
|
|
119
|
+
}
|
|
114
120
|
/** The provider/model pair a fallback-ish line names, as "provider/model", defaulting each
|
|
115
121
|
* half when the event omits it (a bare pi run carries neither). Shared by the budget_fallback
|
|
116
|
-
*
|
|
122
|
+
* line so the pair's shape and defaults cannot drift. */
|
|
117
123
|
function providerModelPair(e, providerDefault, modelDefault) {
|
|
118
124
|
return `${textOr(e.provider, providerDefault)}/${textOr(e.model, modelDefault)}`;
|
|
119
125
|
}
|
|
@@ -213,9 +219,11 @@ function runEventMessage(e) {
|
|
|
213
219
|
? "resuming the run cut off at the context ceiling (compacted pi session, same workspace)"
|
|
214
220
|
: e.cause === "handoff"
|
|
215
221
|
? "handing the interrupted run off to a fresh session on another agent (same workspace, uncommitted edits kept)"
|
|
216
|
-
: e.cause === "
|
|
217
|
-
? "handing the
|
|
218
|
-
:
|
|
222
|
+
: e.cause === "image-reroute"
|
|
223
|
+
? "handing the blind-read run off to a fresh session on an image-capable model (same workspace, uncommitted edits kept)"
|
|
224
|
+
: e.cause === "salvaged"
|
|
225
|
+
? "handing the work an earlier run left unsaved to a fresh session"
|
|
226
|
+
: "resuming the run a shutdown interrupted (same agent session and workspace)") + restored);
|
|
219
227
|
}
|
|
220
228
|
case "wake":
|
|
221
229
|
return `woke (${textOr(e.reason)})`;
|
|
@@ -430,13 +438,6 @@ function budgetEventMessage(e) {
|
|
|
430
438
|
});
|
|
431
439
|
return `fallback: ${parts.join("; ")}`;
|
|
432
440
|
}
|
|
433
|
-
case "model_fallback_started":
|
|
434
|
-
// Routine-with-explanation, like rate_limit_hold: the switch IS the harness handling the
|
|
435
|
-
// failure. Names the pair the role now runs on and why the primary was abandoned.
|
|
436
|
-
return `model fallback — ${providerModelPair(e, "pi's default", "pi's default")} engaged (primary failing: ${textOr(e.reason, "backend failure")})`;
|
|
437
|
-
case "model_fallback_ended":
|
|
438
|
-
// The return counterpart: names the pair runs resume on and how long the episode ran.
|
|
439
|
-
return `model fallback ended — back on ${providerModelPair(e, "pi's default", "pi's default")} after ${minutes(e.durationMs)}m`;
|
|
440
441
|
case "opencode_free_model_billed":
|
|
441
442
|
// The promotion ended (plans/opencode-backend.md §7, part 6c/7): a run billed what the
|
|
442
443
|
// catalog called free, so the team now prices that model as paid from here on. No warning
|
|
@@ -531,19 +532,6 @@ function resourceEventMessage(e) {
|
|
|
531
532
|
return `backend recovered (${backendKindPhrase(e.kind)})${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
532
533
|
return `provider limit lifted${providerAt(e)} — roles on ${providerScope(e)} run again`;
|
|
533
534
|
}
|
|
534
|
-
case "subscription_window_warning": {
|
|
535
|
-
// Routine-with-explanation, like rate_limit_hold — no warning prefix: the account is
|
|
536
|
-
// telling us the window is filling, not failing. It names the window and when it resets.
|
|
537
|
-
const used = finiteNumber(e.utilization, null);
|
|
538
|
-
const fill = used === null ? "" : ` (at ${claudeUtilizationPct(used)}% used)`;
|
|
539
|
-
return `${backendLabel(e)} subscription window ${textOr(e.windowType)} near its limit${fill}${windowResetPhrase(e)} — subscription ${backendLabel(e)} model jobs keep running`;
|
|
540
|
-
}
|
|
541
|
-
case "subscription_window_reserved": {
|
|
542
|
-
const used = finiteNumber(e.utilization, null);
|
|
543
|
-
const line = finiteNumber(e.reserve, null);
|
|
544
|
-
const pct = (f) => (f === null ? "?" : `${claudeUtilizationPct(f)}%`);
|
|
545
|
-
return `${backendLabel(e)} subscription window ${textOr(e.windowType)} at ${pct(used)}, past its ${pct(line)} reserve${windowResetPhrase(e)} — the team leaves it to you (director keeps running)`;
|
|
546
|
-
}
|
|
547
535
|
case "subscription_window_limited":
|
|
548
536
|
// Routine-with-explanation, like rate_limit_hold — no warning prefix: waiting is the
|
|
549
537
|
// harness handling the account's own throttle. Names the window, the reset, and which
|
|
@@ -554,9 +542,9 @@ function resourceEventMessage(e) {
|
|
|
554
542
|
{
|
|
555
543
|
const moved = fallbackTierEntries(isJsonObject(e.tiers) ? e.tiers : undefined);
|
|
556
544
|
if (moved.length > 0)
|
|
557
|
-
return `${
|
|
545
|
+
return `${subscriptionWindowPhrase(e)} rejected — ${backendLabel(e)} model jobs move to ${moved.join(", ")}${windowResetPhrase(e)} (director keeps waiting)`;
|
|
558
546
|
}
|
|
559
|
-
return `waiting: ${
|
|
547
|
+
return `waiting: ${subscriptionWindowPhrase(e)} rejected — ${backendLabel(e)} model jobs wait${windowResetPhrase(e)} (director keeps running)`;
|
|
560
548
|
case "subscription_window_reset":
|
|
561
549
|
return `${backendLabel(e)} subscription window reset — ${backendLabel(e)} model jobs run again`;
|
|
562
550
|
default: return textOr(e.type);
|
|
@@ -568,10 +556,6 @@ function configEventMessage(e) {
|
|
|
568
556
|
// Routine state change, like counters_reset — no warning prefix.
|
|
569
557
|
return `maxConcurrent changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
570
558
|
}
|
|
571
|
-
case "provider_concurrency_changed": {
|
|
572
|
-
// Routine state change, its maxConcurrent sibling's per-provider form — no warning prefix.
|
|
573
|
-
return `concurrency for ${textOr(e.provider)} changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
574
|
-
}
|
|
575
559
|
case "retention_changed": {
|
|
576
560
|
// Routine state change, like its maxConcurrent sibling — no warning prefix.
|
|
577
561
|
return `sessionRetentionDays changed: ${textOr(e.from)} → ${textOr(e.to)}`;
|
|
@@ -604,9 +588,9 @@ function configEventMessage(e) {
|
|
|
604
588
|
function imageRestartEventMessage(e) {
|
|
605
589
|
switch (e.type) {
|
|
606
590
|
case "image_blind": {
|
|
607
|
-
// Routine-with-explanation
|
|
608
|
-
//
|
|
609
|
-
//
|
|
591
|
+
// Routine-with-explanation — no warning prefix: the run was stopped (or a gate run
|
|
592
|
+
// noted) because the model cannot see the image. Names the file, the model, and
|
|
593
|
+
// whether the run failed on it or a gate run only logged it.
|
|
610
594
|
const action = e.action === "logged"
|
|
611
595
|
? " (gate run logged)"
|
|
612
596
|
: e.action === "fail"
|
|
@@ -614,6 +598,13 @@ function imageRestartEventMessage(e) {
|
|
|
614
598
|
: "";
|
|
615
599
|
return `blind image read: ${textOr(e.path)} on ${textOr(e.model, "the model")}${action}`;
|
|
616
600
|
}
|
|
601
|
+
case "image_reroute":
|
|
602
|
+
// The run's work moves to a model that can read the image (plans/image-input.md part
|
|
603
|
+
// 4/5). Routine-with-explanation — no warning prefix: names the hop the way the
|
|
604
|
+
// handoff reads, and whether the traits-of-attachment preflight avoided a failed run.
|
|
605
|
+
return e.preflight === true
|
|
606
|
+
? `image attachment routed to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) to read its image — no failed run`
|
|
607
|
+
: `image rerouted to ${textOr(e.to, "the target")} (${textOr(e.tier, "a peer tier")}) — next run goes there after a blind read on ${textOr(e.from, "the prior model")}`;
|
|
617
608
|
case "build_stale":
|
|
618
609
|
// Self-hosting teams only (src/redeploy/redeploy.ts): the code main describes is not the
|
|
619
610
|
// code running. Not a warning prefix — a stale build is a state, and auto-restart resolves
|
|
@@ -669,22 +660,11 @@ function checkWaitEventMessage(e) {
|
|
|
669
660
|
return `waited ${shortSpanPhrase(finiteNumber(e.waitedMs, 0))} to start (${textOr(e.role)} ${purpose}; ${textOr(e.slots)} workspaces, ${textOr(e.pinned)} pinned)`;
|
|
670
661
|
}
|
|
671
662
|
case "provider_wait": {
|
|
672
|
-
// Routine-with-explanation, like slot_wait: waiting for
|
|
673
|
-
// working as designed, so no warning prefix. Names the wait
|
|
674
|
-
//
|
|
675
|
-
// numbers in hand. An event carrying `cap` names the cap that held the run and its limit
|
|
676
|
-
// (a team-cap wait names no key); one without `cap` (logged before it existed) renders
|
|
677
|
-
// the key-or-run-slot line alone.
|
|
663
|
+
// Routine-with-explanation, like slot_wait: waiting for the team slot is the cap
|
|
664
|
+
// working as designed, so no warning prefix. Names the wait, so the operator can size
|
|
665
|
+
// maxConcurrent with the number in hand.
|
|
678
666
|
const span = shortSpanPhrase(finiteNumber(e.waitedMs, 0));
|
|
679
|
-
|
|
680
|
-
const hasKey = typeof e.key === "string" && e.key.length > 0;
|
|
681
|
-
if (e.cap === "provider" && hasKey) {
|
|
682
|
-
return `waited ${span} for a ${e.key} slot (${textOr(e.role)}${Number.isFinite(limit) ? `; maxConcurrentPerProvider.${e.key} is ${limit}` : ""})`;
|
|
683
|
-
}
|
|
684
|
-
if (e.cap === "team") {
|
|
685
|
-
return `waited ${span} for a run slot (${textOr(e.role)}${Number.isFinite(limit) ? `; maxConcurrent is ${limit}` : ""})`;
|
|
686
|
-
}
|
|
687
|
-
return `waited ${span} for a ${hasKey ? `${e.key} slot` : "run slot"} (${textOr(e.role)})`;
|
|
667
|
+
return `waited ${span} for a run slot (${textOr(e.role)})`;
|
|
688
668
|
}
|
|
689
669
|
case "check_wait":
|
|
690
670
|
// Routine-with-explanation, like slot_wait: waiting for a check permit is the cap working
|
|
@@ -754,8 +734,6 @@ export function eventMessage(e) {
|
|
|
754
734
|
case "budget_resumed":
|
|
755
735
|
case "budget_handback":
|
|
756
736
|
case "fallback_switched":
|
|
757
|
-
case "model_fallback_started":
|
|
758
|
-
case "model_fallback_ended":
|
|
759
737
|
case "opencode_free_model_billed": return budgetEventMessage(e);
|
|
760
738
|
case "team_paused":
|
|
761
739
|
case "team_resumed":
|
|
@@ -775,12 +753,9 @@ export function eventMessage(e) {
|
|
|
775
753
|
case "quiet_hours_ended":
|
|
776
754
|
case "rate_limit_hold":
|
|
777
755
|
case "rate_limit_resumed":
|
|
778
|
-
case "subscription_window_warning":
|
|
779
|
-
case "subscription_window_reserved":
|
|
780
756
|
case "subscription_window_limited":
|
|
781
757
|
case "subscription_window_reset": return resourceEventMessage(e);
|
|
782
758
|
case "max_concurrent_changed":
|
|
783
|
-
case "provider_concurrency_changed":
|
|
784
759
|
case "retention_changed":
|
|
785
760
|
case "config_changed":
|
|
786
761
|
case "model_changed":
|
|
@@ -789,6 +764,7 @@ export function eventMessage(e) {
|
|
|
789
764
|
case "orchestrator_start":
|
|
790
765
|
case "orchestrator_stop": return configEventMessage(e);
|
|
791
766
|
case "image_blind":
|
|
767
|
+
case "image_reroute":
|
|
792
768
|
case "build_stale":
|
|
793
769
|
case "restart_pending":
|
|
794
770
|
case "restart":
|
|
@@ -70,20 +70,16 @@ export const EVENT_TYPE_LABELS = {
|
|
|
70
70
|
check_wait: "waited for a check slot",
|
|
71
71
|
rate_limit_hold: "waiting: provider limit",
|
|
72
72
|
rate_limit_resumed: "provider limit lifted",
|
|
73
|
-
subscription_window_reserved: "usage window reserved",
|
|
74
|
-
subscription_window_warning: "usage window warning",
|
|
75
73
|
subscription_window_limited: "waiting: usage window",
|
|
76
74
|
subscription_window_reset: "usage window reset",
|
|
77
75
|
max_concurrent_changed: "concurrency changed",
|
|
78
|
-
provider_concurrency_changed: "provider concurrency changed",
|
|
79
76
|
retention_changed: "retention changed",
|
|
80
77
|
config_changed: "settings changed",
|
|
81
78
|
model_changed: "model changed",
|
|
82
79
|
backend_binary: "backend checked",
|
|
83
|
-
model_fallback_started: "fallback model started",
|
|
84
|
-
model_fallback_ended: "fallback model ended",
|
|
85
80
|
opencode_free_model_billed: "free model billed",
|
|
86
81
|
image_blind: "image not seen",
|
|
82
|
+
image_reroute: "image sent to another model",
|
|
87
83
|
build_stale: "update available",
|
|
88
84
|
restart_pending: "update waits for idle",
|
|
89
85
|
restart: "restarted onto update",
|
|
@@ -25,8 +25,6 @@ const LEGACY_EVENT_TYPES = {
|
|
|
25
25
|
land_failed: "merge_failed", // vocabulary-ok: legacy event name read from old logs
|
|
26
26
|
// retired-name-ok:end
|
|
27
27
|
// retired-name-ok:start old subscription-window event names
|
|
28
|
-
claude_window_reserved: "subscription_window_reserved", // vocabulary-ok: legacy event name read from old logs
|
|
29
|
-
claude_window_warning: "subscription_window_warning", // vocabulary-ok: legacy event name read from old logs
|
|
30
28
|
claude_window_limited: "subscription_window_limited", // vocabulary-ok: legacy event name read from old logs
|
|
31
29
|
claude_window_reset: "subscription_window_reset", // vocabulary-ok: legacy event name read from old logs
|
|
32
30
|
// retired-name-ok:end
|
|
@@ -34,7 +32,7 @@ const LEGACY_EVENT_TYPES = {
|
|
|
34
32
|
/** The ordered field rewrites; later parts of the series append here, and they run in order. */
|
|
35
33
|
const LEGACY_EVENT_REWRITES = [
|
|
36
34
|
{
|
|
37
|
-
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind.
|
|
35
|
+
// The run number rode `tick` on run_start, run_end, prompt_taken, image_blind, image_reroute.
|
|
38
36
|
name: "tick-to-run", // retired-name-ok: legacy name
|
|
39
37
|
apply(ev) {
|
|
40
38
|
const e = ev;
|
|
@@ -10,8 +10,8 @@ import { childEnv } from "../process/process.js";
|
|
|
10
10
|
*
|
|
11
11
|
* Scope, stated plainly: the notifier subscribes via subscribeEvents (src/events/events.ts), which
|
|
12
12
|
* only sees events THIS process logs. Every allowlisted type but `supervisor_exit` is logged by
|
|
13
|
-
* the orchestrator's own process (budget-gates.ts, gate-polls.ts → streak-
|
|
14
|
-
* role-cap-gates.ts and disk-
|
|
13
|
+
* the orchestrator's own process (budget-gates.ts, gate-polls.ts → streak-gates.ts,
|
|
14
|
+
* role-cap-gates.ts and disk-gates.ts, src/baseline/main-red.ts, src/team/team-polls.ts,
|
|
15
15
|
* merge-slot.ts, merge-questions.ts,
|
|
16
16
|
* src/redeploy/redeployer.ts). `supervisor_exit` is logged by the supervisor after the
|
|
17
17
|
* orchestrator is gone, so src/cli/cli-start.ts pages it directly with notifyTeamDown; events
|
|
@@ -87,7 +87,7 @@ function runStartEvents(events) {
|
|
|
87
87
|
}
|
|
88
88
|
/** The run_start pairing's start epoch ms: `${role}#${run}` → start epoch ms, the projection
|
|
89
89
|
* of runStartEvents onto each event's `ts` — shared by `tumwater history`'s row spans
|
|
90
|
-
* (history-data.ts), the failure digest's time-and-spend fold (time-spend.ts), and the run
|
|
90
|
+
* (history-data.ts), the failure digest's time-and-spend fold (failure-time-spend.ts), and the run
|
|
91
91
|
* drill-down's duration (run-detail-data.ts), so those consumers cannot drift into different
|
|
92
92
|
* notions of a run's span. A run_end whose start is not in `events` (log rotation cut it, or
|
|
93
93
|
* the run was skipped before any start logged) has no entry; callers decide what an unpaired
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The cluster-key rules shared by everything that groups failure messages into causes: the
|
|
2
2
|
* failure digest's collection (failure-data.ts) and the error-storm reducer
|
|
3
|
-
* (src/team/error-storm.ts) both count by these keys, so the normalization, the two
|
|
3
|
+
* (src/team/team-error-storm.ts) both count by these keys, so the normalization, the two
|
|
4
4
|
* run-timeout shapes and their pooling, and the grouping engine live here rather than
|
|
5
5
|
* inside either consumer. Pure string
|
|
6
6
|
* and grouping logic — no event reads, no clock. */
|
|
@@ -12,13 +12,13 @@ import { cutSplitsSurrogatePair } from "../text/text.js";
|
|
|
12
12
|
export const EXAMPLE_MAX = 120;
|
|
13
13
|
/** The example and cluster key every message-less failure takes: errorTextOrPlaceholder
|
|
14
14
|
* returns it for the digest's error clusters (failure-data.ts) and its loss ranking
|
|
15
|
-
* (time-spend.ts), so a broken `run_end` that skipped its error text reads the same on both
|
|
15
|
+
* (failure-time-spend.ts), so a broken `run_end` that skipped its error text reads the same on both
|
|
16
16
|
* surfaces. */
|
|
17
17
|
const NO_ERROR_TEXT = "(no error text recorded)";
|
|
18
18
|
/** The error text a failure event carries, or NO_ERROR_TEXT when it carries none — the field
|
|
19
19
|
* is a non-empty string or absent. The single home of the placeholder rule, shared by the
|
|
20
20
|
* digest's error clusters (failure-data.ts) and the loss ranking's clustered and review_error
|
|
21
|
-
* arms (time-spend.ts), so a message-less failure reads the same on every surface. */
|
|
21
|
+
* arms (failure-time-spend.ts), so a message-less failure reads the same on every surface. */
|
|
22
22
|
export function errorTextOrPlaceholder(v) {
|
|
23
23
|
return typeof v === "string" && v !== "" ? v : NO_ERROR_TEXT;
|
|
24
24
|
}
|
|
@@ -49,7 +49,7 @@ export function truncateExample(message, max = EXAMPLE_MAX) {
|
|
|
49
49
|
* array in ascending localeCompare order, so equal inputs always render identically no matter
|
|
50
50
|
* the Set's insertion order. One home for the rule the failure digest's clusters
|
|
51
51
|
* (clusterMessages) and loss causes (failure-data.ts) and the error-storm warning
|
|
52
|
-
* (src/team/error-storm.ts) all share — an inline `.sort()` without the comparator would
|
|
52
|
+
* (src/team/team-error-storm.ts) all share — an inline `.sort()` without the comparator would
|
|
53
53
|
* silently pick code-unit order instead and let the two rules drift. */
|
|
54
54
|
export function sortedRoles(roles) {
|
|
55
55
|
return [...roles].sort((a, b) => a.localeCompare(b));
|
|
@@ -76,7 +76,7 @@ export function normalizeClusterKey(message) {
|
|
|
76
76
|
.replace(/(?<!exited\s)\b\d+\b/g, "<n>");
|
|
77
77
|
return normalized.trim().slice(0, EXAMPLE_MAX);
|
|
78
78
|
}
|
|
79
|
-
/** normalizeClusterKey's rendering of the two run-timeout errors src/agent/run
|
|
79
|
+
/** normalizeClusterKey's rendering of the two run-timeout errors src/agent/agent-run.ts emits —
|
|
80
80
|
* the plain kill and the still-making-progress variant (session and worktree edits preserved
|
|
81
81
|
* for resume, BUGS.md 2026-09-29). Both are one cause pointing at one knob (runTimeoutSeconds),
|
|
82
82
|
* so every consumer that counts by cluster key pools them under the plain one. */
|
|
@@ -96,7 +96,7 @@ export const RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY = "timed out after <dur>
|
|
|
96
96
|
* wordings name one cause on one knob (review.timeoutSeconds): a still-making-progress kill
|
|
97
97
|
* pools into the plain run-timeout row (a mixed team of plain and progressing kills is one
|
|
98
98
|
* cause's agent-hours, not two half-size rows the top-N cut can drop — the same pooling the
|
|
99
|
-
* error-storm reducer applies, src/team/error-storm.ts), and a resumed review's wording maps
|
|
99
|
+
* error-storm reducer applies, src/team/team-error-storm.ts), and a resumed review's wording maps
|
|
100
100
|
* to itself, because continuing the timed-out session is a different cause from a from-scratch
|
|
101
101
|
* re-review. Enumerated so a future wording edit lands in
|
|
102
102
|
* this table rather than silently changing which row its timeouts count under. */
|
|
@@ -2,7 +2,7 @@ import { assertReportSinceMs, eventWindowCovers, readWindowEvents, sinceWindowTi
|
|
|
2
2
|
import { eventDayKey, eventRole } from "../events/event-read.js";
|
|
3
3
|
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
4
4
|
import { scopeBases } from "../report/report-fold.js";
|
|
5
|
-
import { timeAndSpend } from "./time-spend.js";
|
|
5
|
+
import { timeAndSpend } from "./failure-time-spend.js";
|
|
6
6
|
import { dayAt, dayKey, dayWindow, formatDate } from "../text/datetime.js";
|
|
7
7
|
import { describeStateChange, STATE_CHANGE_TOP, STATE_CHANGE_TYPES } from "./failure-state-change.js";
|
|
8
8
|
import { clusterMessages, errorTextOrPlaceholder, truncateExample } from "./failure-cluster.js";
|
|
@@ -100,9 +100,13 @@ function mergeFailureSection(events) {
|
|
|
100
100
|
}
|
|
101
101
|
return section(clusterMessages(messages, MERGE_FAILURE_TOP), total);
|
|
102
102
|
}
|
|
103
|
+
/** A role with no counted outcomes — the zero baseline roleStats starts a role on (a fresh
|
|
104
|
+
* copy, since roleStats mutates it) and deltaRows hands a role missing from either window.
|
|
105
|
+
* One home for the zero shape so a future RoleStats field is added once. */
|
|
106
|
+
const ZERO_ROLE_STATS = { runs: 0, errors: 0, quietKills: 0, rejections: 0 };
|
|
103
107
|
function roleStats(events) {
|
|
104
108
|
const byRole = new Map();
|
|
105
|
-
const statsFor = (role) => getOrCreate(byRole, role, () => ({
|
|
109
|
+
const statsFor = (role) => getOrCreate(byRole, role, () => ({ ...ZERO_ROLE_STATS }));
|
|
106
110
|
for (const ev of events) {
|
|
107
111
|
// Runs are run_end events; a rejection is now recorded by the merge slot, AFTER the
|
|
108
112
|
// authoring run has already ended `queued` (plans/merge-queue.md 3/5). review_rejected is
|
|
@@ -297,8 +301,8 @@ function deltaRows(current, prior) {
|
|
|
297
301
|
const roles = [...new Set([...curStats.keys(), ...prevStats.keys()])];
|
|
298
302
|
return roles
|
|
299
303
|
.map((role) => {
|
|
300
|
-
const c = curStats.get(role) ??
|
|
301
|
-
const p = prevStats.get(role) ??
|
|
304
|
+
const c = curStats.get(role) ?? ZERO_ROLE_STATS;
|
|
305
|
+
const p = prevStats.get(role) ?? ZERO_ROLE_STATS;
|
|
302
306
|
return {
|
|
303
307
|
role,
|
|
304
308
|
prevRuns: p.runs,
|
|
@@ -209,42 +209,52 @@ function pushTimeAndSpend(lines, data) {
|
|
|
209
209
|
lines.push("## Time and spend by outcome");
|
|
210
210
|
if (data.timeSpend.length === 0) {
|
|
211
211
|
lines.push("_no finished runs in the window_");
|
|
212
|
+
return;
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
? `context cut-off on ${roleCell(c.roles[0] ?? "?")}`
|
|
237
|
-
: `${c.example} (${roleList(c.roles)})`;
|
|
238
|
-
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${cause}`);
|
|
239
|
-
}
|
|
240
|
-
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
241
|
-
// stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
|
|
242
|
-
if (data.lossCausesHidden > 0) {
|
|
243
|
-
const noun = agree(data.lossCausesHidden, "cause", "causes");
|
|
244
|
-
lines.push(`_+${data.lossCausesHidden} more loss ${noun} by time not listed_`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
214
|
+
pushTimeSpendRows(lines, data);
|
|
215
|
+
pushLossCauses(lines, data);
|
|
216
|
+
}
|
|
217
|
+
/** The role × class table of hours and dollars. */
|
|
218
|
+
function pushTimeSpendRows(lines, data) {
|
|
219
|
+
lines.push(...markdownTable(["role", "merged", "no change", "error or blocked"], data.timeSpend.map((row) => [
|
|
220
|
+
roleCell(row.role),
|
|
221
|
+
spendCell(row.classes.merged),
|
|
222
|
+
spendCell(row.classes.no_change),
|
|
223
|
+
spendCell(row.classes.error),
|
|
224
|
+
])));
|
|
225
|
+
}
|
|
226
|
+
/** The top loss causes by time, capped at the LOSS_TOP cut; a refused cause's text (and the
|
|
227
|
+
* empty ranking's placeholder) lives in `lossCauseText`/`pushLossCauses` here. */
|
|
228
|
+
function pushLossCauses(lines, data) {
|
|
229
|
+
lines.push("");
|
|
230
|
+
lines.push("**Top loss causes by time:**");
|
|
231
|
+
if (data.lossCauses.length === 0) {
|
|
232
|
+
lines.push("_no loss to rank_");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const c of data.lossCauses) {
|
|
236
|
+
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${lossCauseText(c)}`);
|
|
247
237
|
}
|
|
238
|
+
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
239
|
+
// stays silent reads as a complete itemization of the window's loss (BUGS.md 2026-10-01).
|
|
240
|
+
if (data.lossCausesHidden > 0) {
|
|
241
|
+
const noun = agree(data.lossCausesHidden, "cause", "causes");
|
|
242
|
+
lines.push(`_+${data.lossCausesHidden} more loss ${noun} by time not listed_`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** One loss cause's bullet text. */
|
|
246
|
+
function lossCauseText(c) {
|
|
247
|
+
return c.kind === "no_change"
|
|
248
|
+
? `no change on ${roleCell(c.roles[0] ?? "?")}`
|
|
249
|
+
: c.kind === "review-rejected"
|
|
250
|
+
? `review-rejected authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
251
|
+
: c.kind === "refused"
|
|
252
|
+
? `refused on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
253
|
+
: c.kind === "merge-conflict" || c.kind === "merge-blocked"
|
|
254
|
+
? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
255
|
+
: c.kind === "cutoff"
|
|
256
|
+
? `context cut-off on ${roleCell(c.roles[0] ?? "?")}`
|
|
257
|
+
: `${c.example} (${roleList(c.roles)})`;
|
|
248
258
|
}
|
|
249
259
|
/** Prompt-token telemetry: the per-role median prompt
|
|
250
260
|
* send and how much of the prompt budget goes out before a run's first edit, plus each
|