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
|
@@ -59,16 +59,11 @@ function screenOperableKey(root, key) {
|
|
|
59
59
|
fail(fieldError);
|
|
60
60
|
}
|
|
61
61
|
else if (parsed.kind === "map") {
|
|
62
|
-
//
|
|
63
|
-
// keyed by provider, not a role id, and `config set` writes it without the role check —
|
|
64
|
-
// so get/unset must agree, or set would succeed while get/unset refused the provider as
|
|
65
|
-
// an "unknown role". A typo'd role id in the other two maps is refused the same way set's
|
|
62
|
+
// Role-keyed maps check the role catalog: a typo'd role id is refused the way set's
|
|
66
63
|
// validator treats it.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
fail(unknownRoleMessage(parsed.role, ids));
|
|
71
|
-
}
|
|
64
|
+
const ids = knownRoleIds(config);
|
|
65
|
+
if (!ids.includes(parsed.role))
|
|
66
|
+
fail(unknownRoleMessage(parsed.role, ids));
|
|
72
67
|
}
|
|
73
68
|
return { parsed, config };
|
|
74
69
|
}
|
|
@@ -89,72 +84,74 @@ function resolvedConfigValue(config, parsed, key) {
|
|
|
89
84
|
? record.roles?.[parsed.id]?.[parsed.field]
|
|
90
85
|
: record[canonicalConfigKey(key)];
|
|
91
86
|
}
|
|
92
|
-
/** The `
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
/** The `config get` verb: screen the key through screenOperableKey, then print its resolved
|
|
88
|
+
* value (defaults merged in) as JSON, or JSON null when nothing sets it. */
|
|
89
|
+
async function configGet(root, key) {
|
|
90
|
+
const { parsed, config } = screenOperableKey(root, key ?? "");
|
|
91
|
+
const value = resolvedConfigValue(config, parsed, key ?? "");
|
|
92
|
+
sayJsonLine(value === undefined ? null : value); // Absent optional key → JSON null.
|
|
93
|
+
}
|
|
94
|
+
/** The `config unset` verb: screen the key, remove it, and confirm what, if anything, was
|
|
95
|
+
* removed (or what the key now resolves to). */
|
|
96
|
+
async function configUnset(root, key) {
|
|
97
|
+
// cli.ts's arity gate requires exactly two args (`unset <key>`) before cmdConfig runs;
|
|
98
|
+
// this is the defensive tail for a direct caller, matching the bare-config tail below.
|
|
99
|
+
if (key === undefined)
|
|
100
|
+
fail(CONFIG_USAGE);
|
|
101
|
+
const k = canonicalConfigKey(key);
|
|
102
|
+
// The same screens get runs first, so a typo'd top-level key, role id, or role field
|
|
103
|
+
// fails identically — unsetConfigKey's own parse/field checks are the mutation-side
|
|
104
|
+
// backstop, not the wording the operator sees.
|
|
105
|
+
const { parsed } = screenOperableKey(root, k);
|
|
106
|
+
const result = unsetConfigKey(root, k);
|
|
107
|
+
if (!result.ok)
|
|
108
|
+
fail(result.error);
|
|
109
|
+
// The reload only feeds the message, so a failed one leaves `now` undefined.
|
|
110
|
+
const reloaded = loadConfigSafe(root).config;
|
|
111
|
+
const now = reloaded === undefined ? undefined : resolvedConfigValue(reloaded, parsed, k);
|
|
112
|
+
const resolves = now === undefined ? "" : JSON.stringify(now);
|
|
113
|
+
if (result.oldValue === undefined) {
|
|
114
|
+
say(`${k} is not set in tumwater.json, so nothing was removed` +
|
|
115
|
+
(now === undefined ? "" : ` (it resolves to ${resolves})`));
|
|
101
116
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (key === undefined)
|
|
106
|
-
fail(CONFIG_USAGE);
|
|
107
|
-
const k = canonicalConfigKey(key);
|
|
108
|
-
// The same screens get runs first, so a typo'd top-level key, role id, or role field
|
|
109
|
-
// fails identically — unsetConfigKey's own parse/field checks are the mutation-side
|
|
110
|
-
// backstop, not the wording the operator sees.
|
|
111
|
-
const { parsed } = screenOperableKey(root, k);
|
|
112
|
-
const result = unsetConfigKey(root, k);
|
|
113
|
-
if (!result.ok)
|
|
114
|
-
fail(result.error);
|
|
115
|
-
// The reload only feeds the message, so a failed one leaves `now` undefined.
|
|
116
|
-
const reloaded = loadConfigSafe(root).config;
|
|
117
|
-
const now = reloaded === undefined ? undefined : resolvedConfigValue(reloaded, parsed, k);
|
|
118
|
-
const resolves = now === undefined ? "" : JSON.stringify(now);
|
|
119
|
-
if (result.oldValue === undefined) {
|
|
120
|
-
say(`${k} is not set in tumwater.json, so nothing was removed` +
|
|
121
|
-
(now === undefined ? "" : ` (it resolves to ${resolves})`));
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const was = JSON.stringify(result.oldValue);
|
|
125
|
-
say(`unset ${k} (was ${was}${now === undefined ? "" : `; now resolves to ${resolves}`})`);
|
|
126
|
-
}
|
|
127
|
-
return;
|
|
117
|
+
else {
|
|
118
|
+
const was = JSON.stringify(result.oldValue);
|
|
119
|
+
say(`unset ${k} (was ${was}${now === undefined ? "" : `; now resolves to ${resolves}`})`);
|
|
128
120
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
say(
|
|
148
|
-
const note = result.note;
|
|
149
|
-
if (note)
|
|
150
|
-
say(`note: ${note}`);
|
|
151
|
-
// A bare `roles` set replaces the FILE's section, but the built-in catalog always merges
|
|
152
|
-
// per entry at load, so the replacement cannot disable a role by omission. Say so here
|
|
153
|
-
// rather than let the confirmation imply the role is gone.
|
|
154
|
-
if (key === "roles")
|
|
155
|
-
say("note: the built-in role catalog still merges per entry — disable a role with `tumwater config set roles.<id>.enabled false`, not by omitting it");
|
|
121
|
+
}
|
|
122
|
+
/** The `config set` verb: write one value through setConfigKey (refusing a flag-looking
|
|
123
|
+
* value first) and confirm the write or that it was already that value. */
|
|
124
|
+
async function configSet(root, key, rest) {
|
|
125
|
+
// A flag-looking value is a dropped `--` in disguise: `config set model --json` would
|
|
126
|
+
// otherwise take the literal string "--json" as the value (the JSON-parse fallback
|
|
127
|
+
// swallows any token) and validateConfig's string keys accept it silently — the same
|
|
128
|
+
// shape gui/gui-command.ts's --token gate refuses, where the value the next flag holds is never
|
|
129
|
+
// what the operator meant. Refuse before setConfigKey reads it; a value that really
|
|
130
|
+
// does begin with `--` has no legitimate shape among the config keys' values
|
|
131
|
+
// (identifiers, durations, windows, JSON scalars and arrays), so nothing is lost.
|
|
132
|
+
const raw = rest[0] ?? "";
|
|
133
|
+
if (raw.startsWith("--"))
|
|
134
|
+
fail(`config set got the flag-looking value ${JSON.stringify(raw)} instead of a value — is a flag missing its value, or did a token slip into the value's position? (e.g. \`tumwater config set model gpt-5\`)`);
|
|
135
|
+
const result = setConfigKey(root, key ?? "", raw);
|
|
136
|
+
if (!result.ok)
|
|
137
|
+
fail(result.error);
|
|
138
|
+
if (result.unchanged) {
|
|
139
|
+
say(`${canonicalConfigKey(key ?? "")} is already ${JSON.stringify(result.value)}, so tumwater.json was not written`);
|
|
156
140
|
return;
|
|
157
141
|
}
|
|
142
|
+
say(`set ${canonicalConfigKey(key ?? "")} to ${JSON.stringify(result.value)}`);
|
|
143
|
+
const note = result.note;
|
|
144
|
+
if (note)
|
|
145
|
+
say(`note: ${note}`);
|
|
146
|
+
// A bare `roles` set replaces the FILE's section, but the built-in catalog always merges
|
|
147
|
+
// per entry at load, so the replacement cannot disable a role by omission. Say so here
|
|
148
|
+
// rather than let the confirmation imply the role is gone.
|
|
149
|
+
if (key === "roles")
|
|
150
|
+
say("note: the built-in role catalog still merges per entry — disable a role with `tumwater config set roles.<id>.enabled false`, not by omitting it");
|
|
151
|
+
}
|
|
152
|
+
/** The bare-config tail: the whole resolved config as pretty JSON, refusing a surplus
|
|
153
|
+
* subcommand the arity gate somehow missed. */
|
|
154
|
+
async function configDump(root, sub) {
|
|
158
155
|
// Bare config, or anything else: the whole-config dump is the default, and cli.ts's arity
|
|
159
156
|
// check has already rejected a malformed get/set — this usage line is the defensive tail.
|
|
160
157
|
if (sub !== undefined)
|
|
@@ -164,3 +161,17 @@ export async function cmdConfig(root, args = []) {
|
|
|
164
161
|
fail(error); // validateConfig's message, via the standard fail()
|
|
165
162
|
sayJson(config);
|
|
166
163
|
}
|
|
164
|
+
/** The `tumwater config` command body: bare prints the resolved config; `get` and `unset`
|
|
165
|
+
* screen their key through screenOperableKey, while `set` writes through setConfigKey. Each
|
|
166
|
+
* verb's guards and confirmation wording live in one named helper — configGet, configUnset,
|
|
167
|
+
* configSet, configDump — that this dispatcher delegates to. */
|
|
168
|
+
export async function cmdConfig(root, args = []) {
|
|
169
|
+
const [sub, key, ...rest] = args;
|
|
170
|
+
if (sub === "get")
|
|
171
|
+
return configGet(root, key);
|
|
172
|
+
if (sub === "unset")
|
|
173
|
+
return configUnset(root, key);
|
|
174
|
+
if (sub === "set")
|
|
175
|
+
return configSet(root, key, rest);
|
|
176
|
+
return configDump(root, sub);
|
|
177
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { rejectUnknownArgs, JSON_FLAG, PROBLEMS_FLAG, KILL_ORPHANS_FLAG, COMMIT_BACKLOG_FLAG, RESYNC_INSTALL_FLAG, DROP_BROKEN_PINS_FLAG, RETIRE_LEFTOVERS_FLAG, } from "./cli-flag-specs.js";
|
|
2
|
+
import { say, sayJsonOrRender } from "./cli-output.js";
|
|
3
|
+
import { parseDoctorChecks, peelPositionals } from "./cli-command-args.js";
|
|
4
|
+
import { loadConfigSafe } from "../config/config.js";
|
|
5
|
+
/** Run one doctor side-effect (`--kill-orphans`, `--commit-backlog`, `--resync-install`,
|
|
6
|
+
* `--drop-broken-pins`, `--retire-leftovers`) and return its report payload for the optional
|
|
7
|
+
* `--json` run — the shape every doctor*Case side-effect wrapper follows: import the effect's
|
|
8
|
+
* module and run it only when its flag is present, then (text form only) print the effect's
|
|
9
|
+
* own lines before the report with a separating blank line when there is anything to print;
|
|
10
|
+
* `--json` keeps stdout one document, carrying the result on the payload instead. The lazy
|
|
11
|
+
* per-effect `load` keeps each module imported only when its flag is asked for. */
|
|
12
|
+
async function doctorSideEffect(args, flag, load) {
|
|
13
|
+
if (!args.includes(flag))
|
|
14
|
+
return undefined;
|
|
15
|
+
const { run, lines } = await load();
|
|
16
|
+
const payload = await run();
|
|
17
|
+
// The text form prints the effect before the report; --json keeps stdout one document.
|
|
18
|
+
if (!args.includes("--json")) {
|
|
19
|
+
const out = lines(payload);
|
|
20
|
+
for (const line of out)
|
|
21
|
+
say(line);
|
|
22
|
+
if (out.length > 0)
|
|
23
|
+
say("");
|
|
24
|
+
}
|
|
25
|
+
return payload;
|
|
26
|
+
}
|
|
27
|
+
async function doctorKillOrphans(root, args) {
|
|
28
|
+
return doctorSideEffect(args, "--kill-orphans", async () => {
|
|
29
|
+
const { killOrphans, orphanKillLines } = await import("../doctor/doctor-orphans.js");
|
|
30
|
+
return { run: () => killOrphans(root), lines: orphanKillLines };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function doctorCommitBacklog(root, args) {
|
|
34
|
+
return doctorSideEffect(args, "--commit-backlog", async () => {
|
|
35
|
+
const { commitUncommittedBacklog, backlogCommitLines } = await import("../doctor/doctor-backlog.js");
|
|
36
|
+
const { config } = loadConfigSafe(root); // the report's own config, so base resolution and the off-base skip match the `uncommitted backlog` row's
|
|
37
|
+
return { run: () => commitUncommittedBacklog(root, config), lines: backlogCommitLines };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function doctorResyncInstall(root, args) {
|
|
41
|
+
return doctorSideEffect(args, "--resync-install", async () => {
|
|
42
|
+
const { resyncRootInstall, rootInstallResyncLines } = await import("../build/dep-install.js");
|
|
43
|
+
return { run: () => resyncRootInstall(root), lines: rootInstallResyncLines };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function doctorDropBrokenPins(root, args) {
|
|
47
|
+
return doctorSideEffect(args, "--drop-broken-pins", async () => {
|
|
48
|
+
const { dropBrokenPins, brokenPinDropLines } = await import("../doctor/doctor-contention-checks.js");
|
|
49
|
+
return { run: () => dropBrokenPins(root), lines: brokenPinDropLines };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function doctorRetireLeftovers(root, args) {
|
|
53
|
+
return doctorSideEffect(args, "--retire-leftovers", async () => {
|
|
54
|
+
const { retireLeftovers, leftoverRetireLines } = await import("../doctor/doctor-contention-checks.js");
|
|
55
|
+
const { config } = loadConfigSafe(root); // the row's own config, so the reap and the report agree on what is retirable
|
|
56
|
+
return { run: () => retireLeftovers(root, config), lines: leftoverRetireLines };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function doctorPayload(payload, parts) {
|
|
60
|
+
const out = { ...payload };
|
|
61
|
+
if (parts.leftoverRetire !== undefined)
|
|
62
|
+
out.leftoverRetire = parts.leftoverRetire;
|
|
63
|
+
if (parts.brokenPinDrop !== undefined)
|
|
64
|
+
out.brokenPinDrop = parts.brokenPinDrop;
|
|
65
|
+
if (parts.resyncInstall !== undefined)
|
|
66
|
+
out.resyncInstall = parts.resyncInstall;
|
|
67
|
+
if (parts.backlogCommit !== undefined)
|
|
68
|
+
out.backlogCommit = parts.backlogCommit;
|
|
69
|
+
if (parts.orphanKill !== undefined)
|
|
70
|
+
out.orphanKill = parts.orphanKill;
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** `tumwater doctor`: report what in the environment is keeping the team from running, and
|
|
74
|
+
* run the optional repair side-effects. No ready-repo gate: doctor's job is to report WHY the
|
|
75
|
+
* environment isn't ready, so it must run outside a git repo and print fail lines rather than
|
|
76
|
+
* throwing. */
|
|
77
|
+
export async function cmdDoctor(root, args) {
|
|
78
|
+
const { positionals: checkNames, rest: doctorFlags } = peelPositionals(args);
|
|
79
|
+
rejectUnknownArgs("doctor", doctorFlags, [JSON_FLAG, PROBLEMS_FLAG, KILL_ORPHANS_FLAG, COMMIT_BACKLOG_FLAG, RESYNC_INSTALL_FLAG, DROP_BROKEN_PINS_FLAG, RETIRE_LEFTOVERS_FLAG]);
|
|
80
|
+
const only = parseDoctorChecks(checkNames);
|
|
81
|
+
const { runDoctor } = await import("../doctor/doctor.js");
|
|
82
|
+
const parts = {
|
|
83
|
+
orphanKill: await doctorKillOrphans(root, args),
|
|
84
|
+
backlogCommit: await doctorCommitBacklog(root, args),
|
|
85
|
+
resyncInstall: await doctorResyncInstall(root, args),
|
|
86
|
+
brokenPinDrop: await doctorDropBrokenPins(root, args),
|
|
87
|
+
leftoverRetire: await doctorRetireLeftovers(root, args),
|
|
88
|
+
};
|
|
89
|
+
const { renderDoctor, renderDoctorProblems, problemsOnly } = await import("../doctor/doctor-render.js");
|
|
90
|
+
const report = await runDoctor(root, undefined, undefined, only);
|
|
91
|
+
// --json prints the collector's own payload (the DoctorReport object), not a re-parse of
|
|
92
|
+
// the render — the `report --json` precedent. The exit-code contract below holds in both
|
|
93
|
+
// forms: 1 when any check fails, 0 otherwise; warnings never fail the exit.
|
|
94
|
+
// --problems keeps the full report for the render (it counts the hidden ok rows) and
|
|
95
|
+
// hands --json the filtered one; the exit code below reads the full report either way.
|
|
96
|
+
// --kill-orphans adds the kill result to the JSON object as `orphanKill`.
|
|
97
|
+
if (args.includes("--problems"))
|
|
98
|
+
sayJsonOrRender(args, args.includes("--json") ? doctorPayload(problemsOnly(report), parts) : report, renderDoctorProblems);
|
|
99
|
+
else
|
|
100
|
+
sayJsonOrRender(args, args.includes("--json") ? doctorPayload(report, parts) : report, renderDoctor);
|
|
101
|
+
if (report.checks.some((c) => c.level === "fail"))
|
|
102
|
+
process.exitCode = 1; // Warnings never fail the exit.
|
|
103
|
+
}
|
|
@@ -465,35 +465,55 @@ export const GUI_FLAG_SPECS = [PORT_FLAG, ALL_INTERFACES_FLAG, TOKEN_FLAG];
|
|
|
465
465
|
export function rejectUnknownArgs(command, args, specs) {
|
|
466
466
|
if (args.length === 0)
|
|
467
467
|
return;
|
|
468
|
-
const claim =
|
|
469
|
-
for (const spec of specs)
|
|
470
|
-
for (const name of spec.names)
|
|
471
|
-
claim.set(name, spec);
|
|
468
|
+
const claim = flagClaims(specs);
|
|
472
469
|
const seen = new Set();
|
|
473
470
|
const consumed = new Array(args.length).fill(false);
|
|
474
471
|
for (let i = 0; i < args.length; i++) {
|
|
475
472
|
if (consumed[i])
|
|
476
473
|
continue;
|
|
477
474
|
const arg = args[i] ?? ""; // Unreachable fallback: the loop bound guarantees a token here.
|
|
478
|
-
const spec = claim.get(arg);
|
|
479
|
-
if (spec === undefined) {
|
|
480
|
-
// An equals-form token names a real flag; refuse it with its own message before the
|
|
481
|
-
// generic "unknown argument" misreports `--role=feature` as a misspelling.
|
|
482
|
-
rejectEqualsForm(arg, specs);
|
|
483
|
-
if (specs.length === 0)
|
|
484
|
-
fail(`tumwater ${command} takes no arguments${gotSuffix(arg)}`);
|
|
485
|
-
failUnknownArgument(command, arg, specs);
|
|
486
|
-
}
|
|
475
|
+
const spec = claim.get(arg) ?? rejectUnknownFlag(command, arg, specs);
|
|
487
476
|
rejectRepeatFlag(seen.has(spec), spec.names[0]);
|
|
488
477
|
seen.add(spec);
|
|
489
|
-
|
|
490
|
-
fail(spec.missingValue ?? `${spec.names[0]} needs a value`);
|
|
491
|
-
// The value exists (the trailing-no-value case failed above): name a malformed one now,
|
|
492
|
-
// before the ready-repo gate or any environment check can mask it.
|
|
493
|
-
if (spec.value)
|
|
494
|
-
spec.validate?.(args[i + 1] ?? "");
|
|
495
|
-
const n = spec.value ? 2 : 1;
|
|
496
|
-
for (let j = 0; j < n && i + j < args.length; j++)
|
|
497
|
-
consumed[i + j] = true;
|
|
478
|
+
consumeFlagOccurrence(spec, args, i, consumed);
|
|
498
479
|
}
|
|
499
480
|
}
|
|
481
|
+
/** The spelling-to-spec claim map rejectUnknownArgs keyed its loop on: every accepted flag
|
|
482
|
+
* spelling maps to the spec declaring it, alias spellings (-f and --follow) sharing one spec
|
|
483
|
+
* entry. Built once up front so a token look-up is a map get, not a scan. */
|
|
484
|
+
function flagClaims(specs) {
|
|
485
|
+
const claim = new Map();
|
|
486
|
+
for (const spec of specs)
|
|
487
|
+
for (const name of spec.names)
|
|
488
|
+
claim.set(name, spec);
|
|
489
|
+
return claim;
|
|
490
|
+
}
|
|
491
|
+
/** The one refusal for a token naming no known flag: an equals-form token first reroutes
|
|
492
|
+
* through rejectEqualsForm, which names a REAL flag's offending `--flag=value` spelling
|
|
493
|
+
* before the generic "unknown argument" misreports `--role=feature` as a misspelling; then
|
|
494
|
+
* a command with no flags at all fails with the takes-no-arguments wording; otherwise the
|
|
495
|
+
* shared unknown-argument wording (with its did-you-mean hint) fires. Returns never, so the
|
|
496
|
+
* caller's `claim.get(arg) ?? rejectUnknownFlag(...)` narrows `spec` to a known FlagSpec. */
|
|
497
|
+
function rejectUnknownFlag(command, arg, specs) {
|
|
498
|
+
// An equals-form token names a real flag; refuse it with its own message before the
|
|
499
|
+
// generic "unknown argument" misreports `--role=feature` as a misspelling.
|
|
500
|
+
rejectEqualsForm(arg, specs);
|
|
501
|
+
if (specs.length === 0)
|
|
502
|
+
fail(`tumwater ${command} takes no arguments${gotSuffix(arg)}`);
|
|
503
|
+
failUnknownArgument(command, arg, specs);
|
|
504
|
+
}
|
|
505
|
+
/** Consume one recognised flag occurrence: fail a valued flag left without its value, name a
|
|
506
|
+
* malformed value via the spec's validate call, and mark the one or two tokens (the flag and,
|
|
507
|
+
* for a valued flag, its value) as consumed so a later loop iteration skips them. The value
|
|
508
|
+
* is marked only after the value checks pass, so a failed value is never silently swallowed. */
|
|
509
|
+
function consumeFlagOccurrence(spec, args, i, consumed) {
|
|
510
|
+
if (spec.value && i === args.length - 1)
|
|
511
|
+
fail(spec.missingValue ?? `${spec.names[0]} needs a value`);
|
|
512
|
+
// The value exists (the trailing-no-value case failed above): name a malformed one now,
|
|
513
|
+
// before the ready-repo gate or any environment check can mask it.
|
|
514
|
+
if (spec.value)
|
|
515
|
+
spec.validate?.(args[i + 1] ?? "");
|
|
516
|
+
const n = spec.value ? 2 : 1;
|
|
517
|
+
for (let j = 0; j < n && i + j < args.length; j++)
|
|
518
|
+
consumed[i + j] = true;
|
|
519
|
+
}
|
package/dist/src/cli/cli-init.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** `tumwater init`'s implementation (src/init/init.ts does the work; this prints the report).
|
|
2
2
|
* Split out of cli/cli-start.ts, whose name is for start alone: every command it dispatches
|
|
3
|
-
* delegates to its own module (
|
|
4
|
-
*
|
|
3
|
+
* delegates to its own module (cli-config-commands.ts, cli-log-commands.ts, doctor.ts, …), and init
|
|
4
|
+
* is no exception. cli.ts stays the pure dispatcher. */
|
|
5
5
|
import { say } from "./cli-output.js";
|
|
6
6
|
import { parseInitArgs } from "./cli-command-args.js";
|
|
7
7
|
import { initProject } from "../init/init.js";
|
|
@@ -12,8 +12,7 @@ import { templateCatalog } from "../init/init-templates.js";
|
|
|
12
12
|
export async function cmdInit(root, args) {
|
|
13
13
|
const { prompt, branch, template, listTemplates, adopt, dryRun } = parseInitArgs(args);
|
|
14
14
|
if (listTemplates) {
|
|
15
|
-
|
|
16
|
-
say(`${id} — ${description}`);
|
|
15
|
+
printTemplateCatalog();
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
const result = await initProject(root, prompt, branch ?? undefined, {
|
|
@@ -21,21 +20,44 @@ export async function cmdInit(root, args) {
|
|
|
21
20
|
dryRun,
|
|
22
21
|
template: template ?? undefined,
|
|
23
22
|
});
|
|
23
|
+
printInitIntro(result);
|
|
24
|
+
if (result.dryRun) {
|
|
25
|
+
printDryRun(result);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
printInitReport(result);
|
|
29
|
+
}
|
|
30
|
+
/** Print the `tumwater init --list-templates` catalog: one line per template. */
|
|
31
|
+
function printTemplateCatalog() {
|
|
32
|
+
for (const { id, description } of templateCatalog())
|
|
33
|
+
say(`${id} — ${description}`);
|
|
34
|
+
}
|
|
35
|
+
/** Print the `template:`/adopt intro lines shared by the dry-run and completed paths. */
|
|
36
|
+
function printInitIntro(result) {
|
|
24
37
|
if (result.template !== "blank")
|
|
25
38
|
say(`template: ${result.template}`);
|
|
26
39
|
if (result.adopted) {
|
|
27
40
|
say(`${result.dryRun ? "would adopt" : "adopting"} an existing repo: the project brief goes in TUMWATER.md and README.md is left untouched`);
|
|
28
41
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
say(`dry run — would
|
|
35
|
-
say(`would leave alone: ${list(result.leftAlone)}`);
|
|
36
|
-
say("nothing written; re-run without --dry-run to apply");
|
|
37
|
-
return;
|
|
42
|
+
}
|
|
43
|
+
/** Print the dry-run preview: repo line, the would-create/would-leave-alone lists, and the
|
|
44
|
+
* "nothing written" line. */
|
|
45
|
+
function printDryRun(result) {
|
|
46
|
+
if (result.repoInitialized && result.branch) {
|
|
47
|
+
say(`dry run — would initialize a new git repository on branch ${result.branch}`);
|
|
38
48
|
}
|
|
49
|
+
const list = (names) => (names.length > 0 ? names.join(", ") : "nothing");
|
|
50
|
+
say(`dry run — would create: ${list(result.created)}`);
|
|
51
|
+
say(`would leave alone: ${list(result.leftAlone)}`);
|
|
52
|
+
say("nothing written; re-run without --dry-run to apply");
|
|
53
|
+
}
|
|
54
|
+
/** Format the created-files line, appending `(committed)` when the repo was seeded. */
|
|
55
|
+
function createdLine(result) {
|
|
56
|
+
return `created ${result.created.join(", ")}${result.committed ? " (committed)" : ""}`;
|
|
57
|
+
}
|
|
58
|
+
/** Print the completed-init report: the already-initialized no-op, the repo line, the
|
|
59
|
+
* created-files line, the bootstrap note, and the "next:" line. */
|
|
60
|
+
function printInitReport(result) {
|
|
39
61
|
if (result.created.length === 0) {
|
|
40
62
|
say("already initialized; nothing to do");
|
|
41
63
|
return;
|
|
@@ -43,7 +65,7 @@ export async function cmdInit(root, args) {
|
|
|
43
65
|
if (result.repoInitialized && result.branch) {
|
|
44
66
|
say(`initialized a new git repository on branch ${result.branch}`);
|
|
45
67
|
}
|
|
46
|
-
say(
|
|
68
|
+
say(createdLine(result));
|
|
47
69
|
if (result.bootstrap !== null) {
|
|
48
70
|
say(`fresh project — maintenance roles wait until ${result.bootstrap} plans are done; remove "bootstrap" from tumwater.json to end that early`);
|
|
49
71
|
}
|
|
@@ -103,6 +103,11 @@ export function followUntilParentGone(file, offset, onLines, seams) {
|
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
|
+
/** The `-n` ceiling for the event-log dump (`tumwater logs -n <count>`): a terminal tail has
|
|
107
|
+
* no use for more than this many scanned events, and bounding it keeps an accidental
|
|
108
|
+
* `-n 999999999` from traversing the whole event file. Sister counts are bounded the same way
|
|
109
|
+
* (history's HISTORY_MAX_RUNS, report's REPORT_MAX_DAYS); the GUI clamps its logs counts too. */
|
|
110
|
+
const LOGS_MAX_EVENTS = 100_000;
|
|
106
111
|
/** `tumwater logs [-f] [-n <count>] [--since <duration>] [--until <duration>]
|
|
107
112
|
* [--role <id>[,<id>...]] [--transcript <id>] [--prompt]
|
|
108
113
|
* [--grep <text>]`: follow or dump the harness event log, with `--since` a bounded
|
|
@@ -126,7 +131,7 @@ export async function cmdLogs(root, args, seams = {}) {
|
|
|
126
131
|
return;
|
|
127
132
|
}
|
|
128
133
|
const nRaw = flagValue(feed.rest, "-n");
|
|
129
|
-
const limit = nRaw !== null ? parseCountFlag("-n", nRaw) : 50;
|
|
134
|
+
const limit = nRaw !== null ? parseCountFlag("-n", nRaw, LOGS_MAX_EVENTS) : 50;
|
|
130
135
|
// The event feed (no --transcript) never needs the config at all: parseRoleScope reads it only
|
|
131
136
|
// when the flag is present.
|
|
132
137
|
const role = parseRoleScope(root, feed.rest, "--transcript", LOGS_TRANSCRIPT_VALUE_ERROR);
|
|
@@ -278,39 +283,54 @@ function sayLogsWindowNoMatch(feed, windowPhrase) {
|
|
|
278
283
|
else
|
|
279
284
|
say(feed.roles !== null ? `${feed.roleEmpty} ${windowPhrase}` : `no events ${windowPhrase}`);
|
|
280
285
|
}
|
|
281
|
-
/** The `-n` tail view and its follow:
|
|
282
|
-
*
|
|
283
|
-
* `followUntilParentGone` seeded from `tail.coveredEnd`. It owns the follow-loop locals. */
|
|
286
|
+
/** The `-n` tail view and its `-f` follow: read the filtered tail, print the `--count`
|
|
287
|
+
* tally or the per-event rows, and seed the follow; each job lives in a named helper. */
|
|
284
288
|
async function sayLogsTail(root, feed, limit, seams) {
|
|
285
|
-
|
|
286
|
-
// appears, so `-n 200 --grep merge_failed` may print 3 rows from 200 scanned. The read is
|
|
287
|
-
// the tail-with-end shape (not the per-poll cached readEvents) so the follow below can seed
|
|
288
|
-
// from the byte end this very read covered instead of a later stat of the file.
|
|
289
|
-
const tail = readEventsTailWithEnd(root, limit);
|
|
290
|
-
const shown = feed.grepLower !== null || feed.type !== null || feed.roles !== null || feed.notable ? tail.events.filter(feed.filter) : tail.events;
|
|
291
|
-
// `--count` over the -n view answers with the tally of exactly the filtered rows the plain
|
|
292
|
-
// view would print, and carries over the plain view's empty-match prose below (a `--type`
|
|
293
|
-
// or `--grep` that matches nothing names its value; an empty unfiltered -n view prints
|
|
294
|
-
// nothing, as the plain view does). In JSON mode the empty output is the answer.
|
|
289
|
+
const { shown, coveredEnd } = readTailShown(root, feed, limit);
|
|
295
290
|
if (feed.count) {
|
|
291
|
+
// `--count` over the -n view answers with the tally of exactly the filtered rows the plain
|
|
292
|
+
// view would print, and carries over the plain view's empty-match prose (a `--type` or
|
|
293
|
+
// `--grep` that matches nothing names its value; an empty unfiltered -n view prints
|
|
294
|
+
// nothing, as the plain view does). In JSON mode the empty output is the answer.
|
|
296
295
|
if (shown.length !== 0)
|
|
297
296
|
sayTally(shown, feed.json);
|
|
298
|
-
else
|
|
299
|
-
|
|
297
|
+
else
|
|
298
|
+
sayTailEmptyNote(shown, feed);
|
|
300
299
|
return;
|
|
301
300
|
}
|
|
302
301
|
for (const e of shown)
|
|
303
302
|
sayEventLine(e, feed.json);
|
|
304
303
|
if (!feed.follow) {
|
|
305
|
-
|
|
306
|
-
// for either filter, naming the flag's value(s) whose match came up empty so the operator
|
|
307
|
-
// knows what to loosen.
|
|
308
|
-
if (shown.length === 0 && !feed.json && (feed.filtersGrepOrType || feed.roles !== null || feed.notable))
|
|
309
|
-
sayLogsTailNoMatch(feed);
|
|
304
|
+
sayTailEmptyNote(shown, feed);
|
|
310
305
|
return;
|
|
311
306
|
}
|
|
307
|
+
await followLogsTail(root, feed, coveredEnd, seams);
|
|
308
|
+
}
|
|
309
|
+
/** How `-n` scans versus prints: `limit` bounds the scan, not the print — the matched
|
|
310
|
+
* subset of the last `limit` events is what appears, so `-n 200 --grep merge_failed` may
|
|
311
|
+
* print 3 rows from 200 scanned. The read is the tail-with-end shape (not the per-poll
|
|
312
|
+
* cached readEvents) so the follow can seed from the byte end this very read covered
|
|
313
|
+
* instead of a later stat of the file. It also owns the share of output to print: the
|
|
314
|
+
* `shown` rows in read order. */
|
|
315
|
+
function readTailShown(root, feed, limit) {
|
|
316
|
+
const tail = readEventsTailWithEnd(root, limit);
|
|
317
|
+
const shown = feed.grepLower !== null || feed.type !== null || feed.roles !== null || feed.notable ? tail.events.filter(feed.filter) : tail.events;
|
|
318
|
+
return { shown, coveredEnd: tail.coveredEnd };
|
|
319
|
+
}
|
|
320
|
+
/** The empty-match guard shared by the `--count` branch and the no-follow plain branch: the
|
|
321
|
+
* empty-match line is prose (in JSON mode the empty output is the answer) and fires for
|
|
322
|
+
* either filter, naming the flag's value(s) whose match came up empty so the operator knows
|
|
323
|
+
* what to loosen. */
|
|
324
|
+
function sayTailEmptyNote(shown, feed) {
|
|
325
|
+
if (shown.length === 0 && !feed.json && (feed.filtersGrepOrType || feed.roles !== null || feed.notable))
|
|
326
|
+
sayLogsTailNoMatch(feed);
|
|
327
|
+
}
|
|
328
|
+
/** The `-f` follow: resolve the event-log path and stream every newly landed event through
|
|
329
|
+
* the same `feed.filter` as the seeded window, seeded from `coveredEnd` (the byte end the
|
|
330
|
+
* initial read covered) and holding the filter across rotation. */
|
|
331
|
+
async function followLogsTail(root, feed, coveredEnd, seams) {
|
|
312
332
|
const file = eventsLogPath(root);
|
|
313
|
-
// Seed the follow from the byte end the initial read covered (
|
|
333
|
+
// Seed the follow from the byte end the initial read covered (coveredEnd), mirroring
|
|
314
334
|
// cmdLogsTranscript's tail.end seed: an event appended between the read and the follow's
|
|
315
335
|
// start lies past the printed window but at or above this offset, so the follow's first
|
|
316
336
|
// poll delivers it — seeding from a fresh statOrNull(file).size instead would start past
|
|
@@ -318,7 +338,7 @@ async function sayLogsTail(root, feed, limit, seams) {
|
|
|
318
338
|
// log scans to coveredEnd 0, and followFile tolerates a missing file (its poll re-stats
|
|
319
339
|
// every interval), so a read-only command never leaves a harness state file behind — the
|
|
320
340
|
// module contract above promises writes to stdout only.
|
|
321
|
-
await followUntilParentGone(file,
|
|
341
|
+
await followUntilParentGone(file, coveredEnd, (lines) => {
|
|
322
342
|
for (const line of lines.filter(Boolean)) {
|
|
323
343
|
const e = parseEventLine(line);
|
|
324
344
|
// The filter holds across rotation: every event the follow callback sees goes through
|
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { fail, say } from "./cli-output.js";
|
|
3
3
|
import { loadConfig } from "../config/config.js";
|
|
4
4
|
import { TUMWATER_SOURCE } from "../events/events.js";
|
|
5
|
-
import { notifyCommandEnv, notifyExitGloss } from "../events/notify.js";
|
|
5
|
+
import { notifyCommandEnv, notifyExitGloss } from "../events/event-notify.js";
|
|
6
6
|
/** The one representative allowlisted event `tumwater notify --test` fires: a failed merge
|
|
7
7
|
* with source `tumwater`. Fixed and opinionated — the goal is to prove the hook runs, not to
|
|
8
8
|
* replay a specific page, so there are no per-type or per-message flags. */
|
|
@@ -16,7 +16,7 @@ const NOTIFY_TEST_MESSAGE = "tumwater notify --test";
|
|
|
16
16
|
* Generous enough not to misclassify a hook that is merely slow. */
|
|
17
17
|
export const NOTIFY_TEST_TIMEOUT_MS = 10_000;
|
|
18
18
|
/** Run one notify hook to completion, returning its exit code. Unlike the notifier's detached
|
|
19
|
-
* fire-and-forget spawn (src/events/notify.ts), this waits with inherited stdio by
|
|
19
|
+
* fire-and-forget spawn (src/events/event-notify.ts), this waits with inherited stdio by
|
|
20
20
|
* default (the dashboard passes "ignore"), so the
|
|
21
21
|
* operator sees the hook's own output and its exit. A shell that cannot start resolves 127 —
|
|
22
22
|
* the conventional "command not found" code. A hook that does not finish within `timeoutMs`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The read-only query commands' bodies (status, diff, queue, backlog), split out of
|
|
2
2
|
* cli.ts's dispatcher: main() routes to a cmd* here exactly as it routes to operator-commands's
|
|
3
|
-
* cmdAbort or config-commands's cmdConfig, and cli.ts keeps only routing and the help/version
|
|
3
|
+
* cmdAbort or cli-config-commands's cmdConfig, and cli.ts keeps only routing and the help/version
|
|
4
4
|
* surfaces. Each command owns its argument gate, ready-repo gating decision, and output shape,
|
|
5
5
|
* with the wording and gating order moved verbatim from main()'s cases. */
|
|
6
6
|
import { fail, say, sayJson, sayJsonOrRender } from "./cli-output.js";
|
|
@@ -25,7 +25,7 @@ import { renderMergeQueue, scopeMergeQueue } from "../merge/merge-queue-render.j
|
|
|
25
25
|
import { mergeChanges } from "../merge/merge-slot.js";
|
|
26
26
|
import { shortSha } from "../text/format.js";
|
|
27
27
|
import { roleLabel } from "../roles/role-labels.js";
|
|
28
|
-
import { orchestratorAlive } from "../team/orchestrator-info.js";
|
|
28
|
+
import { orchestratorAlive } from "../team/team-orchestrator-info.js";
|
|
29
29
|
import { queueInFlight } from "../status/status-polls.js";
|
|
30
30
|
import { roleInScope } from "../roles/runner-ids.js";
|
|
31
31
|
/** `tumwater status [-f/--follow]`: the team snapshot as rendered text or as the JSON
|