tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** Pure formatting and description of a build check's failure output — split out of build-check.ts,
|
|
3
2
|
* which keeps the execution side (detect → run → classify, the toolchain probe, the process-group
|
|
4
3
|
* runner). This module turns a check's raw combined output and a BuildCheckOutcome into the machine
|
|
@@ -6,7 +5,7 @@
|
|
|
6
5
|
* review-verdict.ts's parseVerdict), the clipped output tail (clipBuildTail), the one-line headline
|
|
7
6
|
* a warning names the failure by (failureHeadline), the check's human-facing command name
|
|
8
7
|
* (describeCheck), and the reasons a red check hands its author (checkFailureReasons). Every
|
|
9
|
-
* consumer of a check's tail — the review gate, the merge path, the red-main gate, the
|
|
8
|
+
* consumer of a check's tail — the review gate, the merge path, the red-main gate, the run prompt
|
|
10
9
|
* — imports from here, so "which line is the headline" and "how much output survives" have one
|
|
11
10
|
* answer each. Type-only back-reference to build-check.ts for BuildCheckOutcome (no runtime cycle);
|
|
12
11
|
* BuildCheck comes from build-check-detect.ts. */
|
|
@@ -30,17 +29,47 @@ const ERROR_MESSAGE_LINE = /^\S*Error\b[^:]*:\s/;
|
|
|
30
29
|
* `✖` line but the `✖ failing tests:` section header. The run's LAST one is the failure whose
|
|
31
30
|
* detail ends the output, so it names the test the tail's message and stack belong to. */
|
|
32
31
|
const FAILED_TEST_LINE = /^✖ (?!failing tests:)/;
|
|
32
|
+
/** A stack frame inside Node's own runtime (the test runner, the task queue): the same in every
|
|
33
|
+
* failure and silent about the code under test. Matches `at fn (node:internal/…)` and a bare
|
|
34
|
+
* `at node:internal/…`; not frames in other built-in modules (`node:fs`, `node:async_hooks`) nor
|
|
35
|
+
* in project files. */
|
|
36
|
+
const NODE_INTERNAL_FRAME = /^at\s(?:.*\()?node:internal\//;
|
|
37
|
+
/** How many failing test titles parseFailingTests keeps; a list that long may be cut short. */
|
|
38
|
+
export const FAILING_TESTS_MAX = 50;
|
|
39
|
+
/** The titles of the tests a check's COMPLETE output reports failed: every line FAILED_TEST_LINE
|
|
40
|
+
* matches, its trailing duration stripped, deduplicated (node:test prints a failure in the tree and
|
|
41
|
+
* again under `✖ failing tests:`), in output order, at most FAILING_TESTS_MAX. Read the full
|
|
42
|
+
* output, before clipBuildTail cuts it. Empty for output from another runner. */
|
|
43
|
+
export function parseFailingTests(output) {
|
|
44
|
+
const titles = new Set();
|
|
45
|
+
for (const raw of output.split("\n")) {
|
|
46
|
+
const line = raw.trim();
|
|
47
|
+
if (!FAILED_TEST_LINE.test(line))
|
|
48
|
+
continue;
|
|
49
|
+
const title = line.slice(2).replace(/ \([\d.]+m?s\)$/, "").trim();
|
|
50
|
+
if (title !== "")
|
|
51
|
+
titles.add(title);
|
|
52
|
+
if (titles.size >= FAILING_TESTS_MAX)
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
return [...titles];
|
|
56
|
+
}
|
|
33
57
|
/** Keep the TAIL of a build's combined output: last ≤10 meaningful lines, each via clipReason —
|
|
34
|
-
* so a chatty build cannot bloat persisted state or the injected next-
|
|
58
|
+
* so a chatty build cannot bloat persisted state or the injected next-run note. Blank lines
|
|
35
59
|
* and npm's own script banner (`> pkg@1.0 script`, `> <command>`) are dropped: they name the
|
|
36
60
|
* script that ran, not what broke in it. When the ten-line window cuts off the error-message
|
|
37
61
|
* line that sits above the stack, the nearest such line is kept as well, so failureHeadline can
|
|
38
62
|
* name the failure instead of a stack frame or an error property; likewise the failed test's
|
|
39
63
|
* name line (FAILED_TEST_LINE) above it, so the headline says WHICH test — an assertion's
|
|
40
64
|
* message alone (`Expected values to be strictly equal:`) names none, and a flake reported only
|
|
41
|
-
* that way could not be traced (BUGS.md 2026-09-30).
|
|
65
|
+
* that way could not be traced (BUGS.md 2026-09-30). Frames NODE_INTERNAL_FRAME matches are dropped
|
|
66
|
+
* first so the window keeps more of the assertion's diff (output made only of them is kept as is);
|
|
67
|
+
* when the dropped frame is `at async Test.processPendingSubtests (…) {`, the property dump's
|
|
68
|
+
* opening `{` goes with it. Twelve lines at most. */
|
|
42
69
|
export function clipBuildTail(output) {
|
|
43
|
-
const
|
|
70
|
+
const meaningful = output.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !/^>\s/.test(l));
|
|
71
|
+
const external = meaningful.filter((l) => !NODE_INTERNAL_FRAME.test(l));
|
|
72
|
+
const lines = external.length > 0 ? external : meaningful;
|
|
44
73
|
const tail = lines.slice(-10);
|
|
45
74
|
const cut = [...lines.slice(0, -10)].reverse();
|
|
46
75
|
const message = cut.find((l) => ERROR_MESSAGE_LINE.test(l));
|
|
@@ -89,18 +118,21 @@ export function failureHeadline(tail) {
|
|
|
89
118
|
return what !== undefined ? `${name} — ${what}` : name;
|
|
90
119
|
return what ?? tail[0];
|
|
91
120
|
}
|
|
92
|
-
/** The human/prompt-facing name of a check (plans/portability.md §6/7): the
|
|
121
|
+
/** The human/prompt-facing name of a check (plans/portability.md §6/7): the run prompt and
|
|
93
122
|
* the gate's check reasons name the actual verification command instead of asserting npm —
|
|
94
123
|
* "verify with `pytest -q`" in a Python repo, "`npm run test`" in an npm one. */
|
|
95
124
|
export function describeCheck(check) {
|
|
96
125
|
return check.kind === "npm" ? `\`npm run ${check.script}\`` : `\`${check.command}\``;
|
|
97
126
|
}
|
|
98
127
|
/** The machine-generated reasons a red check hands its author: the headline joined to the rest of
|
|
99
|
-
* the clipped tail, so the compiler error sits right after it in the injected next-
|
|
128
|
+
* the clipped tail, so the compiler error sits right after it in the injected next-run note. The
|
|
100
129
|
* headline is failureHeadline's — the first line that is not a stack frame — not outputTail[0]: a
|
|
101
130
|
* suite that dies on an unhandled rejection opens mid-stack, and naming the frame tells the author
|
|
102
131
|
* where it broke, never what (BUGS.md 2026-09-19). One definition for every red that rejects a
|
|
103
|
-
* change: the gate's pre-check, a batch's single-change red, and a single merge's in-lock check.
|
|
132
|
+
* change: the gate's pre-check, a batch's single-change red, and a single merge's in-lock check.
|
|
133
|
+
* In the verified branch, tail lines the headline already carries are dropped: the headline
|
|
134
|
+
* itself, the test-name or marker line (duration removed) it opens with, and the message line it
|
|
135
|
+
* ends with. */
|
|
104
136
|
export function checkFailureReasons(check, outcome) {
|
|
105
137
|
const tail = outcome.outputTail ?? [];
|
|
106
138
|
// An unverified rejection already says what happened — the check never finished, the tree is
|
|
@@ -114,6 +146,14 @@ export function checkFailureReasons(check, outcome) {
|
|
|
114
146
|
const headline = failureHeadline(tail);
|
|
115
147
|
const what = describeCheck(check);
|
|
116
148
|
return headline !== undefined
|
|
117
|
-
? [`check failed (${what}): ${headline}`, ...tail.filter((l) => l
|
|
149
|
+
? [`check failed (${what}): ${headline}`, ...tail.filter((l) => !headlineCarries(headline, l))]
|
|
118
150
|
: [`check failed (${what})`];
|
|
119
151
|
}
|
|
152
|
+
/** Whether a failureHeadline already says tail line `l`: equal to it, its name part (duration
|
|
153
|
+
* removed), or the message after its ` — `. */
|
|
154
|
+
function headlineCarries(headline, l) {
|
|
155
|
+
if (l === headline || headline.endsWith(` — ${l}`))
|
|
156
|
+
return true;
|
|
157
|
+
const bare = l.replace(/ \([\d.]+m?s\)$/, "");
|
|
158
|
+
return bare === headline || headline.startsWith(`${bare} — `);
|
|
159
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { BUILD_CHECK_TIMEOUT_MS, detectBuildCheck, gateCommandOf, } from "./build-check-detect.js";
|
|
1
|
+
import { BUILD_CHECK_TIMEOUT_MS, detectBuildCheck, gateCommandOf, mergeCommandOf, } from "./build-check-detect.js";
|
|
2
2
|
import { runBuildCheck, checkScriptName, checkTimeoutMs, } from "./build-check.js";
|
|
3
3
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
4
|
-
import { MERGE_SCOPES, SCOPE_WORDS, buildCheckEvent, buildCheckSkipWarning, runWithCleanRetry, installFailedPhrase, killedPhrase, loadedHostPhrase, timedOutPhrase, DEADLINE_LATE_TOLERANCE_MS, SLEEP_SPAN_TOLERANCE_MS, sleptPhrase, starvedDeadlineMs, UNVERIFIED_TREE, } from "./build-check-events.js";
|
|
4
|
+
import { MERGE_SCOPES, SCOPE_WORDS, buildCheckEvent, buildCheckSkipWarning, runWithCleanRetry, installFailedPhrase, killedPhrase, loadedHostPhrase, timedOutPhrase, DEADLINE_LATE_TOLERANCE_MS, logCheckWaitEvent, SLEEP_SPAN_TOLERANCE_MS, sleptPhrase, starvedDeadlineMs, UNVERIFIED_TREE, } from "./build-check-events.js";
|
|
5
5
|
import { CHECK_TIER, withCheckPermit } from "../concurrency/check-permit.js";
|
|
6
6
|
import { sampleHostLoad, sampleSleepClock } from "./host-sleep.js";
|
|
7
7
|
import { npmInstall } from "./dep-install.js";
|
|
@@ -27,14 +27,17 @@ import { npmInstall } from "./dep-install.js";
|
|
|
27
27
|
* own host-sleep evidence) earns that same one retry at any scope, and a failed run the host slept
|
|
28
28
|
* through does too; each attempt is priced as its own build_check event, carrying when the check
|
|
29
29
|
* itself ran (buildCheckRunFields). A configured `check.gateCommand` replaces the command at scope
|
|
30
|
-
* "gate" only (same cwd and timeout); the
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* "gate" only (same cwd and timeout); the in-lock command (Fast merge "Defaults", mergeCommandOf) —
|
|
31
|
+
* `check.mergeCommand` when set, else `check.gateCommand`, else the full check — replaces it at the
|
|
32
|
+
* merge and batch scopes, and those events then carry `full: false`. The red-main baseline, which
|
|
33
|
+
* detects on its own, keeps running check.command. `permitWait` hears
|
|
34
|
+
* about a wait for the check permit (the merge cell's "waiting for a check slot"). Never throws. */
|
|
33
35
|
export async function runScopedBuildCheck(root, role, scope, wt, config, timeoutMs = BUILD_CHECK_TIMEOUT_MS, sampleSleep = sampleSleepClock, install = npmInstall, permitWait, sampleLoad = sampleHostLoad) {
|
|
34
|
-
const
|
|
35
|
-
const check = detectBuildCheck(wt,
|
|
36
|
+
const swapped = scope === "gate" ? gateCommandOf(config) : MERGE_SCOPES.has(scope) ? mergeCommandOf(config) : undefined;
|
|
37
|
+
const check = detectBuildCheck(wt, swapped === undefined ? config : { check: { ...config?.check, command: swapped } });
|
|
36
38
|
if (!check)
|
|
37
39
|
return null;
|
|
40
|
+
const cheaper = scope !== "gate" && swapped !== undefined;
|
|
38
41
|
// A configured check.timeoutSeconds bounds either kind; otherwise an npm check runs under the
|
|
39
42
|
// caller's bound. Effective here so the reason text and the skip warning agree with what
|
|
40
43
|
// runBuildCheck enforced.
|
|
@@ -59,10 +62,14 @@ export async function runScopedBuildCheck(root, role, scope, wt, config, timeout
|
|
|
59
62
|
// outcome stands (runWithCleanRetry). The verdict-less first attempt is priced as its own
|
|
60
63
|
// event (the feed must answer how long a check took), then the final event below records
|
|
61
64
|
// the retry's classified outcome.
|
|
62
|
-
const { outcome, durationMs: d } = await runWithCleanRetry(() => runBuildCheck(wt, runCheck, timeoutMs, undefined, sampleSleep, install), (first, dur) => logEventBestEffort(root, buildCheckEvent(role, scope, first, dur)));
|
|
65
|
+
const { outcome, durationMs: d } = await runWithCleanRetry(() => runBuildCheck(wt, runCheck, timeoutMs, undefined, sampleSleep, install), (first, dur) => logEventBestEffort(root, buildCheckEvent(role, scope, first, dur, cheaper)));
|
|
63
66
|
durationMs = d;
|
|
64
67
|
return outcome;
|
|
65
|
-
},
|
|
68
|
+
}, {
|
|
69
|
+
waiting: () => permitWait?.waiting?.(),
|
|
70
|
+
granted: () => permitWait?.granted?.(),
|
|
71
|
+
waited: (waitedMs, parked) => logCheckWaitEvent(root, role, scope, waitedMs, parked),
|
|
72
|
+
}, `${role} ${scope}`);
|
|
66
73
|
// A signal kill at a merge scope, or a timeout whose deadline fired on time (real slowness, a
|
|
67
74
|
// genuinely hung or oversized suite), made no verdict about the tree — and this is the check
|
|
68
75
|
// whose whole job is to catch a semantic conflict before it merges, so it rejects
|
|
@@ -102,10 +109,10 @@ export async function runScopedBuildCheck(root, role, scope, wt, config, timeout
|
|
|
102
109
|
...(raw.install ? { install: raw.install } : {}),
|
|
103
110
|
}
|
|
104
111
|
: raw;
|
|
105
|
-
logEventBestEffort(root, buildCheckEvent(role, scope, outcome, durationMs));
|
|
112
|
+
logEventBestEffort(root, buildCheckEvent(role, scope, outcome, durationMs, cheaper));
|
|
106
113
|
if (outcome.status === "skipped") {
|
|
107
114
|
// Environmental — deliberately NOT fail-closed, so a hung build script cannot wedge every
|
|
108
|
-
// code
|
|
115
|
+
// code run into the 3-strike discard (gate) or a merge behind the merge lock.
|
|
109
116
|
const w = SCOPE_WORDS[scope];
|
|
110
117
|
warnEventBestEffort(root, role, buildCheckSkipWarning(outcome.skipReason, w.label, w.proceeding, effectiveMs, outcome.skipReason === "killed" && outcome.killedBy
|
|
111
118
|
? { signal: outcome.killedBy, durationMs }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BUILD_CHECK_TIMEOUT_MS } from "./build-check-detect.js";
|
|
2
2
|
import { EXEC_MAX_BUFFER, execFileAsync } from "../process/process.js";
|
|
3
3
|
import { KILL_GRACE_MS, runScriptGroup } from "../process/process-group.js";
|
|
4
|
-
import { clipBuildTail } from "./build-check-report.js";
|
|
4
|
+
import { clipBuildTail, parseFailingTests } from "./build-check-report.js";
|
|
5
5
|
import { parseTestCounts } from "./build-check-counts.js";
|
|
6
6
|
import { sampleSleepClock, sleptMsBetween } from "./host-sleep.js";
|
|
7
7
|
import { npmInstall, syncInstall } from "./dep-install.js";
|
|
@@ -168,6 +168,7 @@ async function runDeclaredCheck(wt, check, script, effectiveMs, killGraceMs, sam
|
|
|
168
168
|
status: "failed",
|
|
169
169
|
script,
|
|
170
170
|
outputTail: clipBuildTail(output),
|
|
171
|
+
failingTests: parseFailingTests(output),
|
|
171
172
|
run,
|
|
172
173
|
counts: parseTestCounts(output),
|
|
173
174
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
3
|
import { gitTry } from "../git/git-run.js";
|
|
5
4
|
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
6
5
|
import { finiteNumber } from "../files/json-object.js";
|
|
7
|
-
import { refSha } from "../git/git.js";
|
|
6
|
+
import { commitExists, refSha } from "../git/git.js";
|
|
8
7
|
/** The paths whose change makes a build stale — everything tsc compiles into dist/ plus the
|
|
9
8
|
* files that steer the compile. Tests are compiled too but never loaded by the running
|
|
10
9
|
* harness, so a test-only commit does not stale the build; docs and markdown never do. */
|
|
@@ -53,7 +52,7 @@ export async function stampBuild(root, dist, sha) {
|
|
|
53
52
|
export async function isSelfHosted(root, info) {
|
|
54
53
|
if (path.resolve(root) !== info.root)
|
|
55
54
|
return false;
|
|
56
|
-
return (
|
|
55
|
+
return commitExists(root, info.sha);
|
|
57
56
|
}
|
|
58
57
|
/** Compare a build's commit against main's head over the build inputs only: commits touching
|
|
59
58
|
* just tests or markdown leave the running code unchanged and never count as stale. Null when
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { distDir, stampBuild } from "./build-info.js";
|
|
5
4
|
import { clipBuildTail } from "./build-check-report.js";
|
|
6
5
|
import { resolveFromNodeModules } from "./build-check-detect.js";
|
|
7
|
-
import { ensureDir, removeTree } from "../files/files.js";
|
|
6
|
+
import { ensureDir, removeTree, tryReadDirEntries } from "../files/files.js";
|
|
8
7
|
import { stagingDir, stagingRootDir } from "../paths.js";
|
|
9
8
|
import { execFileAsync } from "../process/process.js";
|
|
10
9
|
import { warnEvent, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
@@ -36,13 +35,9 @@ export const STAGED_PRUNE_AFTER_MS = 45 * 60_000;
|
|
|
36
35
|
export function pruneStaleStagings(root, keep, now = Date.now()) {
|
|
37
36
|
const stagingRoot = stagingRootDir(root);
|
|
38
37
|
let removed = 0;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
entries = fs.readdirSync(stagingRoot, { withFileTypes: true });
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
38
|
+
const entries = tryReadDirEntries(stagingRoot);
|
|
39
|
+
if (!entries)
|
|
44
40
|
return 0; // No staging root yet (first compile) — nothing to prune.
|
|
45
|
-
}
|
|
46
41
|
for (const entry of entries) {
|
|
47
42
|
if (!entry.isDirectory() || !/^[0-9a-f]{40}$/.test(entry.name))
|
|
48
43
|
continue;
|
|
@@ -61,7 +56,7 @@ export function pruneStaleStagings(root, keep, now = Date.now()) {
|
|
|
61
56
|
removed += 1;
|
|
62
57
|
}
|
|
63
58
|
catch {
|
|
64
|
-
// Left for the next compile or swap: dead weight one
|
|
59
|
+
// Left for the next compile or swap: dead weight one run longer is fine.
|
|
65
60
|
}
|
|
66
61
|
}
|
|
67
62
|
return removed;
|
|
@@ -216,9 +211,9 @@ export function swapDist(root, dist, mainHead) {
|
|
|
216
211
|
* build aside to `dist.prev` and then moves the staged build into `dist`, and a process killed
|
|
217
212
|
* (SIGKILL — the OOM killer) in that microsecond window leaves `dist` missing. Nothing in the
|
|
218
213
|
* swap can close the window, because the survivor is the supervisor and it exits on the dead
|
|
219
|
-
* generation; the next `tumwater
|
|
214
|
+
* generation; the next `tumwater start` then cannot even load `dist/src/cli.js` and fails with
|
|
220
215
|
* MODULE_NOT_FOUND, so the repo is wedged until someone renames `dist.prev` back by hand. The
|
|
221
|
-
* supervisor calls this before it exits (src/cli/cli-
|
|
216
|
+
* supervisor calls this before it exits (src/cli/cli-start.ts, via superviseStart's onDone): while
|
|
222
217
|
* it is still up, it renames the preserved old build back into place so the next boot works.
|
|
223
218
|
*
|
|
224
219
|
* Only acts when `dist` is missing and a `dist.prev` exists — both conditions together are the
|
|
@@ -233,10 +228,10 @@ export function recoverInterruptedSwap(root, dist) {
|
|
|
233
228
|
fs.renameSync(prev, dist); // Atomic: dist is either the restored old build or still missing.
|
|
234
229
|
return true;
|
|
235
230
|
}
|
|
236
|
-
/** The supervisor's end-of-supervision repair (src/cli/cli-
|
|
237
|
-
*
|
|
231
|
+
/** The supervisor's end-of-supervision repair (src/cli/cli-start.ts wires this into
|
|
232
|
+
* superviseStart's onDone): recover dist/ after an interrupted swap and, either way, put the
|
|
238
233
|
* outcome on the event feed. Kept beside recoverInterruptedSwap so the CLI wiring is one call.
|
|
239
|
-
* cli-
|
|
234
|
+
* cli-start.ts imports this module statically, so the code is in memory before a swap can move
|
|
240
235
|
* dist/ away — recovering via an import at call time would fail ERR_MODULE_NOT_FOUND exactly
|
|
241
236
|
* when it is needed, the window this closes. `dist` defaults to this process's own dist
|
|
242
237
|
* (build-info.distDir, a pure path computation captured at load), overridable so a test can
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { resolveFromNodeModules } from "./build-check-detect.js";
|
|
4
3
|
import { readJsonFile } from "../files/json-files.js";
|
|
@@ -9,7 +8,7 @@ import { errorMessage } from "../text/text.js";
|
|
|
9
8
|
/** Keeping a tree's install in step with its lockfile (BUGS.md 2026-10-01). node_modules is
|
|
10
9
|
* gitignored, so a tumwater worktree has none of its own: its toolchain and every import resolve by
|
|
11
10
|
* walking UP to the root checkout's install (detectBuildCheck, resolveFromNodeModules). That
|
|
12
|
-
* install predates any dependency a change adds, and no
|
|
11
|
+
* install predates any dependency a change adds, and no role ever ran `npm install` — so a
|
|
13
12
|
* legitimate dependency-adding change failed its gate check with TS2307 every time, and had it
|
|
14
13
|
* merged, every tree after it would have resolved against a root install missing the package. Two
|
|
15
14
|
* callers close both halves: runBuildCheck installs into the checked tree when its lockfile's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** The collector behind `tumwater diff --role <id>`: what change a
|
|
2
|
-
* and its merge. The committed half is the
|
|
3
|
-
* team's main branch, with the patch); the uncommitted half is the
|
|
1
|
+
/** The collector behind `tumwater diff --role <id>`: what change a role holds between its commit
|
|
2
|
+
* and its merge. The committed half is the role branch's unmerged work (commits ahead of the
|
|
3
|
+
* team's main branch, with the patch); the uncommitted half is the role worktree's dirty state.
|
|
4
4
|
* Everything is read-only git plumbing already used elsewhere — this module only wires the pieces
|
|
5
5
|
* into one operator view, degrading like `report` does when the team (or just this role's
|
|
6
6
|
* worktree) does not exist yet. Lives beside the other collectors (src/report/report-data.ts,
|
|
@@ -9,35 +9,55 @@
|
|
|
9
9
|
import { knownRoleIdsCached, loadConfigSafe } from "../config/config.js";
|
|
10
10
|
import { aheadOfMain, branchExists, countCommitsInRange, currentBranch, targetBranch } from "../git/git.js";
|
|
11
11
|
import { gitTry } from "../git/git-run.js";
|
|
12
|
-
import { aheadOfMainDiff, changedFiles } from "../git/git-diff.js";
|
|
12
|
+
import { aheadOfMainDiff, aheadOfMainFiles, changedFiles } from "../git/git-diff.js";
|
|
13
|
+
import { isAbsolute } from "node:path";
|
|
13
14
|
import { branchName } from "../paths.js";
|
|
14
15
|
import { isUsableWorktree } from "../git/worktree.js";
|
|
15
16
|
import { roleWorktreeDir } from "../git/slots-state.js";
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/** Resolve the team-wide scope both collection entry points share: read the config once for a
|
|
18
|
+
* configured baseBranch, ask the primary checkout for its current branch once, and check the
|
|
19
|
+
* resolved main branch exists once. */
|
|
20
|
+
async function resolveChangeScope(root) {
|
|
21
|
+
const { config } = loadConfigSafe(root);
|
|
22
|
+
const mainBranch = targetBranch(config?.baseBranch, await currentBranch(root));
|
|
23
|
+
return { mainBranch, mainPresent: await branchExists(root, mainBranch) };
|
|
24
|
+
}
|
|
25
|
+
/** Collect the pending change of every known role (built-in plus custom role ids, config
|
|
26
|
+
* order — disabled roles included, since a role stopped mid-flight still holds its branch).
|
|
27
|
+
* Each entry goes through collectRoleChangeAt with the team-wide scope resolved once up
|
|
28
|
+
* front, which keeps the absent/no-base degradation logic single-homed and only computes
|
|
29
|
+
* the patch git-diffs for roles actually holding work (both are "" when a role holds
|
|
30
|
+
* nothing); the team view then drops the patch fields. */
|
|
21
31
|
export async function collectTeamChanges(root) {
|
|
32
|
+
const scope = await resolveChangeScope(root);
|
|
22
33
|
const roles = await Promise.all(knownRoleIdsCached(root).map(async (role) => {
|
|
23
|
-
const view = await
|
|
34
|
+
const view = await collectRoleChangeAt(root, role, scope);
|
|
24
35
|
const { diff: _diff, uncommittedDiff: _uncommittedDiff, ...team } = view;
|
|
25
36
|
return team;
|
|
26
37
|
}));
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
return { mainBranch: scope.mainBranch, roles };
|
|
39
|
+
}
|
|
40
|
+
/** Collect each listed role's full change view, entries in the caller's order. */
|
|
41
|
+
export async function collectRoleChanges(root, roles, opts = {}) {
|
|
42
|
+
const scope = await resolveChangeScope(root);
|
|
43
|
+
const views = await Promise.all(roles.map((role) => collectRoleChangeAt(root, role, scope, opts)));
|
|
44
|
+
return { mainBranch: scope.mainBranch, roles: views };
|
|
30
45
|
}
|
|
31
46
|
/** The uncommitted patch's cap, matching aheadOfMainDiff's — one shared bound so neither
|
|
32
47
|
* half of the view can print unbounded text. */
|
|
33
48
|
const DIFF_MAX_BYTES = 200_000;
|
|
34
|
-
/** Collect the change the role
|
|
49
|
+
/** Collect the change the role holds: both halves of the view, or a degraded state
|
|
35
50
|
* when the worktree or the base branch is missing. Never throws on a degraded team — a
|
|
36
|
-
* query about a
|
|
51
|
+
* query about a role that has not run yet must answer, not fail (report's rationale). */
|
|
37
52
|
export async function collectRoleChange(root, role, opts = {}) {
|
|
53
|
+
return collectRoleChangeAt(root, role, await resolveChangeScope(root), opts);
|
|
54
|
+
}
|
|
55
|
+
/** One role's change against a pre-resolved team scope: the per-role body of every entry point.
|
|
56
|
+
* Shared so the team-wide mainBranch/branch-exists reads are spent once per collection instead
|
|
57
|
+
* of once per role (the roster and list forms pass a scope resolved before the Promise.all). */
|
|
58
|
+
async function collectRoleChangeAt(root, role, scope, opts = {}) {
|
|
38
59
|
const branch = branchName(role);
|
|
39
|
-
const
|
|
40
|
-
const mainBranch = targetBranch(config?.baseBranch, await currentBranch(root));
|
|
60
|
+
const mainBranch = scope.mainBranch;
|
|
41
61
|
const empty = {
|
|
42
62
|
role,
|
|
43
63
|
branch,
|
|
@@ -49,31 +69,45 @@ export async function collectRoleChange(root, role, opts = {}) {
|
|
|
49
69
|
dirtyFiles: [],
|
|
50
70
|
uncommittedDiff: "",
|
|
51
71
|
};
|
|
52
|
-
if (!
|
|
72
|
+
if (!scope.mainPresent)
|
|
53
73
|
return { ...empty, state: "no-base" };
|
|
54
74
|
// The role's checkout comes from the pool resolver (slots-state.ts): a slot leased for its
|
|
55
|
-
//
|
|
56
|
-
// operator situation — this
|
|
75
|
+
// run or pinned for it, else the legacy worktree. A null or unusable directory is the same
|
|
76
|
+
// operator situation — this role holds nothing inspectable yet — so worktree.ts's shared
|
|
57
77
|
// usability probe still answers the final question.
|
|
58
78
|
const wt = await roleWorktreeDir(root, role);
|
|
59
79
|
if (wt === null || !(await isUsableWorktree(wt)))
|
|
60
80
|
return { ...empty, state: "absent" };
|
|
61
81
|
const ahead = await aheadOfMain(wt, mainBranch);
|
|
62
82
|
const behind = await countCommitsInRange(wt, `HEAD..${mainBranch}`);
|
|
63
|
-
|
|
83
|
+
// Under opts.path every git call below is limited to that path through a literal pathspec.
|
|
84
|
+
const spec = opts.path === undefined ? [] : ["--", `:(literal)${opts.path}`];
|
|
85
|
+
const commits = parseOnelineLog(await gitTry(wt, "log", "--oneline", `${mainBranch}..HEAD`, ...spec));
|
|
64
86
|
// Under opts.stat both halves hold the uncapped `git diff --stat` summary (one line per file).
|
|
65
|
-
|
|
87
|
+
// A view: a diff git fails to produce shows empty, as it always did.
|
|
88
|
+
const diff = commits.length === 0
|
|
66
89
|
? ""
|
|
67
90
|
: opts.stat
|
|
68
|
-
? ((await gitTry(wt, "diff", "--stat", `${mainBranch}...HEAD
|
|
69
|
-
: await aheadOfMainDiff(wt, mainBranch);
|
|
70
|
-
|
|
91
|
+
? ((await gitTry(wt, "diff", "--stat", `${mainBranch}...HEAD`, ...spec)) ?? "")
|
|
92
|
+
: await aheadOfMainDiff(wt, mainBranch, DIFF_MAX_BYTES, spec).catch(() => "");
|
|
93
|
+
// A view: a git that fails to answer shows no uncommitted edits, as it always did.
|
|
94
|
+
const dirtyFiles = (await changedFiles(wt).catch(() => [])).filter((f) => inPath(f, opts.path));
|
|
71
95
|
const uncommittedDiff = dirtyFiles.length === 0
|
|
72
96
|
? ""
|
|
73
97
|
: opts.stat
|
|
74
|
-
? ((await gitTry(wt, "diff", "--stat", "HEAD")) ?? "")
|
|
75
|
-
: await cappedWorkingDiff(wt);
|
|
76
|
-
return {
|
|
98
|
+
? ((await gitTry(wt, "diff", "--stat", "HEAD", ...spec)) ?? "")
|
|
99
|
+
: await cappedWorkingDiff(wt, spec);
|
|
100
|
+
return {
|
|
101
|
+
...empty,
|
|
102
|
+
state: "ready",
|
|
103
|
+
ahead,
|
|
104
|
+
behind,
|
|
105
|
+
commits,
|
|
106
|
+
diff,
|
|
107
|
+
dirtyFiles,
|
|
108
|
+
uncommittedDiff,
|
|
109
|
+
...(opts.path === undefined ? {} : { path: opts.path }),
|
|
110
|
+
};
|
|
77
111
|
}
|
|
78
112
|
/** Parse `git log --oneline` output into {sha, subject} pairs — sha up to the first space,
|
|
79
113
|
* the rest of the line the subject. Blank trailing lines (runGit's trimEnd makes them
|
|
@@ -89,16 +123,74 @@ function parseOnelineLog(out) {
|
|
|
89
123
|
return cut < 0 ? { sha: line, subject: "" } : { sha: line.slice(0, cut), subject: line.slice(cut + 1) };
|
|
90
124
|
});
|
|
91
125
|
}
|
|
126
|
+
/** The normalised repo-relative `path` scope of a role's change view (shared by `diff --role
|
|
127
|
+
* --path` and the dashboard's `GET /api/diff?role&path`): leading `./` pieces and trailing `/`
|
|
128
|
+
* characters stripped; null for an empty, `.`, absolute or `..`-bearing result. */
|
|
129
|
+
export function normalizeDiffPath(raw) {
|
|
130
|
+
const norm = raw.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
131
|
+
const bad = norm === "" || norm === "." || isAbsolute(norm) || norm.split("/").includes("..");
|
|
132
|
+
return bad ? null : norm;
|
|
133
|
+
}
|
|
134
|
+
/** Whether a porcelain path lies under the `--path` scope (always true without one): the path
|
|
135
|
+
* itself, a file below it, or an untracked-directory entry (trailing `/`) that contains it. */
|
|
136
|
+
function inPath(f, path) {
|
|
137
|
+
if (path === undefined)
|
|
138
|
+
return true;
|
|
139
|
+
return f === path || f.startsWith(path + "/") || (f.endsWith("/") && path.startsWith(f));
|
|
140
|
+
}
|
|
92
141
|
/** The worktree's uncommitted tracked-change patch, capped at DIFF_MAX_BYTES like
|
|
93
142
|
* aheadOfMainDiff: over the cap, a truncation note plus the --stat summary — bounded
|
|
94
143
|
* output that still names every changed file. `git diff HEAD` covers staged and unstaged
|
|
95
144
|
* tracked edits in one patch (plain `git diff` would show unstaged only, dropping a staged
|
|
96
145
|
* edit the dirtyFiles list already counts); untracked paths have no diff content anywhere,
|
|
97
146
|
* so they appear in the file list alone. */
|
|
98
|
-
async function cappedWorkingDiff(wt) {
|
|
99
|
-
const full = (await gitTry(wt, "diff", "HEAD")) ?? "";
|
|
147
|
+
async function cappedWorkingDiff(wt, spec = []) {
|
|
148
|
+
const full = (await gitTry(wt, "diff", "HEAD", ...spec)) ?? "";
|
|
100
149
|
if (full.length <= DIFF_MAX_BYTES)
|
|
101
150
|
return full;
|
|
102
|
-
const stat = (await gitTry(wt, "diff", "--stat", "HEAD")) ?? "";
|
|
151
|
+
const stat = (await gitTry(wt, "diff", "--stat", "HEAD", ...spec)) ?? "";
|
|
103
152
|
return (`[uncommitted diff truncated: the full diff is ${full.length} bytes; showing --stat]\n\n` + stat);
|
|
104
153
|
}
|
|
154
|
+
/** The paths two or more distinct roles hold: a role's duplicate path counts once, roles stay
|
|
155
|
+
* in holdings order, paths sort by plain `<` comparison. */
|
|
156
|
+
export function fileOverlaps(holdings) {
|
|
157
|
+
const byPath = new Map();
|
|
158
|
+
for (const { role, files } of holdings) {
|
|
159
|
+
for (const path of new Set(files)) {
|
|
160
|
+
const roles = byPath.get(path);
|
|
161
|
+
if (roles === undefined)
|
|
162
|
+
byPath.set(path, [role]);
|
|
163
|
+
else
|
|
164
|
+
roles.push(role);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return [...byPath.entries()]
|
|
168
|
+
.filter(([, roles]) => roles.length >= 2)
|
|
169
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
170
|
+
.map(([path, roles]) => ({ path, roles }));
|
|
171
|
+
}
|
|
172
|
+
/** Keep only the overlaps naming a listed role; each kept entry still names every holder. */
|
|
173
|
+
export function scopeChangeOverlaps(view, roles) {
|
|
174
|
+
return { ...view, overlaps: view.overlaps.filter((o) => o.roles.some((r) => roles.includes(r))) };
|
|
175
|
+
}
|
|
176
|
+
/** Keep only the overlaps at or under `path` (normalized), by the `inPath` rule
|
|
177
|
+
* `diff --role --path` uses. */
|
|
178
|
+
export function scopeChangeOverlapsToPath(view, path) {
|
|
179
|
+
return { ...view, overlaps: view.overlaps.filter((o) => inPath(o.path, path)) };
|
|
180
|
+
}
|
|
181
|
+
/** Collect the files two or more roles hold, committed ahead of main or uncommitted. Computes
|
|
182
|
+
* file names only — no patch — and skips a role with no usable checkout. */
|
|
183
|
+
export async function collectChangeOverlaps(root) {
|
|
184
|
+
const { mainBranch, mainPresent } = await resolveChangeScope(root);
|
|
185
|
+
if (!mainPresent)
|
|
186
|
+
return { mainBranch, state: "no-base", overlaps: [] };
|
|
187
|
+
const held = await Promise.all(knownRoleIdsCached(root).map(async (role) => {
|
|
188
|
+
const wt = await roleWorktreeDir(root, role);
|
|
189
|
+
if (wt === null || !(await isUsableWorktree(wt)))
|
|
190
|
+
return null;
|
|
191
|
+
const committed = (await aheadOfMain(wt, mainBranch)) > 0 ? await aheadOfMainFiles(wt, mainBranch).catch(() => []) : [];
|
|
192
|
+
return { role, files: [...committed, ...(await changedFiles(wt).catch(() => []))] };
|
|
193
|
+
}));
|
|
194
|
+
const holdings = held.filter((h) => h !== null);
|
|
195
|
+
return { mainBranch, state: "ready", overlaps: fileOverlaps(holdings) };
|
|
196
|
+
}
|
|
@@ -13,14 +13,25 @@ function workSummary(ahead, behind, mainBranch, dirtyFiles) {
|
|
|
13
13
|
(behind > 0 ? `, ${plural(behind, "commit")} behind` : "") +
|
|
14
14
|
(dirtyFiles.length > 0 ? `, ${plural(dirtyFiles.length, "uncommitted file")}` : ""));
|
|
15
15
|
}
|
|
16
|
+
/** The one-line phrase a change view prints when the resolved main branch is absent — shared
|
|
17
|
+
* by the roster, the per-role view, the list of roles, and the overlaps view so the operator
|
|
18
|
+
* wording the tests pin cannot drift between them (the workSummary precedent). */
|
|
19
|
+
function mainMissingPhrase(mainBranch) {
|
|
20
|
+
return `main branch ${mainBranch} does not exist`;
|
|
21
|
+
}
|
|
22
|
+
/** True when a team-wide roster is all no-base (every role found the same missing main branch)
|
|
23
|
+
* and holds at least one role, so it opens with the single baseline phrase instead of a column
|
|
24
|
+
* of per-role "does not exist" lines. */
|
|
25
|
+
function allRolesNoBase(roles) {
|
|
26
|
+
return roles.length > 0 && roles.every((r) => r.state === "no-base");
|
|
27
|
+
}
|
|
16
28
|
/** Render the roster as the operator-facing text `tumwater diff` (no --role) prints: one
|
|
17
29
|
* line per role that holds pending work, in roster order; roles with no worktree and roles
|
|
18
30
|
* holding nothing are skipped. Every entry no-base means the baseline itself is gone — the
|
|
19
31
|
* per-role view's line says so, team-wide, exit 0 like every other degraded case. */
|
|
20
32
|
export function renderTeamChange(view) {
|
|
21
|
-
if (view.roles
|
|
22
|
-
return
|
|
23
|
-
}
|
|
33
|
+
if (allRolesNoBase(view.roles))
|
|
34
|
+
return mainMissingPhrase(view.mainBranch);
|
|
24
35
|
const lines = view.roles
|
|
25
36
|
.filter((r) => r.state !== "no-base" && (r.ahead > 0 || r.dirtyFiles.length > 0))
|
|
26
37
|
.map((r) => `${r.role}: ${workSummary(r.ahead, r.behind, view.mainBranch, r.dirtyFiles)}`);
|
|
@@ -31,11 +42,15 @@ export function renderRoleChange(view) {
|
|
|
31
42
|
if (view.state === "absent")
|
|
32
43
|
return `no worktree for ${view.role}`;
|
|
33
44
|
if (view.state === "no-base")
|
|
34
|
-
return
|
|
45
|
+
return mainMissingPhrase(view.mainBranch);
|
|
46
|
+
if (view.path !== undefined && view.commits.length === 0 && view.dirtyFiles.length === 0) {
|
|
47
|
+
return `no pending change to ${view.path} for ${view.role}`;
|
|
48
|
+
}
|
|
35
49
|
if (view.ahead === 0 && view.dirtyFiles.length === 0)
|
|
36
50
|
return `no pending change for ${view.role}`;
|
|
37
51
|
const lines = [
|
|
38
|
-
`${view.role}: ${view.branch}, ${workSummary(view.ahead, view.behind, view.mainBranch, view.dirtyFiles)}
|
|
52
|
+
`${view.role}: ${view.branch}, ${workSummary(view.ahead, view.behind, view.mainBranch, view.dirtyFiles)}` +
|
|
53
|
+
(view.path === undefined ? "" : `; limited to ${view.path}`),
|
|
39
54
|
];
|
|
40
55
|
for (const c of view.commits)
|
|
41
56
|
lines.push(`${c.sha} ${c.subject}`);
|
|
@@ -48,3 +63,29 @@ export function renderRoleChange(view) {
|
|
|
48
63
|
}
|
|
49
64
|
return lines.join("\n");
|
|
50
65
|
}
|
|
66
|
+
/** Render several roles' views in the listed order, a blank line apart; every block opens with
|
|
67
|
+
* its role's name, so the blank line separates them. */
|
|
68
|
+
export function renderRoleChanges(view) {
|
|
69
|
+
if (allRolesNoBase(view.roles))
|
|
70
|
+
return mainMissingPhrase(view.mainBranch);
|
|
71
|
+
return view.roles.map(renderRoleChange).join("\n\n");
|
|
72
|
+
}
|
|
73
|
+
/** Render the overlaps view as the operator-facing text `tumwater diff --overlaps` prints:
|
|
74
|
+
* one `<path>: <role>, <role>` line per shared file. `scope` is the `--role` list the view was
|
|
75
|
+
* narrowed to and `path` the `--path` it was narrowed to; they only change the empty-list
|
|
76
|
+
* wording. */
|
|
77
|
+
export function renderChangeOverlaps(view, scope, path) {
|
|
78
|
+
if (view.state === "no-base")
|
|
79
|
+
return mainMissingPhrase(view.mainBranch);
|
|
80
|
+
if (view.overlaps.length === 0 && path !== undefined) {
|
|
81
|
+
return scope !== undefined
|
|
82
|
+
? `no file under ${path} held by ${scope.join(", ")} is touched by another pending change`
|
|
83
|
+
: `no file under ${path} is touched by two pending changes`;
|
|
84
|
+
}
|
|
85
|
+
if (view.overlaps.length === 0 && scope !== undefined) {
|
|
86
|
+
return `no file held by ${scope.join(", ")} is touched by another pending change`;
|
|
87
|
+
}
|
|
88
|
+
if (view.overlaps.length === 0)
|
|
89
|
+
return "no file is touched by two pending changes";
|
|
90
|
+
return view.overlaps.map((o) => `${o.path}: ${o.roles.join(", ")}`).join("\n");
|
|
91
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { readBrief, renderBrief } from "../brief/brief.js";
|
|
4
|
-
import {
|
|
3
|
+
import { editBriefText } from "../brief/brief-edit.js";
|
|
5
4
|
import { editInEditor } from "./cli-editor.js";
|
|
6
5
|
import { readPromptFileArg } from "./cli-command-args.js";
|
|
7
6
|
import { JSON_FLAG, rejectUnknownArgs } from "./cli-flag-specs.js";
|
|
@@ -11,7 +10,7 @@ import { requireReadyRepo } from "./cli-repo-gate.js";
|
|
|
11
10
|
const FILE_SPEC = { names: ["--file"], value: true, valueName: "<path>" };
|
|
12
11
|
/** `brief`'s `--edit` flag: open the prompt in `$VISUAL`/`$EDITOR`. */
|
|
13
12
|
const EDIT_SPEC = { names: ["--edit"] };
|
|
14
|
-
/** `tumwater brief`: show the
|
|
13
|
+
/** `tumwater brief`: show the brief text and the file holding it, or with `--file <path>` to
|
|
15
14
|
* replace it (or with `--edit`, open it in the editor) under the checks init applies and commit
|
|
16
15
|
* that one file on main. */
|
|
17
16
|
export async function cmdBrief(root, args) {
|
|
@@ -45,7 +44,7 @@ export async function cmdBrief(root, args) {
|
|
|
45
44
|
}
|
|
46
45
|
else
|
|
47
46
|
text = readPromptFileArg(args[fileIdx + 1]);
|
|
48
|
-
const outcome = await
|
|
47
|
+
const outcome = await editBriefText(root, text);
|
|
49
48
|
if (!outcome.ok)
|
|
50
49
|
fail(outcome.error);
|
|
51
50
|
const { file: name, changed, committed } = outcome;
|