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
|
@@ -3,7 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { childEnv } from "../process/process.js";
|
|
4
4
|
import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, boundedProbe } from "./probe.js";
|
|
5
5
|
import { opencodeBackend, opencodeSpillDir } from "../agent/opencode/opencode-backend.js";
|
|
6
|
-
import { opencodeModelKey, readOpencodeCache } from "../agent/opencode/opencode-catalog.js";
|
|
6
|
+
import { opencodeModelFree, opencodeModelKey, readOpencodeCache } from "../agent/opencode/opencode-catalog.js";
|
|
7
|
+
import { formatDate } from "../text/datetime.js";
|
|
7
8
|
import { opencodeDbPath } from "../paths.js";
|
|
8
9
|
import { envPath, isDirectory, readTextOrNull, statOrNull, treeBytes } from "../files/files.js";
|
|
9
10
|
import { formatBytes, tokenWindowLabel } from "../text/format.js";
|
|
@@ -11,7 +12,7 @@ import { escapeRegExp } from "../text/text.js";
|
|
|
11
12
|
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
12
13
|
import { loadConfigForCheck, opencodeSeamsInUse, seamedModels, } from "./doctor-model-checks.js";
|
|
13
14
|
/** The doctor's opencode credential and leak readiness check, split out of doctor-model-checks.ts
|
|
14
|
-
* (whose shared seam/model framework and the pi
|
|
15
|
+
* (whose shared seam/model framework and the pi and tier-models checks stay there):
|
|
15
16
|
* checkOpencode and its helpers (opencodeProviderOf, opencodeAuthLists, jsonKeyPresent,
|
|
16
17
|
* opencodeConfigLeaks). This is the doctor's only check that spawns the `opencode` CLI and reads
|
|
17
18
|
* its catalog and the operator's global opencode config — a distinct concern from the
|
|
@@ -107,6 +108,80 @@ function opencodeConfigLeaks(configHome) {
|
|
|
107
108
|
warns.push(`${agentsMd} still loads into team runs`);
|
|
108
109
|
return warns;
|
|
109
110
|
}
|
|
111
|
+
/** A catalog model's price text — "free" when free, the billed-demotion phrase when `billedAt`
|
|
112
|
+
* is set, else the per-M prices — so opencodeModelRow can name the price without the nested
|
|
113
|
+
* ternary and its two `?? 0` fallbacks. */
|
|
114
|
+
function opencodePriceText(free, billedAt, inputPerM, outputPerM) {
|
|
115
|
+
if (free)
|
|
116
|
+
return "free";
|
|
117
|
+
if (billedAt !== undefined)
|
|
118
|
+
return `no longer free (billed ${formatDate(new Date(billedAt))})`;
|
|
119
|
+
return `$${inputPerM ?? 0}/$${outputPerM ?? 0} per M`;
|
|
120
|
+
}
|
|
121
|
+
/** One opencode catalog model's row: today's per-model body from the cache key through the
|
|
122
|
+
* dropped-effort warning, returning the catalog note and any warnings, or undefined when the
|
|
123
|
+
* model is missing from the cache (the caller owns that failure message). `warnings` holds the
|
|
124
|
+
* billed-at demotion line and the dropped-effort line in today's order. */
|
|
125
|
+
function opencodeModelRow(cache, model, thinking) {
|
|
126
|
+
const key = opencodeModelKey(model);
|
|
127
|
+
const entry = cache.models[key];
|
|
128
|
+
if (entry === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
const warnings = [];
|
|
131
|
+
const free = opencodeModelFree(cache, key);
|
|
132
|
+
if (entry.billedAt !== undefined) {
|
|
133
|
+
warnings.push(`${model} was free in the catalog but billed once on ${formatDate(new Date(entry.billedAt))} — the budget gate now prices it`);
|
|
134
|
+
}
|
|
135
|
+
const window = entry.context !== undefined ? `, ${tokenWindowLabel(entry.context)}` : "";
|
|
136
|
+
const note = `${model} — ${opencodePriceText(free, entry.billedAt, entry.inputPerM, entry.outputPerM)}${window}`;
|
|
137
|
+
// A model with no effort list at all supports no thinking level, so a requested level is
|
|
138
|
+
// dropped just the same as one the list omits.
|
|
139
|
+
if (thinking !== undefined && thinking !== "" && !entry.efforts?.includes(thinking)) {
|
|
140
|
+
warnings.push(`${model} has no ${thinking} effort — that thinking level is dropped`);
|
|
141
|
+
}
|
|
142
|
+
return { note, warnings };
|
|
143
|
+
}
|
|
144
|
+
/** The team's opencode database and spill directory sizes as one information line (`db …,
|
|
145
|
+
* spill …`), reading the db, the spill dir and their byte formatting — today's storage tail. */
|
|
146
|
+
function opencodeStorageNote(root) {
|
|
147
|
+
const db = statOrNull(opencodeDbPath(root));
|
|
148
|
+
const spillDir = opencodeSpillDir(root);
|
|
149
|
+
const spill = spillDir !== "" && isDirectory(spillDir) ? treeBytes(spillDir) : null;
|
|
150
|
+
return `db ${formatBytes(db?.size ?? 0)}${spill !== null ? `, spill ${formatBytes(spill)}` : ", no spill dir"}`;
|
|
151
|
+
}
|
|
152
|
+
/** Walk the opencode-routed model pairs into today's per-model fails, warns and notes: the
|
|
153
|
+
* catalog-miss failure, the catalog note and its warnings from opencodeModelRow, and the
|
|
154
|
+
* non-gateway credential probe each provider. Returns the three arrays checkOpencode assembles. */
|
|
155
|
+
async function collectOpencodeModelRows(cache, models, config, pathEnv, timeoutMs) {
|
|
156
|
+
const fails = [];
|
|
157
|
+
const warns = [];
|
|
158
|
+
const notes = [];
|
|
159
|
+
for (const pair of models) {
|
|
160
|
+
const model = pair.model;
|
|
161
|
+
const row = opencodeModelRow(cache, model, pair.thinking);
|
|
162
|
+
if (row === undefined) {
|
|
163
|
+
fails.push(`${model} is not in the opencode catalog cache`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
notes.push(row.note);
|
|
167
|
+
warns.push(...row.warnings);
|
|
168
|
+
const provider = opencodeProviderOf(model);
|
|
169
|
+
if (provider !== undefined && !(await opencodeAuthLists(config, provider, pathEnv, timeoutMs))) {
|
|
170
|
+
warns.push(`${model} is a non-gateway provider opencode does not list a credential for`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { fails, warns, notes };
|
|
174
|
+
}
|
|
175
|
+
/** The check's outcome from the accumulated fails/warns/notes and the storage tail: the joined
|
|
176
|
+
* base line ("opencode ready — …" when nothing is wrong, else the found lines) and the
|
|
177
|
+
* fail/warn/ok verdict by severity. */
|
|
178
|
+
function opencodeOutcome(fails, warns, notes, sizeNotes) {
|
|
179
|
+
const found = [...fails, ...warns];
|
|
180
|
+
const base = found.length === 0 ? `opencode ready — ${notes.join("; ")}; ${sizeNotes}` : found.join("; ");
|
|
181
|
+
if (fails.length > 0)
|
|
182
|
+
return failOutcome(base);
|
|
183
|
+
return (warns.length > 0 ? warnOutcome : okOutcome)(base);
|
|
184
|
+
}
|
|
110
185
|
/** OpenCode model, credential and leak readiness (plans/opencode-backend.md §7, part 7/7):
|
|
111
186
|
* every model routed to opencode must be in the catalog cache — an unknown model fails, a
|
|
112
187
|
* cached one reads "free" or its per-M prices and context window, and a thinking level with no
|
|
@@ -116,49 +191,23 @@ function opencodeConfigLeaks(configHome) {
|
|
|
116
191
|
* team runs, else warns naming the file and key. Reports the team's opencode database and spill
|
|
117
192
|
* sizes as information. Stands down (ok) when no seam routes to opencode, so a pi-only team
|
|
118
193
|
* spawns nothing and reads nothing. `pathEnv` and `home` are injectable so tests need no real
|
|
119
|
-
* PATH or HOME.
|
|
120
|
-
*
|
|
194
|
+
* PATH or HOME. A model's freeness is read from opencodeModelFree — the same call the budget
|
|
195
|
+
* gate and status use — so a catalog-free model that an observed bill demoted to billed reads
|
|
196
|
+
* as billed here, not free, keeping the row from drifting from what the team is billed. The
|
|
197
|
+
* per-model catalog row lives in opencodeModelRow, the database-and-spill size line in
|
|
198
|
+
* opencodeStorageNote; both are the two extracted jobs the reader names by. The version half of
|
|
199
|
+
* the opencode pre-flight is the sibling checkOpencodeVersion in doctor-checks.ts. */
|
|
121
200
|
export async function checkOpencode(root, pathEnv = envPath(), home = os.homedir(), xdgConfigHome = process.env.XDG_CONFIG_HOME ?? "", timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
122
201
|
const config = loadConfigForCheck(root);
|
|
123
202
|
if ("level" in config)
|
|
124
203
|
return config;
|
|
125
204
|
if (!opencodeSeamsInUse(config))
|
|
126
205
|
return okOutcome("no model jobs run on opencode");
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const entry = cache.models[key];
|
|
135
|
-
if (entry === undefined) {
|
|
136
|
-
fails.push(`${pair.model} is not in the opencode catalog cache`);
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
const price = entry.free ? "free" : `$${entry.inputPerM ?? 0}/$${entry.outputPerM ?? 0} per M`;
|
|
140
|
-
const window = entry.context !== undefined ? `, ${tokenWindowLabel(entry.context)}` : "";
|
|
141
|
-
notes.push(`${pair.model} — ${price}${window}`);
|
|
142
|
-
// A model with no effort list at all supports no thinking level, so a requested level is
|
|
143
|
-
// dropped just the same as one the list omits.
|
|
144
|
-
if (pair.thinking !== undefined &&
|
|
145
|
-
pair.thinking !== "" &&
|
|
146
|
-
!entry.efforts?.includes(pair.thinking)) {
|
|
147
|
-
warns.push(`${pair.model} has no ${pair.thinking} effort — that thinking level is dropped`);
|
|
148
|
-
}
|
|
149
|
-
const provider = opencodeProviderOf(pair.model);
|
|
150
|
-
if (provider !== undefined && !(await opencodeAuthLists(config, provider, pathEnv, timeoutMs))) {
|
|
151
|
-
warns.push(`${pair.model} is a non-gateway provider opencode does not list a credential for`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
warns.push(...opencodeConfigLeaks(xdgConfigHome !== "" ? xdgConfigHome : path.join(home, ".config")));
|
|
155
|
-
const db = statOrNull(opencodeDbPath(root));
|
|
156
|
-
const spillDir = opencodeSpillDir(root);
|
|
157
|
-
const spill = spillDir !== "" && isDirectory(spillDir) ? treeBytes(spillDir) : null;
|
|
158
|
-
const sizeNotes = `db ${formatBytes(db?.size ?? 0)}${spill !== null ? `, spill ${formatBytes(spill)}` : ", no spill dir"}`;
|
|
159
|
-
const found = [...fails, ...warns];
|
|
160
|
-
const base = found.length === 0 ? `opencode ready — ${notes.join("; ")}; ${sizeNotes}` : found.join("; ");
|
|
161
|
-
if (fails.length > 0)
|
|
162
|
-
return failOutcome(base);
|
|
163
|
-
return (warns.length > 0 ? warnOutcome : okOutcome)(base);
|
|
206
|
+
const rows = await collectOpencodeModelRows(readOpencodeCache(root), seamedModels(config, "opencode"), config, pathEnv, timeoutMs);
|
|
207
|
+
const warns = [
|
|
208
|
+
...rows.warns,
|
|
209
|
+
...opencodeConfigLeaks(xdgConfigHome !== "" ? xdgConfigHome : path.join(home, ".config")),
|
|
210
|
+
];
|
|
211
|
+
const sizeNotes = opencodeStorageNote(root);
|
|
212
|
+
return opencodeOutcome(rows.fails, warns, rows.notes, sizeNotes);
|
|
164
213
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readTextOrNull } from "../files/files.js";
|
|
3
|
+
import { PRINCIPLES_MAX_CHARS } from "../prompt/principles.js";
|
|
4
|
+
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
5
|
+
/** Doctor check for PRINCIPLES.md's injection cap. readPrinciples caps the trimmed text at
|
|
6
|
+
* PRINCIPLES_MAX_CHARS for every prompt's <principles> block, and principleBullets keeps only
|
|
7
|
+
* its leading bullets; a file over the cap loses its tail (and a long header eats bullet
|
|
8
|
+
* budget) with nothing telling the operator who edits it. This check warns when the file would
|
|
9
|
+
* truncate, naming the trimmed count, the cap, the marker, and the fix. Read-only, like every
|
|
10
|
+
* doctor check — it never edits PRINCIPLES.md. */
|
|
11
|
+
export function checkPrinciples(root) {
|
|
12
|
+
const raw = readTextOrNull(path.join(root, "PRINCIPLES.md"));
|
|
13
|
+
if (raw === null)
|
|
14
|
+
return okOutcome("no PRINCIPLES.md");
|
|
15
|
+
const length = raw.trim().length;
|
|
16
|
+
if (length <= PRINCIPLES_MAX_CHARS) {
|
|
17
|
+
return okOutcome(`PRINCIPLES.md is within the ${PRINCIPLES_MAX_CHARS}-character prompt cap`);
|
|
18
|
+
}
|
|
19
|
+
return warnOutcome("PRINCIPLES.md has " +
|
|
20
|
+
`${length} trimmed characters, over the ${PRINCIPLES_MAX_CHARS}-character ` +
|
|
21
|
+
"prompt cap; every run's <principles> block carries only the head plus " +
|
|
22
|
+
"`…[PRINCIPLES.md truncated at " +
|
|
23
|
+
`${PRINCIPLES_MAX_CHARS} chars]` +
|
|
24
|
+
" and only the leading bullets survive — keep it to its list bullets and " +
|
|
25
|
+
"move long-form rationale into docs/ (not injected)");
|
|
26
|
+
}
|
|
@@ -26,6 +26,7 @@ import { eventLogState, readEventsSince } from "../events/event-window.js";
|
|
|
26
26
|
import { EVENTS_KEEP } from "../events/events.js";
|
|
27
27
|
import { finiteNumber } from "../files/json-object.js";
|
|
28
28
|
import { agree, plural, shortSpanPhrase } from "../text/phrases.js";
|
|
29
|
+
import { andMore } from "./doctor-backlog.js";
|
|
29
30
|
import { KNOWN_FLAKE_WINDOW_MS, loadQuarantine, repeatedFlakeCounts } from "../review/known-flakes.js";
|
|
30
31
|
import { POST_MERGE_REJECT_WINDOW_MS, postMergeChurn } from "../review/post-merge-review.js";
|
|
31
32
|
import { roleLabel } from "../roles/role-labels.js";
|
|
@@ -216,7 +217,7 @@ export function checkDurationPhrase(events) {
|
|
|
216
217
|
export function checkBuildCheck(root, config) {
|
|
217
218
|
const check = detectBuildCheck(root, config ?? undefined);
|
|
218
219
|
if (!check)
|
|
219
|
-
return warnOutcome("none declared — the review gate's build pre-check, the red-main baseline, and redeploy's green check are all off (set `check.command` in tumwater.json for a non-npm repo)");
|
|
220
|
+
return warnOutcome("none declared — the review gate's build pre-check, the red-main baseline, and redeploy's green check are all off (set `check.command` in tumwater.json for a non-npm repo); the plan role will plan this project's check and the feature role declare it, a test/typecheck/build npm script or check.command");
|
|
220
221
|
// The in-lock command from mergeCommandOf (absent mergeCommand falls back to the gate
|
|
221
222
|
// command): the row names which command the merge lock runs and where the full check lands,
|
|
222
223
|
// facts a reader cannot get from detectBuildCheck alone. The after-merge roles are not named
|
|
@@ -266,13 +267,10 @@ export function checkFlakyChecks(root, now = Date.now()) {
|
|
|
266
267
|
return warnOutcome(phrases.join("; "));
|
|
267
268
|
}
|
|
268
269
|
/** A trailing day of at least `POST_MERGE_REJECT_WARN` post-merge `review_rejected` events — the
|
|
269
|
-
* audit
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* window, threshold and
|
|
274
|
-
* event selection through `postMergeChurn`, so the doctor row and the alert cannot disagree on
|
|
275
|
-
* when churn fires. ok/warn only — a churn burst does not stop the team. */
|
|
270
|
+
* audit of a day that must not read as a clean main: each rejection is an already-landed commit
|
|
271
|
+
* the reviewer turned back, so it stays on main with the reviewer's reasons. Reads the
|
|
272
|
+
* `POST_MERGE_REJECT_WINDOW_MS` window and
|
|
273
|
+
* threshold through `postMergeChurn`. ok/warn only — a churn burst does not stop the team. */
|
|
276
274
|
export function checkPostMergeChurn(root) {
|
|
277
275
|
const { events } = readEventsSince(root, POST_MERGE_REJECT_WINDOW_MS);
|
|
278
276
|
const churn = postMergeChurn(events);
|
|
@@ -283,15 +281,15 @@ export function checkPostMergeChurn(root) {
|
|
|
283
281
|
.join(", ")}) — each commit had already reached main and stays there with the reviewer's reasons; narrow \`review.afterMerge\` for the noisy role or raise the authoring bar`);
|
|
284
282
|
}
|
|
285
283
|
/** The one home of the doctor's `flaky checks` sentence: names each check that flapped more than
|
|
286
|
-
* once, most frequent first, with how many times it flapped — the
|
|
287
|
-
*
|
|
284
|
+
* once, most frequent first, with how many times it flapped — the one sentence the doctor's
|
|
285
|
+
* `flaky checks` row shows. null when `flapped`
|
|
288
286
|
* is empty (nothing crossed the bar). */
|
|
289
|
-
|
|
287
|
+
function flappedChecksPhrase(flapped) {
|
|
290
288
|
if (flapped.length === 0)
|
|
291
289
|
return null;
|
|
292
290
|
const render = ([k, n]) => `"${k}" ×${n}`;
|
|
293
291
|
const [first, ...rest] = flapped;
|
|
294
|
-
const more =
|
|
292
|
+
const more = andMore(rest, render);
|
|
295
293
|
return `${agree(flapped.length, "check", "checks")} flapped more than once in the last 24h: ${render(first)}${more}`;
|
|
296
294
|
}
|
|
297
295
|
/** Build provenance — is the harness about to run (this process's dist/) the code main
|
|
@@ -324,7 +322,7 @@ running = null) {
|
|
|
324
322
|
: running?.restartPending
|
|
325
323
|
? "; auto-restart is under way"
|
|
326
324
|
: "; run `npm run build` and restart `tumwater start` (auto-restart does this for a running team)";
|
|
327
|
-
return warnOutcome(`dist/ from ${sha} is stale — main has ${stale.aheadCommits
|
|
325
|
+
return warnOutcome(`dist/ from ${sha} is stale — main has ${plural(stale.aheadCommits, "later commit")} touching ${STALE_INPUTS_LABEL}${next}`);
|
|
328
326
|
}
|
|
329
327
|
return okOutcome(`dist/ from ${sha}, matches main`);
|
|
330
328
|
}
|
|
@@ -343,7 +341,7 @@ function currentHead(root) {
|
|
|
343
341
|
* on every merge and the ci role runs on its own clock, so a post-merge gap is normal and must
|
|
344
342
|
* not turn the Health card or the exit code red. A stale verdict that stays stale past
|
|
345
343
|
* `CI_PARITY_STALE_MS` (ciParityStalled) is a stall — the full suite has silently stopped
|
|
346
|
-
* covering new tips — and warns
|
|
344
|
+
* covering new tips — and warns. Read-only against
|
|
347
345
|
* .tumwater/. */
|
|
348
346
|
export function checkCiParity(root, config, now = Date.now()) {
|
|
349
347
|
const entry = readCiParity(root);
|
|
@@ -359,9 +357,9 @@ export function checkCiParity(root, config, now = Date.now()) {
|
|
|
359
357
|
}
|
|
360
358
|
// A stale verdict is normal right after a merge (main moves and the ci role runs on its own
|
|
361
359
|
// clock), but one that stays stale past CI_PARITY_STALE_MS is a stall: the full suite has
|
|
362
|
-
// silently stopped covering new tips.
|
|
363
|
-
//
|
|
364
|
-
//
|
|
360
|
+
// silently stopped covering new tips. This warn is the one place ciParityStalled is applied —
|
|
361
|
+
// the row is the standing signal. Inside the window the existing friendly stale wording
|
|
362
|
+
// stands.
|
|
365
363
|
if (ciParityStalled({ state: "stale", lastRunAt: entry.lastRunAt }, now)) {
|
|
366
364
|
return warnOutcome(`stale and stalled — the last run covered ${shortSha(entry.tip)} @ lock ${shortSha(entry.lockHash)} ${age} ago (${entry.result}); main is now ${shortSha(tip)} @ lock ${shortSha(lockHash)} — no full-suite run has covered a tip in over ${CI_PARITY_STALE_MS / DAY_MS} days, so the ci role is disabled, paused, or unable to record`);
|
|
367
365
|
}
|
|
@@ -374,8 +372,8 @@ export function checkCiParity(root, config, now = Date.now()) {
|
|
|
374
372
|
* is not a stall); a fresh ledger reads ok naming the newest exercised flow and its age. A
|
|
375
373
|
* ledger whose newest exercise is older than `QA_COVERAGE_STALE_MS` (qaCoverageStalled) is a
|
|
376
374
|
* `warn` naming that flow and its age — the role is disabled, paused, or unable to record —
|
|
377
|
-
*
|
|
378
|
-
*
|
|
375
|
+
* via the one `qaCoverageStalled` predicate, this row the standing signal. Read-only against
|
|
376
|
+
* .tumwater/. */
|
|
379
377
|
export function checkQaCoverage(root, now = Date.now()) {
|
|
380
378
|
const coverage = readQaCoverage(root);
|
|
381
379
|
const entries = Object.entries(coverage);
|
|
@@ -395,3 +393,55 @@ export function checkQaCoverage(root, now = Date.now()) {
|
|
|
395
393
|
}
|
|
396
394
|
return okOutcome(`newest flow coverage: ${newestFlow} exercised ${age} ago`);
|
|
397
395
|
}
|
|
396
|
+
/** How far back the `main diff rules` row looks — one day, the same 24 h trailing-day convention
|
|
397
|
+
* every event-reading doctor row uses, so a diff-rule failure the main watch logged stays a
|
|
398
|
+
* standing warn until a day passes. Owned here (the check that reads it). */
|
|
399
|
+
const MAIN_RULE_FAILURE_WINDOW_MS = 24 * 60 * 60_000;
|
|
400
|
+
/** The exact substring src/baseline/main-watch.ts's `watchRules` logs in its warning, so this
|
|
401
|
+
* check matches a real rule failure and cannot drift from the wording the watch writes. */
|
|
402
|
+
const MAIN_RULE_FAILURE_MARKER = "fails the diff rules";
|
|
403
|
+
/** The doctor's `main diff rules` row: a `warning` the main watch logged when the review gate's
|
|
404
|
+
* diff rules rejected main's tip in the trailing day. The gate judges one change; the watch
|
|
405
|
+
* judges each first-parent commit's own diff, so a conflict marker, a protected edit, a leaked
|
|
406
|
+
* secret, a phantom fix claim or a broken backlog structure that reached main through a gate
|
|
407
|
+
* blind spot sits on main with only this event log to say so. Reads only the `warning` event
|
|
408
|
+
* the watch already writes, so the row cannot drift from the watch's wording; the most recent
|
|
409
|
+
* hit names the tip and the rule. ok/warn only — it never fails a scripted pre-flight. */
|
|
410
|
+
export function checkMainDiffRules(root) {
|
|
411
|
+
const { events } = readEventsSince(root, MAIN_RULE_FAILURE_WINDOW_MS);
|
|
412
|
+
const hits = events.filter((e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(MAIN_RULE_FAILURE_MARKER));
|
|
413
|
+
if (hits.length === 0)
|
|
414
|
+
return okOutcome("no diff-rule failure on main in the last day");
|
|
415
|
+
const latest = hits[hits.length - 1];
|
|
416
|
+
return warnOutcome(`${plural(hits.length, "diff-rule failure on main", "diff-rule failures on main")} in the last day — most recently ${String(latest.message)} — the gate approved a diff the watch's rules reject; repair the tip or revert it with \`tumwater revert <sha>\``);
|
|
417
|
+
}
|
|
418
|
+
/** The exact prefix src/merge/merge-post-review-drain.ts logs when it drops a post-merge
|
|
419
|
+
* review at POST_REVIEW_FAILURE_LIMIT. The drain drops in two ways and logs each with its own
|
|
420
|
+
* tail — "it stays on main unreviewed" after failed runs (the commit never got a review) and
|
|
421
|
+
* "the fix stays unreviewed" after failed fix merges (the fix ref is deleted, main unmoved) —
|
|
422
|
+
* so this check matches the shared "dropping the post-merge review of" prefix both share, not
|
|
423
|
+
* either tail. The drain also files the drop in BUGS.md as best-effort ordinary backlog work
|
|
424
|
+
* (fileDroppedReview), but a skipped filing (off-base checkout or a dirty BUGS.md) leaves only
|
|
425
|
+
* this event log standing, so this check matches the drain's own wording. */
|
|
426
|
+
const DROPPED_REVIEW_MARKER = "dropping the post-merge review of";
|
|
427
|
+
/** How far back the `dropped post-merge reviews` row looks — one day, the same 24 h trailing-day
|
|
428
|
+
* convention every event-reading doctor row uses. Owned here (the check that reads it). */
|
|
429
|
+
const DROPPED_REVIEW_WINDOW_MS = 24 * 60 * 60_000;
|
|
430
|
+
/** The doctor's `dropped post-merge reviews` row: a `warning` the post-merge-review drain
|
|
431
|
+
* (src/merge/merge-post-review-drain.ts) wrote when it dropped a review at
|
|
432
|
+
* POST_REVIEW_FAILURE_LIMIT — after failed runs its commit stays on main unreviewed, and
|
|
433
|
+
* after failed fix merges the fix ref is deleted (main unmoved) so the commit stays
|
|
434
|
+
* unreviewed; either way the review is gone and no longer in the queue the `post-merge
|
|
435
|
+
* reviews` row reads. The drain files the dropped review in BUGS.md as best-effort ordinary
|
|
436
|
+
* backlog work, so when that filing is skipped this row is the standing signal naming the
|
|
437
|
+
* dropped review. Reads only the `warning` event the drain already writes, so it cannot drift
|
|
438
|
+
* from the drain's wording; the most recent hit names the commit. ok/warn only — an
|
|
439
|
+
* unreviewed commit is operator signal, not a broken environment. */
|
|
440
|
+
export function checkDroppedPostMergeReviews(root) {
|
|
441
|
+
const { events } = readEventsSince(root, DROPPED_REVIEW_WINDOW_MS);
|
|
442
|
+
const hits = events.filter((e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(DROPPED_REVIEW_MARKER));
|
|
443
|
+
if (hits.length === 0)
|
|
444
|
+
return okOutcome("no post-merge review was dropped in the last day");
|
|
445
|
+
const latest = hits[hits.length - 1];
|
|
446
|
+
return warnOutcome(`${plural(hits.length, "post-merge review was dropped", "post-merge reviews were dropped")} in the last day — the dropped review is gone and its commit stays unreviewed; most recently ${String(latest.message)}`);
|
|
447
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readRoleNotesOverCap } from "../status/status-role-notes.js";
|
|
2
|
+
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
3
|
+
import { agree, plural } from "../text/phrases.js";
|
|
4
|
+
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
5
|
+
/** How many oversized notes the `role notes` row names before appending "and N more", the
|
|
6
|
+
* cap the other multi-role observer rows (e.g. workspaces) use so a long tail does not drown
|
|
7
|
+
* the report. */
|
|
8
|
+
const ROLE_NOTES_NAMED_CAP = 3;
|
|
9
|
+
/** The `role notes` check: whether any role's notes file would be truncated when read into
|
|
10
|
+
* that role's run prompt. `readRoleNote` (src/run/run-prompt.ts) cuts every notes file at
|
|
11
|
+
* ROLE_NOTES_MAX_BYTES *characters* on read (truncateWithNote's text.length bound), but every
|
|
12
|
+
* operator surface reads the file whole — so a hand edit or old build can leave a note the
|
|
13
|
+
* operator sees and the role never does. This row warns on that gap without touching the file.
|
|
14
|
+
* The over-cap walk is shared with the status snapshot's collectRoleNotesOverCap
|
|
15
|
+
* (src/status/status-role-notes.ts), so the row and the status alert cannot disagree about
|
|
16
|
+
* which notes are over the bound; null means the notes dir is missing or unreadable.
|
|
17
|
+
* Read-only against .tumwater/, like every check. Never throws. */
|
|
18
|
+
export function checkRoleNotes(root) {
|
|
19
|
+
const over = readRoleNotesOverCap(root);
|
|
20
|
+
if (over === null) {
|
|
21
|
+
// Missing or unreadable notes dir: nothing recorded yet (the write path creates it).
|
|
22
|
+
return okOutcome("no role notes recorded yet");
|
|
23
|
+
}
|
|
24
|
+
if (over.length === 0) {
|
|
25
|
+
return okOutcome(`every role note is within the ${ROLE_NOTES_MAX_BYTES}-character read bound`);
|
|
26
|
+
}
|
|
27
|
+
const first = over
|
|
28
|
+
.slice(0, ROLE_NOTES_NAMED_CAP)
|
|
29
|
+
.map((o) => `${o.role} (${o.chars})`)
|
|
30
|
+
.join(", ");
|
|
31
|
+
const more = over.length > ROLE_NOTES_NAMED_CAP ? ` and ${over.length - ROLE_NOTES_NAMED_CAP} more` : "";
|
|
32
|
+
return warnOutcome(`${plural(over.length, "note", "notes")} across ${plural(over.length, "role", "roles")} ${agree(over.length, "exceeds", "exceed")} the ${ROLE_NOTES_MAX_BYTES}-character read bound: ${first}${more} — each role's prompt shows only the truncated head, so rewrite the note under the cap (tumwater role <id> --notes-file <path> or the tumwater-notes shim)`);
|
|
33
|
+
}
|
|
@@ -6,16 +6,18 @@ import { checkOrphans } from "./doctor-orphans.js";
|
|
|
6
6
|
import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours, checkSubscriptionWindow } from "./doctor-holds.js";
|
|
7
7
|
import { checkRootInstall } from "./doctor-install.js";
|
|
8
8
|
import { checkNotify } from "./doctor-notify.js";
|
|
9
|
+
import { checkRoleNotes } from "./doctor-role-notes.js";
|
|
9
10
|
import { checkLaunchServicesPorts } from "./doctor-launch-services.js";
|
|
10
11
|
import { plural } from "../text/phrases.js";
|
|
11
12
|
import { shortSha } from "../text/format.js";
|
|
12
13
|
import { failOutcome } from "./outcome.js";
|
|
13
14
|
import { errorMessage } from "../text/text.js";
|
|
14
15
|
import { checkBackendBinary, checkBrief, checkBootstrap, checkClaudeTranscripts, checkExecPath, checkFunes, checkGitBinary, checkConfigKeyNames, checkInit, checkOpencodeVersion, checkNodeVersion, checkPiExtensions, checkRepo, } from "./doctor-checks.js";
|
|
15
|
-
import {
|
|
16
|
+
import { checkPrinciples } from "./doctor-principles.js";
|
|
17
|
+
import { checkBuild, checkBuildCheck, checkCiParity, checkDiskSpace, checkDroppedPostMergeReviews, checkEventLog, checkFlakyChecks, checkMainDiffRules, checkPostMergeChurn, checkQaCoverage, checkStateDir, } from "./doctor-project-checks.js";
|
|
16
18
|
import { sampleFreeBytes } from "../gates/disk-gate.js";
|
|
17
19
|
import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock, checkProviderConcurrency, checkProviderContention, checkTumwaterPins, checkWorkInstances, checkWorktreePool, } from "./doctor-contention-checks.js";
|
|
18
|
-
import { checkFeedbackFiling,
|
|
20
|
+
import { checkFeedbackFiling, checkTierModels } from "./doctor-model-checks.js";
|
|
19
21
|
import { checkFallbackChain, checkFallbackModel } from "./doctor-fallback-checks.js";
|
|
20
22
|
import { checkAnthroApiKey, checkClaudeAuth, checkClaudeVersion } from "./doctor-claude-checks.js";
|
|
21
23
|
import { checkOpencode } from "./doctor-opencode-checks.js";
|
|
@@ -23,7 +25,7 @@ import { checkCodexAccount, checkCodexAuth, checkCodexHome, checkCodexLeaks, che
|
|
|
23
25
|
import { backendQualifiedSelector } from "../config/config-views.js";
|
|
24
26
|
import { seamsInUse } from "../config/config-fallbacks.js";
|
|
25
27
|
import { checkBacklogHeadings, checkFixClaims, checkLiveFinished, checkPlanDependencies, checkStalledEntries, checkStrandedPlans, checkValidationGaps } from "./doctor-backlog-plans.js";
|
|
26
|
-
import { checkRefusedEntries, checkRegressedFixes, checkStaleBugs, checkStaleQuestions
|
|
28
|
+
import { checkRefusedEntries, checkRegressedFixes, checkStaleBugs, checkStaleQuestions } from "./doctor-backlog-bugs.js";
|
|
27
29
|
import { checkMergeQueueFiles, checkPostReviews, checkStaleQueuedMerges, checkStalePrompts, checkUncommittedBacklog } from "./doctor-backlog.js";
|
|
28
30
|
/** Pre-flight environment check (`tumwater doctor`). The harness's preconditions are
|
|
29
31
|
* scattered across fail-fast checks that each command re-runs on its own (requireReadyRepo in
|
|
@@ -59,7 +61,7 @@ export async function runDoctorCheck(run) {
|
|
|
59
61
|
* just the named checks, in report order; a skipped check is never evaluated, and a named
|
|
60
62
|
* conditional check that returns null leaves no row. A check that throws becomes its own fail
|
|
61
63
|
* row, and every other check still runs. */
|
|
62
|
-
export async function runDoctor(root, pathEnv = envPath(), probe = systemProcessProbe,
|
|
64
|
+
export async function runDoctor(root, pathEnv = envPath(), probe = systemProcessProbe, only, diskSample = sampleFreeBytes) {
|
|
63
65
|
// Loaded once for the checks that read config (repo's baseBranch); a broken file stays
|
|
64
66
|
// undefined — checkInit reports it verbatim — so doctor still runs every other check.
|
|
65
67
|
const { config } = loadConfigSafe(root);
|
|
@@ -86,6 +88,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
86
88
|
{ name: "init", run: () => checkInit(root) },
|
|
87
89
|
{ name: "config key names", run: () => checkConfigKeyNames(root) },
|
|
88
90
|
{ name: "brief", run: () => checkBrief(root) },
|
|
91
|
+
{ name: "principles", run: () => checkPrinciples(root) },
|
|
89
92
|
// New-project bootstrap (plans/work-ratio.md, part 2/2): an active hold reports as ok
|
|
90
93
|
// information, never a warning — a young project is not misconfigured.
|
|
91
94
|
{ name: "bootstrap", run: () => checkBootstrap(root, config) },
|
|
@@ -104,7 +107,6 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
104
107
|
{ name: "over-cap model", run: () => checkFallbackModel(root, undefined, (orchestratorAlive(root, info) && info?.fallbackDemoted) || null) },
|
|
105
108
|
{ name: "fallback chain", run: () => checkFallbackChain(root) },
|
|
106
109
|
{ name: "models for each tier", run: () => checkTierModels(root) },
|
|
107
|
-
{ name: "image input", run: () => checkImageInput(root, undefined, undefined, undefined, probeImages, pathEnv) },
|
|
108
110
|
{ name: "claude auth", run: () => checkClaudeAuth(root, pathEnv) },
|
|
109
111
|
{ name: "claude api key", run: () => checkAnthroApiKey(root, undefined, pathEnv) },
|
|
110
112
|
{ name: "claude version", run: () => checkClaudeVersion(root, pathEnv) },
|
|
@@ -143,6 +145,10 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
143
145
|
{ name: "check", run: () => checkBuildCheck(root, config) },
|
|
144
146
|
{ name: "ci parity", run: () => checkCiParity(root, config, Date.now()) },
|
|
145
147
|
{ name: "qa coverage", run: () => checkQaCoverage(root, Date.now()) },
|
|
148
|
+
// The role-notes read gap (plans/role-notes): every operator surface reads a role's notes
|
|
149
|
+
// file whole while the run prompt truncates it at ROLE_NOTES_MAX_BYTES; this row surfaces the
|
|
150
|
+
// gap read-only, beside the other per-role observer rows (feedback filing, workspaces).
|
|
151
|
+
{ name: "role notes", run: () => checkRoleNotes(root) },
|
|
146
152
|
{ name: "root install", run: () => checkRootInstall(root) },
|
|
147
153
|
// The review gate's flake memory, surfaced read-only: naming which checks the gate kept
|
|
148
154
|
// excusing as flakes (ok/warn only, so a flaky suite never fails a scripted pre-flight).
|
|
@@ -158,11 +164,12 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
158
164
|
{ name: "plan dependencies", run: () => checkPlanDependencies(root) },
|
|
159
165
|
{ name: "stale questions", run: () => checkStaleQuestions(root) },
|
|
160
166
|
{ name: "stale bugs", run: () => checkStaleBugs(root) },
|
|
161
|
-
{ name: "waiting bugs", run: () => checkWaitingBugs(root) },
|
|
162
167
|
{ name: "regressed fixes", run: () => checkRegressedFixes(root) },
|
|
163
168
|
{ name: "stale merge-queue entries", run: () => checkStaleQueuedMerges(root, Date.now()) },
|
|
164
169
|
{ name: "merge-queue files", run: () => checkMergeQueueFiles(root) },
|
|
165
170
|
{ name: "post-merge reviews", run: () => checkPostReviews(root) },
|
|
171
|
+
{ name: "main diff rules", run: () => checkMainDiffRules(root) },
|
|
172
|
+
{ name: "dropped post-merge reviews", run: () => checkDroppedPostMergeReviews(root) },
|
|
166
173
|
{ name: "stale prompts", run: () => checkStalePrompts(root, Date.now()) },
|
|
167
174
|
{ name: "uncommitted backlog", run: () => checkUncommittedBacklog(root, config) },
|
|
168
175
|
// The running team's own view of its build (staleness and what auto-restart made of it)
|
package/dist/src/doctor/probe.js
CHANGED
|
@@ -20,7 +20,7 @@ export const AUTH_CHECK_MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
|
20
20
|
* spawn closes for git). stderr is ignored rather than piped, since nothing reads it and a
|
|
21
21
|
* full stderr pipe would block the child. `timeoutMs` and `maxOutputBytes` are test seams;
|
|
22
22
|
* production callers pass their own named constants. One home of the shape, shared by the
|
|
23
|
-
* model checks' credential/version probes, the tier-model probes
|
|
23
|
+
* model checks' credential/version probes, and the tier-model probes —
|
|
24
24
|
* before the split each doctor module carried its own byte-for-byte copy. The credential probes'
|
|
25
25
|
* named budgets (AUTH_CHECK_TIMEOUT_MS, AUTH_CHECK_MAX_OUTPUT_BYTES) live beside it here, so the
|
|
26
26
|
* backend check modules share them without importing them from one another. */
|