tumwater 0.4.2 → 0.4.4
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} +27 -13
- 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 +6 -27
- 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 +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- 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 +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- 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 +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- 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 +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- 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-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- 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 +66 -40
- 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 +163 -102
- package/dist/src/config/config-schema.js +10 -12
- 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 +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- 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 +122 -21
- 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 +24 -11
- 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 +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- 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 +32 -74
- 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} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- 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-args.js +12 -0
- 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-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -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 +54 -36
- 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-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- 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 +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- 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 +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- 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 +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- 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 +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- 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/gate-prompts.js +1 -44
- 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/redeploy/redeployer.js +21 -1
- 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/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- 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 +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- 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 +141 -90
- 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 +128 -50
- package/dist/src/run/stage-check.js +60 -50
- 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/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- 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 +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.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-operator.js +7 -5
- 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 +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- 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 +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- 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 +5 -10
- 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 +167 -98
- 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 +5 -4
- 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 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -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 +15 -10
- 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 +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- 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/cli/{brief-command.js → cli-brief-command.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
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { andMore, quoteHeading, readDoc, readBacklogChecked, FIX_CLAIMS_CHECKED, REFUSAL_NOTE_MAX } from "./doctor-backlog.js";
|
|
2
|
+
import { agree } from "../text/phrases.js";
|
|
2
3
|
import { cachedByStat } from "../files/stat-cache.js";
|
|
3
4
|
import { VALIDATION_GAP_TAGS } from "../roles/role-guidance.js";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { truncate } from "../text/text.js";
|
|
6
7
|
import { donePlans, fixedBugEntries, openBugEntries, plannedPlanEntries } from "../backlog/backlog.js";
|
|
7
|
-
import { entryHold, entryKindLabel,
|
|
8
|
+
import { entryHold, entryKindLabel, isPlanStalled } from "../backlog/backlog-eligibility.js";
|
|
8
9
|
import { planDependencyProblems } from "../backlog/backlog-parts.js";
|
|
9
|
-
import { BACKLOG_FILES,
|
|
10
|
-
import { statOrNull } from "../files/files.js";
|
|
10
|
+
import { BACKLOG_FILES, validationGapTag } from "../backlog/backlog-md.js";
|
|
11
11
|
import { duplicateHeadings, strandedPlanEntriesFor } from "../backlog/backlog-structure.js";
|
|
12
12
|
import { liveFinishedHistory, liveFinishedRowList } from "../backlog/backlog-archive.js";
|
|
13
|
-
import { fixSymbols, missingSymbolNames, cachedSourceHaystack, unbackedSymbols } from "../verdict/fix-claim.js";
|
|
14
|
-
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
13
|
+
import { fixSymbols, isCommitRef, missingSymbolNames, cachedSourceHaystack, unbackedSymbols } from "../verdict/fix-claim.js";
|
|
14
|
+
import { okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
15
15
|
/** The doctor's PLANS.md-structure and fix-claim checks, split out of doctor-backlog.ts: the
|
|
16
16
|
* checks that re-verify BUGS.md's newest Fixed records against the tree (checkFixClaims,
|
|
17
17
|
* checkValidationGaps) and the PLANS.md / backlog-file integrity checks that read sections and
|
|
@@ -21,7 +21,7 @@ import { okOutcome, warnOutcome } from "./outcome.js";
|
|
|
21
21
|
* heading-trim bounds,
|
|
22
22
|
* and all of them warn instead of failing: damaged documentation is operator signal, not a
|
|
23
23
|
* broken environment. doctor.ts composes them beside the open-item and queue checks; the shared
|
|
24
|
-
* CheckOutcome shape is imported from outcome.ts. */
|
|
24
|
+
* CheckOutcome shape is imported from doctor-outcome.ts. */
|
|
25
25
|
/** Fix claims — the standalone half of the merge gate's false-fix check (src/verdict/fix-claim.ts):
|
|
26
26
|
* that gate fires only when an md-only diff moves a BUGS.md entry to Fixed, so a phantom fix that
|
|
27
27
|
* reached main any other way (merged before the gate existed, or through a path it never sees) was
|
|
@@ -44,20 +44,22 @@ export function checkFixClaims(root) {
|
|
|
44
44
|
const { heading, missing } = first;
|
|
45
45
|
const names = missingSymbolNames(missing);
|
|
46
46
|
// Every other suspect is still named, shortened: a doctor check is one line.
|
|
47
|
-
const more = andMore(rest, (p) => quoteHeading(p.heading), "record
|
|
47
|
+
const more = andMore(rest, (p) => quoteHeading(p.heading), agree(rest.length, "record", "records"));
|
|
48
48
|
return warnOutcome(`BUGS.md records "${heading}" as Fixed, but none of the symbols its Fix paragraph names ` +
|
|
49
49
|
`exist on this tree: ${names}${more} — land the fix or keep the bug Open / refresh a stale record`);
|
|
50
50
|
}
|
|
51
|
-
/** The fix-claim rows
|
|
51
|
+
/** The fix-claim rows the doctor's check warns about: each newest Fixed
|
|
52
52
|
* record classified through fixSymbols and unbackedSymbols — the gate's own parsing and
|
|
53
53
|
* existence rules — against ONE cachedSourceHaystack built for the whole scan, keeping a record
|
|
54
54
|
* only when EVERY symbol its Fix paragraph names is absent, and skipping a record that names no
|
|
55
|
-
* symbols (pure-documentation fixes are legitimate).
|
|
55
|
+
* symbols (pure-documentation fixes are legitimate). A commit id is a citation, not a symbol:
|
|
56
|
+
* this synchronous scan cannot ask git whether it exists (the gate does), so a record that cites
|
|
57
|
+
* only the commit it reviewed names nothing checkable. */
|
|
56
58
|
function classifyFixClaims(records, root) {
|
|
57
59
|
const problems = [];
|
|
58
60
|
let haystack;
|
|
59
61
|
for (const { title, body } of records) {
|
|
60
|
-
const symbols = fixSymbols(body);
|
|
62
|
+
const symbols = fixSymbols(body).filter((symbol) => !isCommitRef(symbol));
|
|
61
63
|
if (symbols.length === 0)
|
|
62
64
|
continue;
|
|
63
65
|
haystack ??= cachedSourceHaystack(root);
|
|
@@ -70,24 +72,20 @@ function classifyFixClaims(records, root) {
|
|
|
70
72
|
/** The ONE shared fix-claim scan: the newest Fixed records (bounded to FIX_CLAIMS_CHECKED so
|
|
71
73
|
* pre-convention history never warns forever) as fixedBugEntries reads them — the stat-cached
|
|
72
74
|
* section reader BUGS.md + BUGS-FIXED.md, bodies kept whole — classified through
|
|
73
|
-
* classifyFixClaims.
|
|
74
|
-
* what keeps the alert and the doctor on the identical problem set; there is only one scan. */
|
|
75
|
+
* classifyFixClaims. */
|
|
75
76
|
function fixClaimRecords(root) {
|
|
76
77
|
return fixedBugEntries(root).slice(0, FIX_CLAIMS_CHECKED);
|
|
77
78
|
}
|
|
78
|
-
/** The problems the ONE
|
|
79
|
+
/** The problems the ONE fix-claim scan found among the newest Fixed records at `root`
|
|
79
80
|
* (empty when BUGS.md is unreadable or every newest Fixed record names at least one live
|
|
80
|
-
* symbol).
|
|
81
|
-
* cachedSourceHaystack, so it stays in step with the doctor and adds no re-read to the 1 Hz TUI
|
|
82
|
-
* and status-poll path. */
|
|
81
|
+
* symbol). */
|
|
83
82
|
export function fixClaimProblems(root) {
|
|
84
83
|
return classifyFixClaims(fixClaimRecords(root), root);
|
|
85
84
|
}
|
|
86
|
-
/** The validation-gap rows
|
|
85
|
+
/** The validation-gap rows the doctor's check warns about: each newest
|
|
87
86
|
* Fixed record classified against the closed `**Validation gap:**` vocabulary through
|
|
88
87
|
* validationGapTag, which is fence-aware — suspects (missing/unrecognized) first, then
|
|
89
|
-
* `unpinned`, so the first row is the one the doctor's message names
|
|
90
|
-
* picks. */
|
|
88
|
+
* `unpinned`, so the first row is the one the doctor's message names. */
|
|
91
89
|
function classifyValidationGaps(records) {
|
|
92
90
|
const suspects = [];
|
|
93
91
|
const unpinned = [];
|
|
@@ -102,22 +100,13 @@ function classifyValidationGaps(records) {
|
|
|
102
100
|
}
|
|
103
101
|
return [...suspects, ...unpinned];
|
|
104
102
|
}
|
|
105
|
-
/** The ONE scan the doctor's `validation gaps` check
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* through this same reader (and not the doctor's one-off fixedEntriesAt/fixedSection walk) is
|
|
110
|
-
* what keeps the alert and the doctor on the identical problem set; there is only one scan. */
|
|
103
|
+
/** The ONE scan the doctor's `validation gaps` check hands its rows from: the newest Fixed
|
|
104
|
+
* records (bounded to FIX_CLAIMS_CHECKED so pre-convention history never warns forever) as
|
|
105
|
+
* fixedBugEntries reads them — the stat-cached section reader BUGS.md + BUGS-FIXED.md, bodies
|
|
106
|
+
* kept whole — classified through classifyValidationGaps. */
|
|
111
107
|
function validationGapRecords(root) {
|
|
112
108
|
return fixedBugEntries(root).slice(0, FIX_CLAIMS_CHECKED);
|
|
113
109
|
}
|
|
114
|
-
/** The problems the ONE shared validation-gap scan found among the newest Fixed records at `root`
|
|
115
|
-
* (empty when BUGS.md/BUGS-FIXED.md are unreadable or every newest Fixed record carries a known
|
|
116
|
-
* tag/trace). Feeds the always-on amber alert through the stat-cached fixedBugEntries reader, so
|
|
117
|
-
* it stays in step with the doctor and adds no re-read to the 1 Hz TUI and status-poll path. */
|
|
118
|
-
export function validationGapProblems(root) {
|
|
119
|
-
return classifyValidationGaps(validationGapRecords(root));
|
|
120
|
-
}
|
|
121
110
|
/** The fixed-record validation-gap twin of checkFixClaims, standing in for plans/repair-traces.md's
|
|
122
111
|
* invariant 2 ("the vocabulary is closed and lives in one place"): the bugfix guidance tells every
|
|
123
112
|
* run to write exactly one `**Validation gap:**` line with a known tag, and `none` rather than no
|
|
@@ -135,8 +124,7 @@ export function validationGapProblems(root) {
|
|
|
135
124
|
export function checkValidationGaps(root) {
|
|
136
125
|
// The doctor's check reads BUGS.md itself so it can return its no-file outcome (a missing
|
|
137
126
|
// BUGS.md — nothing to verify); the classification then runs on the SAME fixedBugEntries
|
|
138
|
-
// reader
|
|
139
|
-
// is only one scan (validationGapRecords).
|
|
127
|
+
// reader, so the problem set cannot drift. There is only one scan (validationGapRecords).
|
|
140
128
|
const doc = readBacklogChecked(root, "BUGS.md");
|
|
141
129
|
if (typeof doc !== "string")
|
|
142
130
|
return doc;
|
|
@@ -147,14 +135,14 @@ export function checkValidationGaps(root) {
|
|
|
147
135
|
if (!first) {
|
|
148
136
|
if (unpinned.length > 0) {
|
|
149
137
|
const up = unpinned[0];
|
|
150
|
-
const more = andMore(unpinned.slice(1), (s) => quoteHeading(s.title),
|
|
138
|
+
const more = andMore(unpinned.slice(1), (s) => quoteHeading(s.title), agree(unpinned.slice(1).length, "record", "records"));
|
|
151
139
|
return warnOutcome(`BUGS.md record "${up.title}" is an \`unclear-invariant\` fix but names no ` +
|
|
152
140
|
`docs/invariants.md section — record the reconstructed guarantee there (a "Pinned by" line) in the ` +
|
|
153
141
|
`same commit so the next bug in that area does not reconstruct it again${more}`);
|
|
154
142
|
}
|
|
155
143
|
return okOutcome(`newest ${validationGapRecords(root).length} Fixed record(s) carry a known Validation gap tag`);
|
|
156
144
|
}
|
|
157
|
-
const more = andMore(rest, (s) => quoteHeading(s.title), "record
|
|
145
|
+
const more = andMore(rest, (s) => quoteHeading(s.title), agree(rest.length, "record", "records"));
|
|
158
146
|
const what = first.kind === "missing"
|
|
159
147
|
? `BUGS.md record "${first.title}" has no **Validation gap:** line`
|
|
160
148
|
: `BUGS.md record "${first.title}" names unrecognized Validation gap tag "${first.tag}"`;
|
|
@@ -201,22 +189,17 @@ export function checkLiveFinished(root) {
|
|
|
201
189
|
return warnOutcome(`${liveFinishedRowList(live)} — every run that reads the file pays for them; ` +
|
|
202
190
|
"`tumwater archive` moves them to the archive files and commits them (a starting team does too)");
|
|
203
191
|
}
|
|
204
|
-
/** The
|
|
205
|
-
*
|
|
206
|
-
* per file
|
|
207
|
-
* (PLANS-DONE.md and BUGS-FIXED.md run to MBs)
|
|
208
|
-
*
|
|
209
|
-
* uses. cachedByStat serves an unchanged file from cache (dev/ino/mtime/size) and re-reads on
|
|
210
|
-
* a change, so this shares the poll path's freshness contract. The one loop over BACKLOG_FILES
|
|
211
|
-
* that feeds both checkBacklogHeadings and the always-on `backlog-headings` team alert: absent
|
|
192
|
+
/** The one loop over BACKLOG_FILES the doctor's `backlog headings` check scans for duplicated
|
|
193
|
+
* `## ` titles. The per-file read is stat-cached (backlogHeadingFileCache) so repeated checks
|
|
194
|
+
* pay one stat per unchanged file rather than re-reading the whole BACKLOG_FILES member — the
|
|
195
|
+
* archives grow without bound (PLANS-DONE.md and BUGS-FIXED.md run to MBs) — and cachedByStat
|
|
196
|
+
* serves an unchanged file from cache (dev/ino/mtime/size) and re-reads on a change. Absent
|
|
212
197
|
* files (an early repo) contribute nothing, an existing file that cannot be read adds
|
|
213
198
|
* `${file} (unreadable)` to `rows` (its duplicate headings, if any, are invisible to every
|
|
214
|
-
* reader), and each duplicate `## ` title
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* they cannot drift on the file set, the unreadable clause, or the headings. */
|
|
199
|
+
* reader), and each duplicate `## ` title appears as `${file} "## ${title}"` in
|
|
200
|
+
* `rows` — which therefore keeps BACKLOG_FILES order, the order the doctor's `backlog
|
|
201
|
+
* headings` row lists its messages in. */
|
|
218
202
|
function scanBacklogHeadings(root) {
|
|
219
|
-
const duplicates = [];
|
|
220
203
|
const rows = [];
|
|
221
204
|
for (const file of BACKLOG_FILES) {
|
|
222
205
|
const p = path.join(root, file);
|
|
@@ -233,63 +216,14 @@ function scanBacklogHeadings(root) {
|
|
|
233
216
|
continue;
|
|
234
217
|
}
|
|
235
218
|
for (const heading of scan.headings) {
|
|
236
|
-
duplicates.push({ file, heading });
|
|
237
219
|
rows.push(`${file} "## ${heading}"`);
|
|
238
220
|
}
|
|
239
221
|
}
|
|
240
|
-
return
|
|
222
|
+
return rows;
|
|
241
223
|
}
|
|
242
|
-
/** The module-level stat cache backing scanBacklogHeadings,
|
|
243
|
-
*
|
|
224
|
+
/** The module-level stat cache backing scanBacklogHeadings, so a repeated check never re-reads
|
|
225
|
+
* an unchanged backlog or archive file. */
|
|
244
226
|
const backlogHeadingFileCache = new Map();
|
|
245
|
-
/** The duplicated `## ` headings already on main, exported so the always-on `backlog-headings`
|
|
246
|
-
* team alert (src/ui/team-alert-backlog.ts) renders the same set the doctor's `backlog
|
|
247
|
-
* headings` row warns about. */
|
|
248
|
-
export function duplicatedBacklogHeadings(root) {
|
|
249
|
-
return scanBacklogHeadings(root).duplicates;
|
|
250
|
-
}
|
|
251
|
-
/** The module-level stat cache backing liveFinishedEntriesFor, keyed by root, so a steady-state
|
|
252
|
-
* status poll pays four stat calls (two live files, two archives) instead of re-parsing the
|
|
253
|
-
* live files and the unbounded archives (PLANS-DONE.md runs to MBs) on every poll. Keyed by a
|
|
254
|
-
* combined stat signature rather than the shared cachedByStat because each row spans FOUR files
|
|
255
|
-
* (a live file, its finished entries, and the archive it empties into), so no single-file
|
|
256
|
-
* stat-keyed cache entry can serve it. */
|
|
257
|
-
const liveFinishedCache = new Map();
|
|
258
|
-
/** The finished-history rows already on main, exported so the always-on `backlog-archive` team
|
|
259
|
-
* alert (src/ui/team-alert-backlog.ts) renders the same set the doctor's `backlog archive`
|
|
260
|
-
* row warns about — stat-gated so a steady-state poll pays four stats, not a full re-parse.
|
|
261
|
-
* The cache key is the stat identity (`dev:ino:mtimeMs:size`, `absent` when the stat is
|
|
262
|
-
* null) of every
|
|
263
|
-
* FINISHED_ARCHIVES live file and archive under `root`, so any write that changes finished
|
|
264
|
-
* history or the archive refreshes the rows on the next call without a process restart. Returns
|
|
265
|
-
* a copy on a hit; on a miss it recomputes via liveFinishedHistory, and returns `[]` (uncached)
|
|
266
|
-
* when that throws, so an unreadable file stays checkLiveFinished's loud signal and never
|
|
267
|
-
* breaks a status poll. checkLiveFinished keeps calling liveFinishedHistory directly and is
|
|
268
|
-
* unchanged. */
|
|
269
|
-
export function liveFinishedEntriesFor(root) {
|
|
270
|
-
const key = FINISHED_ARCHIVES
|
|
271
|
-
.flatMap((spec) => [path.join(root, spec.file), path.join(root, spec.archive)])
|
|
272
|
-
.map((p) => {
|
|
273
|
-
const st = statOrNull(p);
|
|
274
|
-
return st ? `${st.dev}:${st.ino}:${st.mtimeMs}:${st.size}` : "absent";
|
|
275
|
-
})
|
|
276
|
-
.join("|");
|
|
277
|
-
const cached = liveFinishedCache.get(root);
|
|
278
|
-
if (cached?.key === key)
|
|
279
|
-
return cached.rows.map((r) => ({ ...r }));
|
|
280
|
-
let rows;
|
|
281
|
-
try {
|
|
282
|
-
rows = liveFinishedHistory(root);
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
liveFinishedCache.delete(root); // Never serve a stale read for an unreadable tree.
|
|
286
|
-
return []; // Unreadable — checkLiveFinished's signal; a status poll must not throw.
|
|
287
|
-
}
|
|
288
|
-
if (liveFinishedCache.size >= 64)
|
|
289
|
-
liveFinishedCache.clear(); // Safety cap, like backlogHeadingFileCache.
|
|
290
|
-
liveFinishedCache.set(root, { key, rows });
|
|
291
|
-
return rows.map((r) => ({ ...r }));
|
|
292
|
-
}
|
|
293
227
|
/** Duplicated `## ` headings already on main (the duplicate-heading half of the backlog-structure
|
|
294
228
|
* check, src/backlog/backlog-structure.ts): a `## Done` a past merge or conflict resolution added
|
|
295
229
|
* twice leaves every later entry after the first section invisible to the section readers, and
|
|
@@ -300,7 +234,7 @@ export function liveFinishedEntriesFor(root) {
|
|
|
300
234
|
* that leaves an existing duplicate alone passes and the duplicate itself needs a deliberate
|
|
301
235
|
* removal edit. */
|
|
302
236
|
export function checkBacklogHeadings(root) {
|
|
303
|
-
const
|
|
237
|
+
const rows = scanBacklogHeadings(root);
|
|
304
238
|
if (rows.length === 0)
|
|
305
239
|
return okOutcome("no duplicated ## section headings in the backlog files");
|
|
306
240
|
const [first, ...rest] = rows;
|
|
@@ -309,24 +243,27 @@ export function checkBacklogHeadings(root) {
|
|
|
309
243
|
`later entries are invisible; keep one heading per name (the gate blocks only a merge ` +
|
|
310
244
|
`that adds another copy — remove the duplicate in a deliberate edit)`);
|
|
311
245
|
}
|
|
312
|
-
/** Stalled entries — plans or bugs
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
246
|
+
/** Stalled entries — plans or bugs parked with the plan role by a **Needs review …** note
|
|
247
|
+
* that the plan role clears by splitting or replanning (the feature role's too-large note, the
|
|
248
|
+
* convergence hold, a final-round rejection). The bugfix role's wait-for-the-next-failure
|
|
249
|
+
* parks ("by bugfix:") are excluded — selfcheck lifts them, not the plan role, so they are
|
|
250
|
+
* not the plan role's stalled work. No role takes a held entry (the claim gate refuses one)
|
|
251
|
+
* and nothing clears a plan-role note on its own, so the state is visible only as the
|
|
316
252
|
* `[needs review]` marks in the backlog listings; this names them so an
|
|
317
253
|
* operator gets a single-line health signal from `tumwater doctor`. Reads the tree at `root`
|
|
318
254
|
* — the primary checkout IS main's tree — like checkRefusedEntries, which is also where
|
|
319
|
-
* refusal notes (a different hold) are surfaced. entryHold
|
|
320
|
-
* already
|
|
321
|
-
* exactly needs-review
|
|
255
|
+
* refusal notes (a different hold) are surfaced. entryHold + isPlanStalled
|
|
256
|
+
* (src/backlog/backlog-eligibility.ts) already classify the notes, so this check adds no
|
|
257
|
+
* parsing: only the entries whose hold is exactly needs-review and which isPlanStalled
|
|
258
|
+
* accepts are collected, from both PLANS.md
|
|
322
259
|
* and BUGS.md. A warn, never a fail — the checkRefusedEntries precedent: a parked entry is
|
|
323
|
-
* operator signal, not a broken environment; the plan role resolves
|
|
324
|
-
*
|
|
260
|
+
* operator signal, not a broken environment; the plan role resolves it by splitting or
|
|
261
|
+
* replanning. */
|
|
325
262
|
export function checkStalledEntries(root) {
|
|
326
263
|
const planned = plannedPlanEntries(root);
|
|
327
264
|
const collect = (entry, file) => {
|
|
328
265
|
const hold = entryHold(entry, planned);
|
|
329
|
-
return hold === "needs-review"
|
|
266
|
+
return hold === "needs-review" && isPlanStalled(entry)
|
|
330
267
|
? { file, entry }
|
|
331
268
|
: null;
|
|
332
269
|
};
|
|
@@ -336,15 +273,11 @@ export function checkStalledEntries(root) {
|
|
|
336
273
|
].filter((x) => x !== null);
|
|
337
274
|
const [first, ...rest] = stalled;
|
|
338
275
|
if (!first)
|
|
339
|
-
return okOutcome("no planned plan or open bug is held by a Needs-review
|
|
276
|
+
return okOutcome("no planned plan or open bug is held by a Needs-review note");
|
|
340
277
|
const more = andMore(rest, (s) => quoteHeading(s.entry.title));
|
|
341
278
|
const kind = entryKindLabel(first.file); // "Bug" | "Plan"
|
|
342
279
|
const head = `${first.file === "bugs" ? "BUGS.md" : "PLANS.md"} ${kind.toLowerCase()}`;
|
|
343
|
-
const hold = entryHold(first.entry, planned);
|
|
344
280
|
const note = (first.entry.body.split(/\n/).find((line) => line.trimStart().startsWith("**Needs review ")) ?? "").replace(/^\s*\*\*/, "").replace("**", "");
|
|
345
|
-
if (hold === "held-for-operator") { // vocabulary-ok: the held-for-operator hold's key
|
|
346
|
-
return warnOutcome(`${head} ${quoteHeading(first.entry.title)} is held for the operator ${heldCount(first.entry.body)} times${more} — release it with tumwater ${kind.toLowerCase()} --release`); // vocabulary-ok: a held-for-operator entry is the operator's to release
|
|
347
|
-
}
|
|
348
281
|
return warnOutcome(`${head} ${quoteHeading(first.entry.title)} is held by a Needs review note ` +
|
|
349
282
|
`(${truncate(note, REFUSAL_NOTE_MAX)})${more} — the plan role resolves it by splitting or replanning`);
|
|
350
283
|
}
|
|
@@ -375,16 +308,16 @@ export function planDependencyProblem(root) {
|
|
|
375
308
|
}
|
|
376
309
|
if (firstDangling) {
|
|
377
310
|
return { kind: "dangling", message: danglingMsg(firstDangling) +
|
|
378
|
-
andMore(restDangling, (d) => `${quote(d.title)} → ${d.ref}`, "ref
|
|
311
|
+
andMore(restDangling, (d) => `${quote(d.title)} → ${d.ref}`, agree(restDangling.length, "ref", "refs")) +
|
|
379
312
|
others([[unparsed.length, "unparsed clause(s)"], [misworded.length, "misworded dependency(ies)"]]) };
|
|
380
313
|
}
|
|
381
314
|
if (firstUnparsed) {
|
|
382
|
-
return { kind: "unparsed", message: unparsedMsg(firstUnparsed) + andMore(restUnparsed, quote, "clause
|
|
315
|
+
return { kind: "unparsed", message: unparsedMsg(firstUnparsed) + andMore(restUnparsed, quote, agree(restUnparsed.length, "clause", "clauses")) +
|
|
383
316
|
others([[misworded.length, "misworded dependency(ies)"]]) };
|
|
384
317
|
}
|
|
385
318
|
if (firstMisworded) {
|
|
386
319
|
return { kind: "misworded", message: miswordedMsg(firstMisworded) +
|
|
387
|
-
andMore(restMisworded, (w) => `${quote(w.title)} → ${w.ref}`, "dependency
|
|
320
|
+
andMore(restMisworded, (w) => `${quote(w.title)} → ${w.ref}`, agree(restMisworded.length, "dependency", "dependencies")) };
|
|
388
321
|
}
|
|
389
322
|
return null;
|
|
390
323
|
}
|
|
@@ -4,11 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { errorMessage, truncate } from "../text/text.js";
|
|
5
5
|
import { shortSha } from "../text/format.js";
|
|
6
6
|
import { readPostReviews, POST_REVIEW_FAILURE_LIMIT } from "../review/post-merge-review.js";
|
|
7
|
-
import { humanSeconds,
|
|
7
|
+
import { humanSeconds, humanSince, HOUR_MS, DAY_MS } from "../text/datetime.js";
|
|
8
8
|
import { eventsSince } from "../events/event-window.js";
|
|
9
9
|
import { finiteNumber } from "../files/json-object.js";
|
|
10
|
-
import { lowerMedian } from "../collections.js";
|
|
11
|
-
import { plural,
|
|
10
|
+
import { distinctOrdered, lowerMedian } from "../collections.js";
|
|
11
|
+
import { agree, plural, trailingDayMedianPhrase } from "../text/phrases.js";
|
|
12
12
|
import { queuedMerges, unreadableMergeQueueFiles } from "../merge/merge-queue.js";
|
|
13
13
|
import { mergeChanges, readMergeMarker } from "../merge/merge-slot.js";
|
|
14
14
|
import { listWideScope, queuedListRows } from "../inbox/inbox.js";
|
|
@@ -19,7 +19,7 @@ import { stampedSubject } from "../git/commit-message.js";
|
|
|
19
19
|
import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
|
|
20
20
|
import { briefFile } from "../brief/brief.js";
|
|
21
21
|
import { BACKLOG_FILES } from "../backlog/backlog-md.js";
|
|
22
|
-
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
22
|
+
import { okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
23
23
|
/** The doctor's backlog modules' shared scaffolding and the check groups that do not read a
|
|
24
24
|
* single backlog document: the merge-queue, post-merge-review and queued-prompt checks
|
|
25
25
|
* (checkStaleQueuedMerges, checkMergeQueueFiles, checkPostReviews, checkStalePrompts and the
|
|
@@ -30,7 +30,8 @@ import { okOutcome, warnOutcome } from "./outcome.js";
|
|
|
30
30
|
* checks live in doctor-backlog-plans.ts (the plan-document structure and fix-claim checks) and
|
|
31
31
|
* doctor-backlog-bugs.ts (open bugs, questions and holds). All of them warn instead of failing:
|
|
32
32
|
* damaged documentation is operator signal, not a broken environment. doctor.ts composes them
|
|
33
|
-
* beside the environment checks; the shared CheckOutcome shape is imported from
|
|
33
|
+
* beside the environment checks; the shared CheckOutcome shape is imported from
|
|
34
|
+
* doctor-outcome.ts. */
|
|
34
35
|
/** How many Fixed records the two newest-first Fixed-record checks (fix claims and validation
|
|
35
36
|
* gaps) verify: the newest, since the section is newest-first by template convention. Older
|
|
36
37
|
* records drift as the code evolves — a symbol legitimately fixed long ago gets renamed later,
|
|
@@ -55,7 +56,8 @@ export const STALE_BUG_DAYS = 3;
|
|
|
55
56
|
* shortened, since a doctor check renders one line. `render` makes each extra item's short
|
|
56
57
|
* form — the fix-claim and stranded-plan sites quote a heading trimmed to
|
|
57
58
|
* FIX_CLAIM_HEADING_MAX, the duplicate-headings site passes its items through as built;
|
|
58
|
-
* `noun` names what the extras are
|
|
59
|
+
* `noun` names what the extras are and must already agree with `rest.length` — callers pass
|
|
60
|
+
* `agree(rest.length, "record", "records")`, or empty where no noun is named.
|
|
59
61
|
* Empty string when there are no extras. */
|
|
60
62
|
export function andMore(rest, render, noun = "") {
|
|
61
63
|
return rest.length > 0
|
|
@@ -134,7 +136,7 @@ export function checkStaleQueuedMerges(root, now) {
|
|
|
134
136
|
}
|
|
135
137
|
return phrase ? { ...out, detail: `${out.detail} · ${phrase}` } : out;
|
|
136
138
|
};
|
|
137
|
-
const stale = staleQueuedMergeRows(root, now).map((e) => ({ ...e, age:
|
|
139
|
+
const stale = staleQueuedMergeRows(root, now).map((e) => ({ ...e, age: humanSince(now - e.waitedMs, now) }));
|
|
138
140
|
const [first, ...rest] = stale;
|
|
139
141
|
if (!first)
|
|
140
142
|
return withWait(okOutcome(`no merge-queue entry has waited more than ${humanSeconds(MERGE_QUEUE_STALE_MS / 1000)} to start`));
|
|
@@ -175,7 +177,7 @@ export function checkMergeQueueFiles(root) {
|
|
|
175
177
|
if (files.length === 0)
|
|
176
178
|
return okOutcome("the queue reader skips no merge-queue file");
|
|
177
179
|
const [first, ...rest] = files;
|
|
178
|
-
const more = andMore(rest, (f) => f, "file
|
|
180
|
+
const more = andMore(rest, (f) => f, agree(rest.length, "file", "files"));
|
|
179
181
|
return warnOutcome(`the merge queue holds ${plural(files.length, "file")} the reader skips — ${first}${more}; ` +
|
|
180
182
|
`a torn or foreign file is never cleared, so nothing ages it or surfaces its merge — remove it (a crashed enqueue's merge ref still recovers on the next run)`);
|
|
181
183
|
}
|
|
@@ -260,7 +262,7 @@ export function checkStalePrompts(root, now) {
|
|
|
260
262
|
if (!first)
|
|
261
263
|
return okOutcome("no queued prompt has waited more than 1 day for a role to dequeue it");
|
|
262
264
|
const more = andMore(rest, (p) => `${p.role} #${p.position}`);
|
|
263
|
-
return warnOutcome(`inbox prompt for ${roleLabel(first.role)} #${first.position} has waited ${
|
|
265
|
+
return warnOutcome(`inbox prompt for ${roleLabel(first.role)} #${first.position} has waited ${humanSince(first.queuedAtMs, now)} — ` +
|
|
264
266
|
"the role is not dequeuing it (paused, disabled, or failing); resume or enable it, or drop the prompt with " +
|
|
265
267
|
`\`tumwater prompt --cancel ${first.position} --role ${first.role}\`${more}`);
|
|
266
268
|
}
|
|
@@ -277,7 +279,7 @@ export function mergeWaitPhrase(events) {
|
|
|
277
279
|
return undefined;
|
|
278
280
|
const median = lowerMedian(waits);
|
|
279
281
|
const longest = Math.max(...waits);
|
|
280
|
-
return `merges in the last 24h waited ${
|
|
282
|
+
return `merges in the last 24h waited ${trailingDayMedianPhrase(median, longest, "longest", waits.length, "merge")}`;
|
|
281
283
|
}
|
|
282
284
|
/** Discover the tracked backlog/brief files with uncommitted edits in the primary checkout (the
|
|
283
285
|
* shared discovery behind checkUncommittedBacklog and commitUncommittedBacklog). A primary off
|
|
@@ -299,7 +301,7 @@ export async function dirtyBacklogFiles(root, config) {
|
|
|
299
301
|
// An unreadable brief is a doctor finding of its own; it must not abort the uncommitted-backlog
|
|
300
302
|
// check or `--commit-backlog`, so the tracked backlog set is watched without it.
|
|
301
303
|
}
|
|
302
|
-
const watched =
|
|
304
|
+
const watched = distinctOrdered([...BACKLOG_FILES, ...(brief ? [brief] : [])]);
|
|
303
305
|
const out = await gitTry(root, "status", "--porcelain", "--", ...watched);
|
|
304
306
|
if (out === null)
|
|
305
307
|
return { base, primary, watched, dirty: null };
|
|
@@ -357,9 +359,9 @@ export async function commitUncommittedBacklog(root, config) {
|
|
|
357
359
|
* for a BacklogCommitReport, so the three surfaces cannot drift: one `committed <file> on
|
|
358
360
|
* <base>` line per committed file, one `could not commit <file> — commit it by hand` line per
|
|
359
361
|
* failed file, then the skipped sentence when set. `[]` when the report committed nothing,
|
|
360
|
-
* failed nothing, and carried no skip sentence. `tumwater doctor --commit-backlog`
|
|
361
|
-
* the dashboard Health card's Commit backlog
|
|
362
|
-
* Ctrl+A all render from this one source. */
|
|
362
|
+
* failed nothing, and carried no skip sentence. `tumwater doctor --commit-backlog`
|
|
363
|
+
* (src/cli/cli-doctor-command.ts), the dashboard Health card's Commit backlog
|
|
364
|
+
* (handleCommitBacklog) and the TUI Health tab's Ctrl+A all render from this one source. */
|
|
363
365
|
export function backlogCommitLines(report) {
|
|
364
366
|
const lines = [
|
|
365
367
|
...report.committed.map((file) => `committed ${file} on ${report.base}`),
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/** Every row name `tumwater doctor` can print, in report order — the names `tumwater doctor
|
|
2
2
|
* <check>...` accepts. A leaf with no imports: cli.ts loads doctor.ts with a dynamic import()
|
|
3
3
|
* while cli-command-args.ts is imported eagerly, so the argument parser reads the names from
|
|
4
|
-
* here instead of pulling every check module into each CLI start. `pi extensions
|
|
5
|
-
*
|
|
6
|
-
* returns null leaves no row. */
|
|
4
|
+
* here instead of pulling every check module into each CLI start. `pi extensions` and `funes`
|
|
5
|
+
* are conditional: a named one whose check returns null leaves no row. */
|
|
7
6
|
export const DOCTOR_CHECK_NAMES = [
|
|
8
7
|
"node",
|
|
9
8
|
"node binary",
|
|
@@ -17,11 +16,8 @@ export const DOCTOR_CHECK_NAMES = [
|
|
|
17
16
|
"pause",
|
|
18
17
|
"quiet hours",
|
|
19
18
|
"budget",
|
|
20
|
-
"claude window",
|
|
21
|
-
"codex window",
|
|
22
19
|
"provider waits",
|
|
23
20
|
"maintenance allowance",
|
|
24
|
-
"provider concurrency",
|
|
25
21
|
"provider contention",
|
|
26
22
|
"check contention",
|
|
27
23
|
"repeated failures",
|
|
@@ -29,6 +25,7 @@ export const DOCTOR_CHECK_NAMES = [
|
|
|
29
25
|
"over-cap model",
|
|
30
26
|
"fallback chain",
|
|
31
27
|
"models for each tier",
|
|
28
|
+
"image input",
|
|
32
29
|
"claude auth",
|
|
33
30
|
"claude api key",
|
|
34
31
|
"claude version",
|
|
@@ -17,13 +17,13 @@ import { errorMessage } from "../text/text.js";
|
|
|
17
17
|
import { formatBytes } from "../text/format.js";
|
|
18
18
|
import { BRIEF_TEXT_MAX_CHARS, briefFile, promptBlockOf, readBrief } from "../brief/brief.js";
|
|
19
19
|
import { plural } from "../text/phrases.js";
|
|
20
|
-
import { backendsInUse, unregisteredBackendInUse } from "../gates/startup-
|
|
21
|
-
import { claudeSeamsInUse, minVersionOutcome, opencodeSeamsInUse, versionFromProbe, piSeamsInUse } from "./doctor-model-checks.js";
|
|
20
|
+
import { backendsInUse, unregisteredBackendInUse } from "../gates/startup-gates.js";
|
|
21
|
+
import { claudeSeamsInUse, loadConfigForCheck, minVersionOutcome, opencodeSeamsInUse, versionFromProbe, piSeamsInUse } from "./doctor-model-checks.js";
|
|
22
22
|
import { claudeConfigDir } from "../agent/claude/claude-backend.js";
|
|
23
|
-
import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, boundedProbe } from "./probe.js";
|
|
23
|
+
import { AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS, boundedProbe } from "./doctor-probe.js";
|
|
24
24
|
import { childEnv } from "../process/process.js";
|
|
25
25
|
import { opencodeBackend, OPENCODE_MIN_VERSION } from "../agent/opencode/opencode-backend.js";
|
|
26
|
-
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
26
|
+
import { failOutcome, noConfigOutcome, okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
27
27
|
import { spawnSync } from "node:child_process";
|
|
28
28
|
/** The floor doctor falls back to when package.json — the one source of the declared
|
|
29
29
|
* engines.node spec, read below — cannot be read (a broken install, the version command's
|
|
@@ -236,9 +236,9 @@ export function checkBackendBinary(root, pathEnv = envPath()) {
|
|
|
236
236
|
* (a 1.x binary included) fails naming `brew install opencode`; at or above it reads the
|
|
237
237
|
* version. Stands down when no seam routes to opencode, so a pi-only team spawns no opencode. */
|
|
238
238
|
export async function checkOpencodeVersion(root, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
241
|
-
return
|
|
239
|
+
const config = loadConfigForCheck(root);
|
|
240
|
+
if ("level" in config)
|
|
241
|
+
return config;
|
|
242
242
|
if (!opencodeSeamsInUse(config))
|
|
243
243
|
return okOutcome("no model jobs run on opencode");
|
|
244
244
|
const resolved = opencodeBackend.resolveBin(config);
|
|
@@ -255,7 +255,7 @@ export async function checkOpencodeVersion(root, pathEnv = envPath(), timeoutMs
|
|
|
255
255
|
* `homeDir` and `env` are test seams. */
|
|
256
256
|
export function checkClaudeTranscripts(config, homeDir = os.homedir(), env = process.env) {
|
|
257
257
|
if (!config)
|
|
258
|
-
return
|
|
258
|
+
return noConfigOutcome();
|
|
259
259
|
if (!claudeSeamsInUse(config))
|
|
260
260
|
return okOutcome("no model jobs run on claude");
|
|
261
261
|
const configDir = resolvedBackendConfig(config, "claude").configDir;
|