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
|
@@ -3,7 +3,7 @@ import { betweenWindow, durationLabel, failRivalShapes, failUntilShorterThanSinc
|
|
|
3
3
|
import { HISTORY_DEFAULT_RUNS, HISTORY_MAX_RUNS, HISTORY_RESULT_FLAG_NAMES, HISTORY_SUMMARY_GROUPS, historyClass, parseHistoryResultClass, readRunRows, readRunRowsSince, runRows, summarizeRunRows, summarizeRunRowsByModel, runRowsMatchingGrep, runRowsOnModels } from "./history-data.js";
|
|
4
4
|
import { renderModelSummary, renderRow, renderSummary, widenWidths, widestWidths } from "./history-render.js";
|
|
5
5
|
import { LOGS_SINCE_MAX_MS, SPARSE_WINDOW_NOTE } from "../events/event-window.js";
|
|
6
|
-
import { followUntilParentGone } from "../cli/log-commands.js";
|
|
6
|
+
import { followUntilParentGone } from "../cli/cli-log-commands.js";
|
|
7
7
|
import { hasFollowFlag } from "../cli/cli-flag-specs.js";
|
|
8
8
|
import { parseEventLine, readEventsTailWithEnd } from "../events/event-read.js";
|
|
9
9
|
import { eventsLogPath } from "../paths.js";
|
|
@@ -23,7 +23,7 @@ export function parseHistoryModelList(raw) {
|
|
|
23
23
|
return pieces;
|
|
24
24
|
}
|
|
25
25
|
/** `tumwater history [--role <id>[,<id>...]] [-n N]`: one row per completed run, newest first. The
|
|
26
|
-
* observing half beside cmdLogs (log-commands.ts): read-only over the event log, stdout only —
|
|
26
|
+
* observing half beside cmdLogs (cli-log-commands.ts): read-only over the event log, stdout only —
|
|
27
27
|
* every datum rides the `run_end` events src/run/run-finalize.ts already writes, so this
|
|
28
28
|
* is a rendering of the existing record, not a new one. The rows themselves are collected
|
|
29
29
|
* by history-data.ts (beside src/report/report-data.ts and src/failure/failure-data.ts), and
|
|
@@ -31,7 +31,7 @@ export function parseHistoryModelList(raw) {
|
|
|
31
31
|
* src/report/report-render.ts); this module parses the args, drives the CLI, and prints. */
|
|
32
32
|
/** The missing-pattern error cmdHistory prints for a valueless or empty `--grep`, exported so
|
|
33
33
|
* cli.ts's rejectUnknownArgs spec for --grep can fail a trailing `history --grep` with the same
|
|
34
|
-
* wording. Kept local to this module — log-commands.ts holds its own GREP_VALUE_ERROR — so the
|
|
34
|
+
* wording. Kept local to this module — cli-log-commands.ts holds its own GREP_VALUE_ERROR — so the
|
|
35
35
|
* two observing views' wordings each name the command that printed them. */
|
|
36
36
|
export const HISTORY_GREP_VALUE_ERROR = "history --grep needs a pattern";
|
|
37
37
|
/** The missing-value error cmdHistory prints for a valueless or empty `--result`, exported so
|
|
@@ -166,11 +166,9 @@ function applyHistoryModelFilter(rows, rest) {
|
|
|
166
166
|
}
|
|
167
167
|
return { rows, models };
|
|
168
168
|
}
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
|
|
172
|
-
* row collection and filtering and before `--json`. */
|
|
173
|
-
function sayHistory(rows, q, covered, filtered) {
|
|
169
|
+
/** Validate `--by`'s value and its `--summary` requirement; return whether the grouping is by
|
|
170
|
+
* model (false for `role`). One home for "can this `--by` proceed, and does it group by model?" */
|
|
171
|
+
function historySummaryGroup(q) {
|
|
174
172
|
// `--by role|model` picks how --summary groups its lines (role by default).
|
|
175
173
|
const byRaw = flagValue(q.rest, "--by");
|
|
176
174
|
let byModel = false;
|
|
@@ -184,6 +182,52 @@ function sayHistory(rows, q, covered, filtered) {
|
|
|
184
182
|
fail("history --by needs --summary (it picks how the summary groups runs)");
|
|
185
183
|
byModel = byRaw === "model";
|
|
186
184
|
}
|
|
185
|
+
return byModel;
|
|
186
|
+
}
|
|
187
|
+
/** The empty-set prose: blame the filter that emptied the set (model, then result, then grep),
|
|
188
|
+
* else the log or window. Never the `no runs yet`/`no runs in <window>` prose when rows were
|
|
189
|
+
* scanned and filtered out — the runs are there, none match. */
|
|
190
|
+
function sayHistoryEmpty(q, filtered) {
|
|
191
|
+
// A grep that matched nothing must not borrow the empty-log prose: `no runs yet` and
|
|
192
|
+
// `no runs in 2h` are statements about the LOG and the WINDOW, and both are false when
|
|
193
|
+
// rows were scanned and filtered out — the operator would read a quiet team where the
|
|
194
|
+
// truth is `the runs are there, none match`. The logs --grep rule (its `no events
|
|
195
|
+
// matching "<pattern>"`, src/cli/cli-log-commands.ts) names the pattern instead; --json stays
|
|
196
|
+
// a bare {"rows":[]} document above, never prose in either form.
|
|
197
|
+
//
|
|
198
|
+
// The result class is named only when it emptied a non-empty set (`afterGrep > 0`): an
|
|
199
|
+
// empty log or window scanned no rows, so the result filter removed nothing and the
|
|
200
|
+
// empty-log/window prose is the true one — the dashboard's History view draws the same
|
|
201
|
+
// line. A grep that admitted nothing keeps its own wording regardless of --result.
|
|
202
|
+
say(filtered.models !== null && filtered.afterResult > 0
|
|
203
|
+
? `no runs on model "${filtered.models.join(",")}"`
|
|
204
|
+
: filtered.classes !== null && filtered.afterGrep > 0
|
|
205
|
+
? `no runs with result "${filtered.classes.join(",")}"`
|
|
206
|
+
: q.grepPattern !== null
|
|
207
|
+
? `no runs matching "${q.grepPattern}"`
|
|
208
|
+
: q.sinceMs === null
|
|
209
|
+
? "no runs yet"
|
|
210
|
+
: q.untilMs === null
|
|
211
|
+
? `no runs in ${durationLabel(q.sinceMs)}`
|
|
212
|
+
: `no runs ${betweenWindow(q.sinceMs, q.untilMs)}`);
|
|
213
|
+
}
|
|
214
|
+
/** Render the non-empty rows: the summary (model or role) when `--summary`, else the aligned
|
|
215
|
+
* table. Called only with `rows.length > 0`.
|
|
216
|
+
*/
|
|
217
|
+
function sayHistoryRows(rows, summary, byModel) {
|
|
218
|
+
if (summary)
|
|
219
|
+
say(byModel ? renderModelSummary(summarizeRunRowsByModel(rows)) : renderSummary(summarizeRunRows(rows)));
|
|
220
|
+
else {
|
|
221
|
+
const widths = widestWidths(rows);
|
|
222
|
+
say(rows.map((r) => renderRow(r, widths)).join("\n"));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Render the collected-and-filtered rows: the `--by`/`--summary` summary grouping, the `--json`
|
|
226
|
+
* document, the empty-state wording (blaming the filter that emptied the set, else the log or
|
|
227
|
+
* window), the aligned table, and the sparse-window note. A bad `--by` value fails here, after
|
|
228
|
+
* row collection and filtering and before `--json`. */
|
|
229
|
+
function sayHistory(rows, q, covered, filtered) {
|
|
230
|
+
const byModel = historySummaryGroup(q);
|
|
187
231
|
const summary = q.rest.includes("--summary") && rows.length > 0;
|
|
188
232
|
// --json swaps the renderer for the collector's own payload, exactly as status --json and
|
|
189
233
|
// report --json: the same rows the table prints, each with ts/tokens/costUsd kept raw. A
|
|
@@ -194,36 +238,10 @@ function sayHistory(rows, q, covered, filtered) {
|
|
|
194
238
|
return;
|
|
195
239
|
}
|
|
196
240
|
if (rows.length === 0) {
|
|
197
|
-
|
|
198
|
-
// `no runs in 2h` are statements about the LOG and the WINDOW, and both are false when
|
|
199
|
-
// rows were scanned and filtered out — the operator would read a quiet team where the
|
|
200
|
-
// truth is `the runs are there, none match`. The logs --grep rule (its `no events
|
|
201
|
-
// matching "<pattern>"`, src/cli/log-commands.ts) names the pattern instead; --json stays
|
|
202
|
-
// a bare {"rows":[]} document above, never prose in either form.
|
|
203
|
-
//
|
|
204
|
-
// The result class is named only when it emptied a non-empty set (`afterGrep > 0`): an
|
|
205
|
-
// empty log or window scanned no rows, so the result filter removed nothing and the
|
|
206
|
-
// empty-log/window prose is the true one — the dashboard's History view draws the same
|
|
207
|
-
// line. A grep that admitted nothing keeps its own wording regardless of --result.
|
|
208
|
-
say(filtered.models !== null && filtered.afterResult > 0
|
|
209
|
-
? `no runs on model "${filtered.models.join(",")}"`
|
|
210
|
-
: filtered.classes !== null && filtered.afterGrep > 0
|
|
211
|
-
? `no runs with result "${filtered.classes.join(",")}"`
|
|
212
|
-
: q.grepPattern !== null
|
|
213
|
-
? `no runs matching "${q.grepPattern}"`
|
|
214
|
-
: q.sinceMs === null
|
|
215
|
-
? "no runs yet"
|
|
216
|
-
: q.untilMs === null
|
|
217
|
-
? `no runs in ${durationLabel(q.sinceMs)}`
|
|
218
|
-
: `no runs ${betweenWindow(q.sinceMs, q.untilMs)}`);
|
|
241
|
+
sayHistoryEmpty(q, filtered);
|
|
219
242
|
return;
|
|
220
243
|
}
|
|
221
|
-
|
|
222
|
-
say(byModel ? renderModelSummary(summarizeRunRowsByModel(rows)) : renderSummary(summarizeRunRows(rows)));
|
|
223
|
-
else {
|
|
224
|
-
const widths = widestWidths(rows);
|
|
225
|
-
say(rows.map((r) => renderRow(r, widths)).join("\n"));
|
|
226
|
-
}
|
|
244
|
+
sayHistoryRows(rows, summary, byModel);
|
|
227
245
|
// A sparse window is never mistaken for a quiet team — but the note only ever rides rows
|
|
228
246
|
// (an empty window returned above), it never touches --json output, and it is the shared
|
|
229
247
|
// SPARSE_WINDOW_NOTE wording: the oldest retained event being inside the window is exactly
|
|
@@ -179,10 +179,10 @@ function runPromptImageGate(root, target) {
|
|
|
179
179
|
if (guidance.notice)
|
|
180
180
|
say(guidance.notice);
|
|
181
181
|
}
|
|
182
|
-
/** `--edit
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
function
|
|
182
|
+
/** The `--edit` attachment phase: read the `--attach` image list (undefined when there is
|
|
183
|
+
* none) and run the image-input gate under the role the edit targets — the position's
|
|
184
|
+
* resolved role when no `--role` narrows it. */
|
|
185
|
+
function readEditAttachments(root, position, attachPaths, role) {
|
|
186
186
|
// The same broken-config policy and list-wide resolution as cancel, one verb over: the
|
|
187
187
|
// file's new content rides the same queue file (position, enqueue stamp, and a pending
|
|
188
188
|
// --at deferral untouched), and a concurrent dequeue reports gone and exits clean. An
|
|
@@ -200,48 +200,66 @@ function runPromptEdit(root, position, text, attachPaths, role) {
|
|
|
200
200
|
const targetRole = role ?? resolveListedRole(root, position);
|
|
201
201
|
runPromptImageGate(root, targetRole);
|
|
202
202
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// prompt with no text, which the enqueue forms refuse.
|
|
225
|
-
if (after.trim() === "" && body.trim() !== "")
|
|
226
|
-
fail("the edited prompt is empty; nothing changed (use --cancel <n> to drop it)");
|
|
227
|
-
if (after.trim() === body.trim() && images === undefined) {
|
|
228
|
-
say(`prompt ${position} unchanged`);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
sayPromptOutcome(position, targetRole, editQueuedFile(root, targetRole, name, after, images), role === null);
|
|
233
|
-
}
|
|
234
|
-
catch (err) {
|
|
235
|
-
fail(errorMessage(err));
|
|
236
|
-
}
|
|
203
|
+
return images;
|
|
204
|
+
}
|
|
205
|
+
/** The bare `--edit <n>` arm: resolve the role, read the queued body by file name (so a
|
|
206
|
+
* concurrent dequeue reports gone), open it in the editor and write the result back —
|
|
207
|
+
* unchanged, emptied or edited — with the carried `images`. */
|
|
208
|
+
function editBareQueuedPrompt(root, position, role, images) {
|
|
209
|
+
// Bare `--edit <n>`: open the prompt's body in the editor. The role is resolved first
|
|
210
|
+
// and the write goes by file name, so a dequeue while the editor is open reports gone
|
|
211
|
+
// instead of editing the neighbour that shifted into the position.
|
|
212
|
+
const targetRole = role ?? resolveListedRole(root, position);
|
|
213
|
+
let name;
|
|
214
|
+
let body;
|
|
215
|
+
try {
|
|
216
|
+
name = path.basename(queuedFileAtPosition(root, targetRole, position));
|
|
217
|
+
body = readQueuedPromptBody(root, targetRole, name);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
return fail(errorMessage(err));
|
|
221
|
+
}
|
|
222
|
+
if (body === null) {
|
|
223
|
+
sayPromptOutcome(position, targetRole, { status: "gone" }, role === null);
|
|
237
224
|
return;
|
|
238
225
|
}
|
|
226
|
+
const after = editInEditor(body, "prompt.md");
|
|
227
|
+
// An emptied buffer is an abort, not a blank prompt: the queue would otherwise hold a
|
|
228
|
+
// prompt with no text, which the enqueue forms refuse.
|
|
229
|
+
if (after.trim() === "" && body.trim() !== "")
|
|
230
|
+
fail("the edited prompt is empty; nothing changed (use --cancel <n> to drop it)");
|
|
231
|
+
if (after.trim() === body.trim() && images === undefined) {
|
|
232
|
+
say(`prompt ${position} unchanged`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
sayPromptOutcome(position, targetRole, editQueuedFile(root, targetRole, name, after, images), role === null);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
fail(errorMessage(err));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** The typed `--edit <n> <text>` / `--file` arm: the list-wide `editListedPrompt` path when
|
|
243
|
+
* no `--role`, else the role-scoped `editRolePrompt` command, with the carried `images`. */
|
|
244
|
+
function editTypedQueuedPrompt(root, position, text, role, images) {
|
|
239
245
|
if (role === null) {
|
|
240
|
-
sayListedOutcome(editListedPrompt(root, listWideScope(root), position,
|
|
246
|
+
sayListedOutcome(editListedPrompt(root, listWideScope(root), position, text, images), position);
|
|
241
247
|
return;
|
|
242
248
|
}
|
|
243
249
|
const target = role ?? DIRECTOR_ROLE;
|
|
244
|
-
sayPromptOutcome(position, target, runRolePromptCommand(root, target, position, (r, role, position) => editRolePrompt(r, role, position,
|
|
250
|
+
sayPromptOutcome(position, target, runRolePromptCommand(root, target, position, (r, role, position) => editRolePrompt(r, role, position, text, images)), false);
|
|
251
|
+
}
|
|
252
|
+
/** `--edit <n>` mode — replace a queued prompt's body by position: read the `--attach` image
|
|
253
|
+
* list through the gate, then the editor for a bare position or the typed/`--file` text,
|
|
254
|
+
* list-wide or `--role`-scoped. The three phases live in `readEditAttachments`,
|
|
255
|
+
* `editBareQueuedPrompt` and `editTypedQueuedPrompt`, so each `--edit` job is found by name. */
|
|
256
|
+
function runPromptEdit(root, position, text, attachPaths, role) {
|
|
257
|
+
const images = readEditAttachments(root, position, attachPaths, role);
|
|
258
|
+
if (text === null) {
|
|
259
|
+
editBareQueuedPrompt(root, position, role, images);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
editTypedQueuedPrompt(root, position, text, role, images);
|
|
245
263
|
}
|
|
246
264
|
/** `--top <n>` mode — move a queued prompt to the head of its queue by position, list-wide or
|
|
247
265
|
* `--role`-scoped. One helper so `--top`'s rules are found by name. */
|
|
@@ -283,8 +301,7 @@ function runPromptSubmit(root, text, atDelayMs, attachPaths, role) {
|
|
|
283
301
|
// `--attach <path>`: read and validate through the shared helper (a bad path fails by name
|
|
284
302
|
// before anything is queued). An attachment the target role cannot read is refused here by
|
|
285
303
|
// name — the same gate submitRolePrompt enforces, surfaced early so the CLI exits non-zero
|
|
286
|
-
// with the fix before anything is written or queued
|
|
287
|
-
// not refused, but the operator is told where the prompt will actually run.
|
|
304
|
+
// with the fix before anything is written or queued.
|
|
288
305
|
const images = attachPaths.length > 0 ? readPromptImages(attachPaths) : undefined;
|
|
289
306
|
if (images && images.length > 0) {
|
|
290
307
|
runPromptImageGate(root, target);
|
package/dist/src/init/init.js
CHANGED
|
@@ -234,26 +234,8 @@ async function resolveInitPlan(root, briefText, branch, opts) {
|
|
|
234
234
|
const preferredBranch = await resolveInitPreferredBranch(root, branch, wasRepo);
|
|
235
235
|
return { dryRun, tpl, combinedPrompt, brief, readmeExists, adopted, wasRepo, preferredBranch };
|
|
236
236
|
}
|
|
237
|
-
/**
|
|
238
|
-
|
|
239
|
-
* bookkeeping locally; performs no validation and never refuses. Returns everything the
|
|
240
|
-
* composed `InitResult` needs except `adopted`, `dryRun` and `template`, which `initProject`
|
|
241
|
-
* reads off `plan`. */
|
|
242
|
-
async function seedProject(root, plan) {
|
|
243
|
-
const { dryRun, tpl, combinedPrompt, brief, readmeExists, adopted, wasRepo, preferredBranch } = plan;
|
|
244
|
-
let repoInitialized = false;
|
|
245
|
-
let createdBranch;
|
|
246
|
-
if (!wasRepo) {
|
|
247
|
-
if (!dryRun)
|
|
248
|
-
await git(root, "init", "-b", preferredBranch);
|
|
249
|
-
repoInitialized = true;
|
|
250
|
-
createdBranch = preferredBranch;
|
|
251
|
-
}
|
|
252
|
-
// Decided before any project file is written (init only seeds bootstrap on a fresh project).
|
|
253
|
-
// A repo the `git init` above just created (or a dry run would) has no commits, and init
|
|
254
|
-
// itself commits nothing before the end, so this one answer also picks the commit message.
|
|
255
|
-
const withCommits = wasRepo && (await hasCommits(root));
|
|
256
|
-
const fresh = await isFreshProject(root, withCommits);
|
|
237
|
+
/** The `created`/`leftAlone` arrays and the `claim`/`write` closures above them. */
|
|
238
|
+
function seedWriters(root, dryRun) {
|
|
257
239
|
const created = [];
|
|
258
240
|
const leftAlone = [];
|
|
259
241
|
/** Record a template path this run either created (via `make`, under a real run only) or
|
|
@@ -275,33 +257,64 @@ async function seedProject(root, plan) {
|
|
|
275
257
|
// because claim's existsSync would then treat the torn file as complete on a re-run and
|
|
276
258
|
// never repair it.
|
|
277
259
|
const write = (name, content) => claim(name, path.join(root, name), () => writeTextAtomic(path.join(root, name), content));
|
|
260
|
+
return { created, leftAlone, claim, write };
|
|
261
|
+
}
|
|
262
|
+
/** The new-repo `git init` job: seed the preferred branch unless the run is a dry run. */
|
|
263
|
+
async function initSeedRepo(root, plan) {
|
|
264
|
+
let repoInitialized = false;
|
|
265
|
+
let createdBranch;
|
|
266
|
+
if (!plan.wasRepo) {
|
|
267
|
+
if (!plan.dryRun)
|
|
268
|
+
await git(root, "init", "-b", plan.preferredBranch);
|
|
269
|
+
repoInitialized = true;
|
|
270
|
+
createdBranch = plan.preferredBranch;
|
|
271
|
+
}
|
|
272
|
+
return { repoInitialized, createdBranch };
|
|
273
|
+
}
|
|
274
|
+
/** Whether `root` is a fresh project and the repo has commits — decided before any project
|
|
275
|
+
* file is written (init only seeds bootstrap on a fresh project). A repo the `git init`
|
|
276
|
+
* above just created (or a dry run would) has no commits, and init itself commits nothing
|
|
277
|
+
* before the end, so this one answer also picks the commit message. */
|
|
278
|
+
async function resolveSeedFreshness(root, plan) {
|
|
279
|
+
const withCommits = plan.wasRepo && (await hasCommits(root));
|
|
280
|
+
const fresh = await isFreshProject(root, withCommits);
|
|
281
|
+
return { withCommits, fresh };
|
|
282
|
+
}
|
|
283
|
+
/** Where the brief goes: the existing brief file, TUMWATER.md (adopted), or README.md. */
|
|
284
|
+
function seedBriefFile(root, plan, writers) {
|
|
278
285
|
// README.md is the brief only when none exists yet and there is no README to adopt (a fresh
|
|
279
286
|
// repo): with TUMWATER.md owning the managed sections, a created README.md would carry a
|
|
280
287
|
// duplicate prompt + status block that readBriefText never reaches and the readme role
|
|
281
288
|
// never maintains.
|
|
282
289
|
const name = projectName(root);
|
|
283
|
-
if (brief !== null)
|
|
284
|
-
leftAlone.push(brief);
|
|
285
|
-
else if (adopted) {
|
|
286
|
-
if (readmeExists)
|
|
287
|
-
leftAlone.push("README.md");
|
|
288
|
-
write("TUMWATER.md", briefTemplate(name, combinedPrompt));
|
|
290
|
+
if (plan.brief !== null)
|
|
291
|
+
writers.leftAlone.push(plan.brief);
|
|
292
|
+
else if (plan.adopted) {
|
|
293
|
+
if (plan.readmeExists)
|
|
294
|
+
writers.leftAlone.push("README.md");
|
|
295
|
+
writers.write("TUMWATER.md", briefTemplate(name, plan.combinedPrompt));
|
|
289
296
|
}
|
|
290
297
|
else
|
|
291
|
-
write("README.md", readmeTemplate(name, combinedPrompt));
|
|
298
|
+
writers.write("README.md", readmeTemplate(name, plan.combinedPrompt));
|
|
299
|
+
}
|
|
300
|
+
/** The template backlog files: PLANS/BUGS/QUESTIONS/PRINCIPLES, with starter-plans subst. */
|
|
301
|
+
function seedBacklogFiles(tpl, writers) {
|
|
292
302
|
// A template with starter plans seeds the backlog with them, rendered as `### ` entries —
|
|
293
303
|
// the primary entry format every backlog reader parses — so the team's first runs land on
|
|
294
304
|
// real work. blank keeps the `_None yet._` placeholder byte-identical to today.
|
|
295
305
|
const plansContent = tpl.starterPlans.length > 0
|
|
296
306
|
? PLANS_TEMPLATE.replace("_None yet._", tpl.starterPlans.map((p) => `### ${p}`).join("\n\n"))
|
|
297
307
|
: PLANS_TEMPLATE;
|
|
298
|
-
write("PLANS.md", plansContent);
|
|
299
|
-
write("BUGS.md", BUGS_TEMPLATE);
|
|
300
|
-
write("QUESTIONS.md", QUESTIONS_TEMPLATE);
|
|
301
|
-
write("PRINCIPLES.md", PRINCIPLES_TEMPLATE);
|
|
308
|
+
writers.write("PLANS.md", plansContent);
|
|
309
|
+
writers.write("BUGS.md", BUGS_TEMPLATE);
|
|
310
|
+
writers.write("QUESTIONS.md", QUESTIONS_TEMPLATE);
|
|
311
|
+
writers.write("PRINCIPLES.md", PRINCIPLES_TEMPLATE);
|
|
312
|
+
}
|
|
313
|
+
/** The config seed and its fresh-project bootstrap; returns the seeded bootstrap count. */
|
|
314
|
+
function seedConfigFile(root, fresh, dryRun, writers) {
|
|
302
315
|
let bootstrap = null;
|
|
303
316
|
if (fs.existsSync(configPath(root)))
|
|
304
|
-
leftAlone.push(CONFIG_BASENAME);
|
|
317
|
+
writers.leftAlone.push(CONFIG_BASENAME);
|
|
305
318
|
else {
|
|
306
319
|
const seed = seedConfig(root);
|
|
307
320
|
if (fresh) {
|
|
@@ -310,26 +323,36 @@ async function seedProject(root, plan) {
|
|
|
310
323
|
}
|
|
311
324
|
if (!dryRun)
|
|
312
325
|
saveConfig(root, seed);
|
|
313
|
-
created.push(CONFIG_BASENAME);
|
|
326
|
+
writers.created.push(CONFIG_BASENAME);
|
|
314
327
|
}
|
|
328
|
+
return bootstrap;
|
|
329
|
+
}
|
|
330
|
+
/** The .gitignore edit: gain the missing lines, or record it as left alone. */
|
|
331
|
+
function seedGitignore(root, dryRun, writers) {
|
|
315
332
|
if (ensureGitignore(root, dryRun))
|
|
316
|
-
created.push(".gitignore");
|
|
333
|
+
writers.created.push(".gitignore");
|
|
317
334
|
else if (fs.existsSync(path.join(root, ".gitignore")))
|
|
318
|
-
leftAlone.push(".gitignore");
|
|
335
|
+
writers.leftAlone.push(".gitignore");
|
|
336
|
+
}
|
|
337
|
+
/** The template's starter directories — empty ones only, before the first run. */
|
|
338
|
+
function seedStarterDirs(root, tpl, writers) {
|
|
319
339
|
// The template's starter directories: empty ones only, before the first run — the team's
|
|
320
340
|
// own runs write any code. Existing directories are left alone, like existing files above.
|
|
321
341
|
for (const dir of tpl.starterDirs) {
|
|
322
|
-
claim(dir, path.join(root, dir), () => ensureDir(path.join(root, dir)));
|
|
342
|
+
writers.claim(dir, path.join(root, dir), () => ensureDir(path.join(root, dir)));
|
|
323
343
|
}
|
|
344
|
+
}
|
|
345
|
+
/** What gets committed: the committable filter and the `git add` / staged-check / commit. */
|
|
346
|
+
async function commitSeeded(root, plan, withCommits, writers) {
|
|
324
347
|
// The config stays out of the commit pathspec: `git add -- tumwater.json` fails on a path
|
|
325
348
|
// the just-written .gitignore ignores (plans/portability.md §4a/7). It still heads the
|
|
326
349
|
// `created …` line the CLI prints — the file WAS created, it just must not be tracked. When
|
|
327
350
|
// that leaves the pathspec empty (`git add --` with no pathspec exits 1), there is nothing
|
|
328
351
|
// to commit: a repo that only gained a config reports it and stays uncommitted.
|
|
329
|
-
const dirSet = new Set(tpl.starterDirs);
|
|
330
|
-
const committable = created.filter((name) => name !== CONFIG_BASENAME && !dirSet.has(name));
|
|
352
|
+
const dirSet = new Set(plan.tpl.starterDirs);
|
|
353
|
+
const committable = writers.created.filter((name) => name !== CONFIG_BASENAME && !dirSet.has(name));
|
|
331
354
|
let committed = false;
|
|
332
|
-
if (!dryRun && committable.length > 0) {
|
|
355
|
+
if (!plan.dryRun && committable.length > 0) {
|
|
333
356
|
await git(root, "add", "--", ...committable);
|
|
334
357
|
const staged = await gitTry(root, "diff", "--cached", "--quiet");
|
|
335
358
|
if (staged === null) {
|
|
@@ -339,7 +362,32 @@ async function seedProject(root, plan) {
|
|
|
339
362
|
committed = true;
|
|
340
363
|
}
|
|
341
364
|
}
|
|
342
|
-
return
|
|
365
|
+
return committed;
|
|
366
|
+
}
|
|
367
|
+
/** Seed `root` from a resolved `InitPlan`: git init (a new repo only), every absent project
|
|
368
|
+
* file and starter directory, the config, and the commit. Holds the `created`/`leftAlone`
|
|
369
|
+
* bookkeeping locally; performs no validation and never refuses. Returns everything the
|
|
370
|
+
* composed `InitResult` needs except `adopted`, `dryRun` and `template`, which `initProject`
|
|
371
|
+
* reads off `plan`. */
|
|
372
|
+
async function seedProject(root, plan) {
|
|
373
|
+
const { dryRun, tpl } = plan;
|
|
374
|
+
const { repoInitialized, createdBranch } = await initSeedRepo(root, plan);
|
|
375
|
+
const { withCommits, fresh } = await resolveSeedFreshness(root, plan);
|
|
376
|
+
const writers = seedWriters(root, dryRun);
|
|
377
|
+
seedBriefFile(root, plan, writers);
|
|
378
|
+
seedBacklogFiles(tpl, writers);
|
|
379
|
+
const bootstrap = seedConfigFile(root, fresh, dryRun, writers);
|
|
380
|
+
seedGitignore(root, dryRun, writers);
|
|
381
|
+
seedStarterDirs(root, tpl, writers);
|
|
382
|
+
const committed = await commitSeeded(root, plan, withCommits, writers);
|
|
383
|
+
return {
|
|
384
|
+
created: writers.created,
|
|
385
|
+
leftAlone: writers.leftAlone,
|
|
386
|
+
committed,
|
|
387
|
+
repoInitialized,
|
|
388
|
+
branch: createdBranch,
|
|
389
|
+
bootstrap,
|
|
390
|
+
};
|
|
343
391
|
}
|
|
344
392
|
/** Initialize a repo for tumwater: README (with prompt + status) when no project brief exists
|
|
345
393
|
* yet, PLANS, BUGS, QUESTIONS, PRINCIPLES, tumwater.json, .gitignore — then commit whatever was
|
|
@@ -314,7 +314,7 @@ export async function resolveBacklogInsertConflicts(wt, files) {
|
|
|
314
314
|
}
|
|
315
315
|
// Atomic (tmp + rename): a merge process killed mid-write (SIGKILL, the timeout
|
|
316
316
|
// group-kill) must leave either the conflicted file or the fully resolved one, never a
|
|
317
|
-
// truncated backlog — the CLI's backlog writers (backlog-write.ts, question-answer.ts)
|
|
317
|
+
// truncated backlog — the CLI's backlog writers (backlog-write.ts, backlog-question-answer.ts)
|
|
318
318
|
// already write these unbounded-history files through this helper.
|
|
319
319
|
writeTextAtomic(p, resolved);
|
|
320
320
|
await git(wt, "add", "--", file);
|
|
@@ -3,7 +3,7 @@ import { mergeRefName } from "../paths.js";
|
|
|
3
3
|
import { recordReview } from "../run/run-apply.js";
|
|
4
4
|
import { saveRunnerStateBestEffort } from "../runner/runner-state.js";
|
|
5
5
|
import { unverifiedTreeOutcome } from "../build/build-check-events.js";
|
|
6
|
-
import { checkFailureReasons,
|
|
6
|
+
import { checkFailureReasons, redCheckHeadline } from "../build/build-check-report.js";
|
|
7
7
|
import { onlyKnownMainFailures } from "../baseline/main-watch.js";
|
|
8
8
|
import { mainTipVerdict } from "../baseline/main-red.js";
|
|
9
9
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
@@ -126,7 +126,7 @@ export async function mergeCheckRed(ctx, role, wt, red) {
|
|
|
126
126
|
if (result === "merge_blocked" && !attributed) {
|
|
127
127
|
warnEventBestEffort(ctx.root, role, `merge check failed (kept for one retry): ${reason}`);
|
|
128
128
|
if (ctx.state.mergeCheckFailures) {
|
|
129
|
-
ctx.state.mergeCheckFailures.headline =
|
|
129
|
+
ctx.state.mergeCheckFailures.headline = redCheckHeadline(red.check, red.outcome.outputTail);
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
return result;
|
|
@@ -143,15 +143,18 @@ export function noteMergeRetryPassed(ctx, role, pinPatch) {
|
|
|
143
143
|
if (headline)
|
|
144
144
|
warnEventBestEffort(ctx.root, role, MERGE_FLAKY_WARNING_PREFIX + headline);
|
|
145
145
|
}
|
|
146
|
-
/** A merge blocked by a deterministic cross-check
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* under the rebase, and a differently-rebased tree can pass the cross-check — but at the
|
|
146
|
+
/** A merge blocked by a deterministic cross-check — a property of the tree ahead of main,
|
|
147
|
+
* not a flaky run. The block is counted like a red merge check, keyed by the same patch-id:
|
|
148
|
+
* under MERGE_CHECK_FAILURE_LIMIT the pin is kept for one recovery re-merge — main may have
|
|
149
|
+
* moved under the rebase, and a differently-rebased tree can pass the cross-check — but at the
|
|
151
150
|
* limit the block is attributed to the change: rejected deterministically with the block
|
|
152
151
|
* reason, the pin deleted, no model run. Without this a resolution that trips the same
|
|
153
152
|
* cross-check on every re-resolve pays a fresh conflict-resolver run each run, forever
|
|
154
|
-
* (BUGS.md 2026-10-02: five resolver sessions for one fix-claim-blocked pin).
|
|
153
|
+
* (BUGS.md 2026-10-02: five resolver sessions for one fix-claim-blocked pin). Since part 3m/3
|
|
154
|
+
* the merge path no longer produces cross-check blocks (a fix claim and a broken backlog
|
|
155
|
+
* structure are repairable and land for the main watch), so nothing in the in-lock path calls
|
|
156
|
+
* this today; it stays as the strike-counting sink, still pinned by INV-BF2 and retired with
|
|
157
|
+
* the queue by 3f/3. */
|
|
155
158
|
export async function mergeBlocked(ctx, role, wt, blocked) {
|
|
156
159
|
return keepPinOrAttribute(ctx, wt, blocked, (head) => rejectChange(ctx, role, head, [`merge blocked: ${blocked}`], ctx.state));
|
|
157
160
|
}
|
|
@@ -185,7 +188,7 @@ export async function attributeRedCheck(ctx, role, head, label, red, state) {
|
|
|
185
188
|
const kept = keepForKnownFailures(ctx, role, label, red, state);
|
|
186
189
|
if (kept)
|
|
187
190
|
return kept;
|
|
188
|
-
const headline =
|
|
191
|
+
const headline = redCheckHeadline(red.check, red.outcome.outputTail);
|
|
189
192
|
const key = normalizeClusterKey(headline);
|
|
190
193
|
if (isKnownFlake(ctx.root, headline) && state.mergeFlakeExcused !== key) {
|
|
191
194
|
state.mergeFlakeExcused = key;
|
|
@@ -6,12 +6,11 @@ import { ensureDetachedWorktree } from "../git/worktree.js";
|
|
|
6
6
|
import { useWorktree } from "../git/worktree-use.js";
|
|
7
7
|
import { mergeToMain } from "./merge-to-main.js";
|
|
8
8
|
import { rebaseOntoMain } from "./merge-git.js";
|
|
9
|
-
import { mergeCheckRed,
|
|
9
|
+
import { mergeCheckRed, noteMergeRetryPassed } from "./merge-check-failures.js";
|
|
10
10
|
import { reviewAheadOfMain } from "../review/review.js";
|
|
11
|
-
import {
|
|
12
|
-
import { warnEventBestEffort, logEventBestEffort } from "../events/events.js";
|
|
11
|
+
import { logEventBestEffort } from "../events/events.js";
|
|
13
12
|
import { saveRunnerStateBestEffort } from "../runner/runner-state.js";
|
|
14
|
-
import { REVISION_FINAL_ON_REPEAT, REVISION_BACKSTOP, holdEntryAfterExhaustion } from "../runner/revision.js";
|
|
13
|
+
import { REVISION_FINAL_ON_REPEAT, REVISION_BACKSTOP, holdEntryAfterExhaustion } from "../runner/runner-revision.js";
|
|
15
14
|
import { priorObjectionNumber } from "../review/review-verdict.js";
|
|
16
15
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
17
16
|
import { shortSha } from "../text/format.js";
|
|
@@ -89,16 +88,14 @@ function reviewContextFor(ctx, req, wt, foldUsage) {
|
|
|
89
88
|
* merge (a restart hand-off past its deadline, BUGS.md 2026-09-23) would otherwise sail
|
|
90
89
|
* through its gate into the check. */
|
|
91
90
|
export async function reviewPinnedChange(ctx, req, wt, state, foldUsage) {
|
|
92
|
-
const { root
|
|
91
|
+
const { root } = ctx;
|
|
93
92
|
const { role } = req;
|
|
94
93
|
const ref = mergeRefName(role);
|
|
95
94
|
// Both uses below — a rejection and a strike-cap discard — end a pinned change without
|
|
96
|
-
// keeping it: drop the
|
|
97
|
-
// cannot drift between them. The verify's checkout is a pooled slot (plans/worktree-pool.md,
|
|
95
|
+
// keeping it: drop the pin ref. The verify's checkout is a pooled slot (plans/worktree-pool.md,
|
|
98
96
|
// "Vets and merges"): verifyRequest releases the lease in its finally, and a slot is reused, so
|
|
99
97
|
// this path must not remove the worktree it ran in.
|
|
100
98
|
const discardPin = async () => {
|
|
101
|
-
state.mergeReviewError = undefined;
|
|
102
99
|
await deleteRef(root, ref);
|
|
103
100
|
};
|
|
104
101
|
// Already stopping: the gate never starts, so nothing is persisted or folded — the ref stays
|
|
@@ -163,19 +160,6 @@ export async function reviewPinnedChange(ctx, req, wt, state, foldUsage) {
|
|
|
163
160
|
}
|
|
164
161
|
if (gate.decision === "failed") {
|
|
165
162
|
state.lastError = `review failed: ${gate.detail}`;
|
|
166
|
-
// A permanent provider configuration error can never succeed until the config changes, so
|
|
167
|
-
// record the held pin with the reviewer selector that failed: leftover recovery refuses to
|
|
168
|
-
// re-queue while that selector stands and clears the hold once it changes (BUGS.md
|
|
169
|
-
// 2026-10-06). A discarded pin is gone, so no hold is recorded for it.
|
|
170
|
-
if (gate.configError && !gate.discarded) {
|
|
171
|
-
const selector = modelSelectorField(reviewRunConfig(config)).model;
|
|
172
|
-
state.mergeReviewError = {
|
|
173
|
-
sha: req.sha,
|
|
174
|
-
...(selector ? { selector } : {}),
|
|
175
|
-
message: state.lastError,
|
|
176
|
-
};
|
|
177
|
-
warnEventBestEffort(root, role, `review blocked by a permanent provider configuration error${selector ? ` on ${selector}` : ""}: ${gate.detail} — the pinned change is held until the model config changes`);
|
|
178
|
-
}
|
|
179
163
|
// Strike-cap discard: the gate says so directly (it reset the worktree off the pin) — the
|
|
180
164
|
// commit is gone and the ref goes too. An under-cap failure — a dead reviewer — keeps the
|
|
181
165
|
// pin as it is: the gate never commits, so the pin still names the tree the next re-merge
|
|
@@ -195,8 +179,6 @@ export async function reviewPinnedChange(ctx, req, wt, state, foldUsage) {
|
|
|
195
179
|
await setRef(root, ref, sha);
|
|
196
180
|
req = { ...req, sha };
|
|
197
181
|
}
|
|
198
|
-
// The change may merge: nothing is held against the previous reviewer selector any more.
|
|
199
|
-
state.mergeReviewError = undefined;
|
|
200
182
|
return { kind: "gate", gate, sha };
|
|
201
183
|
}
|
|
202
184
|
/** The failed merge outcomes that KEEP the pin for another attempt: an under-cap review
|
|
@@ -291,7 +273,6 @@ async function mergeApprovedChangeIn(ctx, req) {
|
|
|
291
273
|
const wt = await ensureDetachedWorktree(ctx.root, mergeWorktreePath(ctx.root), req.sha);
|
|
292
274
|
const pinPatch = await patchId(wt, ctx.mainBranch, req.sha).catch(() => null);
|
|
293
275
|
let red;
|
|
294
|
-
let blocked;
|
|
295
276
|
const result = await mergeToMain({
|
|
296
277
|
root: ctx.root,
|
|
297
278
|
role: req.role,
|
|
@@ -303,9 +284,6 @@ async function mergeApprovedChangeIn(ctx, req) {
|
|
|
303
284
|
onMergeCheckRed: (check, outcome) => {
|
|
304
285
|
red = { check, outcome };
|
|
305
286
|
},
|
|
306
|
-
onMergeBlocked: (reason) => {
|
|
307
|
-
blocked = reason;
|
|
308
|
-
},
|
|
309
287
|
recheckResolved: async (resolvedWt) => {
|
|
310
288
|
if (ctx.signal().aborted)
|
|
311
289
|
return { verdict: "retry" };
|
|
@@ -357,12 +335,9 @@ async function mergeApprovedChangeIn(ctx, req) {
|
|
|
357
335
|
}
|
|
358
336
|
if (result === "merge_blocked" && red)
|
|
359
337
|
return mergeCheckRed(ctx, req.role, wt, red);
|
|
360
|
-
if (result === "merge_blocked" && blocked !== undefined) {
|
|
361
|
-
return mergeBlocked(ctx, req.role, wt, blocked);
|
|
362
|
-
}
|
|
363
338
|
// merge_conflict / merge_blocked: keep the ref — the next run's recovery re-merges it.
|
|
364
|
-
// (
|
|
365
|
-
//
|
|
339
|
+
// (The deterministic cross-check blocks — fix-claim and backlog structure — no longer come
|
|
340
|
+
// through here: since part 3m/3 they are repairable and land for the main watch.)
|
|
366
341
|
// A rejected recheck already wrote its own lastError and deleted its ref — leave it.
|
|
367
342
|
if (result !== "rejected")
|
|
368
343
|
ctx.state.lastError = `merge failed: ${result}`;
|