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,36 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
import { aheadOfMain, commitMessage, deleteRef, headOf, isMergedInto, refSha, setRef } from "../git/git.js";
|
|
1
|
+
import { aheadOfMain, commitExists, commitMessage, deleteRef, deleteRefIfAt, headOf, isMergedInto, refSha, setRef } from "../git/git.js";
|
|
3
2
|
import { parseCommitMetadata } from "../git/commit-message.js";
|
|
4
3
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
5
4
|
import { enqueueMerge, queuedMerges } from "../merge/merge-queue.js";
|
|
6
5
|
import { mergeRefName } from "../paths.js";
|
|
7
6
|
import { shortSha } from "../text/format.js";
|
|
8
|
-
/** Salvaging a commit a previous
|
|
7
|
+
/** Salvaging a commit a previous run left unmerged (plans/merge-queue.md). Since merge queue
|
|
9
8
|
* 2/5 the role's branch is reset to main the moment its commit is pinned, so the leftover
|
|
10
9
|
* normally lives in `refs/tumwater/landing/<role>`: recovery puts that sha back on the durable
|
|
11
|
-
* merge queue, so it merges through the SAME slot, gate, and strike cap a fresh
|
|
10
|
+
* merge queue, so it merges through the SAME slot, gate, and strike cap a fresh run's change
|
|
12
11
|
* does — no crash or abort path smuggles unreviewed work into main (invariant 1), and main keeps
|
|
13
|
-
* exactly one writer, the orchestrator's merge slot (an in-
|
|
12
|
+
* exactly one writer, the orchestrator's merge slot (an in-run recovery merge used to race
|
|
14
13
|
* the slot's batches into `merge_blocked`). A commit with NO pin — a crash in the window between
|
|
15
|
-
* the
|
|
14
|
+
* the run's commit and its pin write, or a failed pin write itself — still sits on the branch
|
|
16
15
|
* ahead of main; recovery adopts that tip into the pin scheme and queues it, so invariant 1
|
|
17
|
-
* holds whether or not the pin survived. Split out of
|
|
16
|
+
* holds whether or not the pin survived. Split out of runner.ts — which keeps the run lifecycle
|
|
18
17
|
* around it — because this is a self-contained concern with its own entry condition and git
|
|
19
|
-
* surface; the only things it borrows from the
|
|
18
|
+
* surface; the only things it borrows from the runner are identity, the run number, the
|
|
20
19
|
* worktree (for the no-pin fallback), and three pieces of the role's merge state: its
|
|
21
20
|
* conflict streak, outstanding hand-back, and permanent-reviewer hold. */
|
|
22
21
|
/** Consecutive merges of one pinned sha that may end `merge_conflict` before recovery stops
|
|
23
|
-
* re-queuing it (
|
|
22
|
+
* re-queuing it (RunnerState.mergeConflicts). Each attempt costs a conflict-resolution run, and
|
|
24
23
|
* a role holding a pin never authors: without a cap an unmergeable change would re-queue
|
|
25
24
|
* forever, burning one resolution run per drain. At the cap the pin is handed back to its
|
|
26
25
|
* author with the markers in place rather than discarded (PLANS.md "Robust conflict landing,
|
|
27
26
|
* part 2/2"); a lineage already handed back once is discarded instead. */
|
|
28
27
|
export const MERGE_CONFLICT_LIMIT = 2;
|
|
29
|
-
/**
|
|
28
|
+
/** Delete a pin main already contains — the work merged and a crash skipped its un-pinning —
|
|
29
|
+
* so nothing re-merges what main holds. True when it was stale and is gone. Shared by the
|
|
30
|
+
* author's recovery and the pipeline's re-queue (pinnedRetrySha). */
|
|
31
|
+
async function dropStalePin(root, ref, sha, mainBranch) {
|
|
32
|
+
if (!(await isMergedInto(root, sha, mainBranch)))
|
|
33
|
+
return false;
|
|
34
|
+
await deleteRef(root, ref);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
/** Delete a pin whose commit is missing from the repository, with a warning: it can never merge,
|
|
38
|
+
* and re-queuing it would end every run on an error. True when it was dangling and is gone. */
|
|
39
|
+
async function dropDanglingPin(root, role, ref, sha) {
|
|
40
|
+
if (await commitExists(root, sha))
|
|
41
|
+
return false;
|
|
42
|
+
await deleteRefIfAt(root, ref, sha);
|
|
43
|
+
warnEventBestEffort(root, role, `dropped merge pin ${shortSha(sha)}: its commit is missing from the repository, so it can never merge`);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/** The permanent-reviewer-config hold when it names `sha`, else undefined. */
|
|
47
|
+
function reviewHold(held, sha) {
|
|
48
|
+
return held?.sha === sha ? held : undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Consecutive conflict-ended merges of `sha` (0 when the streak names another sha). */
|
|
51
|
+
function conflictAttempts(streak, sha) {
|
|
52
|
+
return streak?.sha === sha ? streak.count : 0;
|
|
53
|
+
}
|
|
54
|
+
/** The sha of a role's kept pin when the merge pipeline may re-queue it itself: the pin exists,
|
|
55
|
+
* main does not already contain it (a stale pin is deleted, as recovery does), no reviewer
|
|
56
|
+
* configuration hold names it, and its conflict streak is under MERGE_CONFLICT_LIMIT. Null
|
|
57
|
+
* otherwise — a pin at the limit is the author's conflict hand-back, a held one waits for a
|
|
58
|
+
* config change, and both stay with recoverLeftover. */
|
|
59
|
+
export async function pinnedRetrySha(root, role, mainBranch, state) {
|
|
60
|
+
const ref = mergeRefName(role);
|
|
61
|
+
const sha = await refSha(root, ref).catch(() => null);
|
|
62
|
+
if (!sha)
|
|
63
|
+
return null;
|
|
64
|
+
if (await dropDanglingPin(root, role, ref, sha))
|
|
65
|
+
return null;
|
|
66
|
+
if (await dropStalePin(root, ref, sha, mainBranch))
|
|
67
|
+
return null;
|
|
68
|
+
if (reviewHold(state.mergeReviewError, sha))
|
|
69
|
+
return null;
|
|
70
|
+
if (conflictAttempts(state.mergeConflicts, sha) >= MERGE_CONFLICT_LIMIT)
|
|
71
|
+
return null;
|
|
72
|
+
return sha;
|
|
73
|
+
}
|
|
74
|
+
/** Whether the role holds a kept merge pin — the ref `recoverLeftover` reads. Ref presence is the
|
|
75
|
+
* whole answer: a pin main already holds is left to `recoverLeftover`, which deletes it. */
|
|
76
|
+
export async function hasUnmergedPin(root, role) {
|
|
77
|
+
return (await refSha(root, mergeRefName(role)).catch(() => null)) !== null;
|
|
78
|
+
}
|
|
79
|
+
/** Queue a commit a previous run left unmerged. Entry condition: the merge ref exists and
|
|
30
80
|
* its sha is not yet contained in main — or, with no pin at all, the role's branch is ahead of
|
|
31
81
|
* main (crash between the commit and the pin). A present-but-contained ref is stale — a crash
|
|
32
|
-
* between the ff-merge and the ref deletion — and is deleted without queuing anything
|
|
33
|
-
*
|
|
82
|
+
* between the ff-merge and the ref deletion — and is deleted without queuing anything, and so is
|
|
83
|
+
* a pin whose commit is missing from the repository (with a warning — it can never merge).
|
|
84
|
+
* Returns null when there was nothing to salvage. The queued entry carries the pin's sha plus the
|
|
34
85
|
* summary, body, and high-friction flag read back from its commit message, because the
|
|
35
86
|
* authoring run that set them is gone; from there the merge slot keeps or deletes the ref per
|
|
36
87
|
* the merger's usual policy. An unreadable ref or worktree reads as no leftover — only git-level
|
|
@@ -44,18 +95,16 @@ export async function recoverLeftover(ctx) {
|
|
|
44
95
|
const ref = mergeRefName(ctx.role);
|
|
45
96
|
let sha = await refSha(ctx.root, ref).catch(() => null);
|
|
46
97
|
if (sha) {
|
|
47
|
-
if (await
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
await deleteRef(ctx.root, ref);
|
|
98
|
+
if (await dropDanglingPin(ctx.root, ctx.role, ref, sha))
|
|
99
|
+
return null;
|
|
100
|
+
if (await dropStalePin(ctx.root, ref, sha, ctx.mainBranch))
|
|
51
101
|
return null;
|
|
52
|
-
}
|
|
53
102
|
}
|
|
54
103
|
else {
|
|
55
104
|
// No pin: either nothing was left behind, or a crash hit the commit→pin window (or
|
|
56
105
|
// the pin write failed) and the commit still sits on the branch ahead of main. Recover that
|
|
57
106
|
// tip too — invariant 1 must hold whether or not the pin survived. An unreadable worktree
|
|
58
|
-
// reads as "no leftover", exactly like a failed ref read: never propagate into the
|
|
107
|
+
// reads as "no leftover", exactly like a failed ref read: never propagate into the run.
|
|
59
108
|
const ahead = await aheadOfMain(ctx.wt, ctx.mainBranch).catch(() => 0);
|
|
60
109
|
if (ahead <= 0)
|
|
61
110
|
return null;
|
|
@@ -76,10 +125,16 @@ export async function recoverLeftover(ctx) {
|
|
|
76
125
|
// full gate check for a request that can never succeed until the model config changes. The
|
|
77
126
|
// caller clears the hold (and re-queues) once the resolved reviewer selector differs, so a
|
|
78
127
|
// fixed config recovers automatically (BUGS.md 2026-10-06).
|
|
79
|
-
const held = ctx.mergeReviewError;
|
|
80
|
-
if (held
|
|
128
|
+
const held = reviewHold(ctx.mergeReviewError, sha);
|
|
129
|
+
if (held)
|
|
81
130
|
return { kind: "held", sha, message: held.message };
|
|
82
|
-
|
|
131
|
+
// `requeueMainRedPin` owns the re-queue of a main_red pin, once main's head moves. Queuing it
|
|
132
|
+
// here would pay a full gate check against the same red head, whose result is already known.
|
|
133
|
+
if (ctx.mainRedRetry) {
|
|
134
|
+
const message = `kept change ${shortSha(sha)} waits for main to move past red ${shortSha(ctx.mainRedRetry.head)} before its next merge`;
|
|
135
|
+
return { kind: "main_red_wait", sha, message };
|
|
136
|
+
}
|
|
137
|
+
const attempts = conflictAttempts(ctx.mergeConflicts, sha);
|
|
83
138
|
if (attempts >= MERGE_CONFLICT_LIMIT) {
|
|
84
139
|
const summary = meta.subject ?? `leftover ${shortSha(sha)}`;
|
|
85
140
|
if (ctx.conflictHandback) {
|
|
@@ -91,14 +146,14 @@ export async function recoverLeftover(ctx) {
|
|
|
91
146
|
}
|
|
92
147
|
// Main has moved too far under this change for the resolver to reconcile it: keep the pin
|
|
93
148
|
// and hand it back to its author, who re-applies it over current main with the markers in
|
|
94
|
-
// place and resolves them. The
|
|
95
|
-
//
|
|
149
|
+
// place and resolves them. The runner's next step logs the conflict_handback event and sets
|
|
150
|
+
// RunnerState.conflictHandback (the pin survives until that re-apply).
|
|
96
151
|
return { kind: "handback", sha, summary, attempts };
|
|
97
152
|
}
|
|
98
153
|
const entry = {
|
|
99
154
|
role: ctx.role,
|
|
100
155
|
sha,
|
|
101
|
-
run: ctx.
|
|
156
|
+
run: ctx.runNumber,
|
|
102
157
|
// Name what merges, not merely that a recovery happened: the recovered commit's own subject
|
|
103
158
|
// rides the merge, so the failure digest's "Merged in the window" can correlate a
|
|
104
159
|
// recovered merge with the work it names (BUGS.md 2026-09-21). Unreadable messages fall
|
|
@@ -121,7 +176,7 @@ export async function recoverLeftover(ctx) {
|
|
|
121
176
|
/** The review-gate metadata a pinned leftover commit carries in its own message: its
|
|
122
177
|
* subject, high-friction flag, and author's body, or an empty object when the commit
|
|
123
178
|
* predates the contract or the message cannot be read. Recovery merges through the same gate
|
|
124
|
-
* as a fresh
|
|
179
|
+
* as a fresh run, so it must present those fields the same way — otherwise a flagged change
|
|
125
180
|
* is silently reviewed as routine (BUGS.md 2026-09-19), and the merged event names the
|
|
126
181
|
* recovery but not the work (BUGS.md 2026-09-21). */
|
|
127
182
|
async function recoveredMetadata(root, sha) {
|
|
@@ -5,18 +5,18 @@ export const MODEL_FALLBACK_COOLDOWN_MS = 5 * 60_000;
|
|
|
5
5
|
/** The ceiling a repeatedly-failing probe's cooldown doubles toward. */
|
|
6
6
|
export const MODEL_FALLBACK_MAX_COOLDOWN_MS = 30 * 60_000;
|
|
7
7
|
/** True only while the role is in a fallback episode whose probe is still in the future — the
|
|
8
|
-
*
|
|
8
|
+
* runs that use the fallback pair. */
|
|
9
9
|
export function modelFallbackActive(state, now) {
|
|
10
10
|
return state !== undefined && state.since > 0 && now < state.probeAt;
|
|
11
11
|
}
|
|
12
12
|
/** True only while the role is in a fallback episode whose cooldown has elapsed: the next
|
|
13
|
-
*
|
|
13
|
+
* run uses the primary as the probe. */
|
|
14
14
|
export function modelFallbackProbe(state, now) {
|
|
15
15
|
return state !== undefined && state.since > 0 && now >= state.probeAt;
|
|
16
16
|
}
|
|
17
|
-
/** Classify one authoring run's verdict about the primary (src/
|
|
18
|
-
* same flags for the team-wide hold).
|
|
19
|
-
* that read an image its model cannot see (the runner killed it, plans/image-input.md) — is
|
|
17
|
+
/** Classify one authoring agent run's verdict about the primary (src/run/run-usage.ts records the
|
|
18
|
+
* same flags for the team-wide hold). An agent run that was aborted, quiet-killed, or timed out
|
|
19
|
+
* — or that read an image its model cannot see (the runner killed it, plans/image-input.md) — is
|
|
20
20
|
* inconclusive even when it also reports provider evidence: the harness killed it, so it is
|
|
21
21
|
* no verdict about the primary's health. */
|
|
22
22
|
export function modelFallbackVerdict(pi) {
|
|
@@ -26,17 +26,17 @@ export function modelFallbackVerdict(pi) {
|
|
|
26
26
|
return "provider-failure";
|
|
27
27
|
return "answered";
|
|
28
28
|
}
|
|
29
|
-
/** Fold one authoring run's verdict into the role's fallback state, returning the next state
|
|
29
|
+
/** Fold one authoring agent run's verdict into the role's fallback state, returning the next state
|
|
30
30
|
* or undefined for "running the primary" (no episode).
|
|
31
31
|
*
|
|
32
32
|
* - Not in fallback, provider failure: increment the consecutive count; the
|
|
33
33
|
* MODEL_FALLBACK_FAILURES-th trips the episode.
|
|
34
|
-
* - Not in fallback, an answering run: clear the running count (undefined).
|
|
35
|
-
* - Not in fallback, an inconclusive run: unchanged — a harness kill is not evidence the
|
|
34
|
+
* - Not in fallback, an answering agent run: clear the running count (undefined).
|
|
35
|
+
* - Not in fallback, an inconclusive agent run: unchanged — a harness kill is not evidence the
|
|
36
36
|
* primary recovered, so the running count survives it.
|
|
37
|
-
* - In fallback, a fallback
|
|
37
|
+
* - In fallback, a fallback run (`probe` false): unchanged — its outcome neither returns
|
|
38
38
|
* the role to primary nor re-trips it.
|
|
39
|
-
* - In fallback, a probe
|
|
39
|
+
* - In fallback, a probe run: an inconclusive probe is unchanged (retried on the next run);
|
|
40
40
|
* an answering probe clears the episode; a provider-class failed probe doubles the cooldown
|
|
41
41
|
* (capped) and schedules the next one.
|
|
42
42
|
*/
|
|
@@ -68,8 +68,8 @@ export function recordModelFallback(state, outcome) {
|
|
|
68
68
|
}
|
|
69
69
|
return { failures, since: 0, probeAt: 0, cooldownMs: 0, reason };
|
|
70
70
|
}
|
|
71
|
-
/** The reason label one run's provider evidence yields: "rate-limit" for a 429, else the
|
|
72
|
-
* backend-failure kind. Exported so the
|
|
71
|
+
/** The reason label one agent run's provider evidence yields: "rate-limit" for a 429, else the
|
|
72
|
+
* backend-failure kind. Exported so the runner's evidence assembly and tests share one
|
|
73
73
|
* spelling. */
|
|
74
74
|
export function providerFailureReason(rateLimit, kind) {
|
|
75
75
|
return rateLimit ? "rate-limit" : (kind ?? "backend failure");
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readTextIfExists, readTextOrNull, writeTextAtomic } from "../files/files.js";
|
|
3
|
+
import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
|
|
4
|
+
import { gitTry, gitTryStarted } from "../git/git-run.js";
|
|
5
|
+
import { resetWorktreeToMain } from "../git/worktree.js";
|
|
6
|
+
import { resolveBacklogInsertConflicts } from "../merge/backlog-conflicts.js";
|
|
7
|
+
import { conflictedFiles } from "../merge/merge-git.js";
|
|
8
|
+
import { shortSha } from "../text/format.js";
|
|
9
|
+
import { formatDate } from "../text/datetime.js";
|
|
10
|
+
import { stampedSubject } from "../git/commit-message.js";
|
|
11
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
12
|
+
import { entryKey } from "../backlog/backlog-parts.js";
|
|
13
|
+
import { actionableEntryRanges } from "../backlog/backlog-structure.js";
|
|
14
|
+
import { withPrimaryWorktreeLock } from "../concurrency/lock.js";
|
|
15
|
+
import { logEventBestEffort } from "../events/events.js";
|
|
16
|
+
/** The persistence count at which one repeated objection makes a rejection final
|
|
17
|
+
* (plans/revise-rejected.md; the repeat-finality policy in BUGS.md 2026-10-14): each numbered
|
|
18
|
+
* reason records how many consecutive revisions it has survived — a new (untagged) objection is
|
|
19
|
+
* 0, a `(prior N)` restatement is 1 plus the prior round's count for that objection — and when
|
|
20
|
+
* any reason reaches this count it was raised and then survived two revisions, so the change has
|
|
21
|
+
* stopped converging and the rejection is final. A converging chain keeps revising while each
|
|
22
|
+
* round's objections differ. */
|
|
23
|
+
export const REVISION_FINAL_ON_REPEAT = 2;
|
|
24
|
+
/** The hard backstop on one change's revision chain (plans/revise-rejected.md; BUGS.md
|
|
25
|
+
* 2026-10-14): at this many total rejections the rejection is final even when the objections
|
|
26
|
+
* never repeat, so a non-converging author cannot revise forever. Replaces the old fixed
|
|
27
|
+
* `REVISION_LIMIT = 2`, which ended a converging chain at its third rejection whenever each
|
|
28
|
+
* round raised a new objection. */
|
|
29
|
+
export const REVISION_BACKSTOP = 6;
|
|
30
|
+
/** The convergence rule (PLANS.md "Reviewer as a regular role, part 3/4"): when a rejection is
|
|
31
|
+
* final — the round cap or an objection that survived its revisions — the author could not
|
|
32
|
+
* converge, so the PLANS.md plan it was revising is held for the plan role instead of being left
|
|
33
|
+
* eligible for a fresh author pick that would restart the same cycle (plans/revise-rejected.md's
|
|
34
|
+
* motivation: the cap stops one cycle, the re-pick starts another). The hold is a
|
|
35
|
+
* **Needs review …** note under the entry's heading — the same needs-review hold every surface
|
|
36
|
+
* renders `[blocked: needs review]` and that the plan role resolves by splitting or replanning
|
|
37
|
+
* the plan (doctor-backlog-plans' checkStalledPlans names it) — whose text names the surviving
|
|
38
|
+
* objections verbatim (they are already in `RunnerState.lastReview.reasons`), so the plan role
|
|
39
|
+
* sees exactly what would not converge. PLANS.md plans only: no role clears a Needs-review note
|
|
40
|
+
* on a BUGS.md entry (`tumwater bug --release` keeps it, INV-AT1, and no role resolves one for
|
|
41
|
+
* a bug), so holding a bug would be a permanent dead-end; a bug that cannot converge stays with
|
|
42
|
+
* the existing re-pick behaviour (reviewer-feedback delivery, part 4/4, addresses that churn).
|
|
43
|
+
* Written through the primary worktree lock, as every other backlog write is, and then committed
|
|
44
|
+
* on main by commitOperatorBacklogEdit — the commit every other backlog writer makes — so the
|
|
45
|
+
* primary checkout stays clean for the next fast-forward and the hold reaches the worktrees
|
|
46
|
+
* that branch from main. Best-effort (write and commit each avoid throwing), so a non-converged
|
|
47
|
+
* but already-final rejection can never throw on the terminal-exhaustion path. No-op when the
|
|
48
|
+
* entry is gone (already moved or done), the role carries no claim, or the claim is a bug. */
|
|
49
|
+
export async function holdEntryAfterExhaustion(root, role, claim, reasons) {
|
|
50
|
+
if (claim?.file !== "PLANS.md")
|
|
51
|
+
return;
|
|
52
|
+
const note = `**Needs review ${formatDate(new Date())} by ${baseRoleOf(role)}: could not converge` +
|
|
53
|
+
(reasons.length === 0 ? "" : ` — ${reasons.join("; ")}`) +
|
|
54
|
+
`**`;
|
|
55
|
+
try {
|
|
56
|
+
withPrimaryWorktreeLock(root, () => {
|
|
57
|
+
const file = path.join(root, claim.file);
|
|
58
|
+
const md = readTextIfExists(file) ?? "";
|
|
59
|
+
const entry = actionableEntryRanges(md, "Planned").find((e) => entryKey(e.title) === claim.key);
|
|
60
|
+
if (entry === undefined)
|
|
61
|
+
return;
|
|
62
|
+
const lines = md.split("\n");
|
|
63
|
+
// The entry's 0-based line span is [start-1, end-1] inclusive (a non-last entry's range
|
|
64
|
+
// carries its trailing separator blank; the last entry's ends at its last content line).
|
|
65
|
+
// Find the last non-blank line inside that span, then rebuild the file with the note,
|
|
66
|
+
// one blank on each side, directly after the body and before whatever follows (the next
|
|
67
|
+
// heading, or EOF) — never inside the following entry.
|
|
68
|
+
let lastContent = entry.end - 1;
|
|
69
|
+
while (lastContent > entry.start - 1 && (lines[lastContent] ?? "").trim() === "")
|
|
70
|
+
lastContent--;
|
|
71
|
+
const rebuilt = [...lines.slice(0, lastContent + 1), "", note, "", ...lines.slice(entry.end)];
|
|
72
|
+
writeTextAtomic(file, rebuilt.join("\n"));
|
|
73
|
+
});
|
|
74
|
+
// Commit the hold on the base branch so the primary stays clean for ff-only and the note
|
|
75
|
+
// reaches the worktrees that branch from main — the same best-effort commit every other
|
|
76
|
+
// backlog writer makes (a primary not on the base branch just skips the commit).
|
|
77
|
+
await commitOperatorBacklogEdit(root, claim.file, stampedSubject("operator", `Hold converged plan: ${claim.key}`)); // vocabulary-ok: the tumwater(operator): commit prefix is a machine string
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
logEventBestEffort(root, {
|
|
81
|
+
source: role,
|
|
82
|
+
type: "revision",
|
|
83
|
+
action: "hold-failed",
|
|
84
|
+
message: `could not hold the entry after a final rejection: ${String(err)}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** The picks a stopped `cherry-pick` sequence still owes after the one it stopped on, oldest
|
|
89
|
+
* first — read from the sequencer's todo, whose first line is the stopped pick. null when the
|
|
90
|
+
* todo cannot be read, so the caller never settles a stop it cannot finish. */
|
|
91
|
+
async function picksAfterStop(wt) {
|
|
92
|
+
const todo = await gitTry(wt, "rev-parse", "--git-path", "sequencer/todo");
|
|
93
|
+
const text = todo === null ? null : readTextOrNull(path.resolve(wt, todo));
|
|
94
|
+
if (text === null)
|
|
95
|
+
return null;
|
|
96
|
+
const picks = text.split("\n").flatMap((line) => /^(?:p|pick)\s+(\S+)/.exec(line)?.[1] ?? []);
|
|
97
|
+
return picks.slice(1);
|
|
98
|
+
}
|
|
99
|
+
/** Cherry-pick `<base>..<sha>` onto `wt` as uncommitted edits, settling backlog conflicts
|
|
100
|
+
* without a model run on the way. A rejected or handed-back diff that prepends to PLANS-DONE.md
|
|
101
|
+
* or BUGS-FIXED.md conflicts with every entry main archived since, the same insert-only shape the
|
|
102
|
+
* merge's first pass settles (resolveBacklogInsertConflicts); left alone, its author got markers
|
|
103
|
+
* in a 1.9 MB file (feature-5, 2026-10-19). On a stop whose conflicted files all settle, the
|
|
104
|
+
* sequencer is quit (`--continue` refuses in `--no-commit` mode) and the picks it still owed are
|
|
105
|
+
* replayed the same way onto the settled index. */
|
|
106
|
+
async function pickSettlingBacklog(wt, base, sha) {
|
|
107
|
+
let picks = [`${base}..${sha}`];
|
|
108
|
+
for (;;) {
|
|
109
|
+
// diff3 carries the base section of every conflict, which the backlog settler reads; the
|
|
110
|
+
// markers an author is handed keep it too.
|
|
111
|
+
const picked = await gitTryStarted(wt, "-c", "merge.conflictStyle=diff3", "cherry-pick", "--no-commit", ...picks);
|
|
112
|
+
if (picked !== null)
|
|
113
|
+
return { status: "clean" };
|
|
114
|
+
// conflictedFiles, not a raw diff: it decodes git's C-quoted names, so a non-ASCII
|
|
115
|
+
// conflicted path is the on-disk name the prompt and hasConflictMarkers can use.
|
|
116
|
+
const conflicted = await conflictedFiles(wt);
|
|
117
|
+
if (conflicted.length === 0)
|
|
118
|
+
return { status: "failed" };
|
|
119
|
+
const rest = await picksAfterStop(wt);
|
|
120
|
+
// A settler that fails part-way (a failed `git add`) leaves every file to the author.
|
|
121
|
+
const remaining = rest === null ? conflicted : await resolveBacklogInsertConflicts(wt, conflicted).catch(() => conflicted);
|
|
122
|
+
await gitTry(wt, "cherry-pick", "--quit");
|
|
123
|
+
if (remaining.length > 0)
|
|
124
|
+
return { status: "conflicted", files: remaining };
|
|
125
|
+
if (rest === null || rest.length === 0)
|
|
126
|
+
return { status: "clean" };
|
|
127
|
+
picks = rest;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** The exclusive start of the range to re-apply for `sha`: the merge-base with main, except that a
|
|
131
|
+
* commit already on main (a reverted change, plans/fast-merge.md part 4c) has merge-base `sha`
|
|
132
|
+
* itself and an empty range, so its own parent is the base and the range is that one commit. null
|
|
133
|
+
* (the caller drops the hand-back) only on git's own answer, never because git could not start. */
|
|
134
|
+
async function pickBase(wt, mainBranch, sha) {
|
|
135
|
+
const base = await gitTryStarted(wt, "merge-base", mainBranch, sha);
|
|
136
|
+
if (base === null)
|
|
137
|
+
return null;
|
|
138
|
+
const full = await gitTryStarted(wt, "rev-parse", sha);
|
|
139
|
+
if (full?.trim() !== base.trim())
|
|
140
|
+
return base;
|
|
141
|
+
// A commit main merely fast-forwarded onto keeps the empty range; only one main reverted is
|
|
142
|
+
// handed back to be re-applied.
|
|
143
|
+
const reverts = (await gitTryStarted(wt, "log", mainBranch, "--format=%s", "--fixed-strings", `--grep=Revert ${shortSha(sha)}`)) ?? "";
|
|
144
|
+
if (!reverts.split("\n").some((subject) => subject.endsWith(`Revert ${shortSha(sha)}`)))
|
|
145
|
+
return base;
|
|
146
|
+
return gitTryStarted(wt, "rev-parse", `${sha}^`);
|
|
147
|
+
}
|
|
148
|
+
/** Re-apply a rejected change `sha` onto current main as uncommitted edits in `wt`
|
|
149
|
+
* (plans/revise-rejected.md): the author's next run starts from the rejected diff instead of
|
|
150
|
+
* re-deriving it. `git cherry-pick --no-commit <merge-base>..<sha>` leaves the edits in the
|
|
151
|
+
* worktree without committing; a conflict confined to the backlog's insert-only shape is settled
|
|
152
|
+
* on the way (pickSettlingBacklog). On any other git failure — most often a conflict with what
|
|
153
|
+
* main merged since the rejection, but also a missing object — the cherry-pick is aborted and
|
|
154
|
+
* the worktree reset to main, and false is returned so the caller falls back to a plain
|
|
155
|
+
* rejection note. A git that could not start, or a stopped pick whose conflicts git cannot list,
|
|
156
|
+
* throws instead, keeping the revision and its ref for the next run. */
|
|
157
|
+
export async function applyRevision(wt, mainBranch, sha) {
|
|
158
|
+
const base = await pickBase(wt, mainBranch, sha);
|
|
159
|
+
if (base === null) {
|
|
160
|
+
await resetWorktreeToMain(wt, mainBranch);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if ((await pickSettlingBacklog(wt, base, sha)).status !== "clean") {
|
|
164
|
+
await gitTry(wt, "cherry-pick", "--abort");
|
|
165
|
+
await resetWorktreeToMain(wt, mainBranch);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
/** Re-apply a handed-back change's diff onto current main with the conflict markers left in
|
|
171
|
+
* place as ordinary uncommitted edits, so its author can resolve them (PLANS.md "Robust
|
|
172
|
+
* conflict landing, part 2/2"). Like applyRevision it cherry-picks
|
|
173
|
+
* `<merge-base>..<sha> --no-commit` onto the worktree's current main, settling backlog
|
|
174
|
+
* insert-only conflicts on the way (pickSettlingBacklog), so only files that settler cannot
|
|
175
|
+
* settle keep markers. A conflicted pick is NOT aborted: its sequencer state is quit, a mixed
|
|
176
|
+
* `git reset` drops the index's unmerged entries so the marker-bearing files are plain
|
|
177
|
+
* working-tree edits the run can commit normally, and `{ applied: true, conflicted }` is
|
|
178
|
+
* returned. A failure with no unmerged paths (a missing object, a bad revision) aborts, resets
|
|
179
|
+
* to main, and returns `{ applied: false }` so the caller falls back to a fresh start. A git
|
|
180
|
+
* that could not start, or a stopped pick whose conflicts git cannot list, throws instead, so the
|
|
181
|
+
* run errors with the hand-back and its ref kept for the next. */
|
|
182
|
+
export async function applyWithConflicts(wt, mainBranch, sha) {
|
|
183
|
+
const base = await pickBase(wt, mainBranch, sha);
|
|
184
|
+
if (base === null) {
|
|
185
|
+
await resetWorktreeToMain(wt, mainBranch);
|
|
186
|
+
return { applied: false, conflicted: [] };
|
|
187
|
+
}
|
|
188
|
+
const picked = await pickSettlingBacklog(wt, base, sha);
|
|
189
|
+
if (picked.status === "conflicted") {
|
|
190
|
+
// Keep the marker-bearing files, drop only the index's unmerged stage entries: a mixed
|
|
191
|
+
// reset leaves the working tree untouched, so the markers are uncommitted edits the run
|
|
192
|
+
// stages and commits like any other author work.
|
|
193
|
+
await gitTry(wt, "reset");
|
|
194
|
+
return { applied: true, conflicted: picked.files };
|
|
195
|
+
}
|
|
196
|
+
if (picked.status === "failed") {
|
|
197
|
+
await gitTry(wt, "cherry-pick", "--abort");
|
|
198
|
+
await resetWorktreeToMain(wt, mainBranch);
|
|
199
|
+
return { applied: false, conflicted: [] };
|
|
200
|
+
}
|
|
201
|
+
return { applied: true, conflicted: [] };
|
|
202
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { logEventBestEffort } from "../events/events.js";
|
|
2
2
|
import { modelFallbackVerdict, providerFailureReason, recordModelFallback } from "./model-fallback.js";
|
|
3
|
-
/** Fold one AUTHORING run's verdict into the role's model-fallback state
|
|
4
|
-
* (src/
|
|
5
|
-
* that actually happened is evidence: a
|
|
6
|
-
* inbox, a recovered leftover, a red-main block) returns from
|
|
7
|
-
* reached, and modelFallbackVerdict reports an aborted/killed/timed-out run as
|
|
3
|
+
/** Fold one AUTHORING agent run's verdict into the role's model-fallback state
|
|
4
|
+
* (src/runner/model-fallback.ts) and emit the episode's started/ended events. Only the agent run
|
|
5
|
+
* that actually happened is evidence: a run that never invoked pi (a skipped director
|
|
6
|
+
* inbox, a recovered leftover, a red-main block) returns from runOnce before this is
|
|
7
|
+
* reached, and modelFallbackVerdict reports an aborted/killed/timed-out agent run as
|
|
8
8
|
* inconclusive — so neither can end an episode without the primary having answered.
|
|
9
9
|
* A role whose tier resolves to no fallback pair never trips, and an episode persisted from a
|
|
10
10
|
* config that HAD a pair is cleared when a live edit removes it (or sets it to "pause"), so a
|
|
11
11
|
* stale "on fallback" mark cannot outlive the config that created it. Persists on any state
|
|
12
|
-
* change; the
|
|
12
|
+
* change; the run's own finalize save carries it too. */
|
|
13
13
|
export function foldModelFallbackPhase(ctx, fallbackCtx, pi) {
|
|
14
14
|
const s = ctx.state;
|
|
15
15
|
const prior = s.modelFallback;
|
|
@@ -19,8 +19,8 @@ export function foldModelFallbackPhase(ctx, fallbackCtx, pi) {
|
|
|
19
19
|
const wasIn = prior !== undefined && prior.since > 0;
|
|
20
20
|
if (fallbackCtx.fallback === null) {
|
|
21
21
|
// No fallback pair resolves any more: the feature is off for this role. End a persisted
|
|
22
|
-
// episode (the primary just ran, so this
|
|
23
|
-
// so a later config starts from zero. A killed/aborted/timed-out run is no verdict, so it
|
|
22
|
+
// episode (the primary just ran, so this run IS the return), and drop any pre-trip count
|
|
23
|
+
// so a later config starts from zero. A killed/aborted/timed-out agent run is no verdict, so it
|
|
24
24
|
// leaves the episode in place exactly as it does on a configured fallback.
|
|
25
25
|
if (prior === undefined)
|
|
26
26
|
return;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** The post-run outcome half of a role run, split out of src/runner/runner-run.ts: after the
|
|
2
|
+
* authoring pi run finishes, everything that turns the result into an outcome — the verdict
|
|
3
|
+
* classification (abort, config request, quiet kill, timeout, refusal, failure, no-change)
|
|
4
|
+
* through resolveRunVerdict, and the fulfillable path's staging of the work for the merge
|
|
5
|
+
* queue — plus the refusal-note merge path. The run-lifecycle phases that lead up to the pi
|
|
6
|
+
* run live in runner-run.ts; this module keeps only the outcome's own dependencies, and
|
|
7
|
+
* shares runner-run.ts's `RunnerRunContext` binder — the one contract between the two halves —
|
|
8
|
+
* so either half can change without touching the other's imports. */
|
|
9
|
+
import { mergeToMain } from "../merge/merge-to-main.js";
|
|
10
|
+
import { MERGE_LOCK_WAIT_MS } from "../paths.js";
|
|
11
|
+
import { resolveRunVerdict } from "../run/run-verdict.js";
|
|
12
|
+
import { modelTrailerLine } from "../git/commit-message.js";
|
|
13
|
+
import { seamAgent } from "../config/model-selector.js";
|
|
14
|
+
import { stageRunMerge } from "../run/run-stage.js";
|
|
15
|
+
import { stageCheckFindings } from "../run/stage-check.js";
|
|
16
|
+
/** Merge the worktree branch on main (see src/merge/merge-to-main.ts for the
|
|
17
|
+
* rebase → verify → ff-merge → conflict-retry flow): delegates with the runner identity, run
|
|
18
|
+
* number, and shared pi wiring so a conflict-resolution run folds into this run's counters
|
|
19
|
+
* like any other pi run. Only the refusal-note merge (src/verdict/refusal.ts) still uses it —
|
|
20
|
+
* reviewed changes merge through the merge pipeline's leased verification slot instead; md-only
|
|
21
|
+
* notes are review-exempt by construction, so they keep this branch path (plans/merge-queue.md
|
|
22
|
+
* 2/5). The refusal path waits MERGE_LOCK_WAIT_MS for merge.lock, not withLock's default: a
|
|
23
|
+
* merge holds it through its in-lock check, which outlasts the default. */
|
|
24
|
+
async function mergePhase(ctx, wt, summary) {
|
|
25
|
+
return mergeToMain({
|
|
26
|
+
root: ctx.root,
|
|
27
|
+
role: ctx.role,
|
|
28
|
+
mainBranch: ctx.mainBranch,
|
|
29
|
+
exemptPaths: ctx.config.review.exemptPaths,
|
|
30
|
+
config: ctx.config,
|
|
31
|
+
runNumber: ctx.state.runs,
|
|
32
|
+
lockTimeoutMs: MERGE_LOCK_WAIT_MS,
|
|
33
|
+
runPi: (w, prompt, sessionName, config) => ctx.pi.runRolePi(w, prompt, sessionName, false, config),
|
|
34
|
+
}, wt, summary);
|
|
35
|
+
}
|
|
36
|
+
/** Turn a finished pi run into its RunOutcome: the post-run half of runOnce, split off so each
|
|
37
|
+
* half reads on its own screen — the setup above ends at the pi return. The verdict
|
|
38
|
+
* classification (abort, config request, quiet kill, timeout, refusal, failure, no-change) lives
|
|
39
|
+
* in resolveRunVerdict (src/run/run-verdict.ts), and the fulfillable path — staging —
|
|
40
|
+
* stays here. `userPrompt` is the raw director prompt this run is executing (null for role
|
|
41
|
+
* runners) so unfulfilled outcomes can re-queue it; `revisionRound` is the revision this run holds
|
|
42
|
+
* (undefined when it is not a revision); `flow` is the qa observer's FLOW line (null for every
|
|
43
|
+
* other role); `filed` is the feedback observer's FILED line (null for every other role). */
|
|
44
|
+
export async function handlePiResultPhase(ctx, pi, userPrompt, revisionRound, wt, flow, filed, piStartedAt, cfg) {
|
|
45
|
+
// Everything that decides the run left nothing mergeable — abort, config request,
|
|
46
|
+
// quiet kill, timeout, refusal, failure without changes, no change — lives in
|
|
47
|
+
// resolveRunVerdict (src/run/run-verdict.ts); null means the run IS fulfillable.
|
|
48
|
+
const modelLine = modelTrailerLine(seamAgent(cfg), cfg.provider, cfg.model);
|
|
49
|
+
const verdict = await resolveRunVerdict({
|
|
50
|
+
modelLine,
|
|
51
|
+
root: ctx.root,
|
|
52
|
+
role: ctx.role,
|
|
53
|
+
mainBranch: ctx.mainBranch,
|
|
54
|
+
state: ctx.state,
|
|
55
|
+
pending: ctx.pending,
|
|
56
|
+
turns: ctx.usage.turns,
|
|
57
|
+
userPrompt,
|
|
58
|
+
revisionRound,
|
|
59
|
+
wt,
|
|
60
|
+
pi,
|
|
61
|
+
flow,
|
|
62
|
+
filed,
|
|
63
|
+
// The image-reroute arm resolves its target from the live config and the current
|
|
64
|
+
// regime, and names the model this run ran on as the handoff's `from`.
|
|
65
|
+
config: ctx.config,
|
|
66
|
+
rerouteRegime: ctx.rerouteRegime(piStartedAt),
|
|
67
|
+
runModel: cfg.model,
|
|
68
|
+
warn: (message) => ctx.warn(message),
|
|
69
|
+
merge: (w, sum) => mergePhase(ctx, w, sum),
|
|
70
|
+
finishAbortedRun: () => ctx.finishAbortedRun(userPrompt, wt),
|
|
71
|
+
});
|
|
72
|
+
if (verdict)
|
|
73
|
+
return verdict;
|
|
74
|
+
// The commit is pinned and the worktree is free (src/run/run-stage.ts): stage it for the
|
|
75
|
+
// merge queue and END the run — the orchestrator drains the queue on its single merge
|
|
76
|
+
// slot, outside the author semaphore, so this slot is free the moment the work is
|
|
77
|
+
// committed (plans/merge-queue.md 3/5). Staging derives the commit message from the reply's
|
|
78
|
+
// SUMMARY block (one bounded follow-up turn when the run left none), flags high-friction
|
|
79
|
+
// runs, pins the commit by the role's merge ref BEFORE freeing the worktree (invariant
|
|
80
|
+
// 4 — a failed pin defers to next-run recovery), and enqueues the merge, which runs the
|
|
81
|
+
// same gate + merge flow through runMergePi/foldMergeUsage on this same state object
|
|
82
|
+
// — recording the commit count, the outcome, and the reviewer spend into it — and logs
|
|
83
|
+
// merge_done/merge_failed; a non-terminal outcome keeps the pin for next-run leftover recovery.
|
|
84
|
+
// `commits` was NOT incremented above: it counts merged changes only.
|
|
85
|
+
return stageRunMerge({
|
|
86
|
+
modelLine,
|
|
87
|
+
root: ctx.root,
|
|
88
|
+
role: ctx.role,
|
|
89
|
+
state: ctx.state,
|
|
90
|
+
config: ctx.config,
|
|
91
|
+
runTurns: ctx.usage.turns,
|
|
92
|
+
userPrompt,
|
|
93
|
+
revisionRound,
|
|
94
|
+
mainBranch: ctx.mainBranch,
|
|
95
|
+
wt,
|
|
96
|
+
finalText: pi.finalText,
|
|
97
|
+
flow,
|
|
98
|
+
filed,
|
|
99
|
+
piStartedAt,
|
|
100
|
+
warn: (message) => ctx.warn(message),
|
|
101
|
+
requestSummary: (w, n) => ctx.pi.requestSummary(w, cfg, n),
|
|
102
|
+
stageCheck: (w) => stageCheckFindings(w, ctx.mainBranch, ctx.config.review.exemptPaths, ctx.role),
|
|
103
|
+
requestStageFix: (w, findings, change) => ctx.pi.requestStageFix(w, findings, change, cfg),
|
|
104
|
+
pinAndReset: (w, sha) => ctx.pinAndReset(w, sha),
|
|
105
|
+
finishAbortedRun: () => ctx.finishAbortedRun(userPrompt, wt),
|
|
106
|
+
});
|
|
107
|
+
}
|