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
|
@@ -2,12 +2,14 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { worktreesDir } from "../paths.js";
|
|
4
4
|
import { pidAlive } from "../process/process.js";
|
|
5
|
+
import { rootToken } from "../process/run-marker.js";
|
|
5
6
|
import { GROUP_POLL_MS, KILL_GRACE_MS } from "../process/process-group.js";
|
|
6
7
|
import { systemProcessProbe } from "../process/process-table.js";
|
|
7
8
|
import { warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
8
9
|
import { errorMessage, truncate } from "../text/text.js";
|
|
9
10
|
import { plural } from "../text/phrases.js";
|
|
10
|
-
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
11
|
+
import { failOutcome, okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
12
|
+
import { distinctOrdered } from "../collections.js";
|
|
11
13
|
/** The orphan-process check of `tumwater doctor`, split out of doctor.ts: it is the one
|
|
12
14
|
* doctor check that inspects the operating system's process table (argv, cwd, parent-child
|
|
13
15
|
* tree) rather than the repo's own files, so it carries a different shape of logic — path
|
|
@@ -92,6 +94,36 @@ function harnessGone(marker) {
|
|
|
92
94
|
const m = /^(\d+)-/.exec(marker);
|
|
93
95
|
return m ? !pidAlive(Number(m[1])) : null;
|
|
94
96
|
}
|
|
97
|
+
/** The repo-identity token in a run marker's third dash segment — the team root tag
|
|
98
|
+
* `makeRunMarker` stamps when it knows one — or null for a rootless two-segment mark
|
|
99
|
+
* (`<pid>-<hex>`, or a torn or foreign value with no third segment): there is no evidence of
|
|
100
|
+
* which checkout's harness minted it. */
|
|
101
|
+
export function markRootToken(mark) {
|
|
102
|
+
const third = mark.split("-")[2];
|
|
103
|
+
return third === undefined ? null : third;
|
|
104
|
+
}
|
|
105
|
+
/** The repo-identity token `makeRunMarker` stamps into a team-rooted mark — the same `rootToken`
|
|
106
|
+
* run-marker.ts mints, re-exported here so the orphan scan can spell this root's side of the
|
|
107
|
+
* comparison without importing process/run-marker. */
|
|
108
|
+
export { rootToken };
|
|
109
|
+
/** ps's elapsed-time field (`[[D-]HH:]MM:SS`, the string the doctor line already prints) in
|
|
110
|
+
* milliseconds, the minimum-age gate the automatic reap uses so a just-leaked — still
|
|
111
|
+
* possibly owned — process is not killed. Any shape that does not parse, or that carries an
|
|
112
|
+
* out-of-range minute or second, reads 0: the conservative reading that keeps the reap from
|
|
113
|
+
* acting on uncertain evidence. */
|
|
114
|
+
export function parseEtimeMs(etime) {
|
|
115
|
+
const m = /^(?:\d+-)?(?:(\d+):)?(\d{1,2}):(\d{2})$/.exec(etime.trim());
|
|
116
|
+
if (!m)
|
|
117
|
+
return 0;
|
|
118
|
+
const hh = Number(m[1] ?? 0);
|
|
119
|
+
const mm = Number(m[2]);
|
|
120
|
+
const ss = Number(m[3]);
|
|
121
|
+
if (mm > 59 || ss > 59)
|
|
122
|
+
return 0;
|
|
123
|
+
const days = /^(\d+)-/.exec(etime.trim());
|
|
124
|
+
const d = days ? Number(days[1]) : 0;
|
|
125
|
+
return ((d * 24 + hh) * 3600 + mm * 60 + ss) * 1000;
|
|
126
|
+
}
|
|
95
127
|
/** A parent pid → child pids map of the table. */
|
|
96
128
|
function childrenOf(rows) {
|
|
97
129
|
const children = new Map();
|
|
@@ -111,8 +143,10 @@ function shownCommand(command, root) {
|
|
|
111
143
|
return truncate(cut.reduce((c, rt) => c.split(`${rt}/`).join(""), command), ORPHAN_COMMAND_MAX);
|
|
112
144
|
}
|
|
113
145
|
/** The detection half of checkOrphans, shared with killOrphans so the check and the kill cannot
|
|
114
|
-
* disagree about the set.
|
|
115
|
-
|
|
146
|
+
* disagree about the set. Exported so the always-on orphan alert feed (src/ui/alert-feeds.ts)
|
|
147
|
+
* runs the exact same scan the doctor row reads — the alert and the row cannot drift on the
|
|
148
|
+
* orphan set. */
|
|
149
|
+
export async function findOrphans(root, probe) {
|
|
116
150
|
const roots = rootSpellings(root);
|
|
117
151
|
let rows;
|
|
118
152
|
try {
|
|
@@ -148,12 +182,24 @@ async function findOrphans(root, probe) {
|
|
|
148
182
|
// Best-effort per the probe contract: argv and cwd still carry the check alone.
|
|
149
183
|
}
|
|
150
184
|
}
|
|
185
|
+
const ours = rootToken(root);
|
|
151
186
|
const byMark = new Set();
|
|
152
187
|
for (const r of parentless) {
|
|
153
188
|
if (byArgv.has(r.pid))
|
|
154
189
|
continue;
|
|
155
|
-
const
|
|
156
|
-
|
|
190
|
+
const rMarks = marks.get(r.pid) ?? [];
|
|
191
|
+
const verdicts = rMarks.map(harnessGone);
|
|
192
|
+
// A dead-marked process is this checkout's only when no mark names a different root: a
|
|
193
|
+
// token that is present and not `ours` proves another checkout's harness stamped that
|
|
194
|
+
// process, and signalling it would break the "never signal a process not attributable"
|
|
195
|
+
// rule (BUGS.md, "Reap the doctor's orphans, part 1/3"). A rootless mark keeps today's
|
|
196
|
+
// report — whatever started it, nothing else will reap it.
|
|
197
|
+
if (verdicts.length > 0 &&
|
|
198
|
+
verdicts.every((v) => v === true) &&
|
|
199
|
+
rMarks.every((m) => {
|
|
200
|
+
const t = markRootToken(m);
|
|
201
|
+
return t === null || t === ours;
|
|
202
|
+
}))
|
|
157
203
|
byMark.add(r.pid);
|
|
158
204
|
}
|
|
159
205
|
const orphans = parentless.filter((r) => {
|
|
@@ -199,16 +245,17 @@ async function findOrphans(root, probe) {
|
|
|
199
245
|
* never a crashed doctor; unreadable cwds degrade to the argv match and say so. Any orphan is
|
|
200
246
|
* a fail — the exit code is the point: a scripted doctor must notice. `probe` is injectable so
|
|
201
247
|
* tests run against a fake table. */
|
|
202
|
-
|
|
203
|
-
|
|
248
|
+
/** The orphan-problem sentence for the standing alert feed, shared with checkOrphans so the
|
|
249
|
+
* alert and the doctor row cannot drift on the wording: the count, each listed orphan's pid,
|
|
250
|
+
* age, cpu and command (with its descendant count and the run mark that caught it), and the
|
|
251
|
+
* nothing-reaps-these remediation. null when the scan errored or found no orphans. Public so
|
|
252
|
+
* the feed (src/ui/alert-feeds.ts) can stage a scan and get exactly the row's message. */
|
|
253
|
+
export function orphanFailDetail(root, scan) {
|
|
204
254
|
if ("error" in scan)
|
|
205
|
-
return
|
|
255
|
+
return null;
|
|
206
256
|
const { rows, orphans, marks, cwdProblem } = scan;
|
|
207
|
-
if (orphans.length === 0)
|
|
208
|
-
|
|
209
|
-
return warnOutcome(`none named in argv, but process cwds are unreadable (${cwdProblem}) — an orphan started inside a worktree, like a leaked test runner, would be missed`);
|
|
210
|
-
return okOutcome("none — no process reparented to PID 1 runs from .tumwater/worktrees/ or carries only dead runs' marks");
|
|
211
|
-
}
|
|
257
|
+
if (orphans.length === 0)
|
|
258
|
+
return null;
|
|
212
259
|
const children = childrenOf(rows);
|
|
213
260
|
const listed = orphans.slice(0, ORPHANS_LISTED_MAX).map((r) => {
|
|
214
261
|
const n = descendantPids(children, r.pid).length;
|
|
@@ -221,9 +268,22 @@ export async function checkOrphans(root, probe = systemProcessProbe) {
|
|
|
221
268
|
});
|
|
222
269
|
const more = orphans.length - listed.length;
|
|
223
270
|
const count = `${plural(orphans.length, "orphaned process", "orphaned processes")} (PPID 1)`;
|
|
224
|
-
return
|
|
271
|
+
return `${count}: ${listed.join("; ")}${more > 0 ? `; and ${more} more` : ""}` +
|
|
225
272
|
" — nothing reaps these; `tumwater doctor --kill-orphans` (or the dashboard Health card's Kill orphans, or the TUI Health tab's Ctrl+K pressed twice) kills each with its descendants" +
|
|
226
|
-
(cwdProblem !== null ? ` (process cwds unreadable — ${cwdProblem}; argv matched only)` : "")
|
|
273
|
+
(cwdProblem !== null ? ` (process cwds unreadable — ${cwdProblem}; argv matched only)` : "");
|
|
274
|
+
}
|
|
275
|
+
/** The doctor's `orphans` row. */
|
|
276
|
+
export async function checkOrphans(root, probe = systemProcessProbe) {
|
|
277
|
+
const scan = await findOrphans(root, probe);
|
|
278
|
+
if ("error" in scan)
|
|
279
|
+
return warnOutcome(`could not scan the process table — ${scan.error}`);
|
|
280
|
+
const { orphans, cwdProblem } = scan;
|
|
281
|
+
if (orphans.length === 0) {
|
|
282
|
+
if (cwdProblem !== null)
|
|
283
|
+
return warnOutcome(`none named in argv, but process cwds are unreadable (${cwdProblem}) — an orphan started inside a worktree, like a leaked test runner, would be missed`);
|
|
284
|
+
return okOutcome("none — no process reparented to PID 1 runs from .tumwater/worktrees/ or carries only dead runs' marks");
|
|
285
|
+
}
|
|
286
|
+
return failOutcome(orphanFailDetail(root, scan));
|
|
227
287
|
}
|
|
228
288
|
const systemSignals = {
|
|
229
289
|
kill: (pid, signal) => void process.kill(pid, signal),
|
|
@@ -271,7 +331,7 @@ async function killTrees(root, rows, chosen, signals) {
|
|
|
271
331
|
}
|
|
272
332
|
};
|
|
273
333
|
const trees = chosen.map((r) => ({ r, pids: [r.pid, ...descendantPids(children, r.pid)] }));
|
|
274
|
-
const all =
|
|
334
|
+
const all = distinctOrdered(trees.flatMap((t) => t.pids));
|
|
275
335
|
for (const pid of all)
|
|
276
336
|
send(pid, "SIGTERM");
|
|
277
337
|
const stillAlive = () => all.filter((pid) => !refused.has(pid) && signals.alive(pid));
|
|
@@ -291,6 +351,20 @@ async function killTrees(root, rows, chosen, signals) {
|
|
|
291
351
|
}
|
|
292
352
|
return { killed, failed };
|
|
293
353
|
}
|
|
354
|
+
/** The `pid N (+N descendant) command` line per killed tree and the `could not kill` line per
|
|
355
|
+
* refused tree, in the read the reapers' best-effort warning logs — the one shape both
|
|
356
|
+
* reapPreviousTeamOrphans and reapAttributableOrphans report, so the two cannot drift on the
|
|
357
|
+
* itemized wording. Callers join `[...items, ...refused]` with "; " behind their own verb
|
|
358
|
+
* phrase. */
|
|
359
|
+
function orphanKillReportLines(killed, failed) {
|
|
360
|
+
return {
|
|
361
|
+
items: killed.map((k) => {
|
|
362
|
+
const tree = k.descendants > 0 ? ` (+${plural(k.descendants, "descendant")})` : "";
|
|
363
|
+
return `pid ${k.pid}${tree} ${k.command}`;
|
|
364
|
+
}),
|
|
365
|
+
refused: failed.map((f) => `could not kill pid ${f.pid}: ${f.reason}`),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
294
368
|
/** Team start's reap of a crashed predecessor's leaks: kill the parentless processes whose run
|
|
295
369
|
* marks all name dead harnesses and at least one names `previousPid`, the orchestrator the
|
|
296
370
|
* info file recorded. Safe without an operator's judgment: a mark proves a tumwater start
|
|
@@ -314,11 +388,7 @@ export async function reapPreviousTeamOrphans(root, previousPid, probe = systemP
|
|
|
314
388
|
if (chosen.length === 0)
|
|
315
389
|
return 0;
|
|
316
390
|
const { killed, failed } = await killTrees(root, scan.rows, chosen, signals);
|
|
317
|
-
const items = killed
|
|
318
|
-
const tree = k.descendants > 0 ? ` (+${plural(k.descendants, "descendant")})` : "";
|
|
319
|
-
return `pid ${k.pid}${tree} ${k.command}`;
|
|
320
|
-
});
|
|
321
|
-
const refused = failed.map((f) => `could not kill pid ${f.pid}: ${f.reason}`);
|
|
391
|
+
const { items, refused } = orphanKillReportLines(killed, failed);
|
|
322
392
|
warnEventBestEffort(root, TUMWATER_SOURCE, `killed ${plural(killed.length, "orphaned process", "orphaned processes")} left by tumwater ` +
|
|
323
393
|
`${previousPid}'s runs at team start: ${[...items, ...refused].join("; ")}`);
|
|
324
394
|
return killed.length;
|
|
@@ -327,3 +397,34 @@ export async function reapPreviousTeamOrphans(root, previousPid, probe = systemP
|
|
|
327
397
|
return 0;
|
|
328
398
|
}
|
|
329
399
|
}
|
|
400
|
+
/** How long an orphan must have been running before the automatic reap may kill it: a young
|
|
401
|
+
* orphan may still be a live run's process that findOrphans read as parentless mid-hand-off,
|
|
402
|
+
* and ten minutes is far longer than any such attribution would hold. The operator's
|
|
403
|
+
* `--kill-orphans`/dashboard/TUI click bypasses this — doctors judge with their eyes. */
|
|
404
|
+
const ORPHAN_REAP_MIN_AGE_MS = 10 * 60_000;
|
|
405
|
+
/** The orchestrator's once-per-interval sweep of the orphans `checkOrphans` already reports:
|
|
406
|
+
* kill the same set the operator's `--kill-orphans` kills, once each is old enough that no
|
|
407
|
+
* live run can still own it. Reuses findOrphans' attribution and killTrees' signal-and-wait,
|
|
408
|
+
* so the automatic reap cannot drift from the doctor row or the operator kill and never
|
|
409
|
+
* signals a process findOrphans does not list. An unreadable table, or no old-enough orphan,
|
|
410
|
+
* kills nothing and logs nothing; it never throws. Returns the number of orphan trees killed. */
|
|
411
|
+
export async function reapAttributableOrphans(root, probe = systemProcessProbe, signals = systemSignals) {
|
|
412
|
+
try {
|
|
413
|
+
const scan = await findOrphans(root, probe);
|
|
414
|
+
if ("error" in scan)
|
|
415
|
+
return 0;
|
|
416
|
+
const chosen = scan.orphans.filter((r) => parseEtimeMs(r.etime) >= ORPHAN_REAP_MIN_AGE_MS);
|
|
417
|
+
if (chosen.length === 0)
|
|
418
|
+
return 0;
|
|
419
|
+
const { killed, failed } = await killTrees(root, scan.rows, chosen, signals);
|
|
420
|
+
if (killed.length === 0)
|
|
421
|
+
return 0;
|
|
422
|
+
const { items, refused } = orphanKillReportLines(killed, failed);
|
|
423
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `reaped ${plural(killed.length, "orphaned process", "orphaned processes")}: ` +
|
|
424
|
+
[...items, ...refused].join("; "));
|
|
425
|
+
return killed.length;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
return 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -9,6 +9,13 @@
|
|
|
9
9
|
export function okOutcome(detail) {
|
|
10
10
|
return { level: "ok", detail };
|
|
11
11
|
}
|
|
12
|
+
/** The doctor's stand-down on a missing or unreadable tumwater.json: an ok outcome saying
|
|
13
|
+
* there is nothing to verify, because the config check reports the broken file itself. Shared
|
|
14
|
+
* by the four checks that skip their walk when config is unavailable, so the message reads
|
|
15
|
+
* identically across them. */
|
|
16
|
+
export function noConfigOutcome() {
|
|
17
|
+
return okOutcome("no readable tumwater.json — nothing to verify");
|
|
18
|
+
}
|
|
12
19
|
export function warnOutcome(detail) {
|
|
13
20
|
return { level: "warn", detail };
|
|
14
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { childEnv } from "../process/process.js";
|
|
2
|
-
import { boundedProbe, AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS } from "./probe.js";
|
|
2
|
+
import { boundedProbe, AUTH_CHECK_MAX_OUTPUT_BYTES, AUTH_CHECK_TIMEOUT_MS } from "./doctor-probe.js";
|
|
3
3
|
import { parseJsonObject } from "../files/json-object.js";
|
|
4
4
|
import { envPath } from "../files/files.js";
|
|
5
5
|
import { resolveBackendBin } from "../pi/pi-bin.js";
|
|
@@ -12,7 +12,7 @@ import { parsePiModelImages, parsePiModelTable, writePiListedModels } from "../p
|
|
|
12
12
|
* a crash, so readiness is read from the body alone and a reply that did not run or does not
|
|
13
13
|
* parse is the only "unknown" (never not-ready, which would blame credentials). Never throws;
|
|
14
14
|
* every failure mode ends on "unknown". The bounded subprocess is the shared boundedProbe
|
|
15
|
-
* (probe.ts), also used by the claude probes. */
|
|
15
|
+
* (doctor-probe.ts), also used by the claude probes. */
|
|
16
16
|
export async function piProviderAuth(config, provider, pathEnv = envPath(), timeoutMs = AUTH_CHECK_TIMEOUT_MS) {
|
|
17
17
|
const resolved = resolveBackendBin(config);
|
|
18
18
|
const result = await boundedProbe(resolved.bin, ["auth", "check", "--provider", provider, "--json"], childEnv({ PATH: pathEnv }), timeoutMs, AUTH_CHECK_MAX_OUTPUT_BYTES);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { readRegularTextOrNull } from "../files/files.js";
|
|
3
3
|
import { PRINCIPLES_MAX_CHARS } from "../prompt/principles.js";
|
|
4
|
-
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
4
|
+
import { okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
5
5
|
/** Doctor check for PRINCIPLES.md's injection cap. readPrinciples caps the trimmed text at
|
|
6
6
|
* PRINCIPLES_MAX_CHARS for every prompt's <principles> block, and principleBullets keeps only
|
|
7
7
|
* its leading bullets; a file over the cap loses its tail (and a long header eats bullet
|
|
@@ -9,7 +9,7 @@ import { okOutcome, warnOutcome } from "./outcome.js";
|
|
|
9
9
|
* truncate, naming the trimmed count, the cap, the marker, and the fix. Read-only, like every
|
|
10
10
|
* doctor check — it never edits PRINCIPLES.md. */
|
|
11
11
|
export function checkPrinciples(root) {
|
|
12
|
-
const raw =
|
|
12
|
+
const raw = readRegularTextOrNull(path.join(root, "PRINCIPLES.md"));
|
|
13
13
|
if (raw === null)
|
|
14
14
|
return okOutcome("no PRINCIPLES.md");
|
|
15
15
|
const length = raw.trim().length;
|
|
@@ -17,7 +17,7 @@ import { buildStaleness, isSelfHosted, readBuildInfo, STALE_INPUTS_LABEL } from
|
|
|
17
17
|
import { dirEntryNames, statOrNull } from "../files/files.js";
|
|
18
18
|
import { refSha } from "../git/git.js";
|
|
19
19
|
import { STATE_DIR, eventsArchivePath, eventsLogPath } from "../paths.js";
|
|
20
|
-
import { BYTES_PER_GB, diskVolumePath, sampleFreeBytes } from "../gates/disk-
|
|
20
|
+
import { BYTES_PER_GB, diskVolumePath, sampleFreeBytes } from "../gates/disk-gates.js";
|
|
21
21
|
import { DAY_MS, dayKey } from "../text/datetime.js";
|
|
22
22
|
import { errorMessage } from "../text/text.js";
|
|
23
23
|
import { lowerMedian } from "../collections.js";
|
|
@@ -25,7 +25,7 @@ import { formatGB, shortSha } from "../text/format.js";
|
|
|
25
25
|
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
|
-
import { agree, plural, shortSpanPhrase } from "../text/phrases.js";
|
|
28
|
+
import { agree, plural, shortSpanPhrase, trailingDayMedianPhrase } from "../text/phrases.js";
|
|
29
29
|
import { andMore } from "./doctor-backlog.js";
|
|
30
30
|
import { KNOWN_FLAKE_WINDOW_MS, loadQuarantine, repeatedFlakeCounts } from "../review/known-flakes.js";
|
|
31
31
|
import { POST_MERGE_REJECT_WINDOW_MS, postMergeChurn } from "../review/post-merge-review.js";
|
|
@@ -33,7 +33,7 @@ import { roleLabel } from "../roles/role-labels.js";
|
|
|
33
33
|
import { quarantineWarning } from "../baseline/main-guardrails.js";
|
|
34
34
|
import { readCiParity, currentCiParityState, parityIsCurrent, ciParityStalled, CI_PARITY_STALE_MS } from "../run/ci-parity.js";
|
|
35
35
|
import { readQaCoverage, qaCoverageStalled, QA_COVERAGE_STALE_MS } from "../run/qa-coverage.js";
|
|
36
|
-
import { failOutcome, okOutcome, warnOutcome } from "./outcome.js";
|
|
36
|
+
import { failOutcome, okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
37
37
|
/** .tumwater writable — absent is fine (created on first run); present, prove it by writing
|
|
38
38
|
* and deleting a temp file inside. The probe leaves the dir's listing unchanged after the run.
|
|
39
39
|
* The write is the property under test: only a write that fails is "not writable", so the
|
|
@@ -204,7 +204,7 @@ export function checkDurationPhrase(events) {
|
|
|
204
204
|
return undefined;
|
|
205
205
|
const median = lowerMedian(durations);
|
|
206
206
|
const slowest = Math.max(...durations);
|
|
207
|
-
return `passing merge checks in the last 24h took ${
|
|
207
|
+
return `passing merge checks in the last 24h took ${trailingDayMedianPhrase(median, slowest, "slowest", durations.length, "run")}`;
|
|
208
208
|
}
|
|
209
209
|
/** Declared project check — names what the review gate's deterministic pre-check, the
|
|
210
210
|
* red-main baseline, and redeploy's green check will run, without running it: a configured
|
|
@@ -251,7 +251,7 @@ export function checkBuildCheck(root, config) {
|
|
|
251
251
|
* once, so a routine one-off flake stays quiet. ok/warn only — never a fail — so a flaky suite
|
|
252
252
|
* cannot fail a scripted doctor pre-flight (the checkStalledEntries precedent; the exit-code
|
|
253
253
|
* rule is
|
|
254
|
-
* outcome.ts's). */
|
|
254
|
+
* doctor-outcome.ts's). */
|
|
255
255
|
export function checkFlakyChecks(root, now = Date.now()) {
|
|
256
256
|
const { events } = readEventsSince(root, KNOWN_FLAKE_WINDOW_MS);
|
|
257
257
|
const flapped = repeatedFlakeCounts(events);
|
|
@@ -278,7 +278,7 @@ export function checkPostMergeChurn(root) {
|
|
|
278
278
|
return okOutcome("no post-merge rejection burst in the last day");
|
|
279
279
|
return warnOutcome(`${plural(churn.count, "post-merge review rejection")} in the last day (${churn.roles
|
|
280
280
|
.map((r) => `${roleLabel(r.role)} ×${r.count}`)
|
|
281
|
-
.join(", ")}) — each commit had already reached main and stays there with the reviewer's reasons;
|
|
281
|
+
.join(", ")}) — each commit had already reached main and stays there with the reviewer's reasons; raise the authoring bar`);
|
|
282
282
|
}
|
|
283
283
|
/** The one home of the doctor's `flaky checks` sentence: names each check that flapped more than
|
|
284
284
|
* once, most frequent first, with how many times it flapped — the one sentence the doctor's
|
|
@@ -321,7 +321,10 @@ running = null) {
|
|
|
321
321
|
? `; auto-restart is BLOCKED (${running.restartBlocked}) and will not retry until main moves or the block clears`
|
|
322
322
|
: running?.restartPending
|
|
323
323
|
? "; auto-restart is under way"
|
|
324
|
-
:
|
|
324
|
+
: running?.waitingForCheck
|
|
325
|
+
? `; auto-restart waits for a commit that passes main's check — none newer than ` +
|
|
326
|
+
`${shortSha(running.checkedHead ?? info.sha)} has yet (the main's check row says why)`
|
|
327
|
+
: "; run `npm run build` and restart `tumwater start` (auto-restart does this for a running team)";
|
|
325
328
|
return warnOutcome(`dist/ from ${sha} is stale — main has ${plural(stale.aheadCommits, "later commit")} touching ${STALE_INPUTS_LABEL}${next}`);
|
|
326
329
|
}
|
|
327
330
|
return okOutcome(`dist/ from ${sha}, matches main`);
|
|
@@ -396,7 +399,7 @@ export function checkQaCoverage(root, now = Date.now()) {
|
|
|
396
399
|
/** How far back the `main diff rules` row looks — one day, the same 24 h trailing-day convention
|
|
397
400
|
* every event-reading doctor row uses, so a diff-rule failure the main watch logged stays a
|
|
398
401
|
* standing warn until a day passes. Owned here (the check that reads it). */
|
|
399
|
-
const MAIN_RULE_FAILURE_WINDOW_MS =
|
|
402
|
+
const MAIN_RULE_FAILURE_WINDOW_MS = DAY_MS;
|
|
400
403
|
/** The exact substring src/baseline/main-watch.ts's `watchRules` logs in its warning, so this
|
|
401
404
|
* check matches a real rule failure and cannot drift from the wording the watch writes. */
|
|
402
405
|
const MAIN_RULE_FAILURE_MARKER = "fails the diff rules";
|
|
@@ -407,9 +410,19 @@ const MAIN_RULE_FAILURE_MARKER = "fails the diff rules";
|
|
|
407
410
|
* blind spot sits on main with only this event log to say so. Reads only the `warning` event
|
|
408
411
|
* the watch already writes, so the row cannot drift from the watch's wording; the most recent
|
|
409
412
|
* hit names the tip and the rule. ok/warn only — it never fails a scripted pre-flight. */
|
|
413
|
+
/** The `warning` events whose message contains `marker` — the read both marker-driven
|
|
414
|
+
* project rows (checkMainDiffRules and checkDroppedPostMergeReviews) ask for, so their
|
|
415
|
+
* filter predicate cannot drift: a warning whose message is absent (an older event) is
|
|
416
|
+
* never a hit, and the marker match is a plain substring like the watch's wording.
|
|
417
|
+
* `e.type === "warning"` gates on the literal type, mirroring the warn shape warnEvent
|
|
418
|
+
* writes. Since every hit is a `warning`, `String` on its message downstream reads safely
|
|
419
|
+
* for the events this returns. */
|
|
420
|
+
function warningEventsWithMarker(events, marker) {
|
|
421
|
+
return events.filter((e) => e.type === "warning" && typeof e.message === "string" && e.message.includes(marker));
|
|
422
|
+
}
|
|
410
423
|
export function checkMainDiffRules(root) {
|
|
411
424
|
const { events } = readEventsSince(root, MAIN_RULE_FAILURE_WINDOW_MS);
|
|
412
|
-
const hits = events
|
|
425
|
+
const hits = warningEventsWithMarker(events, MAIN_RULE_FAILURE_MARKER);
|
|
413
426
|
if (hits.length === 0)
|
|
414
427
|
return okOutcome("no diff-rule failure on main in the last day");
|
|
415
428
|
const latest = hits[hits.length - 1];
|
|
@@ -426,7 +439,7 @@ export function checkMainDiffRules(root) {
|
|
|
426
439
|
const DROPPED_REVIEW_MARKER = "dropping the post-merge review of";
|
|
427
440
|
/** How far back the `dropped post-merge reviews` row looks — one day, the same 24 h trailing-day
|
|
428
441
|
* convention every event-reading doctor row uses. Owned here (the check that reads it). */
|
|
429
|
-
const DROPPED_REVIEW_WINDOW_MS =
|
|
442
|
+
const DROPPED_REVIEW_WINDOW_MS = DAY_MS;
|
|
430
443
|
/** The doctor's `dropped post-merge reviews` row: a `warning` the post-merge-review drain
|
|
431
444
|
* (src/merge/merge-post-review-drain.ts) wrote when it dropped a review at
|
|
432
445
|
* POST_REVIEW_FAILURE_LIMIT — after failed runs its commit stays on main unreviewed, and
|
|
@@ -439,7 +452,7 @@ const DROPPED_REVIEW_WINDOW_MS = 24 * 60 * 60_000;
|
|
|
439
452
|
* unreviewed commit is operator signal, not a broken environment. */
|
|
440
453
|
export function checkDroppedPostMergeReviews(root) {
|
|
441
454
|
const { events } = readEventsSince(root, DROPPED_REVIEW_WINDOW_MS);
|
|
442
|
-
const hits = events
|
|
455
|
+
const hits = warningEventsWithMarker(events, DROPPED_REVIEW_MARKER);
|
|
443
456
|
if (hits.length === 0)
|
|
444
457
|
return okOutcome("no post-merge review was dropped in the last day");
|
|
445
458
|
const latest = hits[hits.length - 1];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readRoleNotesOverCap } from "../status/status-role-notes.js";
|
|
2
2
|
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
3
3
|
import { agree, plural } from "../text/phrases.js";
|
|
4
|
-
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
4
|
+
import { okOutcome, warnOutcome } from "./doctor-outcome.js";
|
|
5
5
|
/** How many oversized notes the `role notes` row names before appending "and N more", the
|
|
6
6
|
* cap the other multi-role observer rows (e.g. workspaces) use so a long tail does not drown
|
|
7
7
|
* the report. */
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { loadConfigSafe } from "../config/config.js";
|
|
2
2
|
import { envPath } from "../files/files.js";
|
|
3
|
-
import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
3
|
+
import { orchestratorAlive, readOrchestratorInfo } from "../team/team-orchestrator-info.js";
|
|
4
4
|
import { systemProcessProbe } from "../process/process-table.js";
|
|
5
5
|
import { checkOrphans } from "./doctor-orphans.js";
|
|
6
|
-
import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours
|
|
6
|
+
import { checkBudget, checkErrorStreaks, checkMainRed, checkMaintenanceQuota, checkPause, checkProviderHolds, checkQuietHours } from "./doctor-holds.js";
|
|
7
7
|
import { checkRootInstall } from "./doctor-install.js";
|
|
8
8
|
import { checkNotify } from "./doctor-notify.js";
|
|
9
9
|
import { checkRoleNotes } from "./doctor-role-notes.js";
|
|
10
10
|
import { checkLaunchServicesPorts } from "./doctor-launch-services.js";
|
|
11
11
|
import { plural } from "../text/phrases.js";
|
|
12
12
|
import { shortSha } from "../text/format.js";
|
|
13
|
-
import { failOutcome } from "./outcome.js";
|
|
13
|
+
import { failOutcome } from "./doctor-outcome.js";
|
|
14
14
|
import { errorMessage } from "../text/text.js";
|
|
15
15
|
import { checkBackendBinary, checkBrief, checkBootstrap, checkClaudeTranscripts, checkExecPath, checkFunes, checkGitBinary, checkConfigKeyNames, checkInit, checkOpencodeVersion, checkNodeVersion, checkPiExtensions, checkRepo, } from "./doctor-checks.js";
|
|
16
16
|
import { checkPrinciples } from "./doctor-principles.js";
|
|
17
17
|
import { checkBuild, checkBuildCheck, checkCiParity, checkDiskSpace, checkDroppedPostMergeReviews, checkEventLog, checkFlakyChecks, checkMainDiffRules, checkPostMergeChurn, checkQaCoverage, checkStateDir, } from "./doctor-project-checks.js";
|
|
18
|
-
import { sampleFreeBytes } from "../gates/disk-
|
|
19
|
-
import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock,
|
|
20
|
-
import { checkFeedbackFiling, checkTierModels } from "./doctor-model-checks.js";
|
|
18
|
+
import { sampleFreeBytes } from "../gates/disk-gates.js";
|
|
19
|
+
import { checkCheckContention, checkLeftoverRoleBranches, checkMergeLock, checkProviderContention, checkTumwaterPins, checkWorkInstances, checkWorktreePool, } from "./doctor-contention-checks.js";
|
|
20
|
+
import { checkFeedbackFiling, checkImageInput, checkTierModels } from "./doctor-model-checks.js";
|
|
21
21
|
import { checkFallbackChain, checkFallbackModel } from "./doctor-fallback-checks.js";
|
|
22
22
|
import { checkAnthroApiKey, checkClaudeAuth, checkClaudeVersion } from "./doctor-claude-checks.js";
|
|
23
23
|
import { checkOpencode } from "./doctor-opencode-checks.js";
|
|
@@ -95,11 +95,8 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
95
95
|
{ name: "pause", run: () => checkPause(root) },
|
|
96
96
|
{ name: "quiet hours", run: () => checkQuietHours(config) },
|
|
97
97
|
{ name: "budget", run: () => checkBudget(root, config, (orchestratorAlive(root, info) && info?.budget) || null) },
|
|
98
|
-
{ name: "claude window", run: () => checkSubscriptionWindow("claude", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
|
|
99
|
-
{ name: "codex window", run: () => checkSubscriptionWindow("codex", root, undefined, (orchestratorAlive(root, info) && info?.chain) || undefined, config) },
|
|
100
98
|
{ name: "provider waits", run: () => checkProviderHolds(root) },
|
|
101
99
|
{ name: "maintenance allowance", run: () => checkMaintenanceQuota(root, config, orchestratorAlive(root, info)) },
|
|
102
|
-
{ name: "provider concurrency", run: () => checkProviderConcurrency(config, root) },
|
|
103
100
|
{ name: "provider contention", run: () => checkProviderContention(root, config) },
|
|
104
101
|
{ name: "check contention", run: () => checkCheckContention(root, config) },
|
|
105
102
|
{ name: "repeated failures", run: () => checkErrorStreaks(root, config) },
|
|
@@ -107,6 +104,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
107
104
|
{ name: "over-cap model", run: () => checkFallbackModel(root, undefined, (orchestratorAlive(root, info) && info?.fallbackDemoted) || null) },
|
|
108
105
|
{ name: "fallback chain", run: () => checkFallbackChain(root) },
|
|
109
106
|
{ name: "models for each tier", run: () => checkTierModels(root) },
|
|
107
|
+
{ name: "image input", run: () => checkImageInput(root) },
|
|
110
108
|
{ name: "claude auth", run: () => checkClaudeAuth(root, pathEnv) },
|
|
111
109
|
{ name: "claude api key", run: () => checkAnthroApiKey(root, undefined, pathEnv) },
|
|
112
110
|
{ name: "claude version", run: () => checkClaudeVersion(root, pathEnv) },
|
|
@@ -127,8 +125,7 @@ export async function runDoctor(root, pathEnv = envPath(), probe = systemProcess
|
|
|
127
125
|
// The receive-side guards name what a running team skips, so an operator tool meant for the
|
|
128
126
|
// team (a pi package, an extension file, an mcp.json server, or funes) does not vanish
|
|
129
127
|
// silently. Each check returns null to omit its row: the pi row for a team with no pi seam,
|
|
130
|
-
// the funes row when funes is not on PATH
|
|
131
|
-
// has no subscription account.
|
|
128
|
+
// the funes row when funes is not on PATH.
|
|
132
129
|
{ name: "pi extensions", run: () => checkPiExtensions(config, undefined, pathEnv) },
|
|
133
130
|
{ name: "funes", run: () => checkFunes(config, pathEnv) },
|
|
134
131
|
{ name: "state dir", run: () => checkStateDir(root) },
|