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
|
@@ -52,7 +52,18 @@ export async function followView(frameText, seams) {
|
|
|
52
52
|
const timer = setInterval(() => {
|
|
53
53
|
if (painting)
|
|
54
54
|
return;
|
|
55
|
-
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
result = frameText();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// A sync frame (status, backlog, report) may throw the same transient failure as an
|
|
61
|
+
// async one — a snapshot lift, a momentarily unreadable file — and a throw out of a
|
|
62
|
+
// setInterval callback is an uncaught exception that kills the whole interactive
|
|
63
|
+
// follow, while the identical failure a tick later (async path) is skipped. Skip this
|
|
64
|
+
// repaint and retry on the next tick, the same policy the async path applies.
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
56
67
|
if (result instanceof Promise) {
|
|
57
68
|
painting = true;
|
|
58
69
|
void result
|
|
@@ -63,7 +74,14 @@ export async function followView(frameText, seams) {
|
|
|
63
74
|
});
|
|
64
75
|
return;
|
|
65
76
|
}
|
|
66
|
-
paint(
|
|
77
|
+
// A sync frame's paint is guarded like its read above: a paint that throws (a broken
|
|
78
|
+
// stdout mid-follow) is skipped and retried, never fatal.
|
|
79
|
+
try {
|
|
80
|
+
paint(result);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Transient — retry on the next tick, like the async path.
|
|
84
|
+
}
|
|
67
85
|
}, interval);
|
|
68
86
|
startParentDeathWatch(() => {
|
|
69
87
|
clearInterval(timer);
|
package/dist/src/cli/help.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* <command>` from it by splitting the usage block on its ` tumwater <command>` lines, so a
|
|
5
5
|
* topic can never drift out of sync with the full listing. */
|
|
6
6
|
import { suggestClosest } from "../text/suggest.js";
|
|
7
|
+
import { distinctOrdered } from "../collections.js";
|
|
7
8
|
/** The role-grouped summary's JSON keys, quoted verbatim in `history --summary --json` help
|
|
8
9
|
* (its `--by model` twin is the hardcoded `{ models, total }` beside it). */
|
|
9
10
|
const SUMMARY_JSON_KEYS = "{ roles, total }";
|
|
@@ -313,9 +314,8 @@ Usage:
|
|
|
313
314
|
non-blank line is the symptom, the rest is the body kept
|
|
314
315
|
line for line; wakes the bugfix role
|
|
315
316
|
tumwater bug --release <n> [--json]
|
|
316
|
-
Lift the Refused or
|
|
317
|
-
Needs
|
|
318
|
-
(one per run) from the Nth entry in BUGS.md ## Open (as
|
|
317
|
+
Lift the Refused or the
|
|
318
|
+
Needs review notes from the Nth entry in BUGS.md ## Open (as
|
|
319
319
|
\`tumwater bug --list\` lists it), commit it on main, and wake
|
|
320
320
|
the bugfix role; --json prints {file, title, lifted}
|
|
321
321
|
tumwater bug --top <n> [--json]
|
|
@@ -370,9 +370,8 @@ Usage:
|
|
|
370
370
|
the first non-blank line is the title, the rest is the body
|
|
371
371
|
kept line for line; wakes the feature role
|
|
372
372
|
tumwater plan --release <n> [--json]
|
|
373
|
-
Lift the Refused or
|
|
374
|
-
Needs
|
|
375
|
-
(one per run) from the Nth entry in PLANS.md ## Planned
|
|
373
|
+
Lift the Refused or the
|
|
374
|
+
Needs review notes from the Nth entry in PLANS.md ## Planned
|
|
376
375
|
(as \`tumwater plan --list\` lists it), commit it on main,
|
|
377
376
|
and wake the feature role; --json prints {file, title, lifted}
|
|
378
377
|
tumwater plan --top <n> [--json]
|
|
@@ -528,12 +527,6 @@ Usage:
|
|
|
528
527
|
written to roles.<id>.instances for the base id
|
|
529
528
|
tumwater role <id> --clear-instances
|
|
530
529
|
Clear that role's instance count (back to 1)
|
|
531
|
-
tumwater role <id> --review-after-merge
|
|
532
|
-
Review that role's commits after they merge, written
|
|
533
|
-
to review.afterMerge (the base id for an instance)
|
|
534
|
-
tumwater role <id> --clear-review-after-merge
|
|
535
|
-
Review it before merge again, removing it from
|
|
536
|
-
review.afterMerge
|
|
537
530
|
tumwater prompt <text...> Send a message to the director's inbox
|
|
538
531
|
tumwater prompt --file <path> Send a message read from a file ("-" reads stdin)
|
|
539
532
|
tumwater prompt --role <id> <text...>
|
|
@@ -648,7 +641,7 @@ sleep while there is nothing to do and wake when main moves. Everything is local
|
|
|
648
641
|
* stanza; the distance mechanics are text/suggest.ts's suggestClosest, shared with the config-key
|
|
649
642
|
* suggestion. */
|
|
650
643
|
export function suggestCommand(input, help = HELP) {
|
|
651
|
-
return suggestClosest(input,
|
|
644
|
+
return suggestClosest(input, distinctOrdered(helpStanzas(help).map((s) => s.command)));
|
|
652
645
|
}
|
|
653
646
|
/** Split HELP's usage block into per-command stanzas. A stanza starts at a two-space-indented
|
|
654
647
|
* `tumwater <command>` line; any further indented line continues it, and a column-0 line (the
|
package/dist/src/cli.js
CHANGED
|
@@ -13,16 +13,14 @@
|
|
|
13
13
|
* ready-repo gate. An error escaping main() funnels through its catch to fail(errorMessage(err)),
|
|
14
14
|
* the CLI's one error path. */
|
|
15
15
|
import { parseCountFlag, parseGrepFlag, parseModelFlag, parseResultFlag } from "./cli/cli-args.js";
|
|
16
|
-
import { grepFlagSpec, GUI_FLAG_SPECS, JSON_FLAG, LAST_FLAG, N_FLAG, rejectUnknownArgs, resultFlagSpec, modelFlagSpec, byFlagSpec, LOGS_TRANSCRIPT_FLAG, ROLE_FLAG, START_FLAG_SPECS, sinceFlagSpec, untilFlagSpec, STAT_FLAG, PATCH_FLAG, PROMPT_FLAG, TRANSCRIPT_FLAG, SUMMARY_FLAG, COUNT_FLAG, NOTABLE_FLAG, typeFlagSpec, DRY_RUN_FLAG, FORCE_FLAG, FOLLOW_FLAG, STOP_CANCEL_FLAG, STOP_IN_FLAG,
|
|
17
|
-
import { fail, say
|
|
18
|
-
import { historyTrailForm,
|
|
16
|
+
import { grepFlagSpec, GUI_FLAG_SPECS, JSON_FLAG, LAST_FLAG, N_FLAG, rejectUnknownArgs, resultFlagSpec, modelFlagSpec, byFlagSpec, LOGS_TRANSCRIPT_FLAG, ROLE_FLAG, START_FLAG_SPECS, sinceFlagSpec, untilFlagSpec, STAT_FLAG, PATCH_FLAG, PROMPT_FLAG, TRANSCRIPT_FLAG, SUMMARY_FLAG, COUNT_FLAG, NOTABLE_FLAG, typeFlagSpec, DRY_RUN_FLAG, FORCE_FLAG, FOLLOW_FLAG, STOP_CANCEL_FLAG, STOP_IN_FLAG, } from "./cli/cli-flag-specs.js";
|
|
17
|
+
import { fail, say } from "./cli/cli-output.js";
|
|
18
|
+
import { historyTrailForm, parsePromptArgs, peelPositionals } from "./cli/cli-command-args.js";
|
|
19
19
|
import { refuseTeamWorkspace, requireReadyRepo } from "./cli/cli-repo-gate.js";
|
|
20
|
-
import { loadConfigSafe } from "./config/config.js";
|
|
21
20
|
import { repoToplevel } from "./git/git.js";
|
|
22
21
|
import { didYouMean } from "./text/suggest.js";
|
|
23
22
|
import { errorMessage, gotSuffix, stripTerminalControls } from "./text/text.js";
|
|
24
23
|
import { typoSuffix } from "./text/suggest.js";
|
|
25
|
-
import { completionConfigKeys, completionModels, completionRoleIds, completionScript, configValueWords, SUPPORTED_SHELLS } from "./cli/completion.js";
|
|
26
24
|
import { HELP, helpTopic, helpTopicForArgs, suggestCommand } from "./cli/help.js";
|
|
27
25
|
import { PACKAGE_JSON, nodeFloorProblem, packageEnginesNode, packageVersion } from "./version.js";
|
|
28
26
|
// The CLI's help text and its per-command topic parser live in help.ts — importing cli.ts
|
|
@@ -75,7 +73,7 @@ async function cmdGuiCase(root, args) {
|
|
|
75
73
|
await (await import("./gui/gui-command.js")).cmdGui(root, args);
|
|
76
74
|
}
|
|
77
75
|
async function cmdConfigCase(root, args) {
|
|
78
|
-
const { cmdConfig, CONFIG_USAGE } = await import("./cli/config-commands.js");
|
|
76
|
+
const { cmdConfig, CONFIG_USAGE } = await import("./cli/cli-config-commands.js");
|
|
79
77
|
// Subcommand arity before the ready-repo gate, so a malformed get/set fails with its
|
|
80
78
|
// usage no matter the directory: bare config takes nothing, `get`/`unset` exactly one
|
|
81
79
|
// key, `set` exactly a key and a value. cmdConfig dispatches on the (now well-shaped)
|
|
@@ -126,98 +124,15 @@ async function cmdReportCase(root, args) {
|
|
|
126
124
|
await (await import("./report/report.js")).cmdReport(root, args);
|
|
127
125
|
}
|
|
128
126
|
async function cmdDoctorCase(root, args) {
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let orphanKill;
|
|
136
|
-
if (args.includes("--kill-orphans")) {
|
|
137
|
-
const { killOrphans, orphanKillLines } = await import("./doctor/doctor-orphans.js");
|
|
138
|
-
orphanKill = await killOrphans(root);
|
|
139
|
-
// The text form prints the kill before the report; --json keeps stdout one document.
|
|
140
|
-
if (!args.includes("--json")) {
|
|
141
|
-
for (const line of orphanKillLines(orphanKill))
|
|
142
|
-
say(line);
|
|
143
|
-
say("");
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
let backlogCommit;
|
|
147
|
-
if (args.includes("--commit-backlog")) {
|
|
148
|
-
const { commitUncommittedBacklog, backlogCommitLines } = await import("./doctor/doctor-backlog.js");
|
|
149
|
-
const { config } = loadConfigSafe(root); // the report's own config, so base resolution and the off-base skip match the `uncommitted backlog` row's
|
|
150
|
-
backlogCommit = await commitUncommittedBacklog(root, config);
|
|
151
|
-
// The text form prints the commits before the report; --json keeps stdout one document.
|
|
152
|
-
if (!args.includes("--json")) {
|
|
153
|
-
for (const line of backlogCommitLines(backlogCommit))
|
|
154
|
-
say(line);
|
|
155
|
-
say("");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
let resyncInstall;
|
|
159
|
-
if (args.includes("--resync-install")) {
|
|
160
|
-
const { resyncRootInstall, rootInstallResyncLines } = await import("./build/dep-install.js");
|
|
161
|
-
resyncInstall = await resyncRootInstall(root);
|
|
162
|
-
// The text form prints the re-sync outcome before the report; --json keeps stdout one
|
|
163
|
-
// document, carrying the result as `resyncInstall` on the payload.
|
|
164
|
-
if (!args.includes("--json")) {
|
|
165
|
-
for (const line of rootInstallResyncLines(resyncInstall))
|
|
166
|
-
say(line);
|
|
167
|
-
say("");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
let brokenPinDrop;
|
|
171
|
-
if (args.includes("--drop-broken-pins")) {
|
|
172
|
-
const { dropBrokenPins, brokenPinDropLines } = await import("./doctor/doctor-contention-checks.js");
|
|
173
|
-
brokenPinDrop = await dropBrokenPins(root);
|
|
174
|
-
// The text form prints the drop before the report; --json keeps stdout one document,
|
|
175
|
-
// carrying the result as `brokenPinDrop` on the payload.
|
|
176
|
-
if (!args.includes("--json")) {
|
|
177
|
-
const lines = brokenPinDropLines(brokenPinDrop);
|
|
178
|
-
for (const line of lines)
|
|
179
|
-
say(line);
|
|
180
|
-
if (lines.length > 0)
|
|
181
|
-
say("");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
let leftoverRetire;
|
|
185
|
-
if (args.includes("--retire-leftovers")) {
|
|
186
|
-
const { retireLeftovers, leftoverRetireLines } = await import("./doctor/doctor-contention-checks.js");
|
|
187
|
-
const { config } = loadConfigSafe(root); // the row's own config, so the reap and the report agree on what is retirable
|
|
188
|
-
leftoverRetire = await retireLeftovers(root, config);
|
|
189
|
-
// The text form prints the retire lines before the report; --json keeps stdout one
|
|
190
|
-
// document, carrying the result as `leftoverRetire` on the payload.
|
|
191
|
-
if (!args.includes("--json")) {
|
|
192
|
-
const lines = leftoverRetireLines(leftoverRetire);
|
|
193
|
-
for (const line of lines)
|
|
194
|
-
say(line);
|
|
195
|
-
if (lines.length > 0)
|
|
196
|
-
say("");
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const { renderDoctor, renderDoctorProblems, problemsOnly } = await import("./doctor/doctor-render.js");
|
|
200
|
-
const report = await runDoctor(root, undefined, undefined, only);
|
|
201
|
-
// --json prints the collector's own payload (the DoctorReport object), not a re-parse of
|
|
202
|
-
// the render — the `report --json` precedent. The exit-code contract below holds in both
|
|
203
|
-
// forms: 1 when any check fails, 0 otherwise; warnings never fail the exit.
|
|
204
|
-
// --problems keeps the full report for the render (it counts the hidden ok rows) and
|
|
205
|
-
// hands --json the filtered one; the exit code below reads the full report either way.
|
|
206
|
-
// --kill-orphans adds the kill result to the JSON object as `orphanKill`.
|
|
207
|
-
const withKill = (payload) => (orphanKill === undefined ? payload : { ...payload, orphanKill });
|
|
208
|
-
const withBacklogCommit = (payload) => backlogCommit === undefined ? withKill(payload) : withKill({ ...payload, backlogCommit });
|
|
209
|
-
const withResyncInstall = (payload) => resyncInstall === undefined ? withBacklogCommit(payload) : withBacklogCommit({ ...payload, resyncInstall });
|
|
210
|
-
const withBrokenPinDrop = (payload) => brokenPinDrop === undefined ? withResyncInstall(payload) : withResyncInstall({ ...payload, brokenPinDrop });
|
|
211
|
-
const withLeftoverRetire = (payload) => leftoverRetire === undefined ? withBrokenPinDrop(payload) : withBrokenPinDrop({ ...payload, leftoverRetire });
|
|
212
|
-
if (args.includes("--problems"))
|
|
213
|
-
sayJsonOrRender(args, args.includes("--json") ? withLeftoverRetire(problemsOnly(report)) : report, renderDoctorProblems);
|
|
214
|
-
else
|
|
215
|
-
sayJsonOrRender(args, args.includes("--json") ? withLeftoverRetire(report) : report, renderDoctor);
|
|
216
|
-
if (report.checks.some((c) => c.level === "fail"))
|
|
217
|
-
process.exitCode = 1; // Warnings never fail the exit.
|
|
127
|
+
// cmdDoctor lives in cli-doctor-command.ts (the doctor command body, its side-effect
|
|
128
|
+
// wrappers and payload shaping) and holds no ready-repo gate: doctor's job is to report
|
|
129
|
+
// WHY the environment isn't ready, so it must run outside a git repo and print fail
|
|
130
|
+
// lines rather than throwing. Imported lazily like every command body, so the doctor
|
|
131
|
+
// subgraph is only compiled when `tumwater doctor` is asked for.
|
|
132
|
+
await (await import("./cli/cli-doctor-command.js")).cmdDoctor(root, args);
|
|
218
133
|
}
|
|
219
134
|
async function cmdLogsCase(root, args) {
|
|
220
|
-
const { cmdLogs, GREP_VALUE_ERROR, TYPE_VALUE_ERROR } = await import("./cli/log-commands.js");
|
|
135
|
+
const { cmdLogs, GREP_VALUE_ERROR, TYPE_VALUE_ERROR } = await import("./cli/cli-log-commands.js");
|
|
221
136
|
rejectUnknownArgs("logs", args, [
|
|
222
137
|
FOLLOW_FLAG,
|
|
223
138
|
N_FLAG,
|
|
@@ -238,7 +153,7 @@ async function cmdLogsCase(root, args) {
|
|
|
238
153
|
async function cmdNotifyCase(root, args) {
|
|
239
154
|
rejectUnknownArgs("notify", args, [{ names: ["--test"] }]);
|
|
240
155
|
await requireReadyRepo(root);
|
|
241
|
-
await (await import("./cli/notify-command.js")).cmdNotify(root, args);
|
|
156
|
+
await (await import("./cli/cli-notify-command.js")).cmdNotify(root, args);
|
|
242
157
|
}
|
|
243
158
|
async function cmdHistoryCase(root, args) {
|
|
244
159
|
const { cmdHistory, HISTORY_GREP_VALUE_ERROR, HISTORY_RESULT_VALUE_ERROR, HISTORY_MODEL_VALUE_ERROR, HISTORY_BY_VALUE_ERROR } = await import("./history/history.js");
|
|
@@ -267,69 +182,81 @@ async function cmdArchiveCase(root, args) {
|
|
|
267
182
|
await requireReadyRepo(root);
|
|
268
183
|
await (await import("./backlog/backlog-cli.js")).cmdArchive(root, args);
|
|
269
184
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
185
|
+
/** The one dispatch `bug` and `plan` both run through (see BacklogCommandSpec): route on the
|
|
186
|
+
* leading flag token and hand off to the matching shared AndAnnounce shell, or file a text
|
|
187
|
+
* entry by default. */
|
|
188
|
+
async function runBacklogCommand(root, args, s) {
|
|
189
|
+
const { cancelAndAnnounce, editAndAnnounce, fileAndAnnounce, releaseAndAnnounce, topAndAnnounce } = await import("./backlog/backlog-cli.js");
|
|
274
190
|
const { listAndAnnounce, showAndAnnounce } = await import("./backlog/backlog-cli-view.js");
|
|
275
|
-
const usage = 'tumwater bug "<symptom>"';
|
|
276
191
|
if (args.includes("--show")) {
|
|
277
|
-
showAndAnnounce(root, args,
|
|
192
|
+
showAndAnnounce(root, args, s.command);
|
|
278
193
|
return;
|
|
279
194
|
}
|
|
280
195
|
if (args.includes("--list")) {
|
|
281
|
-
listAndAnnounce(root, args,
|
|
196
|
+
listAndAnnounce(root, args, s.command, s.file, s.section);
|
|
282
197
|
return;
|
|
283
198
|
}
|
|
284
199
|
if (args.includes("--edit")) {
|
|
285
|
-
await editAndAnnounce(root, args,
|
|
200
|
+
await editAndAnnounce(root, args, s.command, s.edit, s.editText);
|
|
286
201
|
return;
|
|
287
202
|
}
|
|
288
203
|
if (args.includes("--cancel")) {
|
|
289
|
-
await cancelAndAnnounce(root, args,
|
|
204
|
+
await cancelAndAnnounce(root, args, s.command, s.cancel);
|
|
290
205
|
return;
|
|
291
206
|
}
|
|
292
207
|
if (args.includes("--top")) {
|
|
293
|
-
await topAndAnnounce(root, args,
|
|
208
|
+
await topAndAnnounce(root, args, s.command, s.top);
|
|
294
209
|
return;
|
|
295
210
|
}
|
|
296
211
|
if (args.includes("--release")) {
|
|
297
|
-
await releaseAndAnnounce(root, args,
|
|
212
|
+
await releaseAndAnnounce(root, args, s.command, s.release, s.releaseRole);
|
|
298
213
|
return;
|
|
299
214
|
}
|
|
300
|
-
await fileAndAnnounce(root, args,
|
|
215
|
+
await fileAndAnnounce(root, args, s.command, s.fileOp, s.releaseRole, s.wakeText, s.titleOf, s.fileText);
|
|
216
|
+
}
|
|
217
|
+
async function cmdBugCase(root, args) {
|
|
218
|
+
const { OPERATOR_FILE_KINDS, bugWakeText, cancelBug, editBug, editBugFromText, fileBug, fileBugFromText } = await import("./backlog/backlog-write.js");
|
|
219
|
+
const { moveEntryToTop, releaseEntry } = await import("./backlog/backlog-release.js");
|
|
220
|
+
const { bugTitleOf } = await import("./backlog/backlog-cli.js");
|
|
221
|
+
const usage = 'tumwater bug "<symptom>"';
|
|
222
|
+
await runBacklogCommand(root, args, {
|
|
223
|
+
command: "bug",
|
|
224
|
+
usage,
|
|
225
|
+
file: "BUGS.md",
|
|
226
|
+
section: "Open",
|
|
227
|
+
edit: (n, positionals) => editBug(root, n, positionals.join(" "), usage),
|
|
228
|
+
editText: (n, text) => editBugFromText(root, n, text, usage),
|
|
229
|
+
cancel: (n) => cancelBug(root, n),
|
|
230
|
+
top: (n) => moveEntryToTop(root, "bug", n),
|
|
231
|
+
release: (n) => releaseEntry(root, "bug", n),
|
|
232
|
+
releaseRole: OPERATOR_FILE_KINDS.bug.role,
|
|
233
|
+
fileOp: (positionals) => fileBug(root, positionals.join(" "), usage),
|
|
234
|
+
fileText: (text) => fileBugFromText(root, text, usage),
|
|
235
|
+
wakeText: bugWakeText,
|
|
236
|
+
titleOf: bugTitleOf,
|
|
237
|
+
});
|
|
301
238
|
}
|
|
302
239
|
async function cmdPlanCase(root, args) {
|
|
303
240
|
const { OPERATOR_FILE_KINDS, cancelPlan, editPlan, editPlanFromText, filePlan, filePlanFromText, planWakeText } = await import("./backlog/backlog-write.js");
|
|
304
241
|
const { moveEntryToTop, releaseEntry } = await import("./backlog/backlog-release.js");
|
|
305
|
-
const {
|
|
306
|
-
const { listAndAnnounce, showAndAnnounce } = await import("./backlog/backlog-cli-view.js");
|
|
242
|
+
const { planTitleOf } = await import("./backlog/backlog-cli.js");
|
|
307
243
|
const usage = 'tumwater plan "<title>" [body...]';
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
if (args.includes("--top")) {
|
|
325
|
-
await topAndAnnounce(root, args, "plan", (n) => moveEntryToTop(root, "plan", n));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (args.includes("--release")) {
|
|
329
|
-
await releaseAndAnnounce(root, args, "plan", (n) => releaseEntry(root, "plan", n), OPERATOR_FILE_KINDS.plan.role);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
await fileAndAnnounce(root, args, "plan", (positionals) => filePlan(root, positionals[0] ?? "", positionals.slice(1).join(" "), usage), OPERATOR_FILE_KINDS.plan.role, planWakeText, planTitleOf, (text) => filePlanFromText(root, text, usage));
|
|
244
|
+
await runBacklogCommand(root, args, {
|
|
245
|
+
command: "plan",
|
|
246
|
+
usage,
|
|
247
|
+
file: "PLANS.md",
|
|
248
|
+
section: "Planned",
|
|
249
|
+
edit: (n, positionals) => editPlan(root, n, positionals[0] ?? "", positionals.slice(1).join(" "), usage),
|
|
250
|
+
editText: (n, text) => editPlanFromText(root, n, text, usage),
|
|
251
|
+
cancel: (n) => cancelPlan(root, n),
|
|
252
|
+
top: (n) => moveEntryToTop(root, "plan", n),
|
|
253
|
+
release: (n) => releaseEntry(root, "plan", n),
|
|
254
|
+
releaseRole: OPERATOR_FILE_KINDS.plan.role,
|
|
255
|
+
fileOp: (positionals) => filePlan(root, positionals[0] ?? "", positionals.slice(1).join(" "), usage),
|
|
256
|
+
fileText: (text) => filePlanFromText(root, text, usage),
|
|
257
|
+
wakeText: planWakeText,
|
|
258
|
+
titleOf: planTitleOf,
|
|
259
|
+
});
|
|
333
260
|
}
|
|
334
261
|
async function cmdPromptCase(root, args) {
|
|
335
262
|
// Parse before the gate: prompt's positionals are free-form, so rejectUnknownArgs
|
|
@@ -364,43 +291,6 @@ async function cmdStopCase(root, args) {
|
|
|
364
291
|
await requireReadyRepo(root);
|
|
365
292
|
await (await import("./operator/operator-commands.js")).cmdStop(root, args);
|
|
366
293
|
}
|
|
367
|
-
async function cmdCompletionCase(root, args) {
|
|
368
|
-
// Works outside a repo: no requireReadyRepo.
|
|
369
|
-
if (args.length === 1 && args[0] === "--roles") {
|
|
370
|
-
for (const id of completionRoleIds(root))
|
|
371
|
-
say(id);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (args.length === 1 && args[0] === "--config-keys") {
|
|
375
|
-
for (const key of completionConfigKeys(root))
|
|
376
|
-
say(key);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (args.length === 2 && args[0] === "--config-value") {
|
|
380
|
-
// The vocabulary `config set <key> <value>` completes for a discrete-valued key
|
|
381
|
-
// (boolean/backend/model/thinking); free-form keys print nothing, so a `set` on one
|
|
382
|
-
// completes nothing instead of offering a wrong vocabulary.
|
|
383
|
-
for (const word of configValueWords(args[1]))
|
|
384
|
-
say(word);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (args.length === 1 && args[0] === "--models") {
|
|
388
|
-
for (const m of completionModels(root))
|
|
389
|
-
say(m);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
const shell = args[0];
|
|
393
|
-
// A wrong word count names the count, not a bad shell. A single near-miss shell word gets
|
|
394
|
-
// the same typoSuffix `logs --type`/`history --result` give, so `completion zs` reads
|
|
395
|
-
// "did you mean `zsh`?" while a far-off value (`tcsh`) stays hint-free.
|
|
396
|
-
if (args.length === 0)
|
|
397
|
-
fail("completion needs a shell: bash, zsh, or fish");
|
|
398
|
-
if (args.length > 1)
|
|
399
|
-
fail(`completion takes one shell: bash, zsh, or fish (got ${args.length} words: ${args.join(" ")})`);
|
|
400
|
-
if (!SUPPORTED_SHELLS.includes(shell))
|
|
401
|
-
fail(`completion takes one shell: bash, zsh, or fish${gotSuffix(shell)}${typoSuffix(shell ?? "", SUPPORTED_SHELLS)}`);
|
|
402
|
-
say(completionScript(shell));
|
|
403
|
-
}
|
|
404
294
|
async function cmdVersionCase(args) {
|
|
405
295
|
// A stray flag fails like every other command's: `tumwater version --json` (a plausible
|
|
406
296
|
// slip from `status --json`) must not print a version as if it had answered the query.
|
|
@@ -535,10 +425,10 @@ async function main() {
|
|
|
535
425
|
break;
|
|
536
426
|
}
|
|
537
427
|
case "questions":
|
|
538
|
-
await (await import("./cli/question-commands.js")).cmdQuestions(root, args);
|
|
428
|
+
await (await import("./cli/cli-question-commands.js")).cmdQuestions(root, args);
|
|
539
429
|
break;
|
|
540
430
|
case "brief":
|
|
541
|
-
await (await import("./cli/brief-command.js")).cmdBrief(root, args);
|
|
431
|
+
await (await import("./cli/cli-brief-command.js")).cmdBrief(root, args);
|
|
542
432
|
break;
|
|
543
433
|
case "role":
|
|
544
434
|
await (await import("./cli/cli-role-command.js")).cmdRole(root, args);
|
|
@@ -574,7 +464,11 @@ async function main() {
|
|
|
574
464
|
break;
|
|
575
465
|
}
|
|
576
466
|
case "completion": {
|
|
577
|
-
|
|
467
|
+
// Works outside a repo (no requireReadyRepo): the body lives in
|
|
468
|
+
// cli-completion-command.ts, beside its data (src/cli/completion.ts), and is imported
|
|
469
|
+
// lazily like every command body so the dispatcher never compiles the completion
|
|
470
|
+
// subgraph until `tumwater completion` is asked for.
|
|
471
|
+
await (await import("./cli/cli-completion-command.js")).cmdCompletion(root, args);
|
|
578
472
|
break;
|
|
579
473
|
}
|
|
580
474
|
case "version":
|
package/dist/src/collections.js
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* utilities module. */
|
|
5
5
|
/** Group items by a key, preserving first-seen key order and input order within each group.
|
|
6
6
|
* The single home of the "append to a Map of arrays" step, shared by the event bucketers
|
|
7
|
-
* (history-queued.ts's bucketMergeEvents
|
|
8
|
-
* the team-hold poll's observations-by-provider grouping (team-polls.ts's
|
|
9
|
-
* those sites cannot drift on how a missing key is seeded. Returns a fresh
|
|
10
|
-
* never mutated. */
|
|
7
|
+
* (history-queued.ts's bucketMergeEvents and failure/failure-time-spend.ts's rejected-by-role
|
|
8
|
+
* pass) and the team-hold poll's observations-by-provider grouping (team-polls.ts's
|
|
9
|
+
* pollTeamHold), so those sites cannot drift on how a missing key is seeded. Returns a fresh
|
|
10
|
+
* Map; the input is never mutated. */
|
|
11
11
|
export function groupBy(items, key) {
|
|
12
12
|
const groups = new Map();
|
|
13
13
|
for (const item of items) {
|
|
@@ -15,11 +15,31 @@ export function groupBy(items, key) {
|
|
|
15
15
|
}
|
|
16
16
|
return groups;
|
|
17
17
|
}
|
|
18
|
+
/** The distinct items of an iterable, in first-seen order — the single home of the
|
|
19
|
+
* `seen.includes -> push` dedupe, shared by the pending pane's three scope lists
|
|
20
|
+
* (mergeQueueRoles, postReviewRoles, overlapHolders in ui/tui/tui-pane-pending.ts), the
|
|
21
|
+
* session pane's scope cycle (ui/tui/tui-pane-session.ts's scopeCycle), the report
|
|
22
|
+
* scope fold (report/report-fold.ts's scopeBases), and the rest of the harness's
|
|
23
|
+
* `[...new Set(...)]` dedupes (completion.ts's commands/config keys, operator-commands.ts's
|
|
24
|
+
* role lists, text.ts's splitCommaListUnique/pathTokens, startup-gates.ts's backendsInUse,
|
|
25
|
+
* failure-data.ts's delta roles, doctor-backlog.ts's watched set, doctor-orphans.ts's pid
|
|
26
|
+
* list, main-watch.ts's knownMainFailures, help.ts's suggestCommand candidates, and
|
|
27
|
+
* tui-pane-activity.ts's present types), so those sites cannot drift on how a repeat is
|
|
28
|
+
* skipped. Returns a fresh array; the input is never mutated. (The dashboard's
|
|
29
|
+
* role-scope builder in ui/gui/gui-client-report.ts keeps its own copy by design: it runs
|
|
30
|
+
* in the browser, where harness modules cannot be imported.) */
|
|
31
|
+
export function distinctOrdered(items) {
|
|
32
|
+
const seen = [];
|
|
33
|
+
for (const item of items)
|
|
34
|
+
if (!seen.includes(item))
|
|
35
|
+
seen.push(item);
|
|
36
|
+
return seen;
|
|
37
|
+
}
|
|
18
38
|
/** Return `map`'s value for `key`, creating and storing `make()`'s result first when the key is
|
|
19
39
|
* absent — the single home of the `map.get(key) ?? fresh; …; map.set(key, value)` step, shared
|
|
20
40
|
* by the failure digest's per-role stats, outcome, and prompt-token accumulators (failure-data.ts),
|
|
21
|
-
* its time-and-spend per-role row (time-spend.ts), the team hold's provider+kind grouping
|
|
22
|
-
* (team-hold.ts), the error-storm reducer's roles-by-key set (src/team/error-storm.ts), and
|
|
41
|
+
* its time-and-spend per-role row (failure-time-spend.ts), the team hold's provider+kind grouping
|
|
42
|
+
* (team-hold.ts), the error-storm reducer's roles-by-key set (src/team/team-error-storm.ts), and
|
|
23
43
|
* `groupBy` above, so those sites cannot drift on how a missing key is seeded. `make` runs only
|
|
24
44
|
* when the key is absent. A stored value of `undefined` reads as absent, matching the `??` idiom
|
|
25
45
|
* this replaces; the callers above never store one. */
|
|
@@ -50,9 +70,9 @@ export function filterKeepPositions(items, keep) {
|
|
|
50
70
|
}
|
|
51
71
|
/** Increment a Map counter, seeding a missing key at 1 — the single home of the
|
|
52
72
|
* `map.set(k, (map.get(k) ?? 0) + 1)` step, shared by the failure-spread kind tally
|
|
53
|
-
* (src/team/failure-spread.ts), the diff multiset's line tally (merge/merge-diff.ts), the
|
|
73
|
+
* (src/team/team-failure-spread.ts), the diff multiset's line tally (merge/merge-diff.ts), the
|
|
54
74
|
* section-title tally (backlog/backlog-structure.ts), the logs' per-type tally
|
|
55
|
-
* (cli/log-commands.ts), the flaky-check tally
|
|
75
|
+
* (cli/cli-log-commands.ts), the flaky-check tally
|
|
56
76
|
* (doctor/doctor-project-checks.ts), and the multi-runner base tally
|
|
57
77
|
* (orchestrator/orchestrator-scheduling.ts) — the exit-summary round's outcome tally
|
|
58
78
|
* (cli/cli-start-summary.ts) seeds its own copy of this step inline — so those sites cannot
|
|
@@ -125,11 +145,11 @@ export function trimToLast(arr, max) {
|
|
|
125
145
|
* order the same way. These two helpers are the single home of that rule; every surface that
|
|
126
146
|
* ranks counters by count calls one of them rather than re-implementing the comparator —
|
|
127
147
|
* `rankCountEntries` serves the `[key, count]` entry shape (src/report/report-render.ts's
|
|
128
|
-
* rankedRoleMap, src/team/failure-spread.ts's strongest-kind pick) and `rankByCount` serves
|
|
129
|
-
* callers whose counts are fields or computations on other shapes
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
148
|
+
* rankedRoleMap, src/team/team-failure-spread.ts's strongest-kind pick) and `rankByCount` serves
|
|
149
|
+
* callers whose counts are fields or computations on other shapes (src/failure/failure-cluster.ts's
|
|
150
|
+
* clusters and src/team/team-error-storm.ts's strongest-cause pick; failure-data.ts's outcome
|
|
151
|
+
* table and failure-time-spend.ts's time-and-spend table). `sortByValueDesc` is the deliberate
|
|
152
|
+
* sibling for the same value-descending shape that wants a code-unit tiebreak
|
|
133
153
|
* instead of localeCompare, and unlike these two it sorts its input array in place. The one
|
|
134
154
|
* deliberate exception to both orderings is ui/gui/gui-client-report-chart.ts's reportRoleOrder,
|
|
135
155
|
* which keeps its own copy by design: it runs in the browser, where harness modules cannot be
|
|
@@ -143,9 +163,9 @@ export function rankByCount(items, count, key) {
|
|
|
143
163
|
* aliases `items` when it already fits; `hidden` is `max(0, items.length - top)`, which the
|
|
144
164
|
* slice-length arithmetic yields without the explicit clamp (a slice never returns more than
|
|
145
165
|
* `top` even when the list is shorter). Shared by the merge-intent and conflict-hunk caps
|
|
146
|
-
* (merge-git.ts), the failure digest's loss-cause and message-cluster tops
|
|
147
|
-
*
|
|
148
|
-
*
|
|
166
|
+
* (merge-git.ts), the failure digest's loss-cause and message-cluster tops (failure-time-spend.ts
|
|
167
|
+
* and failure-cluster.ts), and the answered-questions cap (backlog-question-answer.ts), so those
|
|
168
|
+
* cuts cannot drift on how the remainder is counted. */
|
|
149
169
|
export function topAndHidden(items, top) {
|
|
150
170
|
const visible = items.slice(0, top);
|
|
151
171
|
return { visible, hidden: items.length - visible.length };
|
|
@@ -159,17 +179,9 @@ export function lowerMedian(values) {
|
|
|
159
179
|
const sorted = [...values].sort((a, b) => a - b);
|
|
160
180
|
return sorted[Math.floor((sorted.length - 1) / 2)];
|
|
161
181
|
}
|
|
162
|
-
/** Most-days-waited-first comparator, ties in ascending index (file order) — `b.days - a.days ||
|
|
163
|
-
* a.index - b.index`, the single home of that one-line ordering. Shared by the stale-questions
|
|
164
|
-
* feed (src/ui/alert-feeds.ts's staleOpenQuestions), which builds `{index, days}`
|
|
165
|
-
* rows off the same backlog scan, so its tie order cannot drift. The generic holds
|
|
166
|
-
* any row type carrying both fields; a row without a natural index passes its first-seen order. */
|
|
167
|
-
export function byDaysThenIndex(a, b) {
|
|
168
|
-
return b.days - a.days || a.index - b.index;
|
|
169
|
-
}
|
|
170
182
|
/** Ascending string comparator by UTF-16 code unit — the locale-independent order a bare `.sort()`
|
|
171
183
|
* uses, unlike `localeCompare` (which `rankByCount` uses for its tiebreak). Used directly by
|
|
172
|
-
* src/cli/cli-start-summary.ts, src/cli/completion.ts, src/cli/log-commands.ts,
|
|
184
|
+
* src/cli/cli-start-summary.ts, src/cli/completion.ts, src/cli/cli-log-commands.ts,
|
|
173
185
|
* src/history/history-data.ts, src/review/post-merge-review.ts, src/review/quarantine-render.ts,
|
|
174
186
|
* src/budget/budget.ts and src/change/change-data.ts, and by `sortByValueDesc` for its tiebreak. */
|
|
175
187
|
export function compareCodeUnits(a, b) {
|
|
@@ -178,7 +190,7 @@ export function compareCodeUnits(a, b) {
|
|
|
178
190
|
/** Rank `[key, value, …]` pairs by value descending, breaking ties in code-unit order
|
|
179
191
|
* (compareCodeUnits — the locale-independent order, unlike rankByCount's localeCompare), so
|
|
180
192
|
* equal values always order the same way. The single home of that comparator, which
|
|
181
|
-
* src/cli/log-commands.ts's per-type event-count lines and src/report/report-render.ts's
|
|
193
|
+
* src/cli/cli-log-commands.ts's per-type event-count lines and src/report/report-render.ts's
|
|
182
194
|
* mergeDone sort hand-rolled before; `…rest` lets a site carry extra trailing fields
|
|
183
195
|
* (report-render's third column) through the sort untouched. Mutates and returns the input
|
|
184
196
|
* array, matching the `.sort()` the call sites replaced. */
|
|
@@ -200,7 +212,7 @@ export function sortEntriesByKey(entries) {
|
|
|
200
212
|
return [...entries].sort(([a], [b]) => (a ?? "").localeCompare(b ?? ""));
|
|
201
213
|
}
|
|
202
214
|
/** rankByCount for `[key, count]` entry pairs (count first): src/report/report-render.ts's
|
|
203
|
-
* rankedRoleMap, src/team/failure-spread.ts's strongest-kind pick, and
|
|
215
|
+
* rankedRoleMap, src/team/team-failure-spread.ts's strongest-kind pick, and
|
|
204
216
|
* src/doctor/doctor-contention-checks.ts's check-wait and provider-wait rankings. */
|
|
205
217
|
export function rankCountEntries(entries) {
|
|
206
218
|
return rankByCount(entries, (e) => e[1], (e) => e[0]);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { classifyLock, releaseOwnedLockDir, tryClaimLockDir } from "./lock.js";
|
|
3
|
+
import { ensureParentDir, isDirectory, removeTree, walkUp } from "../files/files.js";
|
|
4
|
+
/** The host-wide suite-slot ledger that bounds how many project check scripts (an agent's bare
|
|
5
|
+
* `npm run test:gate` and the harness's own spawned check alike) run their suite step at once,
|
|
6
|
+
* sized to the same `config.maxConcurrentChecks` the in-process check permit enforces. Where
|
|
7
|
+
* withCheckPermit is one process-wide semaphore (every suite spawned by THIS process shares it),
|
|
8
|
+
* the ledger is files on disk so suites started by DIFFERENT processes — the harness and the pi
|
|
9
|
+
* agent's bash tools, or two agents at once — still contend for the same cap. It exists to close
|
|
10
|
+
* the gap where an agent's own suite run takes no team check permit at all (BUGS.md 2026-10-28),
|
|
11
|
+
* so the two mechanisms coexist: withCheckPermit keeps its in-process tier queue, reentrancy and
|
|
12
|
+
* callers exactly as they are, and the npm check scripts additionally claim a slot here.
|
|
13
|
+
*
|
|
14
|
+
* A slot is a lock dir under `.tumwater/state/check-permits/slot-<n>` using lock.ts's directory-
|
|
15
|
+
* and-pid protocol unchanged (tryClaimLockDir / releaseOwnedLockDir / classifyLock), so a killed
|
|
16
|
+
* suite's slot is reclaimed by lock.ts's stale rules (dead pid, no-pid grace, then age) on a later
|
|
17
|
+
* claim — no separate sweep. A waiter gives up after a bounded wait and runs the suite anyway with
|
|
18
|
+
* a warning rather than hanging past the check deadline or the bash tool's 600 s cap: the ledger
|
|
19
|
+
* is a scheduling throttle, not a correctness mutex, so a full ledger must degrade to "run
|
|
20
|
+
* anyway", never to "hang forever". */
|
|
21
|
+
/** How long acquireCheckSlot keeps trying for a free slot before giving up and returning null (the
|
|
22
|
+
* callers then run the suite unpermitted with a warning). Bounded well under the bash tool's 600 s
|
|
23
|
+
* cap so a full ledger never prices a run's own timeout. */
|
|
24
|
+
const CHECK_SLOT_WAIT_TIMEOUT_MS = 60_000;
|
|
25
|
+
/** The retry cadence between full passes over a full ledger — a slot frees quickly (a suite's
|
|
26
|
+
* release is the moment its check process exits), so a 200 ms repoll finds it almost immediately
|
|
27
|
+
* without hammering the filesystem. */
|
|
28
|
+
const CHECK_SLOT_RETRY_MS = 200;
|
|
29
|
+
/** The ancestor of `startDir` that holds the team's `.tumwater/state`, or null when none within the
|
|
30
|
+
* walk's bound does. The slot ledger lives under that state dir, so `scripts/check-permit.mjs`
|
|
31
|
+
* locates the root the same way the harness's own root lookup does (shared walkUp climb), and a
|
|
32
|
+
* suite run outside any team root has no ledger to contend for. Exported for tests. */
|
|
33
|
+
export function findTeamRoot(startDir = process.cwd()) {
|
|
34
|
+
return walkUp(startDir, 63, (dir) => (isDirectory(path.join(dir, ".tumwater", "state")) ? dir : null));
|
|
35
|
+
}
|
|
36
|
+
/** The slot directory for `index` of a ledger rooted at `root` (under `root/.tumwater/state`). One
|
|
37
|
+
* function owns the path so the acquirer, the releaser and the reclaim all name the same dir. */
|
|
38
|
+
export function checkSlotDir(root, index) {
|
|
39
|
+
return path.join(root, ".tumwater", "state", "check-permits", `slot-${index}`);
|
|
40
|
+
}
|
|
41
|
+
/** Claim one of the `cap` slots of `root`'s ledger, waiting up to `waitMs` for one to free. Walks
|
|
42
|
+
* slots `0..cap-1`, breaking any it finds lock.ts's classifyLock calls "stale" (a dead holder, a
|
|
43
|
+
* leftover no-pid dir past its grace, or an old dir), then trying to claim each by atomic publish;
|
|
44
|
+
* when none is free it sleeps and retries until the budget is spent, returning null. A stale slot
|
|
45
|
+
* is broken as encountered because a dead holder's dir would otherwise block that index forever. */
|
|
46
|
+
export async function acquireCheckSlot(root, cap, waitMs = CHECK_SLOT_WAIT_TIMEOUT_MS) {
|
|
47
|
+
// The ledger's parent must exist before any slot's atomic publish can create it: lock.ts's
|
|
48
|
+
// publishLockDir never makes its own parent (withStateLock is where ensureParentDir runs), so a
|
|
49
|
+
// first claim on a root with no check-permits dir would fail its mkdirSync and never acquire.
|
|
50
|
+
ensureParentDir(checkSlotDir(root, 0));
|
|
51
|
+
const startedAt = Date.now();
|
|
52
|
+
for (;;) {
|
|
53
|
+
for (let index = 0; index < cap; index++) {
|
|
54
|
+
const dir = checkSlotDir(root, index);
|
|
55
|
+
if (classifyLock(dir) === "stale")
|
|
56
|
+
removeTree(dir);
|
|
57
|
+
if (tryClaimLockDir(dir))
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
if (Date.now() - startedAt >= waitMs)
|
|
61
|
+
return null;
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, CHECK_SLOT_RETRY_MS));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Return a slot claimed by THIS process. Deleting a dir that another pid now owns must not
|
|
66
|
+
* happen (releaseOwnedLockDir checks the pid first); a slot stolen from under us stays for its
|
|
67
|
+
* new owner or for the stale rules. */
|
|
68
|
+
export function releaseCheckSlot(dir) {
|
|
69
|
+
releaseOwnedLockDir(dir);
|
|
70
|
+
}
|