tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The operator's deliberate acceptance of a pending post-merge review without running it
|
|
2
|
+
* (src/cli/cli-review-commands.ts `tumwater reviews --accept <n>`), split out of the CLI so
|
|
3
|
+
* the review queue stays a pure reader/editor and this module owns the destructive half —
|
|
4
|
+
* removing an unattempted entry on purpose. It is the sibling of `tumwater queue --cancel
|
|
5
|
+
* <n>` (src/merge/merge-cancel.ts): the queue's no-third-path gap that the at-risk alert's
|
|
6
|
+
* shared sentence names ("…or accept the commit unreviewed deliberately"). It reads the same
|
|
7
|
+
* FIFO list `readPostReviews` numbers 1..N and settles the entry by its commit, so the removed
|
|
8
|
+
* entry is the very one the printed position named. */
|
|
9
|
+
import { readPostReviews, settlePostReview } from "./post-merge-review.js";
|
|
10
|
+
import { warnEventBestEffort } from "../events/events.js";
|
|
11
|
+
import { shortSha } from "../text/format.js";
|
|
12
|
+
/** Remove one readable entry the operator accepts unreviewed: refuse it (writing nothing) when
|
|
13
|
+
* it carries `fixed` — the repair already merged, so accepting it unreviewed is meaningless
|
|
14
|
+
* and the false "accepted unreviewed" warning would mislead — otherwise settle it with no
|
|
15
|
+
* verdict (`settlePostReview`; its failed write already warns and never throws, INV-BT2),
|
|
16
|
+
* log one `warning` under the entry's role, and return the `accepted` outcome naming what was
|
|
17
|
+
* retired. A rejected entry waits on nothing, so it is accepted like any other pending entry.
|
|
18
|
+
* The single removal both `acceptPostReview` (by position) and `acceptPostReviewBySha`
|
|
19
|
+
* (by sha) share, so the CLI and the dashboard's post-review-accept cannot drift on what
|
|
20
|
+
* accepting means. */
|
|
21
|
+
function removeAcceptedEntry(root, entry) {
|
|
22
|
+
if (entry.fixed !== undefined)
|
|
23
|
+
return { status: "settled", commit: entry.commit };
|
|
24
|
+
settlePostReview(root, entry.commit, null);
|
|
25
|
+
const message = `post-merge review of ${shortSha(entry.commit)} accepted unreviewed at the operator's request — the commit stays on main unreviewed`; // vocabulary-ok: the accepted-unreviewed wording those reasons it is the operator's choice
|
|
26
|
+
warnEventBestEffort(root, entry.role, message);
|
|
27
|
+
return { status: "accepted", role: entry.role, commit: entry.commit, summary: entry.summary };
|
|
28
|
+
}
|
|
29
|
+
/** Retire the Nth pending post-merge review — 1-based, as `tumwater reviews` numbers it —
|
|
30
|
+
* accepting the commit unreviewed at the operator's request. `position` is resolved against
|
|
31
|
+
* `readPostReviews` (the same readable FIFO list the command renders), so the position matches
|
|
32
|
+
* the line `tumwater reviews` printed. An out-of-range position writes nothing and returns the
|
|
33
|
+
* current readable depth; otherwise it runs the same `removeAcceptedEntry` the sha variant
|
|
34
|
+
* shares, so the two never drift on what accepting means (see `removeAcceptedEntry` for the
|
|
35
|
+
* removal, the fixed-guard, the warning, and the never-throws guarantee). */
|
|
36
|
+
export function acceptPostReview(root, position) {
|
|
37
|
+
const entries = readPostReviews(root);
|
|
38
|
+
if (position < 1 || position > entries.length)
|
|
39
|
+
return { status: "out-of-range", depth: entries.length };
|
|
40
|
+
return removeAcceptedEntry(root, entries[position - 1]);
|
|
41
|
+
}
|
|
42
|
+
/** Remove the pending post-merge review whose entry's commit is `sha` (the browser twin of
|
|
43
|
+
* `tumwater reviews --accept <n>`, resolving by commit because the dashboard rows carry one).
|
|
44
|
+
* A commit that matches none is `{ status: "gone" }` and changes nothing; otherwise it runs the
|
|
45
|
+
* same `removeAcceptedEntry` the position variant shares. */
|
|
46
|
+
export function acceptPostReviewBySha(root, sha) {
|
|
47
|
+
const entry = readPostReviews(root).find((e) => e.commit.toLowerCase() === sha.toLowerCase());
|
|
48
|
+
if (!entry)
|
|
49
|
+
return { status: "gone" };
|
|
50
|
+
return removeAcceptedEntry(root, entry);
|
|
51
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** The renderer behind `tumwater reviews` — the operator-facing text for the post-merge review
|
|
2
|
+
* queue (.tumwater/state/post-review.json): one line per well-formed entry in the given order,
|
|
3
|
+
* plus a `post-merge review queue (N):` header. Pure function of the entry list — the optional
|
|
4
|
+
* `now` is the only clock input, and when it is a number and an entry carries `enqueuedAt` the
|
|
5
|
+
* line names the wait since the entry was first queued, exactly as the merge queue names its
|
|
6
|
+
* age — so a test can pin its output exactly. The `merge queue empty` convention
|
|
7
|
+
* (src/merge/merge-queue-render.ts) is reused for the empty case. */
|
|
8
|
+
import { roleLabel } from "../roles/role-labels.js";
|
|
9
|
+
import { shortSha } from "../text/format.js";
|
|
10
|
+
import { squash } from "../text/text.js";
|
|
11
|
+
import { filterKeepPositions } from "../collections.js";
|
|
12
|
+
import { plural } from "../text/phrases.js";
|
|
13
|
+
import { humanAgo } from "../text/datetime.js";
|
|
14
|
+
/** The one state every observer surface narrows on: `fixed` when the repair already merged
|
|
15
|
+
* (the terminal Forward-never-back 1a/6 record — naming both shas, it is not pending anything),
|
|
16
|
+
* `rejected` when the reviewer turned the commit back (empty `rejected` array still counts),
|
|
17
|
+
* `failing` when the review runs keep ending without a verdict (`failures` positive), else
|
|
18
|
+
* `waiting`. The precedence matches what postReviewStatusLabel already printed — an entry can
|
|
19
|
+
* never be both — so the status label and `tumwater reviews --status` share this one definition
|
|
20
|
+
* and cannot drift. */
|
|
21
|
+
export function postReviewState(entry) {
|
|
22
|
+
if (entry.fixed !== undefined)
|
|
23
|
+
return "fixed";
|
|
24
|
+
if (entry.rejected !== undefined)
|
|
25
|
+
return "rejected";
|
|
26
|
+
if (typeof entry.failures === "number" && entry.failures > 0)
|
|
27
|
+
return "failing";
|
|
28
|
+
return "waiting";
|
|
29
|
+
}
|
|
30
|
+
/** The states `tumwater reviews --status` accepts, in the order the filter's error message
|
|
31
|
+
* lists them (the terminal `fixed` last). `postReviewState` is the definition; this only names
|
|
32
|
+
* the vocabulary. */
|
|
33
|
+
export const POST_REVIEW_STATES = ["waiting", "failing", "rejected", "fixed"];
|
|
34
|
+
/** The bracketed status suffix one post-merge review entry carries on every observer surface
|
|
35
|
+
* (`tumwater reviews`, the dashboard's Post-merge reviews card, and the TUI Pending pane's
|
|
36
|
+
* review block): the reviewer's recorded rejected reasons when `rejected` is set; else the
|
|
37
|
+
* number of verdict-less review runs when `failures` is positive, naming the
|
|
38
|
+
* last failure's reason beside the count when `lastFailure` is a non-empty string; else `""`.
|
|
39
|
+
* Reasons are squashed to 60 as `tumwater reviews` has always printed them, and the failure
|
|
40
|
+
* detail to 80. Branches on `postReviewState` so the label and the `--status` filter share one
|
|
41
|
+
* state definition; extracted so the
|
|
42
|
+
* CLI, the dashboard and the TUI share one status wording and cannot drift. */
|
|
43
|
+
export function postReviewStatusLabel(entry) {
|
|
44
|
+
switch (postReviewState(entry)) {
|
|
45
|
+
case "fixed":
|
|
46
|
+
return `[fixed — the repair ${shortSha(entry.fixed)} already merged on main]`;
|
|
47
|
+
case "rejected":
|
|
48
|
+
return `[rejected — ${entry.rejected.map((r) => squash(r, 60)).join("; ")}]`;
|
|
49
|
+
case "failing": {
|
|
50
|
+
const failures = entry.failures; // postReviewState only says "failing" when failures > 0
|
|
51
|
+
if (typeof entry.lastFailure === "string" && entry.lastFailure.length > 0) {
|
|
52
|
+
return `[${plural(failures, "failed review run")} — last: ${squash(entry.lastFailure, 80)}]`;
|
|
53
|
+
}
|
|
54
|
+
return `[${plural(failures, "failed review run")}]`;
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Narrows a whole post-merge review list to the entries `keep` accepts, and returns
|
|
61
|
+
* the `scope` renderPostReviews takes: the kept roles, each kept entry's 1-based place in the
|
|
62
|
+
* whole list, and the whole list's entry count — plus, when a status filter is active, the
|
|
63
|
+
* `filter` text the header and no-match line print. The filtering-and-positioning step is the
|
|
64
|
+
* shared `filterKeepPositions` (src/collections.ts) that `scopeMergeQueue` (src/merge/
|
|
65
|
+
* merge-queue-render.ts) also uses, so `tumwater reviews --role` keeps the same whole-queue
|
|
66
|
+
* positions `--accept <n>` still addresses. */
|
|
67
|
+
export function scopePostReviews(entries, roles, keep, filter) {
|
|
68
|
+
const { kept, positions } = filterKeepPositions(entries, keep);
|
|
69
|
+
return {
|
|
70
|
+
entries: kept,
|
|
71
|
+
scope: { roles, positions, total: entries.length, ...(filter !== undefined ? { filter } : {}) },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** The text `tumwater reviews` prints. A totally empty queue renders `post-merge review queue
|
|
75
|
+
* empty`; otherwise a header and one line per entry — position, role, short commit, and, when
|
|
76
|
+
* `now` and the entry's `enqueuedAt` are both numbers, the wait since the commit was queued
|
|
77
|
+
* (`<age> ago`, between the short commit and the summary) — then the squashed summary and the
|
|
78
|
+
* status suffix from `postReviewStatusLabel`, appended as `" · " + label` only when it is
|
|
79
|
+
* non-empty. Without a `now`, or for an entry with no `enqueuedAt`, the line is byte-identical
|
|
80
|
+
* to before, so an older `post-review.json` and every existing renderer test read unchanged.
|
|
81
|
+
* With the optional `scope` (`reviews --role`/`--status`: `entries` already holds only the kept
|
|
82
|
+
* entries', `positions` their 1-based places in the whole queue, `total` the whole queue's
|
|
83
|
+
* entry count, and `filter` the `--role`/`--status` filter text when one is active) each line
|
|
84
|
+
* keeps its whole-queue number and the header reads
|
|
85
|
+
* `post-merge review queue (<kept> of <total>, <filter>):`, where `<filter>` is `scope.filter`
|
|
86
|
+
* when set else `--role <ids>`. A scope that keeps nothing of a non-empty queue prints
|
|
87
|
+
* `no post-merge reviews matching <filter> (post-merge review queue holds <total>)` when a
|
|
88
|
+
* filter is set, else `no post-merge reviews for <ids> (…)`. Without `scope` and `now` the
|
|
89
|
+
* output is byte-identical to before. */
|
|
90
|
+
export function renderPostReviews(entries, scope, now) {
|
|
91
|
+
const total = scope ? scope.total : entries.length;
|
|
92
|
+
if (total === 0)
|
|
93
|
+
return "post-merge review queue empty";
|
|
94
|
+
if (scope && entries.length === 0) {
|
|
95
|
+
const reason = scope.filter !== undefined ? `matching ${scope.filter}` : `for ${scope.roles.join(",")}`;
|
|
96
|
+
return `no post-merge reviews ${reason} (post-merge review queue holds ${total})`;
|
|
97
|
+
}
|
|
98
|
+
const lines = [
|
|
99
|
+
scope
|
|
100
|
+
? `post-merge review queue (${entries.length} of ${total}, ${scope.filter ?? `--role ${scope.roles.join(",")}`}):`
|
|
101
|
+
: `post-merge review queue (${plural(total, "entry", "entries")}):`,
|
|
102
|
+
];
|
|
103
|
+
entries.forEach((entry, i) => {
|
|
104
|
+
const label = postReviewStatusLabel(entry);
|
|
105
|
+
const suffix = label === "" ? "" : ` · ${label}`;
|
|
106
|
+
const age = typeof now === "number" && typeof entry.enqueuedAt === "number"
|
|
107
|
+
? ` · ${humanAgo(entry.enqueuedAt, now)}`
|
|
108
|
+
: "";
|
|
109
|
+
lines.push(` ${scope ? scope.positions[i] : i + 1}. ${roleLabel(entry.role)} ${shortSha(entry.commit)}${age} · ${squash(entry.summary, 80)}${suffix}`);
|
|
110
|
+
});
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
2
|
+
import { postReviewPath, postReviewWorktreePath, nativeLogPath, piLogPath, reviewRunSessionDir, reviewFixRef } from "../paths.js";
|
|
3
|
+
import { reviewedAfterMerge, reviewRunConfig } from "../config/config-views.js";
|
|
4
|
+
import { logEventBestEffort, TUMWATER_SOURCE, tryOrWarn } from "../events/events.js";
|
|
5
|
+
import { isJsonObject } from "../files/json-object.js";
|
|
6
|
+
import { git, gitCouldNotStart, gitTry } from "../git/git-run.js";
|
|
7
|
+
import { commitAll, deleteRef, setRef } from "../git/git.js";
|
|
8
|
+
import { ensureDetachedWorktree } from "../git/worktree.js";
|
|
9
|
+
import { useWorktree } from "../git/worktree-use.js";
|
|
10
|
+
import { readPrinciples } from "../prompt/principles.js";
|
|
11
|
+
import { buildReviewPrompt } from "../prompt/gate-prompts.js";
|
|
12
|
+
import { shortSha } from "../text/format.js";
|
|
13
|
+
import { errorMessage } from "../text/text.js";
|
|
14
|
+
import { revisionRoundOf } from "../git/commit-message.js";
|
|
15
|
+
import { REVISION_BACKSTOP } from "../runner/revision.js";
|
|
16
|
+
import { eventRole } from "../events/event-read.js";
|
|
17
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
18
|
+
import { fixLine } from "../verdict/reply-contract.js";
|
|
19
|
+
import { parseVerdict } from "./review-verdict.js";
|
|
20
|
+
/** A commit's review is dropped, with a warning, after this many runs with no verdict. */
|
|
21
|
+
export const POST_REVIEW_FAILURE_LIMIT = 3;
|
|
22
|
+
/** The bar a day's post-merge rejections must reach before the `post-merge-churn` amber alert
|
|
23
|
+
* (src/ui/team-alerts.ts) fires: one post-merge rejection is the tiering's ordinary cost, two in
|
|
24
|
+
* a day is a role whose already-landed changes keep being turned back — the reviewer-fix signal
|
|
25
|
+
* for an operator to narrow `review.afterMerge` or raise the authoring bar. */
|
|
26
|
+
export const POST_MERGE_REJECT_WARN = 2;
|
|
27
|
+
/** How far back the post-merge-churn rule looks — one day, the 24 h event-window convention
|
|
28
|
+
* every event-reading alert uses (PRIMARY_OVERWRITE_WINDOW_MS sorts beside it). */
|
|
29
|
+
export const POST_MERGE_REJECT_WINDOW_MS = 24 * 60 * 60_000;
|
|
30
|
+
/** Group the `afterMerge: true` `review_rejected` events of `events` by base role — the same
|
|
31
|
+
* `e.type === "review_rejected" && e.afterMerge === true` test the report fold
|
|
32
|
+
* (src/report/report-fold.ts `postMergeRejectionsByRole`) applies, so the churn alert and the
|
|
33
|
+
* report cannot disagree on which rejections are post-merge. The key is the event's base role
|
|
34
|
+
* (`baseRoleOf(eventRole(ev))`), not the raw source: an event's `source` is the runner id
|
|
35
|
+
* (`bugfix-2`), and both the alert's remedy ("narrow `review.afterMerge`") and the scoped
|
|
36
|
+
* report are keyed by base role, so one base role's churn must read as one row. Pre-merge
|
|
37
|
+
* rejections and other event types contribute nothing. */
|
|
38
|
+
export function postMergeRejectionCounts(events) {
|
|
39
|
+
const byRole = new Map();
|
|
40
|
+
for (const ev of events) {
|
|
41
|
+
if (ev.type === "review_rejected" && ev.afterMerge === true) {
|
|
42
|
+
const role = baseRoleOf(eventRole(ev));
|
|
43
|
+
byRole.set(role, (byRole.get(role) ?? 0) + 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...byRole.entries()]
|
|
47
|
+
.map(([role, count]) => ({ role, count }))
|
|
48
|
+
.sort((a, b) => b.count - a.count || (a.role < b.role ? -1 : a.role > b.role ? 1 : 0));
|
|
49
|
+
}
|
|
50
|
+
/** The one home of the trigger and selection the `post-merge-churn` alert (src/ui/team-alerts.ts)
|
|
51
|
+
* and the doctor's `post-merge churn` row (src/doctor/doctor-project-checks.ts) share: whether
|
|
52
|
+
* the trailing `POST_MERGE_REJECT_WINDOW_MS` holds at least `POST_MERGE_REJECT_WARN` post-merge
|
|
53
|
+
* `review_rejected` events, and, when it does, which roles and how many each. null when the
|
|
54
|
+
* window holds fewer than the bar, or none — so the two surfaces cannot disagree on when churn
|
|
55
|
+
* fires. */
|
|
56
|
+
export function postMergeChurn(events) {
|
|
57
|
+
const roles = postMergeRejectionCounts(events);
|
|
58
|
+
const count = roles.reduce((n, r) => n + r.count, 0);
|
|
59
|
+
return count >= POST_MERGE_REJECT_WARN ? { count, roles } : null;
|
|
60
|
+
}
|
|
61
|
+
/** Whether `role`'s changes are reviewed after merging instead of before. One home for the
|
|
62
|
+
* membership test, config-views.ts's reviewedAfterMerge — the same predicate the review gate,
|
|
63
|
+
* the runner's `already_queued` recovery arm and the role view read. */
|
|
64
|
+
export function isAfterMergeRole(config, role) {
|
|
65
|
+
return reviewedAfterMerge(config, role);
|
|
66
|
+
}
|
|
67
|
+
/** The queued entries. A torn or hand-edited entry (not an object with a string commit, role and
|
|
68
|
+
* summary, or a `rejected` that is not an array — a non-string `lastFailure` is dropped like the
|
|
69
|
+
* other fields) is dropped, and the next write leaves it out. */
|
|
70
|
+
export function readPostReviews(root) {
|
|
71
|
+
const saved = readJsonFile(postReviewPath(root));
|
|
72
|
+
if (!Array.isArray(saved?.pending))
|
|
73
|
+
return [];
|
|
74
|
+
const entries = [];
|
|
75
|
+
for (const raw of saved.pending) {
|
|
76
|
+
if (!isJsonObject(raw))
|
|
77
|
+
continue;
|
|
78
|
+
const { commit, role, summary, body, failures, lastFailure, rejected, enqueuedAt, fixed } = raw;
|
|
79
|
+
if (typeof commit !== "string" || typeof role !== "string" || typeof summary !== "string")
|
|
80
|
+
continue;
|
|
81
|
+
if (rejected !== undefined && !Array.isArray(rejected))
|
|
82
|
+
continue;
|
|
83
|
+
const entry = { commit, role, summary };
|
|
84
|
+
if (typeof body === "string")
|
|
85
|
+
entry.body = body;
|
|
86
|
+
if (typeof failures === "number" && Number.isFinite(failures))
|
|
87
|
+
entry.failures = failures;
|
|
88
|
+
if (typeof lastFailure === "string")
|
|
89
|
+
entry.lastFailure = lastFailure;
|
|
90
|
+
if (Array.isArray(rejected))
|
|
91
|
+
entry.rejected = rejected.filter((r) => typeof r === "string");
|
|
92
|
+
if (typeof fixed === "string")
|
|
93
|
+
entry.fixed = fixed;
|
|
94
|
+
if (typeof enqueuedAt === "number" && Number.isFinite(enqueuedAt) && enqueuedAt >= 0)
|
|
95
|
+
entry.enqueuedAt = enqueuedAt;
|
|
96
|
+
entries.push(entry);
|
|
97
|
+
}
|
|
98
|
+
return entries;
|
|
99
|
+
}
|
|
100
|
+
function writePostReviews(root, pending) {
|
|
101
|
+
tryOrWarn(root, TUMWATER_SOURCE, "write post-review.json", () => writeJsonAtomic(postReviewPath(root), { pending }));
|
|
102
|
+
}
|
|
103
|
+
/** Queue the merged commit for its review. Never throws: a failed write warns and leaves the file
|
|
104
|
+
* as it was, so the commit goes unreviewed. Stamps `enqueuedAt` when the entry carries none —
|
|
105
|
+
* the file's existing stamp on a re-add of a known commit, else `now` (the default clock when the
|
|
106
|
+
* write path drives the drain) — so a re-queued entry keeps its original stamp and tests can pin
|
|
107
|
+
* a fixed instant. */
|
|
108
|
+
export function addPostReview(root, entry, now = Date.now()) {
|
|
109
|
+
const rest = readPostReviews(root);
|
|
110
|
+
const prior = rest.find((e) => e.commit === entry.commit)?.enqueuedAt;
|
|
111
|
+
const stamped = { ...entry, enqueuedAt: entry.enqueuedAt ?? prior ?? now };
|
|
112
|
+
writePostReviews(root, [...rest.filter((e) => e.commit !== entry.commit), stamped]);
|
|
113
|
+
}
|
|
114
|
+
/** Replace the entry for `commit` with `next`, or drop it when `next` is null. Never throws: a
|
|
115
|
+
* failed write warns and leaves the file as it was, so a later poll handles the entry again. */
|
|
116
|
+
export function settlePostReview(root, commit, next) {
|
|
117
|
+
const rest = readPostReviews(root).filter((e) => e.commit !== commit);
|
|
118
|
+
writePostReviews(root, next ? [...rest, next] : rest);
|
|
119
|
+
}
|
|
120
|
+
/** Cut a review finding's first reason short enough to ride a commit subject (the
|
|
121
|
+
* `tumwater(reviewer): Fix …` subject names it): trimmed of leading/trailing and repeated
|
|
122
|
+
* whitespace, clipped at a word boundary around 100 chars with an ellipsis. */
|
|
123
|
+
function clipFixReason(reason) {
|
|
124
|
+
const trimmed = reason.trim().replace(/\s+/g, " ");
|
|
125
|
+
if (trimmed.length <= 100)
|
|
126
|
+
return trimmed;
|
|
127
|
+
return `${trimmed.slice(0, 97).replace(/\s+\S*$/, "")}…`;
|
|
128
|
+
}
|
|
129
|
+
/** Commit the review worktree's edited paths as the one `tumwater(reviewer): Fix <short sha> —
|
|
130
|
+
* <first finding>` fix commit, made by the harness's own git call (never pi) — commitAll stages
|
|
131
|
+
* and commits everything, so however the reviewer edited a path, the single commit captures it
|
|
132
|
+
* on top of the reviewed commit C (the detached HEAD the worktree reset to); the caller advances
|
|
133
|
+
* the review-fix ref to the returned sha. The harness stamps the fix's body with the `Revision:`
|
|
134
|
+
* trailer one past the reviewed commit's round, so the next review of the fix reads its own round
|
|
135
|
+
* from the same place a rejection's hand-back reads it (Forward never back 2/6) and the self-fix
|
|
136
|
+
* chain counts toward the shared REVISION_BACKSTOP. Returns the new commit's full sha. */
|
|
137
|
+
async function commitReviewerFix(wt, commit, reasons, nextRound) {
|
|
138
|
+
const first = reasons[0] ? clipFixReason(reasons[0]) : "the reviewed change";
|
|
139
|
+
const subject = `tumwater(reviewer): Fix ${shortSha(commit)} — ${first}`;
|
|
140
|
+
const body = reasons.map((r, i) => `${i + 1}. ${r}`).join("\n");
|
|
141
|
+
const withRound = body ? `${body}\nRevision: ${nextRound}` : `Revision: ${nextRound}`;
|
|
142
|
+
return commitAll(wt, `${subject}\n\n${withRound}`);
|
|
143
|
+
}
|
|
144
|
+
/** Review the merged commit C: the usual reviewer prompt over `git diff C^ C`, run in a detached
|
|
145
|
+
* checkout of C. Logs `review_start` and `review_verdict` / `review_rejected` with
|
|
146
|
+
* `afterMerge: true`. A diff git fails to produce is a failed review with no reviewer run: read
|
|
147
|
+
* as empty, the reviewer approved a commit it never saw. Throws only when git could not start
|
|
148
|
+
* (gitCouldNotStart), which says nothing about C, so the caller keeps the entry as it was for
|
|
149
|
+
* the next poll instead of counting a failed run toward dropping it. */
|
|
150
|
+
export async function reviewMergedCommit(root, config, entry, runGatePi, signal) {
|
|
151
|
+
const { commit, role } = entry;
|
|
152
|
+
try {
|
|
153
|
+
const reviewCfg = reviewRunConfig(config);
|
|
154
|
+
const startedAt = Date.now();
|
|
155
|
+
const fixRef = reviewFixRef(commit);
|
|
156
|
+
// The whole review — the detached checkout, the reviewer run, and (on a repair) the status
|
|
157
|
+
// read, the fix commit and its ref — runs under one hold of the post-review worktree
|
|
158
|
+
// (plans/disk-floor.md part 2/4), so nothing can reset or reuse the shared worktree between
|
|
159
|
+
// the reviewer's edit and the commit that captures it.
|
|
160
|
+
return await useWorktree(root, postReviewWorktreePath(root), async () => {
|
|
161
|
+
// The review-fix ref tracks the reviewed commit C (and later the reviewer's fix commit), so
|
|
162
|
+
// a repair survives as a durable reachable ref the pin housekeeping sees. The ref is reset
|
|
163
|
+
// to C at every review and ensureDetachedWorktree hard-resets the worktree to C, so a
|
|
164
|
+
// leftover ref or edit from an earlier review cannot change what is reviewed. Checking the
|
|
165
|
+
// worktree out at C itself (not the ref) keeps the "git could not start" semantics of the
|
|
166
|
+
// detached checkout it replaces: a review whose git cannot start throws gitCouldNotStart.
|
|
167
|
+
const wt = await ensureDetachedWorktree(root, postReviewWorktreePath(root), commit);
|
|
168
|
+
await setRef(root, fixRef, commit);
|
|
169
|
+
const diff = await git(wt, "diff", `${commit}^`, commit);
|
|
170
|
+
logEventBestEffort(root, { source: role, type: "review_start", head: commit, afterMerge: true });
|
|
171
|
+
const pi = await runGatePi({
|
|
172
|
+
cwd: wt,
|
|
173
|
+
kind: "gate",
|
|
174
|
+
prompt: buildReviewPrompt(diff, entry.summary, entry.body, readPrinciples(root), undefined, `${commit}^`, undefined, undefined, undefined, true),
|
|
175
|
+
config: reviewCfg,
|
|
176
|
+
sessionDir: reviewRunSessionDir(root, role, `post-${shortSha(commit)}`),
|
|
177
|
+
sessionName: `tumwater-review-${role}-post-${shortSha(commit)}`,
|
|
178
|
+
rawLogFile: piLogPath(root, role),
|
|
179
|
+
nativeLogFile: nativeLogPath(root, role, reviewCfg.agent ?? "pi"),
|
|
180
|
+
label: "review",
|
|
181
|
+
signal,
|
|
182
|
+
});
|
|
183
|
+
const verdict = pi.ok && !pi.aborted ? parseVerdict(pi.verdictText ?? "") : null;
|
|
184
|
+
if (!verdict) {
|
|
185
|
+
await deleteRef(root, fixRef);
|
|
186
|
+
return { kind: "failed", detail: pi.errorMessage ?? "no parseable VERDICT line in the reviewer's reply" };
|
|
187
|
+
}
|
|
188
|
+
const durationMs = Date.now() - startedAt;
|
|
189
|
+
// One record for the reviewed commit C whatever way the review ends — reject or approve —
|
|
190
|
+
// so the fix-forward landing does not lose the verdict that motivated it.
|
|
191
|
+
if (verdict.verdict === "reject") {
|
|
192
|
+
logEventBestEffort(root, { source: role, type: "review_rejected", head: commit, reasons: verdict.reasons, durationMs, afterMerge: true });
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
logEventBestEffort(root, { source: role, type: "review_verdict", head: commit, reason: verdict.reasons[0], durationMs, afterMerge: true });
|
|
196
|
+
}
|
|
197
|
+
// A rejecting review whose reply carries a FIX: line and whose worktree holds an edit
|
|
198
|
+
// survives as one fix commit on the review ref, kept for merge-post-review.ts to land on
|
|
199
|
+
// main through the ordinary change path. A clean worktree or a reply with no FIX line keeps
|
|
200
|
+
// today's outcomes and deletes the ref.
|
|
201
|
+
const paths = pi.ok && !pi.aborted ? fixLine(pi.verdictText ?? "") : null;
|
|
202
|
+
if (paths && verdict.verdict === "reject" && (await gitTry(wt, "status", "--porcelain"))?.trim().length) {
|
|
203
|
+
// The self-fix chain's round is one past the reviewed commit's `Revision:` trailer; at
|
|
204
|
+
// REVISION_BACKSTOP the reviewer stops fixing and records the surviving findings — the
|
|
205
|
+
// same terminal stop as a rejection's exhaustion, one shared counter, never a second
|
|
206
|
+
// one (Forward never back 2/6). The answered plan is deliberately NOT held here: it was
|
|
207
|
+
// already archived to PLANS-DONE.md when its implementation landed, so it is no longer
|
|
208
|
+
// under `## Planned` and there is nothing left to hold or re-pick.
|
|
209
|
+
const reviewedMessage = (await gitTry(root, "log", "-1", "--format=%B", commit)) ?? "";
|
|
210
|
+
const nextRound = (revisionRoundOf(reviewedMessage) ?? 0) + 1;
|
|
211
|
+
if (nextRound >= REVISION_BACKSTOP) {
|
|
212
|
+
await deleteRef(root, fixRef); // the chain stops here; nothing to land
|
|
213
|
+
logEventBestEffort(root, {
|
|
214
|
+
source: entry.role,
|
|
215
|
+
type: "revision",
|
|
216
|
+
action: "exhausted",
|
|
217
|
+
round: nextRound,
|
|
218
|
+
sha: commit,
|
|
219
|
+
});
|
|
220
|
+
return { kind: "exhausted", reasons: verdict.reasons };
|
|
221
|
+
}
|
|
222
|
+
const fix = await commitReviewerFix(wt, commit, verdict.reasons, nextRound);
|
|
223
|
+
// Advance the review-fix ref from C to the new fix commit, so the repair is reachable and
|
|
224
|
+
// the merge-post-review landing (which checks the fix out by this sha) finds it.
|
|
225
|
+
await setRef(root, fixRef, fix);
|
|
226
|
+
return { kind: "fixed", fix, reasons: verdict.reasons };
|
|
227
|
+
}
|
|
228
|
+
await deleteRef(root, fixRef);
|
|
229
|
+
if (verdict.verdict === "reject")
|
|
230
|
+
return { kind: "reject", reasons: verdict.reasons };
|
|
231
|
+
return { kind: "approve", reasons: verdict.reasons };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
if (gitCouldNotStart(err))
|
|
236
|
+
throw err;
|
|
237
|
+
return { kind: "failed", detail: errorMessage(err) };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { promptBlockOf } from "../brief/brief.js";
|
|
4
3
|
import { readTextOrNull } from "../files/files.js";
|
|
5
4
|
import { changeBaseRev, fileContentAt } from "../git/git.js";
|
|
6
5
|
import { briefCandidates } from "../paths.js";
|
|
7
|
-
import { baseRoleOf } from "../roles/
|
|
8
|
-
/** The brief files that may carry the
|
|
6
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
7
|
+
/** The brief files that may carry the brief text block: the basenames of `briefCandidates`. */
|
|
9
8
|
const BRIEF_FILES = briefCandidates("").map((p) => path.basename(p));
|
|
10
9
|
/** The roles that may edit PRINCIPLES.md. */
|
|
11
10
|
const PRINCIPLES_ROLES = new Set(["director", "steward"]);
|
|
12
|
-
/** The deterministic gate/stage finding for a change that edits
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
11
|
+
/** The deterministic gate/stage finding for a change that edits the brief text block of a brief
|
|
12
|
+
* file (changed, removed, or the file deleted — a block that only moves passes); the role-
|
|
13
|
+
* independent half of protectedEditReason, split out so the main watch still runs it when it
|
|
14
|
+
* cannot name the tip's merging role (an unknown role is no basis for calling a PRINCIPLES.md
|
|
15
|
+
* edit unauthorized, but the brief block forbids its edit for every role alike): a brief edit
|
|
16
|
+
* could otherwise land on main un-watched ("Every commit is verified on main, part 1/3").
|
|
17
|
+
* undefined when no brief-file change touches the block. Reads the worktree and the change's
|
|
18
|
+
* baseRev (a commit or branch) like backlogStructureReason; a missing file reads as empty and
|
|
19
|
+
* never throws. */
|
|
20
|
+
export async function briefEditReason(wt, baseRev, files) {
|
|
18
21
|
const briefs = BRIEF_FILES.filter((f) => files.includes(f));
|
|
19
22
|
if (briefs.length > 0) {
|
|
20
|
-
const baseRev = await changeBaseRev(wt, mainBranch);
|
|
21
23
|
for (const file of briefs) {
|
|
22
24
|
const base = promptBlockOf(await fileContentAt(wt, baseRev, file));
|
|
23
25
|
if (base === null)
|
|
@@ -27,6 +29,18 @@ export async function protectedEditReason(wt, mainBranch, files, role) {
|
|
|
27
29
|
return `${file} changes the brief between the tumwater:prompt markers — only you edit it (tumwater brief); restore the block`;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/** The deterministic gate/stage finding for a change that edits what the role prompts forbid
|
|
35
|
+
* editing: the brief text block of a brief file (changed, removed, or the file deleted —
|
|
36
|
+
* a block that only moves passes), or PRINCIPLES.md from a role other than director or
|
|
37
|
+
* steward. undefined when neither happened. Reads the worktree and the change's merge-base
|
|
38
|
+
* like backlogStructureReason; a missing file reads as empty and never throws. */
|
|
39
|
+
export async function protectedEditReason(wt, mainBranch, files, role) {
|
|
40
|
+
const baseRev = await changeBaseRev(wt, mainBranch);
|
|
41
|
+
const brief = await briefEditReason(wt, baseRev, files);
|
|
42
|
+
if (brief !== undefined)
|
|
43
|
+
return brief;
|
|
30
44
|
if (files.includes("PRINCIPLES.md") && !PRINCIPLES_ROLES.has(baseRoleOf(role)))
|
|
31
45
|
return `PRINCIPLES.md is edited by the ${role} role — only the director and steward roles may edit it; record an objection in PLANS.md instead and revert the edit`;
|
|
32
46
|
return undefined;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** The read-only render of the review gate's flake quarantine for the `tumwater
|
|
2
|
+
* quarantine` command. The quarantine list (.tumwater/state/quarantine.json) is the tests
|
|
3
|
+
* that failed then passed on the same tree; the doctor's `flaky checks` row and the
|
|
4
|
+
* selfcheck digest share the underlying quarantineWarning, and this module renders the full
|
|
5
|
+
* list — every entry oldest first with its age, flake count, last sighting and filed bug —
|
|
6
|
+
* so an operator can see which tests the gate is excusing and why. Pure over its inputs
|
|
7
|
+
* (no I/O, no clock): every branch has its own unit tests. */
|
|
8
|
+
import { plural } from "../text/phrases.js";
|
|
9
|
+
import { squash } from "../text/text.js";
|
|
10
|
+
import { humanAgo } from "../text/datetime.js";
|
|
11
|
+
import { quarantineWarning } from "../baseline/main-guardrails.js";
|
|
12
|
+
/** The quarantine as sorted, positioned rows, oldest first (firstSeen ascending, the order
|
|
13
|
+
* quarantineWarning names its oldest), ties by test name, numbered 1..N. `bug` passes
|
|
14
|
+
* through with `""` staying `""`. */
|
|
15
|
+
export function quarantineRows(entries) {
|
|
16
|
+
return [...entries]
|
|
17
|
+
.sort((a, b) => a.firstSeen - b.firstSeen || (a.test < b.test ? -1 : a.test > b.test ? 1 : 0))
|
|
18
|
+
.map((e, i) => ({
|
|
19
|
+
position: i + 1,
|
|
20
|
+
test: e.test,
|
|
21
|
+
firstSeen: e.firstSeen,
|
|
22
|
+
lastSeen: e.lastSeen,
|
|
23
|
+
count: e.count,
|
|
24
|
+
bug: e.bug,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
/** The command's text for `rows`: an empty list prints the empty line (the merge queue empty
|
|
28
|
+
* convention), otherwise a `flake quarantine: N tests` header, the shared quarantineWarning
|
|
29
|
+
* phrase on its own line when one fires (so the command and the doctor's row agree), then one
|
|
30
|
+
* numbered line per entry naming its test, first-seen and last-seen age, flake count and the
|
|
31
|
+
* BUGS.md heading filed for it (or "not filed"). */
|
|
32
|
+
export function renderQuarantine(rows, now) {
|
|
33
|
+
if (rows.length === 0)
|
|
34
|
+
return "flake quarantine is empty";
|
|
35
|
+
const lines = [`flake quarantine: ${plural(rows.length, "test")}`];
|
|
36
|
+
const warning = quarantineWarning(rows, now);
|
|
37
|
+
if (warning !== null)
|
|
38
|
+
lines.push(warning);
|
|
39
|
+
for (const r of rows) {
|
|
40
|
+
const filed = r.bug ? ` — filed: ${squash(r.bug, 80)}` : " — not filed";
|
|
41
|
+
lines.push(`${r.position}. ${squash(r.test, 80)} — first seen ${humanAgo(r.firstSeen, now)}, ` +
|
|
42
|
+
`last seen ${humanAgo(r.lastSeen, now)}, ${plural(r.count, "flake")}${filed}`);
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
/** The Markdown section the Failures surfaces (the dashboard's Failures view and the TUI's
|
|
47
|
+
* Failures pane) append below the failure digest — the read twin of the amber quarantine
|
|
48
|
+
* alert, whose `See failures` action lands there. It reads the same `quarantineRows` rule as
|
|
49
|
+
* `tumwater quarantine` (and shares its `quarantineWarning` phrase when one fires), so the CLI
|
|
50
|
+
* and the always-on surfaces cannot disagree. Empty for an empty list, so a window with a
|
|
51
|
+
* digest but a clean quarantine adds nothing. Pure over its inputs (no I/O, no clock). */
|
|
52
|
+
export function renderQuarantineSection(rows, now) {
|
|
53
|
+
if (rows.length === 0)
|
|
54
|
+
return "";
|
|
55
|
+
const lines = ["## Flake quarantine", ""];
|
|
56
|
+
const warning = quarantineWarning(rows, now);
|
|
57
|
+
if (warning !== null)
|
|
58
|
+
lines.push(warning, "");
|
|
59
|
+
for (const r of rows) {
|
|
60
|
+
const filed = r.bug ? `filed: ${squash(r.bug, 80)}` : "not filed";
|
|
61
|
+
lines.push(`- ${squash(r.test, 80)} — first seen ${humanAgo(r.firstSeen, now)}, ` +
|
|
62
|
+
`last seen ${humanAgo(r.lastSeen, now)}, ${plural(r.count, "flake")} — ${filed}`);
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { reviewRunConfig,
|
|
1
|
+
import { backendSessionResumable } from "../agent/agent-backend.js";
|
|
2
|
+
import { reviewRunConfig, resolvedBackendConfig } from "../config/config-views.js";
|
|
3
3
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
4
4
|
import { nativeLogPath, piLogPath } from "../paths.js";
|
|
5
5
|
import { buildNoRerunPrompt, buildVerdictRequestPrompt } from "../prompt/gate-prompts.js";
|
|
6
6
|
import { cappedRequestTimeouts } from "../request-timeouts.js";
|
|
7
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
|
|
8
|
+
* so it never gets the review run's own budget (mirrors RunnerPi's SUMMARY-request caps). */
|
|
9
9
|
const VERDICT_REQUEST_TIMEOUT_S = 900;
|
|
10
10
|
const VERDICT_REQUEST_QUIET_S = 300;
|
|
11
11
|
/** One bounded follow-up turn on the reviewer's just-finished session: the shared session
|
|
@@ -19,7 +19,7 @@ async function runFollowupTurn(ctx, opts) {
|
|
|
19
19
|
// backend's own resumable rule keeps a newer line of another kind from being the session a
|
|
20
20
|
// pi continue would actually reach; claude resumes its recorded id (looked up under the
|
|
21
21
|
// team's config dir, so a team that set one continues under it).
|
|
22
|
-
if (!
|
|
22
|
+
if (!backendSessionResumable(dir, { kind: "gate", agent: cfg.agent ?? "pi" }, resolvedBackendConfig(ctx.config, "claude").configDir)) {
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
return ctx.runGatePi({
|
|
@@ -46,7 +46,7 @@ async function runFollowupTurn(ctx, opts) {
|
|
|
46
46
|
onBlindRead: (path, action) => logEventBestEffort(ctx.root, {
|
|
47
47
|
source: ctx.role,
|
|
48
48
|
type: "image_blind",
|
|
49
|
-
run: ctx.
|
|
49
|
+
run: ctx.runNumber,
|
|
50
50
|
path,
|
|
51
51
|
model: cfg.model,
|
|
52
52
|
action,
|
|
@@ -55,14 +55,14 @@ async function runFollowupTurn(ctx, opts) {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
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
|
|
58
|
+
* bounded turn on the just-finished review's session, mirroring RunnerPi.requestSummary on the
|
|
59
59
|
* author side (BUGS.md 2026-09-29). Null when there is no session to continue — the caller
|
|
60
60
|
* then counts the strike exactly as before. The run is returned even when it failed so the
|
|
61
61
|
* caller can honor a shutdown abort and fold the spend. */
|
|
62
62
|
export async function requestVerdict(ctx) {
|
|
63
63
|
return runFollowupTurn(ctx, {
|
|
64
64
|
prompt: buildVerdictRequestPrompt(),
|
|
65
|
-
sessionName: `tumwater-review-${ctx.role}-${ctx.
|
|
65
|
+
sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-verdict`,
|
|
66
66
|
label: "review-verdict",
|
|
67
67
|
});
|
|
68
68
|
}
|
|
@@ -76,7 +76,7 @@ export async function requestVerdict(ctx) {
|
|
|
76
76
|
export async function requestNoRerun(ctx, rerun, calls) {
|
|
77
77
|
return runFollowupTurn(ctx, {
|
|
78
78
|
prompt: buildNoRerunPrompt(rerun),
|
|
79
|
-
sessionName: `tumwater-review-${ctx.role}-${ctx.
|
|
79
|
+
sessionName: `tumwater-review-${ctx.role}-${ctx.runNumber}-rerun`,
|
|
80
80
|
label: "review-rerun",
|
|
81
81
|
// Collected unconditionally: a nudge only runs after a detected rerun, so the no-re-run
|
|
82
82
|
// rule stood and the repeat check below needs every call the nudge started.
|