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
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { mergeApproved } from "./merge-batch.js";
|
|
2
2
|
import { removeMergeChange, resolveWaitStage, setMergeChangeStatus } from "./merge-slot.js";
|
|
3
|
-
import {
|
|
4
|
-
import { withProviderLease } from "../concurrency/provider-lease.js";
|
|
3
|
+
import { withRunPermit } from "../concurrency/run-permit.js";
|
|
5
4
|
import { dropMerge, entryRevisionFields, queuedMergeFiles } from "./merge-queue.js";
|
|
6
5
|
import { errorMessage } from "../text/text.js";
|
|
7
6
|
import { warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
@@ -23,8 +22,9 @@ async function postReviewDue(config, root, commit) {
|
|
|
23
22
|
return files === null || !isExemptDiff(files, config.review.exemptPaths);
|
|
24
23
|
}
|
|
25
24
|
/** One poll of the merge queue (the scheduler's WHEN: after the gates, never while a restart
|
|
26
|
-
* or a disk hold is pending — a reviewer-provider hold does not stop the whole drain
|
|
27
|
-
* only the
|
|
25
|
+
* or a disk hold is pending — a reviewer-provider hold does not stop the whole drain: it parks
|
|
26
|
+
* only the post-merge review drain, whose no-role startHeld reads it, never a merge entry, so
|
|
27
|
+
* no verify or landing waits on the reviewer): start a
|
|
28
28
|
* verification for every queued change the pipeline does not already hold (drainVerify, in
|
|
29
29
|
* merge-verify.ts), and start the merge when the slot is free and something is approved
|
|
30
30
|
* (drainMerge). A queued merge is COMMITTED work awaiting completion, not a new run, so the
|
|
@@ -123,22 +123,20 @@ function startMerge(ctx, p, picks) {
|
|
|
123
123
|
runPi: async (w, prompt, s, cfg) => {
|
|
124
124
|
// A parked resolver names its wait (resolve-wait, then resolving once granted) the way
|
|
125
125
|
// a parked check does (withCheckPermit): on 2026-10-12 one waited 2.5 min for a permit
|
|
126
|
-
// with the merge queue behind it, reading as "merging". The
|
|
127
|
-
//
|
|
128
|
-
// counts against the strong tier's provider cap like any other strong-tier run
|
|
126
|
+
// with the merge queue behind it, reading as "merging". The resolution waits on the
|
|
127
|
+
// one team total (maxConcurrent) like any other MERGE_TIER run
|
|
129
128
|
// (plans/provider-concurrency.md §4). canLeaseAtOnce is acquireLease's own fast path
|
|
130
129
|
// decided synchronously, so a free permit shows no wait; MERGE_TIER ignores the
|
|
131
130
|
// maintenance reserve.
|
|
132
|
-
const key = cfg ? seamConcurrencyKey(cfg, ctx.piDefault) : undefined;
|
|
133
131
|
const hooks = resolveWaitStage(root, v.entry.role);
|
|
134
|
-
if (!semaphore.canLeaseAtOnce(MERGE_TIER
|
|
132
|
+
if (!semaphore.canLeaseAtOnce(MERGE_TIER))
|
|
135
133
|
hooks.waiting();
|
|
136
|
-
const lease = await semaphore.acquireLease(MERGE_TIER
|
|
134
|
+
const lease = await semaphore.acquireLease(MERGE_TIER);
|
|
137
135
|
hooks.granted();
|
|
138
136
|
try {
|
|
139
|
-
// The resolver's lease rides in the
|
|
140
|
-
//
|
|
141
|
-
return await
|
|
137
|
+
// The resolver's lease rides in the run-permit store for its run, so an idle
|
|
138
|
+
// pause inside it (a failed review's in-place retry) re-takes the same permit.
|
|
139
|
+
return await withRunPermit(lease, () => v.author.runMergePi(w, prompt, s, cfg));
|
|
142
140
|
}
|
|
143
141
|
finally {
|
|
144
142
|
lease.release();
|
|
@@ -43,15 +43,14 @@ export function guardMergeTask(root, role, promise) {
|
|
|
43
43
|
warnEventBestEffort(root, TUMWATER_SOURCE, `merge task for ${role} could not settle: ${errorMessage(err)}`);
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
/** Take a permit at `tier`
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
export async function acquireUnlessAborted(semaphore, tier, signal, key) {
|
|
46
|
+
/** Take a permit at `tier` unless `signal` fires first: resolves to its release, or to null
|
|
47
|
+
* when the signal won — a grant that arrives after that is handed straight back (a hop, never a
|
|
48
|
+
* leak), so a parked verification that a shutdown settles holds nothing. A verification's
|
|
49
|
+
* permit is the one team total (`maxConcurrent`). */
|
|
50
|
+
export async function acquireUnlessAborted(semaphore, tier, signal) {
|
|
52
51
|
if (signal.aborted)
|
|
53
52
|
return null;
|
|
54
|
-
const granted = semaphore.acquireLease(tier
|
|
53
|
+
const granted = semaphore.acquireLease(tier);
|
|
55
54
|
const stopped = new Promise((resolve) => signal.addEventListener("abort", () => resolve(false), { once: true }));
|
|
56
55
|
if (await Promise.race([granted, stopped])) {
|
|
57
56
|
return granted;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { reviewRunConfig, seamConcurrencyKey } from "../config/config-views.js";
|
|
1
|
+
import { withRunPermit } from "../concurrency/run-permit.js";
|
|
3
2
|
import { subscribeEvents, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
4
3
|
import { deleteRef, headOf, subjectOfCommit } from "../git/git.js";
|
|
5
4
|
import { ensureDetachedWorktree } from "../git/worktree.js";
|
|
@@ -14,7 +13,7 @@ import { abortOnShutdown, acquireUnlessAborted, guardMergeTask } from "./merge-p
|
|
|
14
13
|
import { resolveWaitStage } from "./merge-slot.js";
|
|
15
14
|
import { mergeToMain } from "./merge-to-main.js";
|
|
16
15
|
import { MERGE_TIER, resolveAuthor, VERIFY_TIER } from "./merge-verify.js";
|
|
17
|
-
/** One poll of the post-merge reviews
|
|
16
|
+
/** One poll of the post-merge reviews: a rejected-and-not-fixed commit is
|
|
18
17
|
* answered by the reviewer's own landed fix (Forward, never back 1/6) — never by a revert — and
|
|
19
18
|
* the next unreviewed commit starts its review when a permit is free right now. One review at a
|
|
20
19
|
* time; starts a task and returns. */
|
|
@@ -35,12 +34,11 @@ function startPostReview(ctx, p, entry) {
|
|
|
35
34
|
const task = { promise: Promise.resolve(), controller: new AbortController(), roles: [], userAborted: false, parked: true };
|
|
36
35
|
const detach = abortOnShutdown(ctx.signal, task.controller);
|
|
37
36
|
const author = resolveAuthor(ctx, entry.role);
|
|
38
|
-
const key = seamConcurrencyKey(reviewRunConfig(ctx.liveConfig), ctx.piDefault);
|
|
39
37
|
const run = (async () => {
|
|
40
38
|
try {
|
|
41
|
-
if (!semaphore.canLeaseAtOnce(VERIFY_TIER
|
|
39
|
+
if (!semaphore.canLeaseAtOnce(VERIFY_TIER))
|
|
42
40
|
return; // permits are busy; try again next poll
|
|
43
|
-
const lease = await acquireUnlessAborted(semaphore, VERIFY_TIER, task.controller.signal
|
|
41
|
+
const lease = await acquireUnlessAborted(semaphore, VERIFY_TIER, task.controller.signal);
|
|
44
42
|
task.parked = false;
|
|
45
43
|
if (lease === null)
|
|
46
44
|
return;
|
|
@@ -125,19 +123,18 @@ async function fileDroppedReview(ctx, entry, failure) {
|
|
|
125
123
|
* is never a throw. */
|
|
126
124
|
async function mergeReviewerFix(ctx, entry, fix) {
|
|
127
125
|
const author = resolveAuthor(ctx, entry.role);
|
|
128
|
-
// The fix merge's conflict resolver rides the same
|
|
129
|
-
// does (merge-drain.ts startMerge): the MERGE_TIER lease is
|
|
130
|
-
//
|
|
131
|
-
//
|
|
126
|
+
// The fix merge's conflict resolver rides the same team permit the merge slot's resolver
|
|
127
|
+
// does (merge-drain.ts startMerge): the MERGE_TIER lease is acquired on the one team total
|
|
128
|
+
// and rides in the run-permit store, so a conflict resolution counts against maxConcurrent
|
|
129
|
+
// like any other strong-tier run.
|
|
132
130
|
const runPi = async (wt, prompt, sessionName, config) => {
|
|
133
|
-
const key = config ? seamConcurrencyKey(config, ctx.piDefault) : undefined;
|
|
134
131
|
const hooks = resolveWaitStage(ctx.root, entry.role);
|
|
135
|
-
if (!ctx.semaphore.canLeaseAtOnce(MERGE_TIER
|
|
132
|
+
if (!ctx.semaphore.canLeaseAtOnce(MERGE_TIER))
|
|
136
133
|
hooks.waiting();
|
|
137
|
-
const lease = await ctx.semaphore.acquireLease(MERGE_TIER
|
|
134
|
+
const lease = await ctx.semaphore.acquireLease(MERGE_TIER);
|
|
138
135
|
hooks.granted();
|
|
139
136
|
try {
|
|
140
|
-
return await
|
|
137
|
+
return await withRunPermit(lease, () => author.runMergePi(wt, prompt, sessionName, config));
|
|
141
138
|
}
|
|
142
139
|
finally {
|
|
143
140
|
lease.release();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* in execution order, plus the count of queue files the reader skipped. Pure function of the
|
|
4
4
|
* collected view and a `now` instant — no I/O, no clock reads — so the age it prints can be
|
|
5
5
|
* pinned by a test. */
|
|
6
|
-
import { humanAgo, humanSeconds,
|
|
6
|
+
import { humanAgo, humanSeconds, humanSince, secondsUntil } from "../text/datetime.js";
|
|
7
7
|
import { roleLabel } from "../roles/role-labels.js";
|
|
8
8
|
import { shortSha } from "../text/format.js";
|
|
9
9
|
import { filterKeepPositions } from "../collections.js";
|
|
@@ -24,11 +24,11 @@ export function queueEntryMergeLabel(sha, inFlight, now) {
|
|
|
24
24
|
return "approved, waiting to merge";
|
|
25
25
|
let label = "merging";
|
|
26
26
|
if (change.startedAt)
|
|
27
|
-
label += ` ${
|
|
27
|
+
label += ` ${humanSince(change.startedAt, now)}`;
|
|
28
28
|
if (change.stage)
|
|
29
29
|
label += ` · ${MERGE_STAGE_LABELS[change.stage]}`;
|
|
30
30
|
if (change.stageSince !== undefined)
|
|
31
|
-
label += ` ${
|
|
31
|
+
label += ` ${humanSince(change.stageSince, now)}`;
|
|
32
32
|
return label;
|
|
33
33
|
}
|
|
34
34
|
/** One re-submitted change's revision note (plans/revise-rejected.md): `revision <round>`,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* retriable result) or once main's head stops being red (a `main_red` pin). */
|
|
7
7
|
import { enqueueMerge, queuedMerges } from "./merge-queue.js";
|
|
8
8
|
import { RETRIABLE_MERGE_RESULTS } from "./merge-core.js";
|
|
9
|
-
import { pinnedRetrySha } from "../runner/leftover.js";
|
|
9
|
+
import { pinnedRetrySha } from "../runner/runner-leftover.js";
|
|
10
10
|
import { branchHead } from "../git/git.js";
|
|
11
11
|
import { cachedBaselineVerdict } from "../baseline/main-baseline.js";
|
|
12
12
|
import { saveRunnerStateBestEffort } from "../runner/runner-state.js";
|
|
@@ -192,14 +192,6 @@ export function checkWaitStage(root, roles) {
|
|
|
192
192
|
export function resolveWaitStage(root, role) {
|
|
193
193
|
return waitStageHooks(root, [role], "resolve-wait", "resolving");
|
|
194
194
|
}
|
|
195
|
-
/** The verification's hooks around a re-key onto a full provider (plans/provider-concurrency.md §5,
|
|
196
|
-
* "A re-key wait is visible"): `provider-wait` while the verification's lease parks on the target
|
|
197
|
-
* provider's full cap, `reviewing` once the key frees and the review proceeds. resolveWaitStage's
|
|
198
|
-
* shape for the re-key path — a verification that normally shares its review seam never
|
|
199
|
-
* re-keys, so the stage fires only when a run actually moves to a different, full provider. */
|
|
200
|
-
export function providerWaitStage(root, role) {
|
|
201
|
-
return waitStageHooks(root, [role], "provider-wait", "reviewing");
|
|
202
|
-
}
|
|
203
195
|
/** The marker's per-change records, whichever shape it has: `changes` as written, or — for a
|
|
204
196
|
* marker an older generation wrote with one top-level change — that change, `merging` since the
|
|
205
197
|
* marker's start at the marker's stage. Reads both shapes into records, so addMergeChange's
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* merge-batch.ts, whose header scopes that file to the verify and merge halves): assemble the
|
|
3
3
|
* approved changes as a cherry-picked stack on main's current tip, run ONE scope-`batch` build
|
|
4
4
|
* check over the combined tree, and fast-forward main through the captured shas — with the
|
|
5
|
-
* re-stack loop for a main that moves mid-check, the doc-only exemption and
|
|
6
|
-
*
|
|
5
|
+
* re-stack loop for a main that moves mid-check, the doc-only exemption, and the per-attempt
|
|
6
|
+
* abort observation. The bisect over a red stack (mergeApproved, in
|
|
7
7
|
* merge-batch.ts) drives this file's mergeStack one prefix at a time. */
|
|
8
8
|
import { committerIdentArgs, gitTry } from "../git/git-run.js";
|
|
9
9
|
import { branchHead, deleteRef, headOf } from "../git/git.js";
|
|
@@ -11,7 +11,6 @@ import { treeDiffFiles } from "../git/git-diff.js";
|
|
|
11
11
|
import { mergeRefName, mergeLockDir } from "../paths.js";
|
|
12
12
|
import { ensureDetachedWorktree } from "../git/worktree.js";
|
|
13
13
|
import { useWorktree } from "../git/worktree-use.js";
|
|
14
|
-
import { exemptSkipBlockReason } from "./merge-to-main.js";
|
|
15
14
|
import { logNewQuestions } from "./merge-questions.js";
|
|
16
15
|
import { openQuestions } from "../backlog/backlog.js";
|
|
17
16
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
@@ -81,11 +80,13 @@ async function assembleStack(root, mainBranch, wtPath, entries) {
|
|
|
81
80
|
* the gate's own doc-only test (isExemptDiff over config.review.exemptPaths). A re-stack whose
|
|
82
81
|
* new commits from main were doc-only rebuilds the checked tree with nothing but doc bytes
|
|
83
82
|
* changed, and a doc-only delta cannot break the build: the reasoning verifyMerge
|
|
84
|
-
* (src/merge/merge-to-main.ts) applies when it
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
83
|
+
* (src/merge/merge-to-main.ts) applies when it lets a moved doc-only merge through with no
|
|
84
|
+
* check. Since part 3m/3 that arm runs no cross-check, and neither does this delta — a false
|
|
85
|
+
* fix claim and a broken backlog structure are repairable by a later commit and reported by
|
|
86
|
+
* the main watch, and this delta is commits main already merged through their own gate, not a
|
|
87
|
+
* claim this batch makes. --no-renames so a rename out of a code path lists the deleted
|
|
88
|
+
* source, not only its exempt destination. An unreadable diff is not exempt: the caller runs
|
|
89
|
+
* the check. */
|
|
89
90
|
async function exemptTreeDelta(root, from, to, exemptPaths) {
|
|
90
91
|
const files = await treeDiffFiles(root, from, to);
|
|
91
92
|
return files !== null && isExemptDiff(files, exemptPaths);
|
|
@@ -97,9 +98,10 @@ async function exemptTreeDelta(root, from, to, exemptPaths) {
|
|
|
97
98
|
* won, up to BATCH_RESTACK_ATTEMPTS times, and a re-stack whose tree is the checked tree plus
|
|
98
99
|
* doc-only commits skips its re-check (exemptTreeDelta). A stack whose whole delta ahead of
|
|
99
100
|
* its assembly base is review-exempt skips the check too — a doc-only delta cannot break the
|
|
100
|
-
* build (the reasoning verifyMerge's exempt arm applies)
|
|
101
|
-
*
|
|
102
|
-
*
|
|
101
|
+
* build (the reasoning verifyMerge's exempt arm applies). Since part 3m/3 that arm runs no
|
|
102
|
+
* cross-check and neither does this skip: a false fix claim and a broken backlog structure are
|
|
103
|
+
* repairable by a later commit and reported by the main watch, so the doc-only stack merges on
|
|
104
|
+
* the exemption for the watch to report on main's tip.
|
|
103
105
|
* A primary checkout on main that holds uncommitted or untracked files the stack changes
|
|
104
106
|
* returns `blocked` before any check runs, with no re-stack.
|
|
105
107
|
* On `landed` every entry's ref is deleted and the red-main baseline is seeded with the tip
|
|
@@ -146,13 +148,10 @@ async function mergeStackIn(ctx, wtPath, entries) {
|
|
|
146
148
|
// exemption instead of paying a suite run no code change asked for).
|
|
147
149
|
const stackFiles = await treeDiffFiles(ctx.root, base, tip);
|
|
148
150
|
if (stackFiles !== null && isExemptDiff(stackFiles, exemptPaths)) {
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// with the proper handling. No baseline seeds — nothing ran on this tip.
|
|
154
|
-
if (await exemptSkipBlockReason({ root: ctx.root, role: entries[0].role, mainBranch: ctx.mainBranch }, wtPath, stackFiles))
|
|
155
|
-
return { kind: "conflict" };
|
|
151
|
+
// Since part 3m/3 an md-only stack's repairable findings (a false fix claim, a broken
|
|
152
|
+
// backlog structure) no longer refuse anything — the main watch reports them on main's
|
|
153
|
+
// tip after the merge — so the skip runs no cross-check and no check: doc-only cannot
|
|
154
|
+
// break the build. No baseline seeds — nothing ran on this tip.
|
|
156
155
|
}
|
|
157
156
|
else {
|
|
158
157
|
// The merge cell names the check while it runs, then the merge steps after it — the ff
|
|
@@ -10,12 +10,10 @@ import { runScopedBuildCheck } from "../build/build-check-scoped.js";
|
|
|
10
10
|
import { detectBuildCheck, gateCommandOf, mergeCommandOf } from "../build/build-check-detect.js";
|
|
11
11
|
import { onlyKnownMainFailures } from "../baseline/main-watch.js";
|
|
12
12
|
import { unverifiedTreeOutcome } from "../build/build-check-events.js";
|
|
13
|
-
import {
|
|
13
|
+
import { redCheckHeadline } from "../build/build-check-report.js";
|
|
14
14
|
import { MERGE_FLAKY_WARNING_PREFIX, quarantineTests } from "../review/known-flakes.js";
|
|
15
15
|
import { noteGreenBaseline, noteInheritedGreen } from "../baseline/main-baseline.js";
|
|
16
16
|
import { isExemptDiff } from "../review/exemptions.js";
|
|
17
|
-
import { falseFixReason } from "../verdict/fix-claim.js";
|
|
18
|
-
import { backlogStructureReason } from "../backlog/backlog-structure.js";
|
|
19
17
|
import { withLock } from "../concurrency/lock.js";
|
|
20
18
|
import { buildConflictPrompt } from "../prompt/gate-prompts.js";
|
|
21
19
|
import { mergeLockDir } from "../paths.js";
|
|
@@ -121,42 +119,13 @@ async function tryMerge(ctx, wt, summary, verifiedHead) {
|
|
|
121
119
|
return "changed";
|
|
122
120
|
}, ctx.lockTimeoutMs);
|
|
123
121
|
}
|
|
124
|
-
/** The backlog-structure cross-check both verifyMerge paths run (exempt and code diffs
|
|
125
|
-
* alike): when the tree ahead of main duplicates or drops a `## ` section heading, warn on the
|
|
126
|
-
* role's feed with the one "merge blocked:" phrasing and return the reason (null when the
|
|
127
|
-
* headings are intact). Shared so the wording and the block action cannot drift between the
|
|
128
|
-
* paths. */
|
|
129
|
-
async function structureBlocked(ctx, wt, files) {
|
|
130
|
-
const structure = await backlogStructureReason(wt, ctx.mainBranch, files);
|
|
131
|
-
if (!structure)
|
|
132
|
-
return null;
|
|
133
|
-
warnEventBestEffort(ctx.root, ctx.role, `merge blocked: ${structure}`);
|
|
134
|
-
return structure;
|
|
135
|
-
}
|
|
136
|
-
/** The exempt arm's full cross-check — backlog structure, then fix-claim — shared by
|
|
137
|
-
* verifyMerge and merge-batch.ts's stack skip: an md-only tree delta that skips the build
|
|
138
|
-
* check must not wave through an edit the gate would have rejected. Returns the first blocking
|
|
139
|
-
* reason (a structure reason already warned as "merge blocked: ..."), or null when the skip
|
|
140
|
-
* may proceed. */
|
|
141
|
-
export async function exemptSkipBlockReason(ctx, wt, files) {
|
|
142
|
-
const structure = await structureBlocked(ctx, wt, files);
|
|
143
|
-
if (structure)
|
|
144
|
-
return structure;
|
|
145
|
-
const fix = await falseFixReason(wt, ctx.mainBranch, files);
|
|
146
|
-
// Warn like structureBlocked does: a fix-claim block that used to be silent read as five
|
|
147
|
-
// unexplained merge_blocked retries on the feed (BUGS.md 2026-10-02). The batch stack-skip
|
|
148
|
-
// caller may warn twice for one change (here, then again when the fallback's exempt arm
|
|
149
|
-
// blocks it) — a duplicated line is harmless next to a silent block.
|
|
150
|
-
if (fix)
|
|
151
|
-
warnEventBestEffort(ctx.root, ctx.role, `merge blocked: ${fix}`);
|
|
152
|
-
return fix ?? null;
|
|
153
|
-
}
|
|
154
122
|
/** Verify the exact tree about to reach main — the post-rebase head (BUGS.md 2026-09-08: the
|
|
155
123
|
* gate's pre-check ran outside this lock against a head the rebase may have rewritten, so the
|
|
156
|
-
* bytes that become main were never run through a check). Returns false when the
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
124
|
+
* bytes that become main were never run through a check). Returns false only when the project's
|
|
125
|
+
* declared check FAILS on the rebased tree; every other outcome merges. Since part 3m/3 no
|
|
126
|
+
* other finding refuses a diff here: a conflict resolution that duplicates or drops a `## `
|
|
127
|
+
* section heading, or a false fix claim, is repairable by a later commit, so it lands and the
|
|
128
|
+
* main watch reports it on main's tip after the merge. Skips:
|
|
160
129
|
* - a no-op rebase whose exact tree the gate verified (`rebasedHead === verifiedHead`): main
|
|
161
130
|
* did not move under us, so the merge tree is byte-identical to what the gate's pre-check
|
|
162
131
|
* already ran green. Seed the red-main baseline with the SHA that becomes main: every role's
|
|
@@ -200,27 +169,13 @@ async function verifyMerge(ctx, wt, rebasedHead, verifiedHead) {
|
|
|
200
169
|
return true;
|
|
201
170
|
}
|
|
202
171
|
const files = await aheadOfMainFiles(wt, ctx.mainBranch);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const reason = await exemptSkipBlockReason(ctx, wt, files);
|
|
210
|
-
if (reason !== null) {
|
|
211
|
-
ctx.onMergeBlocked?.(reason);
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
172
|
+
// An exempt (md-only) diff cannot break the build and — since part 3m/3 — carries no finding
|
|
173
|
+
// this merge must refuse: a false fix claim and a broken backlog structure are repairable by
|
|
174
|
+
// a later commit, so they land for the main watch to report on main's tip. The old rationale —
|
|
175
|
+
// "must not wave through an md-only edit the gate would have rejected" — is gone with the
|
|
176
|
+
// gate's narrowing: the gate no longer rejects those findings.
|
|
177
|
+
if (isExemptDiff(files, ctx.exemptPaths))
|
|
214
178
|
return true;
|
|
215
|
-
}
|
|
216
|
-
// The heading check for code diffs too, before the build check: a conflict resolution that
|
|
217
|
-
// broke backlog structure reads as an explained block on the dashboards, not as an
|
|
218
|
-
// unexplained red check run on a tree that could never merge.
|
|
219
|
-
const structure = await structureBlocked(ctx, wt, files);
|
|
220
|
-
if (structure) {
|
|
221
|
-
ctx.onMergeBlocked?.(structure);
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
179
|
if (await onlyExemptDriftFromVerified(ctx, wt, rebasedHead, verifiedHead))
|
|
225
180
|
return true;
|
|
226
181
|
// The run itself (build_check event, environmental-skip warning) lives in
|
|
@@ -235,7 +190,7 @@ async function verifyMerge(ctx, wt, rebasedHead, verifiedHead) {
|
|
|
235
190
|
const failed = check.outcome;
|
|
236
191
|
const retry = await runMergeCheck();
|
|
237
192
|
if (retry?.outcome.status === "passed") {
|
|
238
|
-
warnEventBestEffort(ctx.root, ctx.role, `${MERGE_FLAKY_WARNING_PREFIX}${
|
|
193
|
+
warnEventBestEffort(ctx.root, ctx.role, `${MERGE_FLAKY_WARNING_PREFIX}${redCheckHeadline(check.check, failed.outputTail)}`);
|
|
239
194
|
quarantineTests(ctx.root, failed.failingTests, failed.outputTail);
|
|
240
195
|
check = retry;
|
|
241
196
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { gateRoleConfig } from "../gates/budget-gates.js";
|
|
4
|
-
import {
|
|
5
|
-
import { providerWaitStage } from "./merge-slot.js";
|
|
6
|
-
import { withProviderLease } from "../concurrency/provider-lease.js";
|
|
4
|
+
import { withRunPermit } from "../concurrency/run-permit.js";
|
|
7
5
|
import { RoleRunner } from "../runner/runner.js";
|
|
8
6
|
import { branchHead, commitMessage, isMergedInto, refSha } from "../git/git.js";
|
|
9
7
|
import { commitRunOf } from "../git/commit-message.js";
|
|
10
|
-
import { recoveredMergeEntry, recoveredMetadata } from "../runner/leftover.js";
|
|
8
|
+
import { recoveredMergeEntry, recoveredMetadata } from "../runner/runner-leftover.js";
|
|
11
9
|
import { addMergeChange, mergeUsage, removeMergeChange, setMergeChangeStatus } from "./merge-slot.js";
|
|
12
10
|
import { dropMerge, enqueueMerge, entryRevisionFields, queuedMergeFiles, staleHeadFile, tornHeadRole } from "./merge-queue.js";
|
|
13
11
|
import { mergeRefName } from "../paths.js";
|
|
@@ -160,9 +158,10 @@ export async function drainVerify(ctx, p) {
|
|
|
160
158
|
// (its role was busy when the queue was listed) or claimed its role. Start nothing for it.
|
|
161
159
|
if (!fs.existsSync(file) || liveRoles(p).has(entry.role))
|
|
162
160
|
continue;
|
|
163
|
-
// A
|
|
164
|
-
//
|
|
165
|
-
//
|
|
161
|
+
// A reviewer hold parks no named entry — every entry keeps draining (no verify, start or
|
|
162
|
+
// landing waits on the reviewer); only a restart or disk hold (the start gate's other legs)
|
|
163
|
+
// parks it here, and a parked verification re-checks the gate when its permit comes. The
|
|
164
|
+
// start gate decides.
|
|
166
165
|
if (ctx.startHeld(entry.role))
|
|
167
166
|
continue;
|
|
168
167
|
startVerify(ctx, p, entry, file);
|
|
@@ -186,13 +185,6 @@ export function startVerify(ctx, p, entry, file) {
|
|
|
186
185
|
const { root, mainBranch, signal, semaphore } = ctx;
|
|
187
186
|
const { role } = entry;
|
|
188
187
|
const author = resolveAuthor(ctx, role);
|
|
189
|
-
// The verification's provider key: the review seam's key for the author's role, so its reviewer
|
|
190
|
-
// run counts toward the same provider cap as the roles that share it
|
|
191
|
-
// (plans/provider-concurrency.md §4). The gate off runs no model, so then there is no key — the
|
|
192
|
-
// verification is bounded by the team total alone.
|
|
193
|
-
const reviewKey = ctx.liveConfig.review.enabled
|
|
194
|
-
? seamConcurrencyKey(reviewRunConfig(author.config), ctx.piDefault)
|
|
195
|
-
: undefined;
|
|
196
188
|
const verify = {
|
|
197
189
|
promise: Promise.resolve(), // Replaced below; the placeholder satisfies the type.
|
|
198
190
|
controller: new AbortController(),
|
|
@@ -204,7 +196,7 @@ export function startVerify(ctx, p, entry, file) {
|
|
|
204
196
|
p.verifying.set(role, verify);
|
|
205
197
|
const task = (async () => {
|
|
206
198
|
try {
|
|
207
|
-
const lease = await acquireUnlessAborted(semaphore, VERIFY_TIER, verify.controller.signal
|
|
199
|
+
const lease = await acquireUnlessAborted(semaphore, VERIFY_TIER, verify.controller.signal);
|
|
208
200
|
verify.parked = false;
|
|
209
201
|
if (lease === null)
|
|
210
202
|
return; // a shutdown while parked
|
|
@@ -217,11 +209,10 @@ export function startVerify(ctx, p, entry, file) {
|
|
|
217
209
|
const startedAt = Date.now();
|
|
218
210
|
try {
|
|
219
211
|
addMergeChange(root, entry, liveRoles(p));
|
|
220
|
-
// The verification's lease rides in the
|
|
221
|
-
// so
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
verdict = await withProviderLease(lease, semaphore, () => verifyRequest({ root, mainBranch, config: author.config, signal: () => verify.controller.signal, runGatePi: (opts) => author.runGatePi(opts), reviewHeld: () => ctx.startHeld() }, {
|
|
212
|
+
// The verification's lease rides in the run-permit store for the verification's body,
|
|
213
|
+
// so an idle pause inside it (a failed review's in-place retry) re-takes the same
|
|
214
|
+
// team permit; its slot wait hands the permit back too.
|
|
215
|
+
verdict = await withRunPermit(lease, () => verifyRequest({ root, mainBranch, config: author.config, signal: () => verify.controller.signal, runGatePi: (opts) => author.runGatePi(opts), reviewHeld: () => ctx.startHeld() }, {
|
|
225
216
|
role,
|
|
226
217
|
sha: entry.sha,
|
|
227
218
|
runNumber: entry.run,
|
|
@@ -229,7 +220,7 @@ export function startVerify(ctx, p, entry, file) {
|
|
|
229
220
|
body: entry.body,
|
|
230
221
|
highFriction: entry.highFriction,
|
|
231
222
|
...entryRevisionFields(entry),
|
|
232
|
-
}, { state: author.state, foldUsage, runPi: (w, prompt, s, cfg) => author.runMergePi(w, prompt, s, cfg), runGatePi: (opts) => author.runGatePi(opts) })
|
|
223
|
+
}, { state: author.state, foldUsage, runPi: (w, prompt, s, cfg) => author.runMergePi(w, prompt, s, cfg), runGatePi: (opts) => author.runGatePi(opts) }));
|
|
233
224
|
}
|
|
234
225
|
catch (err) {
|
|
235
226
|
verdict = { kind: "result", result: "error" };
|
package/dist/src/notes-cli.js
CHANGED
|
@@ -58,57 +58,67 @@ function writeNote(text) {
|
|
|
58
58
|
process.stdout.write(`notes saved (${Buffer.byteLength(text, "utf8")} bytes)\n`);
|
|
59
59
|
return 0;
|
|
60
60
|
}
|
|
61
|
+
function cmdWrite(args) {
|
|
62
|
+
const [, flag, flagArg] = args;
|
|
63
|
+
if (flag === undefined)
|
|
64
|
+
return writeNote(readStdin());
|
|
65
|
+
if (flag === "--file") {
|
|
66
|
+
if (flagArg === undefined || args.length > 3)
|
|
67
|
+
return refuse(USAGE);
|
|
68
|
+
let text;
|
|
69
|
+
try {
|
|
70
|
+
text = fs.readFileSync(flagArg, "utf8");
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return refuse(`cannot read ${JSON.stringify(flagArg)}: ${errorMessage(err)}`);
|
|
74
|
+
}
|
|
75
|
+
return writeNote(text);
|
|
76
|
+
}
|
|
77
|
+
return refuse(USAGE);
|
|
78
|
+
}
|
|
79
|
+
function cmdShow(flag) {
|
|
80
|
+
if (flag !== undefined)
|
|
81
|
+
return refuse(USAGE);
|
|
82
|
+
let note;
|
|
83
|
+
try {
|
|
84
|
+
note = readNote();
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return refuse(`could not read the note: ${errorMessage(err)}`);
|
|
88
|
+
}
|
|
89
|
+
// The note is model-authored text, so strip terminal controls at this display
|
|
90
|
+
// boundary — the same treatment say()/fail()/the TUI frame give every untrusted
|
|
91
|
+
// string, so a prompt-injected note with an ANSI escape (cursor move, title set,
|
|
92
|
+
// OSC 52 clipboard write, carriage-return overwrite) cannot drive the operator's
|
|
93
|
+
// terminal when they run `tumwater-notes show`.
|
|
94
|
+
process.stdout.write(stripTerminalControls(note));
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
function cmdClear(flag) {
|
|
98
|
+
if (flag !== undefined)
|
|
99
|
+
return refuse(USAGE);
|
|
100
|
+
try {
|
|
101
|
+
writeRoleNote(NOTES_PATH, "");
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return refuse(`could not clear the note: ${errorMessage(err)}`);
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write("notes cleared\n");
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
61
109
|
function main() {
|
|
62
110
|
if (NOTES_PATH === undefined || NOTES_PATH === "") {
|
|
63
111
|
return refuse("this run keeps no notes");
|
|
64
112
|
}
|
|
65
113
|
const args = process.argv.slice(2);
|
|
66
|
-
const [command, flag
|
|
114
|
+
const [command, flag] = args;
|
|
67
115
|
switch (command) {
|
|
68
116
|
case "write":
|
|
69
|
-
|
|
70
|
-
return writeNote(readStdin());
|
|
71
|
-
if (flag === "--file") {
|
|
72
|
-
if (flagArg === undefined || args.length > 3)
|
|
73
|
-
return refuse(USAGE);
|
|
74
|
-
let text;
|
|
75
|
-
try {
|
|
76
|
-
text = fs.readFileSync(flagArg, "utf8");
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
return refuse(`cannot read ${JSON.stringify(flagArg)}: ${errorMessage(err)}`);
|
|
80
|
-
}
|
|
81
|
-
return writeNote(text);
|
|
82
|
-
}
|
|
83
|
-
return refuse(USAGE);
|
|
117
|
+
return cmdWrite(args);
|
|
84
118
|
case "show":
|
|
85
|
-
|
|
86
|
-
return refuse(USAGE);
|
|
87
|
-
let note;
|
|
88
|
-
try {
|
|
89
|
-
note = readNote();
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
return refuse(`could not read the note: ${errorMessage(err)}`);
|
|
93
|
-
}
|
|
94
|
-
// The note is model-authored text, so strip terminal controls at this display
|
|
95
|
-
// boundary — the same treatment say()/fail()/the TUI frame give every untrusted
|
|
96
|
-
// string, so a prompt-injected note with an ANSI escape (cursor move, title set,
|
|
97
|
-
// OSC 52 clipboard write, carriage-return overwrite) cannot drive the operator's
|
|
98
|
-
// terminal when they run `tumwater-notes show`.
|
|
99
|
-
process.stdout.write(stripTerminalControls(note));
|
|
100
|
-
return 0;
|
|
119
|
+
return cmdShow(flag);
|
|
101
120
|
case "clear":
|
|
102
|
-
|
|
103
|
-
return refuse(USAGE);
|
|
104
|
-
try {
|
|
105
|
-
writeRoleNote(NOTES_PATH, "");
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
return refuse(`could not clear the note: ${errorMessage(err)}`);
|
|
109
|
-
}
|
|
110
|
-
process.stdout.write("notes cleared\n");
|
|
111
|
-
return 0;
|
|
121
|
+
return cmdClear(flag);
|
|
112
122
|
default:
|
|
113
123
|
process.stderr.write(`${USAGE}\n`);
|
|
114
124
|
return 2;
|