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
|
@@ -42,12 +42,19 @@
|
|
|
42
42
|
* heading.
|
|
43
43
|
* - A `path:line` location (`status-roles.ts:66`) was checked as a symbol, and no file is named
|
|
44
44
|
* that. A location now claims its file, a bare line reference (`:368`) claims nothing, and a
|
|
45
|
-
* bare file name is backed by a file of that name anywhere in the walked source dirs.
|
|
45
|
+
* bare file name is backed by a file of that name anywhere in the walked source dirs.
|
|
46
|
+
*
|
|
47
|
+
* Another false positive (BUGS.md 2026-10-29): a commit id was checked as a symbol, and no code
|
|
48
|
+
* file ever spells one. A dropped post-merge review is filed as a bug whose whole fix is a review
|
|
49
|
+
* of the commit, so the Fix paragraph cites `17c0bbbc…` and nothing else — and six bugfix runners
|
|
50
|
+
* resubmitted that close 29 times in five hours, each rejected the same way. A commit id is now
|
|
51
|
+
* backed when it names a commit in the repository (withoutKnownCommits); a made-up one is still
|
|
52
|
+
* rejected. */
|
|
46
53
|
import { existsSync, lstatSync, readFileSync, statSync } from "node:fs";
|
|
47
54
|
import { extname, join } from "node:path";
|
|
48
55
|
import { readTextOrNull, statOrNull, walkFiles } from "../files/files.js";
|
|
49
56
|
import { fenceTracker, finishedArchiveOf, parseEntryDetails, sectionLines, trailingParenthetical, } from "../backlog/backlog-md.js";
|
|
50
|
-
import { changeBaseRev, fileContentAt } from "../git/git.js";
|
|
57
|
+
import { changeBaseRev, commitExists, fileContentAt } from "../git/git.js";
|
|
51
58
|
import { collapseWhitespace, truncate } from "../text/text.js";
|
|
52
59
|
/** How much of a one-line record a rejection quotes: the record is one line by construction but
|
|
53
60
|
* can run to several hundred characters, and the reason rides in a one-line event. */
|
|
@@ -263,12 +270,13 @@ function sourceHaystackSignature(root) {
|
|
|
263
270
|
sigs.sort();
|
|
264
271
|
return sigs.join("|");
|
|
265
272
|
}
|
|
266
|
-
/** Every file under the
|
|
267
|
-
*
|
|
273
|
+
/** Every file under the tree the haystack reads — the whole root, so a project whose code lives
|
|
274
|
+
* outside src//test//scripts/ (tools/, tests/, lib/, …) backs its symbols like a src/ one — with
|
|
275
|
+
* build output, vendored code, VCS and harness state skipped at any depth: node_modules, dist,
|
|
276
|
+
* .git and .tumwater. */
|
|
268
277
|
function walkSourceFiles(root, visit) {
|
|
269
|
-
const skipDirs = new Set(["node_modules", "dist"]);
|
|
270
|
-
|
|
271
|
-
walkFiles(join(root, dir), visit, skipDirs);
|
|
278
|
+
const skipDirs = new Set(["node_modules", "dist", ".git", ".tumwater"]);
|
|
279
|
+
walkFiles(root, visit, skipDirs);
|
|
272
280
|
}
|
|
273
281
|
/** A slash-free name with a file extension: `status-roles.ts`, not `src/a.ts` or `signalTree`. */
|
|
274
282
|
const FILE_NAME_RE = /^[^/]+\.[A-Za-z][\w-]*$/;
|
|
@@ -292,28 +300,42 @@ export function unbackedSymbols(root, symbols, haystack) {
|
|
|
292
300
|
return !fileNames.has(s);
|
|
293
301
|
});
|
|
294
302
|
}
|
|
303
|
+
/** A commit id as a Fix paragraph cites one: 7 to 40 lowercase hex digits, optionally cut short
|
|
304
|
+
* with a trailing ellipsis (`3c1c1c6176...`). Group 1 is the id itself. */
|
|
305
|
+
const COMMIT_REF_RE = /^([0-9a-f]{7,40})(?:\.\.\.|…)?$/;
|
|
306
|
+
/** Whether a Fix-paragraph span is shaped like a commit id: a citation of history rather than a
|
|
307
|
+
* name code could hold. The doctor's synchronous scan cannot ask git, so it leaves these out of a
|
|
308
|
+
* record's names; the gate checks them with withoutKnownCommits. */
|
|
309
|
+
export function isCommitRef(span) {
|
|
310
|
+
return COMMIT_REF_RE.test(span);
|
|
311
|
+
}
|
|
312
|
+
/** `missing` without the commit ids that name a commit in the repository at `wt`. No code file
|
|
313
|
+
* ever spells a commit id, so the haystack could never back one: a Fix paragraph citing the
|
|
314
|
+
* commit it reviewed or landed in read as a phantom fix. An id that names no commit stays
|
|
315
|
+
* missing, so an invented one is still rejected. */
|
|
316
|
+
export async function withoutKnownCommits(wt, missing) {
|
|
317
|
+
const out = [];
|
|
318
|
+
for (const name of missing) {
|
|
319
|
+
const sha = COMMIT_REF_RE.exec(name)?.[1];
|
|
320
|
+
if (sha === undefined || !(await commitExists(wt, sha)))
|
|
321
|
+
out.push(name);
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
295
325
|
/** The unbacked names for a one-line message: all of them when 3 or fewer, otherwise the
|
|
296
326
|
* first 3 and an ellipsis — a doctor check or gate message has room for one line. */
|
|
297
327
|
export function missingSymbolNames(missing) {
|
|
298
328
|
return missing.length <= 3 ? missing.join(", ") : `${missing.slice(0, 3).join(", ")}…`;
|
|
299
329
|
}
|
|
300
|
-
/** The
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return undefined;
|
|
310
|
-
// A deleted, unreadable, or non-regular (symlinked) file reads as empty: there is no Fixed
|
|
311
|
-
// transition to verify, so this check passes and lets the gate's sibling structural check
|
|
312
|
-
// (backlog-structure.ts) report the dropped section. Reading a symlink raw would pull an
|
|
313
|
-
// outside host file's `## Fixed` heading into this gate's verdict (the same lstat policy the
|
|
314
|
-
// backlog-conflict resolver and backlogStructureReason pin under INV-G3), so a head is read
|
|
315
|
-
// only when lstat shows a regular file; ENOENT and symlinks both read as empty.
|
|
316
|
-
const heads = ["BUGS.md", BUGS_ARCHIVE].map((f) => {
|
|
330
|
+
/** The `## Fixed` heads of BUGS.md and its archive as read under the lstat policy: a deleted,
|
|
331
|
+
* unreadable, or non-regular (symlinked) file reads as empty — there is no Fixed transition to
|
|
332
|
+
* verify, so the gate's sibling structural check (backlog-structure.ts) reports the dropped
|
|
333
|
+
* section instead. Reading a symlink raw would pull an outside host file's `## Fixed` heading
|
|
334
|
+
* into the gate's verdict (the same lstat policy the backlog-conflict resolver and
|
|
335
|
+
* backlogStructureReason pin under INV-G3), so a head is read only when lstat shows a regular
|
|
336
|
+
* file; ENOENT and symlinks both read as empty. */
|
|
337
|
+
function readFixedHeads(wt) {
|
|
338
|
+
return ["BUGS.md", BUGS_ARCHIVE].map((f) => {
|
|
317
339
|
const full = join(wt, f);
|
|
318
340
|
try {
|
|
319
341
|
if (!lstatSync(full).isFile())
|
|
@@ -324,40 +346,40 @@ export async function falseFixReason(wt, mainBranch, files) {
|
|
|
324
346
|
}
|
|
325
347
|
return readTextOrNull(full) ?? "";
|
|
326
348
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
349
|
+
}
|
|
350
|
+
/** The base Fixed section bodies the diff's own merge-base holds, keyed by normalized heading.
|
|
351
|
+
* The base is the diff's own merge-base, not main's tip: the gate diffs `mainBranch...HEAD`
|
|
352
|
+
* against the merge-base, and the entry's Open/Fixed state lives in the tree the change builds
|
|
353
|
+
* on — pre-batch main can still carry the record as (falsely) Fixed and make an Open→Fixed
|
|
354
|
+
* restoration in a stacked batch compare as already done (BUGS.md 2026-09-23). Every base body
|
|
355
|
+
* under a heading counts: two distinct entries can share one normalized heading, and keeping
|
|
356
|
+
* only the last made the other read as rewritten (BUGS.md 2026-10-23). */
|
|
357
|
+
async function baseFixedBodies(wt, mainBranch) {
|
|
333
358
|
const baseRev = await changeBaseRev(wt, mainBranch);
|
|
334
359
|
const bases = [await fileContentAt(wt, baseRev, "BUGS.md"), await fileContentAt(wt, baseRev, BUGS_ARCHIVE)];
|
|
335
|
-
|
|
336
|
-
// rewrites an existing Fixed record's narrative must face the symbol check too (BUGS.md
|
|
337
|
-
// 2026-09-23 — the in-place narrative rewrite is how a second phantom merge evaded it).
|
|
338
|
-
// Every base body under a heading counts: two distinct entries can share one normalized
|
|
339
|
-
// heading, and keeping only the last made the other read as rewritten (BUGS.md 2026-10-23).
|
|
340
|
-
const baseBodies = new Map();
|
|
360
|
+
const bodies = new Map();
|
|
341
361
|
for (const e of bases.flatMap((base) => fixedEntries(base))) {
|
|
342
362
|
const key = normalizeFixedHeading(e.title);
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
363
|
+
const known = bodies.get(key) ?? new Set();
|
|
364
|
+
known.add(e.body);
|
|
365
|
+
bodies.set(key, known);
|
|
346
366
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
367
|
+
return { bases, bodies };
|
|
368
|
+
}
|
|
369
|
+
/** A changed full Fixed entry's rejection, or undefined when every changed entry backs its
|
|
370
|
+
* symbols. An already-Fixed entry is skipped only when its body is unchanged: an md-only edit
|
|
371
|
+
* that rewrites an existing Fixed record's narrative must face the symbol check too (BUGS.md
|
|
372
|
+
* 2026-09-23 — the in-place narrative rewrite is how a second phantom merge evaded it). The
|
|
373
|
+
* haystack is built lazily — only once a Fixed record's body actually changed — because it
|
|
374
|
+
* walks and reads the entire source tree (~5 MB here), and the common BUGS.md edit moves
|
|
375
|
+
* nothing to Fixed: an Open bug added, a narrative touched elsewhere, so the gate and the
|
|
376
|
+
* in-lock recheck each paid that walk for a check that never reached a symbol. */
|
|
377
|
+
async function movedEntryReason(wt, entries, bodies, getHaystack) {
|
|
355
378
|
for (const { title: heading, body } of entries) {
|
|
356
|
-
const knownBodies =
|
|
379
|
+
const knownBodies = bodies.get(normalizeFixedHeading(heading));
|
|
357
380
|
if (knownBodies?.has(body))
|
|
358
381
|
continue;
|
|
359
|
-
|
|
360
|
-
const missing = unbackedSymbols(wt, fixSymbols(body), haystack);
|
|
382
|
+
const missing = await withoutKnownCommits(wt, unbackedSymbols(wt, fixSymbols(body), getHaystack()));
|
|
361
383
|
if (missing.length === 0)
|
|
362
384
|
continue;
|
|
363
385
|
const names = missingSymbolNames(missing);
|
|
@@ -372,10 +394,15 @@ export async function falseFixReason(wt, mainBranch, files) {
|
|
|
372
394
|
`paragraph now names code that exists nowhere on this tree: ${names} — include that ` +
|
|
373
395
|
`code in the same commit, or leave the entry's text as it was`;
|
|
374
396
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
/** An added or edited one-line Fixed record's rejection, or undefined when each record either
|
|
400
|
+
* reproduces a base line or carries only names the base already holds. One-line records are
|
|
401
|
+
* compressions of entries the base already holds as Fixed, and history keeps a renamed symbol's
|
|
402
|
+
* old name, so a record the diff adds or edits may carry forward any name the base's Fixed
|
|
403
|
+
* section already records. A name it introduces must exist on the tree: a bug moved straight
|
|
404
|
+
* from Open into a one-line record faces the same check as a full entry. */
|
|
405
|
+
async function addedRecordReason(wt, records, bases, getHaystack) {
|
|
379
406
|
const baseLines = bases.flatMap((base) => sectionLines(base, "Fixed")).map((line) => line.trim());
|
|
380
407
|
const seen = new Set(baseLines);
|
|
381
408
|
const baseText = baseLines.join("\n");
|
|
@@ -385,8 +412,7 @@ export async function falseFixReason(wt, mainBranch, files) {
|
|
|
385
412
|
const introduced = fixSymbols(record).filter((symbol) => !baseText.includes(symbol));
|
|
386
413
|
if (introduced.length === 0)
|
|
387
414
|
continue;
|
|
388
|
-
|
|
389
|
-
const missing = unbackedSymbols(wt, introduced, haystack);
|
|
415
|
+
const missing = await withoutKnownCommits(wt, unbackedSymbols(wt, introduced, getHaystack()));
|
|
390
416
|
if (missing.length === 0)
|
|
391
417
|
continue;
|
|
392
418
|
return (`md-only BUGS.md edit adds the one-line Fixed record "${truncate(record, RECORD_QUOTE_MAX)}", ` +
|
|
@@ -396,3 +422,28 @@ export async function falseFixReason(wt, mainBranch, files) {
|
|
|
396
422
|
}
|
|
397
423
|
return undefined;
|
|
398
424
|
}
|
|
425
|
+
/** The first false-fix claim in an md-only diff that touches BUGS.md, or undefined when the
|
|
426
|
+
* diff backs its Fixed transitions (or makes none). `files` is the ahead-of-main path list
|
|
427
|
+
* of the diff merging on `mainBranch`; the worktree `wt` holds the tree being merged. */
|
|
428
|
+
export async function falseFixReason(wt, mainBranch, files) {
|
|
429
|
+
// Fixed entries live in BUGS.md's own section and in its archive, BUGS-FIXED.md
|
|
430
|
+
// (plans/backlog-archive.md): tumwater moves a fixed bug there right before the commit, so a
|
|
431
|
+
// fix claim reaches the gate as an archive edit, and an archived entry whose body is intact
|
|
432
|
+
// is no claim at all. Both sides of the comparison read both files.
|
|
433
|
+
if (!files.includes("BUGS.md") && !files.includes(BUGS_ARCHIVE))
|
|
434
|
+
return undefined;
|
|
435
|
+
const heads = readFixedHeads(wt);
|
|
436
|
+
if (!heads.some((head) => head.includes("## Fixed")))
|
|
437
|
+
return undefined;
|
|
438
|
+
const { bases, bodies } = await baseFixedBodies(wt, mainBranch);
|
|
439
|
+
const sections = heads.map((head) => fixedSection(head));
|
|
440
|
+
const entries = sections.flatMap((section) => section.entries);
|
|
441
|
+
const records = sections.flatMap((section) => section.records);
|
|
442
|
+
// Built lazily, shared by both checks, only once a Fixed record's body actually changed: the
|
|
443
|
+
// haystack walks and reads the entire source tree (~5 MB here), and the common BUGS.md edit
|
|
444
|
+
// moves nothing to Fixed — an Open bug added, a narrative touched elsewhere — so the gate and
|
|
445
|
+
// the in-lock recheck each paid that walk for a check that never reached a symbol.
|
|
446
|
+
let haystack;
|
|
447
|
+
const getHaystack = () => (haystack ??= sourceHaystack(wt));
|
|
448
|
+
return (await movedEntryReason(wt, entries, bodies, getHaystack)) ?? (await addedRecordReason(wt, records, bases, getHaystack));
|
|
449
|
+
}
|
|
@@ -106,7 +106,7 @@ async function stripBlockedPlanRefusalNotes(ctx, wt) {
|
|
|
106
106
|
// and a plain writeFileSync that a kill interrupted would commit a truncated file,
|
|
107
107
|
// permanently dropping the plan entries past the cut point. writeTextAtomic is the
|
|
108
108
|
// harness's one write-to-temp-then-rename entry point — the same one backlog-write.ts
|
|
109
|
-
// and question-answer.ts use for the markdown backlog files.
|
|
109
|
+
// and backlog-question-answer.ts use for the markdown backlog files.
|
|
110
110
|
try {
|
|
111
111
|
writeTextAtomic(plansFile, md);
|
|
112
112
|
for (const title of dropped) {
|
|
@@ -74,10 +74,9 @@ export const REPLY_ENDINGS = `- End with exactly ONE of these three endings:
|
|
|
74
74
|
${SUMMARY_BLOCK.replace(/^ {2}/gm, " ")}`;
|
|
75
75
|
/** The review gate's closing rule as stated to pi: the two accepted verdict forms and the
|
|
76
76
|
* numbered-reasons line that must follow them. buildReviewPrompt (gate-prompts.ts) ends its
|
|
77
|
-
* rules list with this,
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* indents both lines, the two follow-ups indent only the VERDICT line. The machine detector for
|
|
77
|
+
* rules list with this — one home, so the advertised pair is edited in one place
|
|
78
|
+
* instead of drifting between the prompts. Indentation is the caller's: the review prompt
|
|
79
|
+
* indents both lines. The machine detector for
|
|
81
80
|
* these forms lives beside it (VERDICT_LINE_SOURCE below); gate-prompts.test.ts pins the forms
|
|
82
81
|
* buildReviewPrompt advertises against what parseVerdict accepts. */
|
|
83
82
|
export const VERDICT_ENDING = `VERDICT: approve or VERDICT: reject
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tumwater",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Opinionated autonomous development harness built on pi. You write the initial prompt; role-driven loops build the project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"build": "rm -rf dist && tsc && node scripts/stamp-build.mjs",
|
|
38
38
|
"prepack": "npm run build",
|
|
39
39
|
"lint": "eslint . --cache",
|
|
40
|
-
"test": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js",
|
|
41
|
-
"test:gate": "node scripts/live-checkout-guard.mjs &&
|
|
42
|
-
"test:e2e": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js e2e",
|
|
43
|
-
"test:coverage": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node dist/test/test-runner.js --coverage",
|
|
40
|
+
"test": "node scripts/live-checkout-guard.mjs && eslint . --cache && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js",
|
|
41
|
+
"test:gate": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node scripts/lint-changed.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js --affected --budget=30",
|
|
42
|
+
"test:e2e": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js e2e",
|
|
43
|
+
"test:coverage": "node scripts/live-checkout-guard.mjs && tsc --incremental && node scripts/stamp-build.mjs && node scripts/check-permit.mjs node dist/test/test-runner.js --coverage",
|
|
44
44
|
"smoke:claude": "node scripts/smoke-claude.mjs",
|
|
45
45
|
"smoke:opencode": "node scripts/smoke-opencode.mjs",
|
|
46
46
|
"smoke:codex": "node scripts/smoke-codex.mjs",
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/** The AsyncLocalStorage store that lets a run's lease follow its runs across providers
|
|
2
|
-
* (plans/provider-concurrency.md §5): a lease rides here for the length of a run, a verify task,
|
|
3
|
-
* or a resolver run, and `RunnerPi.runWithTransientRetry` re-keys it to the provider each run
|
|
4
|
-
* actually uses. The one choke point every run a role makes (authoring, gate, merge and resolver)
|
|
5
|
-
* passes through, so a budget handback or a model-failure switch that moves a run in flight to
|
|
6
|
-
* another provider moves the lease with it instead of leaving it parked on the key it was admitted
|
|
7
|
-
* on. The check-permit.ts `holdingPermit` store is the pattern: nothing here is a separate permit,
|
|
8
|
-
* only the single lease the run path already holds.
|
|
9
|
-
*
|
|
10
|
-
* The store carries the `piDefault` (the caller's cached readPiDefaultModel result) beside the
|
|
11
|
-
* lease so `ensureProviderLease` can resolve a bare no-model pi seam's key exactly as the
|
|
12
|
-
* admission/review seam did — a run whose key were computed without it would re-key a default
|
|
13
|
-
* team's run off pi's own provider. It also carries the semaphore and, optionally, wait
|
|
14
|
-
* hooks, so a re-key that has to park on a full target key reports its wait the way a parked
|
|
15
|
-
* resolver does (merge-verify.ts's providerWaitStage). */
|
|
16
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
-
import { seamConcurrencyKey } from "../config/config-views.js";
|
|
18
|
-
const providerLeaseStore = new AsyncLocalStorage();
|
|
19
|
-
/** Run `run` with `lease` in the provider-lease store, so any run it makes re-keys that lease to
|
|
20
|
-
* the provider it actually uses (ensureProviderLease). `opts.piDefault` is the caller's cached pi
|
|
21
|
-
* default model, so a bare no-model seam resolves to the same key the admission or review seam
|
|
22
|
-
* used; `opts.hooks` (the verify: providerWaitStage) hears a re-key wait, if there is one. The same
|
|
23
|
-
* lease object is released by the caller after the store is unwrapped — a re-key mutates it in
|
|
24
|
-
* place, so release() frees whatever key the run path ended on. */
|
|
25
|
-
export function withProviderLease(lease, semaphore, run, opts = {}) {
|
|
26
|
-
return providerLeaseStore.run({ lease, semaphore, piDefault: opts.piDefault, hooks: opts.hooks }, run);
|
|
27
|
-
}
|
|
28
|
-
/** Re-key the calling lease to the provider `cfg` runs on, if it is not there yet. No-op when
|
|
29
|
-
* the current async context carries no lease (a direct RunnerPi unit test, `tumwater history`) or
|
|
30
|
-
* when the run is already on the lease's key. When it does re-key and `hooks` are present and
|
|
31
|
-
* the target key is at its cap, the hooks hear `waiting` before the lease parks and `granted`
|
|
32
|
-
* once the key frees — a re-key never waits on the team total (it already holds a team
|
|
33
|
-
* permit), only on the target key's headroom. */
|
|
34
|
-
export async function ensureProviderLease(cfg) {
|
|
35
|
-
const ctx = providerLeaseStore.getStore();
|
|
36
|
-
if (!ctx)
|
|
37
|
-
return;
|
|
38
|
-
const key = seamConcurrencyKey(cfg, ctx.piDefault);
|
|
39
|
-
if (ctx.lease.key === key)
|
|
40
|
-
return;
|
|
41
|
-
// The leased currently holds `old` (or nothing); the target key's in-use does not include it,
|
|
42
|
-
// so the key being at its cap is exactly whether rekey() will park.
|
|
43
|
-
const parks = ctx.hooks !== undefined && keyAtCap(ctx, key);
|
|
44
|
-
if (parks)
|
|
45
|
-
ctx.hooks.waiting();
|
|
46
|
-
await ctx.lease.rekey(key);
|
|
47
|
-
if (parks)
|
|
48
|
-
ctx.hooks.granted();
|
|
49
|
-
}
|
|
50
|
-
/** Whether `key` is at its configured cap right now (an absent or uncapped key is not) — the
|
|
51
|
-
* same test semaphore.drive uses to withhold a re-key grant, decided from the live snapshot so
|
|
52
|
-
* a re-key wait can be announced before the lease parks (providerWaitStage). */
|
|
53
|
-
function keyAtCap(ctx, key) {
|
|
54
|
-
if (key === undefined)
|
|
55
|
-
return false;
|
|
56
|
-
const entry = ctx.semaphore.snapshot().byKey[key];
|
|
57
|
-
return entry?.limit !== undefined && entry.inUse >= entry.limit;
|
|
58
|
-
}
|
|
59
|
-
/** The pool-slot wait rule (plans/provider-concurrency.md §5 "No deadlock"): a lease never holds a
|
|
60
|
-
* provider key while it waits for a worktree pool slot. Called by leaseSlot just before it parks on
|
|
61
|
-
* a busy pool; it re-keys the calling lease to no key (which never waits), so a run or verify
|
|
62
|
-
* waiting for a slot does not hold — and cannot deadlock — a provider slot whose in-use a re-key
|
|
63
|
-
* waiter is waiting to drain. No-op without a lease in context. */
|
|
64
|
-
export async function dropProviderLeaseKey() {
|
|
65
|
-
const ctx = providerLeaseStore.getStore();
|
|
66
|
-
if (!ctx)
|
|
67
|
-
return;
|
|
68
|
-
await ctx.lease.rekey(undefined);
|
|
69
|
-
}
|
|
70
|
-
/** Run `wait`, an idle stretch inside the calling lease's work, with the lease's team permit and
|
|
71
|
-
* key slot handed back (SemaphoreLease.pause), so a parked run can use them meanwhile, then take
|
|
72
|
-
* them back (resume) before returning. A failed review's in-place retry pause is the caller: with
|
|
73
|
-
* one team permit, a reviewer that kept failing held it through two 30 s pauses and starved every
|
|
74
|
-
* other role. Once `signal` has fired, nothing is taken back and the caller's release() settles a
|
|
75
|
-
* still-paused lease. No-op without a lease in context. */
|
|
76
|
-
export async function idleWithoutProviderLease(wait, signal) {
|
|
77
|
-
const ctx = providerLeaseStore.getStore();
|
|
78
|
-
if (!ctx)
|
|
79
|
-
return wait();
|
|
80
|
-
ctx.lease.pause();
|
|
81
|
-
try {
|
|
82
|
-
await wait();
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
if (!signal?.aborted)
|
|
86
|
-
await untilAborted(ctx.lease.resume(), signal);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/** `p`, or an earlier return once `signal` fires: a resume parked on a full team must not keep an
|
|
90
|
-
* aborted verification waiting for a permit it no longer needs. */
|
|
91
|
-
async function untilAborted(p, signal) {
|
|
92
|
-
if (!signal)
|
|
93
|
-
return p;
|
|
94
|
-
let onAbort = () => { };
|
|
95
|
-
const aborted = new Promise((resolve) => {
|
|
96
|
-
onAbort = resolve;
|
|
97
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
98
|
-
});
|
|
99
|
-
try {
|
|
100
|
-
await Promise.race([p, aborted]);
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
signal.removeEventListener("abort", onAbort);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { backendSessionResumable } from "../agent/agent-backend.js";
|
|
2
|
-
import { reviewRunConfig, resolvedBackendConfig } from "../config/config-views.js";
|
|
3
|
-
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
4
|
-
import { nativeLogPath, piLogPath } from "../paths.js";
|
|
5
|
-
import { buildNoRerunPrompt, buildVerdictRequestPrompt } from "../prompt/gate-prompts.js";
|
|
6
|
-
import { cappedRequestTimeouts } from "../request-timeouts.js";
|
|
7
|
-
/** Hard caps on the VERDICT follow-up turn: it should take one short reply on a warm session,
|
|
8
|
-
* so it never gets the review run's own budget (mirrors RunnerPi's SUMMARY-request caps). */
|
|
9
|
-
const VERDICT_REQUEST_TIMEOUT_S = 900;
|
|
10
|
-
const VERDICT_REQUEST_QUIET_S = 300;
|
|
11
|
-
/** One bounded follow-up turn on the reviewer's just-finished session: the shared session
|
|
12
|
-
* guard, capped budgets, and runGatePi wiring behind requestVerdict and requestNoRerun —
|
|
13
|
-
* exactly two call sites, differing only in prompt, session name, label, and (the nudge only)
|
|
14
|
-
* a started-tool-call collector. Null when there is no session to continue. */
|
|
15
|
-
async function runFollowupTurn(ctx, opts) {
|
|
16
|
-
const dir = ctx.sessionDir;
|
|
17
|
-
const cfg = reviewRunConfig(ctx.config);
|
|
18
|
-
// The verdict/re-run follow-up continues the review session the gate just wrote. The
|
|
19
|
-
// backend's own resumable rule keeps a newer line of another kind from being the session a
|
|
20
|
-
// pi continue would actually reach; claude resumes its recorded id (looked up under the
|
|
21
|
-
// team's config dir, so a team that set one continues under it).
|
|
22
|
-
if (!backendSessionResumable(dir, { kind: "gate", agent: cfg.agent ?? "pi" }, resolvedBackendConfig(ctx.config, "claude").configDir)) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
return ctx.runGatePi({
|
|
26
|
-
cwd: ctx.wt,
|
|
27
|
-
kind: "gate",
|
|
28
|
-
prompt: opts.prompt,
|
|
29
|
-
config: {
|
|
30
|
-
...cfg,
|
|
31
|
-
...cappedRequestTimeouts(cfg, VERDICT_REQUEST_TIMEOUT_S, VERDICT_REQUEST_QUIET_S),
|
|
32
|
-
},
|
|
33
|
-
sessionDir: dir,
|
|
34
|
-
// The whole point: continue the just-finished review's session, which already holds
|
|
35
|
-
// everything the reviewer read and concluded.
|
|
36
|
-
continueSession: true,
|
|
37
|
-
sessionName: opts.sessionName,
|
|
38
|
-
rawLogFile: piLogPath(ctx.root, ctx.role),
|
|
39
|
-
nativeLogFile: nativeLogPath(ctx.root, ctx.role, cfg.agent ?? "pi"),
|
|
40
|
-
label: opts.label,
|
|
41
|
-
signal: ctx.signal,
|
|
42
|
-
onToolCallStalled: (message) => warnEventBestEffort(ctx.root, ctx.role, message),
|
|
43
|
-
// A gate run that read an image its model cannot see is logged, not killed (plans/
|
|
44
|
-
// image-input.md "Detecting a blind read") — same shape as the main review run in
|
|
45
|
-
// review.ts.
|
|
46
|
-
onBlindRead: (path, action) => logEventBestEffort(ctx.root, {
|
|
47
|
-
source: ctx.role,
|
|
48
|
-
type: "image_blind",
|
|
49
|
-
run: ctx.runNumber,
|
|
50
|
-
path,
|
|
51
|
-
model: cfg.model,
|
|
52
|
-
action,
|
|
53
|
-
}),
|
|
54
|
-
...(opts.onToolCallStart ? { onToolCallStart: opts.onToolCallStart } : {}),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/** Ask the reviewer's own session (--continue) for the missing VERDICT line: one tightly
|
|
58
|
-
* bounded turn on the just-finished review's session, mirroring RunnerPi.requestSummary on the
|
|
59
|
-
* author side (BUGS.md 2026-09-29). Null when there is no session to continue — the caller
|
|
60
|
-
* then counts the strike exactly as before. The run is returned even when it failed so the
|
|
61
|
-
* caller can honor a shutdown abort and fold the spend. */
|
|
62
|
-
export async function requestVerdict(ctx) {
|
|
63
|
-
return runFollowupTurn(ctx, {
|
|
64
|
-
prompt: buildVerdictRequestPrompt(),
|
|
65
|
-
sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-verdict`,
|
|
66
|
-
label: "review-verdict",
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/** The suite-rerun nudge (BUGS.md 2026-10-02): the reviewer broke the no-re-run rule, so one
|
|
70
|
-
* tightly bounded turn on the just-finished review's session names the exact tool call and
|
|
71
|
-
* asks it to finish without re-running. Mirrors requestVerdict (same caps); `calls` collects
|
|
72
|
-
* the nudge turn's own started tool calls so the caller can detect a repeat. Null when there
|
|
73
|
-
* is no session to continue — the caller then has no repeat evidence, only the warning. The
|
|
74
|
-
* run is returned even when it failed so the caller can honor a shutdown abort and fold the
|
|
75
|
-
* spend. */
|
|
76
|
-
export async function requestNoRerun(ctx, rerun, calls) {
|
|
77
|
-
return runFollowupTurn(ctx, {
|
|
78
|
-
prompt: buildNoRerunPrompt(rerun),
|
|
79
|
-
sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-rerun`,
|
|
80
|
-
label: "review-rerun",
|
|
81
|
-
// Collected unconditionally: a nudge only runs after a detected rerun, so the no-re-run
|
|
82
|
-
// rule stood and the repeat check below needs every call the nudge started.
|
|
83
|
-
onToolCallStart: (toolName, args) => {
|
|
84
|
-
calls.push({ toolName, args });
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
}
|