tumwater 0.1.2 → 0.2.0
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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -1,50 +1,66 @@
|
|
|
1
|
-
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
3
|
+
import { pausedRoles, pausedRolesUntil, standingTeamPause } from "../team/team-state.js";
|
|
4
|
+
import { MODEL_TIERS } from "../config/config-schema.js";
|
|
5
|
+
import { withModelOverrides } from "../config/config-views.js";
|
|
2
6
|
import { commitTier, DIRECTOR_ROLE } from "../roles/roles.js";
|
|
3
|
-
import { budgetReached, budgetWarning,
|
|
7
|
+
import { budgetReached, budgetWarning, teamDailyCost } from "../budget/budget.js";
|
|
4
8
|
import { roleCapVerdicts } from "../gates/role-cap-gates.js";
|
|
5
|
-
import {
|
|
9
|
+
import { readMergeQueue } from "../merge/merge-queue.js";
|
|
6
10
|
import { MAINTENANCE_DAILY_FLOOR } from "../gates/maintenance-quota.js";
|
|
7
|
-
import { maintenanceQuotaForPoll } from "../status/status-polls.js";
|
|
11
|
+
import { heldByMainRed, mainCheckForPoll, maintenanceQuotaForPoll } from "../status/status-polls.js";
|
|
8
12
|
import { loadLoopState } from "../loop/loop-state.js";
|
|
9
|
-
import { loopIds } from "../roles/loop-ids.js";
|
|
13
|
+
import { baseRoleOf, loopIds, loopPaused } from "../roles/loop-ids.js";
|
|
14
|
+
import { ERROR_STREAK_BREAKER, ERROR_STREAK_WARN } from "../tick/tick-apply.js";
|
|
15
|
+
import { squash } from "../text/text.js";
|
|
10
16
|
import { usd } from "../text/format.js";
|
|
11
17
|
import { parseQuietHours, quietHoursStatus, quietWindowEnd, roleQuietHold } from "../scheduling/quiet-hours.js";
|
|
12
|
-
import {
|
|
13
|
-
import { budgetPhrase, pauseReasonSuffix } from "../text/phrases.js";
|
|
18
|
+
import { humanUntil } from "../text/datetime.js";
|
|
19
|
+
import { budgetPhrase, pauseReasonSuffix, plural } from "../text/phrases.js";
|
|
20
|
+
import { claudeUtilizationPct, claudeWindowEntries, claudeWindowLabel, readClaudeAccount } from "../agent/claude/claude-account.js";
|
|
21
|
+
import { claudeWindowObservation } from "../team/team-polls.js";
|
|
22
|
+
import { readEventsSince } from "../events/event-window.js";
|
|
23
|
+
import { errorMessage } from "../text/text.js";
|
|
14
24
|
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
15
25
|
/** The standing-hold checks: `pause` (checkPause), `quiet hours` (checkQuietHours), and `budget`
|
|
16
|
-
* (checkBudget),
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
* (checkBudget), `claude window` (checkClaudeWindow), `maintenance quota`
|
|
27
|
+
* (checkMaintenanceQuota), and `error streaks` (checkErrorStreaks), all read-only reports of
|
|
28
|
+
* holds on ticking. */
|
|
29
|
+
export function loadLoopStates(root, config) {
|
|
30
|
+
return loopIds(config)
|
|
31
|
+
.map((id) => loadLoopState(root, id))
|
|
32
|
+
.sort((a, b) => a.role.localeCompare(b.role));
|
|
33
|
+
}
|
|
34
|
+
/** The `pause` check: names a standing operator pause so doctor does not print "ready to run"
|
|
35
|
+
* over a team that starts no tick. Read-only: it reads the same team and per-role pause
|
|
36
|
+
* markers the scheduler reads (standingTeamPause, pausedRoles, pausedRolesUntil — all treat
|
|
37
|
+
* an expired marker as absent), so its verdict cannot disagree with what the team does. A
|
|
38
|
+
* team pause is a warn because it stops every loop; paused roles are ok because the operator
|
|
23
39
|
* chose them by name and every other loop keeps ticking. `now` only drives the countdowns. */
|
|
24
40
|
export function checkPause(root, now = Date.now()) {
|
|
25
|
-
const
|
|
41
|
+
const team = standingTeamPause(root);
|
|
26
42
|
const roles = pausedRoles(root);
|
|
27
43
|
let rolesPhrase = "";
|
|
28
44
|
if (roles.length > 0) {
|
|
29
45
|
const until = pausedRolesUntil(root);
|
|
30
|
-
rolesPhrase = `paused
|
|
46
|
+
rolesPhrase = `paused roles: ${roles.join(", ")}${until !== undefined ? ` (auto-resume in ${humanUntil(until, now)})` : ""}`;
|
|
31
47
|
}
|
|
32
|
-
if (!
|
|
48
|
+
if (!team)
|
|
33
49
|
return rolesPhrase ? okOutcome(rolesPhrase) : okOutcome("not paused");
|
|
34
|
-
const head =
|
|
35
|
-
? `
|
|
36
|
-
: "
|
|
37
|
-
return warnOutcome(`${head}${pauseReasonSuffix(
|
|
50
|
+
const head = team.until !== undefined
|
|
51
|
+
? `team paused — auto-resumes in ${humanUntil(team.until, now)}`
|
|
52
|
+
: "team paused — no role starts a run until `tumwater resume`";
|
|
53
|
+
return warnOutcome(`${head}${pauseReasonSuffix(team.reason)}${rolesPhrase ? `; ${rolesPhrase}` : ""}`);
|
|
38
54
|
}
|
|
39
|
-
/** The `quiet hours` check: names the
|
|
40
|
-
* so doctor does not print "ready to run" over a
|
|
55
|
+
/** The `quiet hours` check: names the team quiet window and any per-role windows holding now,
|
|
56
|
+
* so doctor does not print "ready to run" over a team that starts no tick until the window
|
|
41
57
|
* ends. Read-only: membership comes from the scheduler's own parser and predicate
|
|
42
|
-
* (quietHoursStatus, roleQuietHold), so the verdict cannot disagree with the gate. A
|
|
58
|
+
* (quietHoursStatus, roleQuietHold), so the verdict cannot disagree with the gate. A team
|
|
43
59
|
* window holding is a warn because it stops every loop (the director is exempt); held per-role
|
|
44
60
|
* windows are ok because the operator chose those roles by name. A malformed value reads as
|
|
45
61
|
* unset here — checkInit owns that error. `now` is injected so tests never read the clock. */
|
|
46
62
|
export function checkQuietHours(config, now = new Date()) {
|
|
47
|
-
const
|
|
63
|
+
const team = quietHoursStatus(config?.quietHours, now);
|
|
48
64
|
const perRole = config?.quietHoursPerRole;
|
|
49
65
|
const roleKeys = Object.keys(perRole ?? {}).filter((k) => k !== DIRECTOR_ROLE);
|
|
50
66
|
const held = roleKeys.filter((k) => roleQuietHold(perRole, k, now)).sort();
|
|
@@ -53,13 +69,13 @@ export function checkQuietHours(config, now = new Date()) {
|
|
|
53
69
|
return parsed.ok && parsed.window !== null;
|
|
54
70
|
});
|
|
55
71
|
const phrases = [];
|
|
56
|
-
if (
|
|
57
|
-
phrases.push(
|
|
58
|
-
? `quiet now (${
|
|
59
|
-
: `${
|
|
72
|
+
if (team.window !== null) {
|
|
73
|
+
phrases.push(team.inWindow
|
|
74
|
+
? `quiet now (${team.window}) — no role starts a run until ${quietWindowEnd(team.window)}; the director is exempt`
|
|
75
|
+
: `${team.window} (not in the window now)`);
|
|
60
76
|
}
|
|
61
77
|
if (held.length > 0) {
|
|
62
|
-
phrases.push(`quiet
|
|
78
|
+
phrases.push(`quiet roles: ${held.map((k) => `${k} until ${quietWindowEnd(String(perRole?.[k]))}`).join(", ")}`);
|
|
63
79
|
}
|
|
64
80
|
else if (anyRoleWindow) {
|
|
65
81
|
phrases.push("per-role windows set, none holding now");
|
|
@@ -67,56 +83,180 @@ export function checkQuietHours(config, now = new Date()) {
|
|
|
67
83
|
if (phrases.length === 0)
|
|
68
84
|
return okOutcome("none configured");
|
|
69
85
|
const detail = phrases.join("; ");
|
|
70
|
-
return
|
|
86
|
+
return team.inWindow ? warnOutcome(detail) : okOutcome(detail);
|
|
71
87
|
}
|
|
72
|
-
/** The `budget` check: today's
|
|
88
|
+
/** The `budget` check: today's team spend against `maxDailyCostUsd`, plus base roles over their
|
|
73
89
|
* own `maxDailyCostUsdPerRole` cap. Read-only and built on the scheduler's own definitions
|
|
74
|
-
* (
|
|
90
|
+
* (teamDailyCost, budgetReached, roleCapVerdicts), so the verdict cannot disagree with the gate.
|
|
75
91
|
* `published` is the running scheduler's figure (it includes runs charged since the last save)
|
|
76
|
-
* and wins over the persisted sum, as in status. A reached
|
|
92
|
+
* and wins over the persisted sum, as in status. A reached team cap is a warn because role loops
|
|
77
93
|
* run on their tier fallback or hold until local midnight (the `fallback` row says which);
|
|
78
94
|
* per-role caps reached alone are ok because the operator chose them by name. */
|
|
79
95
|
export function checkBudget(root, config, published, now = Date.now()) {
|
|
80
96
|
if (config === undefined)
|
|
81
97
|
return okOutcome("not checked — tumwater.json unreadable");
|
|
82
|
-
const states =
|
|
83
|
-
const spent = published?.spentUsd ??
|
|
98
|
+
const states = loadLoopStates(root, config);
|
|
99
|
+
const spent = published?.spentUsd ?? teamDailyCost(states, now);
|
|
84
100
|
const caps = config.maxDailyCostUsdPerRole;
|
|
85
101
|
const capped = [...roleCapVerdicts(states.map((s) => ({ role: s.role, state: s })), caps, now)]
|
|
86
102
|
.filter(([, v]) => v.over)
|
|
87
103
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
88
104
|
.map(([base, v]) => `${base} (${budgetPhrase(v.spentUsd, caps?.[base])})`);
|
|
89
|
-
const cappedPhrase = capped.length > 0 ? `; capped
|
|
105
|
+
const cappedPhrase = capped.length > 0 ? `; capped roles: ${capped.join(", ")}` : "";
|
|
90
106
|
const cap = config.maxDailyCostUsd;
|
|
91
107
|
if (!(cap > 0))
|
|
92
108
|
return okOutcome(`no daily cap; ${usd(spent)} spent today${cappedPhrase}`);
|
|
93
109
|
const view = { spentUsd: spent, capUsd: cap };
|
|
94
110
|
const phrase = budgetPhrase(spent, cap);
|
|
95
111
|
if (budgetReached(view)) {
|
|
96
|
-
return warnOutcome(`daily cap reached — ${phrase} today;
|
|
112
|
+
return warnOutcome(`daily cap reached — ${phrase} today; roles run on their over-cap model or hold until local midnight (see the over-cap model row)${cappedPhrase}`);
|
|
97
113
|
}
|
|
98
114
|
if (budgetWarning(view))
|
|
99
115
|
return okOutcome(`${phrase} today — past 80% of the cap${cappedPhrase}`);
|
|
100
116
|
return okOutcome(`${phrase} today${cappedPhrase}`);
|
|
101
117
|
}
|
|
102
|
-
/** The `maintenance quota` check: rolling-24 h maintenance
|
|
103
|
-
* (`
|
|
104
|
-
*
|
|
105
|
-
* (maintenanceQuotaForPoll). Only a live
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
export function checkMaintenanceQuota(root, config,
|
|
118
|
+
/** The `maintenance quota` check: rolling-24 h maintenance merges against the allowance
|
|
119
|
+
* (`maintenancePerWorkMerge` per work merge + the daily floor), naming the maintenance loops held
|
|
120
|
+
* when it is full. Read-only and built on the same verdict the scheduler and status use
|
|
121
|
+
* (maintenanceQuotaForPoll). Only a live team's running loops and merge queue count as in flight,
|
|
122
|
+
* since a dead team's leftover `running` flag and queue drain nothing. A held allowance is a warn
|
|
123
|
+
* because it stops a whole tier. */
|
|
124
|
+
export function checkMaintenanceQuota(root, config, aliveTeam, now = Date.now()) {
|
|
109
125
|
if (config === undefined)
|
|
110
126
|
return okOutcome("not checked — tumwater.json unreadable");
|
|
111
|
-
const loops =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const queued = aliveFleet ? new Set(readLandingQueue(root).entries.map((e) => e.role)) : new Set();
|
|
115
|
-
const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkLanding, loops, queued, now);
|
|
127
|
+
const loops = loadLoopStates(root, config).map((s) => ({ role: s.role, running: aliveTeam && s.running === true }));
|
|
128
|
+
const queued = aliveTeam ? new Set(readMergeQueue(root).entries.map((e) => e.role)) : new Set();
|
|
129
|
+
const { allowance, used, held } = maintenanceQuotaForPoll(root, config.maintenancePerWorkMerge, loops, queued, now);
|
|
116
130
|
if (held.length > 0) {
|
|
117
|
-
return warnOutcome(`allowance reached — ${used}/${allowance} maintenance
|
|
131
|
+
return warnOutcome(`allowance reached — ${used}/${allowance} maintenance merges in the last 24h; held until the window rolls under: ${[...held].sort().join(", ")}`);
|
|
118
132
|
}
|
|
119
|
-
const base = `${used}/${allowance} maintenance
|
|
133
|
+
const base = `${used}/${allowance} maintenance merges in the last 24h (${config.maintenancePerWorkMerge} per work merge + ${MAINTENANCE_DAILY_FLOOR} floor)`;
|
|
120
134
|
const anyMaintenance = loops.some((l) => commitTier(l.role) === "maintenance");
|
|
121
|
-
return okOutcome(anyMaintenance ? base : `${base}; no maintenance
|
|
135
|
+
return okOutcome(anyMaintenance ? base : `${base}; no maintenance role enabled`);
|
|
136
|
+
}
|
|
137
|
+
/** The `claude window` check: whether a rejected subscription window holds the subscription claude
|
|
138
|
+
* seams. Read-only and built on claudeWindowObservation, the verdict the scheduler's team hold
|
|
139
|
+
* uses, so it cannot disagree with the hold. A hold is a warn because it stops every subscription
|
|
140
|
+
* claude seam until the window resets; otherwise the most constrained window's state is reported
|
|
141
|
+
* as ok (a rejected window spending overage reads `rejected` here: overage spends, it does not
|
|
142
|
+
* hold). API-billed claude never holds. */
|
|
143
|
+
export function checkClaudeWindow(root, now = Date.now(), chain, config) {
|
|
144
|
+
const account = readClaudeAccount(root, now);
|
|
145
|
+
if (account?.billing !== "subscription")
|
|
146
|
+
return okOutcome("no subscription window reported");
|
|
147
|
+
const held = claudeWindowObservation(account, now);
|
|
148
|
+
if (held?.kind === "subscription-window" && held.windowType !== undefined && held.resetsAt !== undefined) {
|
|
149
|
+
const label = claudeWindowLabel(held.windowType);
|
|
150
|
+
const resets = humanUntil(held.resetsAt, now);
|
|
151
|
+
const moved = MODEL_TIERS.filter((t) => {
|
|
152
|
+
const reason = chain?.[t]?.reason;
|
|
153
|
+
return reason === "window" || reason === "reserve";
|
|
154
|
+
});
|
|
155
|
+
if (moved.length > 0) {
|
|
156
|
+
const waiting = MODEL_TIERS.filter((t) => !chain?.[t] && config !== undefined && withModelOverrides(config, t, {}).agent === "claude");
|
|
157
|
+
return warnOutcome(`${label} window rejected — ${moved.join(", ")} moved to fallbacks until it resets in ${resets}${waiting.length > 0 ? `; ${waiting.join(", ")} wait` : ""}`);
|
|
158
|
+
}
|
|
159
|
+
return warnOutcome(`${label} window rejected — subscription claude model jobs wait until it resets in ${resets}`);
|
|
160
|
+
}
|
|
161
|
+
const top = claudeWindowEntries(account)[0];
|
|
162
|
+
if (top === undefined)
|
|
163
|
+
return okOutcome("subscription; no window reported yet");
|
|
164
|
+
const pct = typeof top.utilization === "number" ? ` (${claudeUtilizationPct(top.utilization)}% used)` : "";
|
|
165
|
+
const label = claudeWindowLabel(top.type);
|
|
166
|
+
return okOutcome(top.status === "allowed_warning" ? `${label} window nearing its limit${pct}` : `${label} window ${top.status}${pct}`);
|
|
167
|
+
}
|
|
168
|
+
/** How far back the `provider hold` check reads for hold/resume crossings. The longest a
|
|
169
|
+
* hold can stay is HOLD_CAP_MS (team-hold's 15-minute cap that escalation doubles against),
|
|
170
|
+
* so a window a comfortable multiple of that covers any standing hold's crossing event plus
|
|
171
|
+
* its resume — an older resumed event must not be missed or a re-opened provider reads held. */
|
|
172
|
+
const HOLD_CHECK_WINDOW_MS = 2 * 3600_000;
|
|
173
|
+
/** The `provider hold` check: names each provider whose team-wide failure hold
|
|
174
|
+
* (src/team/team-hold.ts, polled by pollTeamHold) stands now — a 429/backend-failure storm
|
|
175
|
+
* that stops the roles on that provider from starting new ticks until it re-opens. Read-only:
|
|
176
|
+
* it infers the standing set from the event log's own `rate_limit_hold`/`rate_limit_resumed`
|
|
177
|
+
* crossings, the events pollTeamHold writes, so it cannot disagree with the hold. A standing
|
|
178
|
+
* hold is a warn (as with the `claude window` row) because it stops every role on that
|
|
179
|
+
* provider; `now` is injected so a test can pin a hold standing before its deadline and
|
|
180
|
+
* re-opened after. The ended hold's kind rides the resumed event (see pollTeamHold), and any
|
|
181
|
+
* resumed event ends that provider's current hold whatever its kind. */
|
|
182
|
+
export function checkProviderHolds(root, now = Date.now()) {
|
|
183
|
+
let events;
|
|
184
|
+
try {
|
|
185
|
+
events = readEventsSince(root, HOLD_CHECK_WINDOW_MS).events;
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return warnOutcome(`could not read the event log for provider holds: ${errorMessage(err)}`);
|
|
189
|
+
}
|
|
190
|
+
const standing = new Map();
|
|
191
|
+
for (const ev of events) {
|
|
192
|
+
if (ev.type === "rate_limit_hold") {
|
|
193
|
+
const provider = typeof ev.provider === "string" ? ev.provider : undefined;
|
|
194
|
+
const end = (typeof ev.ts === "number" ? ev.ts : 0) + (typeof ev.holdMs === "number" ? ev.holdMs : 0);
|
|
195
|
+
standing.set(provider, { end, kind: ev.kind, roles: ev.roles });
|
|
196
|
+
}
|
|
197
|
+
else if (ev.type === "rate_limit_resumed") {
|
|
198
|
+
standing.delete(typeof ev.provider === "string" ? ev.provider : undefined);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const held = [...standing.entries()]
|
|
202
|
+
.filter(([, h]) => h.end > now)
|
|
203
|
+
.sort(([a], [b]) => (a ?? "").localeCompare(b ?? ""));
|
|
204
|
+
if (held.length === 0)
|
|
205
|
+
return okOutcome("no provider holds");
|
|
206
|
+
const lines = held.map(([provider, h]) => {
|
|
207
|
+
const name = provider ?? "pi's default backend";
|
|
208
|
+
const roles = Array.isArray(h.roles) && h.roles.length > 0 ? `; roles: ${h.roles.join(", ")}` : "";
|
|
209
|
+
return `${name} (${h.kind}) holds until it re-opens in ${humanUntil(h.end, now)}${roles}`;
|
|
210
|
+
});
|
|
211
|
+
return warnOutcome(lines.join("; "));
|
|
212
|
+
}
|
|
213
|
+
/** The `error streaks` check: names each enabled loop whose `consecutiveErrors` is at or past
|
|
214
|
+
* ERROR_STREAK_WARN, with its streak and last error, and whether it is paused. Read-only; it reads
|
|
215
|
+
* the same `consecutiveErrors` the error-streak breaker and `teamAlerts` read, and the same
|
|
216
|
+
* `pausedRoles` marker the scheduler reads. The marker does not record who wrote it, so a paused
|
|
217
|
+
* loop at ERROR_STREAK_BREAKER's bar is described, not attributed to the breaker. A warn, not a
|
|
218
|
+
* fail: a failing loop does not stop the team from running. */
|
|
219
|
+
export function checkErrorStreaks(root, config) {
|
|
220
|
+
if (config === undefined)
|
|
221
|
+
return okOutcome("not checked — tumwater.json unreadable");
|
|
222
|
+
const failing = loadLoopStates(root, config)
|
|
223
|
+
.filter((s) => (s.consecutiveErrors ?? 0) >= ERROR_STREAK_WARN);
|
|
224
|
+
if (failing.length === 0)
|
|
225
|
+
return okOutcome("no role failing repeatedly");
|
|
226
|
+
const paused = new Set(pausedRoles(root));
|
|
227
|
+
const phrases = failing.map((s) => {
|
|
228
|
+
const n = s.consecutiveErrors ?? 0;
|
|
229
|
+
let phrase = `${roleLabel(s.role)}: ${n} failed runs`;
|
|
230
|
+
if (loopPaused(paused, s.role)) {
|
|
231
|
+
const key = paused.has(s.role) ? s.role : paused.has(baseRoleOf(s.role)) ? baseRoleOf(s.role) : `${s.role}-1`;
|
|
232
|
+
phrase +=
|
|
233
|
+
n >= ERROR_STREAK_BREAKER
|
|
234
|
+
? `, paused at the repeated-failure pause's bar — \`tumwater resume --role ${key}\` once the cause is fixed`
|
|
235
|
+
: ", paused";
|
|
236
|
+
}
|
|
237
|
+
if (s.lastError)
|
|
238
|
+
phrase += ` — last error: ${squash(s.lastError, 120)}`;
|
|
239
|
+
return phrase;
|
|
240
|
+
});
|
|
241
|
+
return warnOutcome(`failing run after run: ${phrases.join("; ")}`);
|
|
242
|
+
}
|
|
243
|
+
/** The `main red` check: names the code loops the red-main gate (mainRedGate) holds, ending
|
|
244
|
+
* each blocked tick `main_red` while main's suite is red. Read-only: it asks heldByMainRed,
|
|
245
|
+
* the predicate the status phase's `main red` label reads, so the two cannot disagree. A warn,
|
|
246
|
+
* not a fail: a red main does not stop the team from running. */
|
|
247
|
+
export function checkMainRed(root, config) {
|
|
248
|
+
if (config === undefined)
|
|
249
|
+
return okOutcome("not checked — tumwater.json unreadable");
|
|
250
|
+
const mainCheck = mainCheckForPoll(root, config);
|
|
251
|
+
const ids = loopIds(config);
|
|
252
|
+
const held = loadLoopStates(root, config)
|
|
253
|
+
.filter((s) => heldByMainRed(s, mainCheck));
|
|
254
|
+
if (held.length === 0)
|
|
255
|
+
return okOutcome("no role waiting on main's check");
|
|
256
|
+
const err = held.find((s) => s.lastError)?.lastError;
|
|
257
|
+
const cause = err ? ` — ${squash(err, 160)}` : "";
|
|
258
|
+
const healer = ids.some((id) => baseRoleOf(id) === "bugfix")
|
|
259
|
+
? "; the bugfix role is exempt and repairs it"
|
|
260
|
+
: "; no bugfix role is enabled to repair it — fix main by hand or enable bugfix";
|
|
261
|
+
return warnOutcome(`main's check is failing; ${plural(held.length, "code role")} waiting: ${held.map((s) => roleLabel(s.role)).join(", ")}${cause}${healer}`);
|
|
122
262
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The doctor's `root install` row: whether the root checkout's node_modules still matches
|
|
2
|
+
* package-lock.json's direct pins. A failed post-merge re-sync (syncRootInstall) leaves the root
|
|
3
|
+
* behind, and every check then installs into its own tree until a later merge retries. */
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { installDrift } from "../build/dep-install.js";
|
|
7
|
+
import { readEventsSince } from "../events/event-window.js";
|
|
8
|
+
import { plural, shortSpanPhrase } from "../text/phrases.js";
|
|
9
|
+
import { errorMessage, textOr } from "../text/text.js";
|
|
10
|
+
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
11
|
+
/** How far back a failed `dep_install` event may sit and still explain the drift. */
|
|
12
|
+
const DEP_INSTALL_CHECK_WINDOW_MS = 24 * 3600_000;
|
|
13
|
+
export function checkRootInstall(root, now = Date.now()) {
|
|
14
|
+
if (!fs.existsSync(path.join(root, "package-lock.json")))
|
|
15
|
+
return okOutcome("no package-lock.json — nothing pinned");
|
|
16
|
+
const drift = installDrift(root);
|
|
17
|
+
if (drift.length === 0)
|
|
18
|
+
return okOutcome("node_modules matches package-lock.json");
|
|
19
|
+
let detail = `${plural(drift.length, "direct dependency", "direct dependencies")} not installed as package-lock.json pins (${drift.join(", ")}) — run npm install in ${root}; until then each check installs into its own tree`;
|
|
20
|
+
try {
|
|
21
|
+
const installs = readEventsSince(root, DEP_INSTALL_CHECK_WINDOW_MS).events.filter((ev) => ev.type === "dep_install");
|
|
22
|
+
const ev = installs[installs.length - 1];
|
|
23
|
+
if (ev?.status === "failed")
|
|
24
|
+
detail += `; the last re-sync failed ${shortSpanPhrase(now - ev.ts)} ago: ${textOr(ev.error)}`;
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
detail += `; could not read the event log: ${errorMessage(err)}`;
|
|
28
|
+
}
|
|
29
|
+
return warnOutcome(detail);
|
|
30
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/** Doctor's `mach ports` line: a warning at or past launchservicesd's port threshold, a warning
|
|
2
|
-
* when the count cannot be read on macOS, ok otherwise. Never a failure — the
|
|
2
|
+
* when the count cannot be read on macOS, ok otherwise. Never a failure — the team runs fine on
|
|
3
3
|
* a leaking Mac; it is the Mac that does not. Split from process/launch-services.ts (2026-10-07,
|
|
4
4
|
* organize) so the doctor's environment checks live together (doctor-orphans.ts reads the process
|
|
5
5
|
* table the same way) and the process layer no longer imports the doctor's report contract. The
|
|
6
6
|
* launchservicesd facts this reads — the thresholds and the warning text — stay in
|
|
7
|
-
* src/process/launch-services.ts, shared with the running
|
|
7
|
+
* src/process/launch-services.ts, shared with the running team's watch. `platform` is a
|
|
8
8
|
* parameter so the macOS branches are testable on any host. */
|
|
9
9
|
import { okOutcome, warnOutcome } from "./outcome.js";
|
|
10
10
|
import { systemProcessProbe } from "../process/process-table.js";
|