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
|
@@ -1,15 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { readMergeQueue, queuedMergeFiles } from "../../merge/merge-queue.js";
|
|
2
|
+
import { renderMergeQueue, revisionNote, retryNote, scopeMergeQueue } from "../../merge/merge-queue-render.js";
|
|
3
|
+
import { cancelQueuedMerge } from "../../merge/merge-cancel.js";
|
|
4
|
+
import { moveQueuedMergeToTop } from "../../merge/merge-top.js";
|
|
5
|
+
import { mergeChanges } from "../../merge/merge-slot.js";
|
|
4
6
|
import { orchestratorAlive } from "../../team/orchestrator-info.js";
|
|
5
7
|
import { queueInFlight } from "../../status/status-polls.js";
|
|
6
|
-
import { renderTeamChange } from "../../change/change-render.js";
|
|
8
|
+
import { renderChangeOverlaps, renderRoleChanges, renderTeamChange } from "../../change/change-render.js";
|
|
9
|
+
import { scopeChangeOverlaps } from "../../change/change-data.js";
|
|
10
|
+
import { roleLabel } from "../../roles/role-labels.js";
|
|
11
|
+
import { shortSha } from "../../text/format.js";
|
|
12
|
+
import { humanAgo } from "../../text/datetime.js";
|
|
13
|
+
import { readPostReviews } from "../../review/post-merge-review.js";
|
|
14
|
+
import { renderPostReviews, postReviewStatusLabel, scopePostReviews, POST_REVIEW_STATES, postReviewState } from "../../review/post-merge-review-render.js";
|
|
15
|
+
import { acceptPostReview } from "../../review/post-merge-review-accept.js";
|
|
16
|
+
import { squash } from "../../text/text.js";
|
|
7
17
|
import { promptLineFree } from "./tui-pane-ctx.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
18
|
+
import { arrowDir } from "./tui-keymap.js";
|
|
19
|
+
import { moveEntrySelection } from "./tui-backlog.js";
|
|
20
|
+
/** The overlapping-files block under the roster: empty for a no-base view (the roster above
|
|
21
|
+
* already says the main branch is missing), else the CLI's renderChangeOverlaps under a
|
|
22
|
+
* "overlapping files (N):" heading shaped like the queue's. */
|
|
23
|
+
export function pendingOverlapsText(view, scope) {
|
|
24
|
+
if (view.state === "no-base")
|
|
25
|
+
return "";
|
|
26
|
+
if (scope !== undefined) {
|
|
27
|
+
const narrowed = scopeChangeOverlaps(view, [scope]);
|
|
28
|
+
if (narrowed.overlaps.length === 0)
|
|
29
|
+
return renderChangeOverlaps(narrowed, [scope]);
|
|
30
|
+
return `overlapping files held by ${scope} (${narrowed.overlaps.length}):\n` + renderChangeOverlaps(narrowed);
|
|
31
|
+
}
|
|
32
|
+
if (view.overlaps.length === 0)
|
|
33
|
+
return renderChangeOverlaps(view);
|
|
34
|
+
return `overlapping files (${view.overlaps.length}):\n` + renderChangeOverlaps(view);
|
|
35
|
+
}
|
|
36
|
+
/** The distinct roles named across the overlaps, in first-seen order; empty for a no-base view. */
|
|
37
|
+
export function overlapHolders(view) {
|
|
38
|
+
if (view.state === "no-base")
|
|
39
|
+
return [];
|
|
40
|
+
const seen = [];
|
|
41
|
+
for (const o of view.overlaps)
|
|
42
|
+
for (const r of o.roles)
|
|
43
|
+
if (!seen.includes(r))
|
|
44
|
+
seen.push(r);
|
|
45
|
+
return seen;
|
|
46
|
+
}
|
|
47
|
+
/** The distinct roles named across the merge queue, in first-seen (queue) order; empty for an
|
|
48
|
+
* empty queue. The queue's own roles lead the pane's Ctrl+G scope cycle, before the overlap
|
|
49
|
+
* holders — the queue scope is the `tumwater queue --role <id>` twin. */
|
|
50
|
+
export function mergeQueueRoles(view) {
|
|
51
|
+
const seen = [];
|
|
52
|
+
for (const e of view.entries)
|
|
53
|
+
if (!seen.includes(e.role))
|
|
54
|
+
seen.push(e.role);
|
|
55
|
+
return seen;
|
|
56
|
+
}
|
|
57
|
+
/** The distinct roles named across the post-merge review queue, in first-seen (FIFO) order;
|
|
58
|
+
* empty for an empty queue. The review block's contribution to the pane's Ctrl+G scope cycle,
|
|
59
|
+
* after the queue's roles — the review scope is the `tumwater reviews --role <id>` twin. */
|
|
60
|
+
export function postReviewRoles(entries) {
|
|
61
|
+
const seen = [];
|
|
62
|
+
for (const e of entries)
|
|
63
|
+
if (!seen.includes(e.role))
|
|
64
|
+
seen.push(e.role);
|
|
65
|
+
return seen;
|
|
66
|
+
}
|
|
67
|
+
/** The heading over one overlap's drill-down: its place in the list and the path. */
|
|
68
|
+
export function overlapDrillHeading(index, total, path) {
|
|
69
|
+
return `overlap ${index + 1}/${total}: ${path} (Ctrl+N next)`;
|
|
70
|
+
}
|
|
13
71
|
export function createPendingPane(session) {
|
|
14
72
|
// Pending is the cycle's one async collector: collectTeamChanges awaits git plumbing,
|
|
15
73
|
// so kick it off fire-and-forget, cache the rendered text when it resolves, and repaint.
|
|
@@ -20,21 +78,225 @@ export function createPendingPane(session) {
|
|
|
20
78
|
// Guard on the PENDING pane still being current — view === roleIds.length + 5 — not just
|
|
21
79
|
// on the raw index: an operator who cycled away, or a roster change that shifted which
|
|
22
80
|
// pane the same index names, must not have this resolution overwrite the pane now shown.
|
|
81
|
+
// The overlaps are read by the same collectChangeOverlaps that `tumwater diff --overlaps`
|
|
82
|
+
// and `GET /api/diff?overlaps=1` serve, and rendered through the CLI's renderChangeOverlaps.
|
|
23
83
|
// The default collector degrades rather than throws, so the rejection arm only covers an
|
|
24
84
|
// injected seam (or an unexpected throw).
|
|
85
|
+
// Ctrl+N steps through the overlapping files, painting each holder's change limited to that
|
|
86
|
+
// file (the `tumwater diff --role a,b --path <path>` view); `base` is the pane text to
|
|
87
|
+
// return to after the last one. Ctrl+G cycles roleScope through each role the queue, the
|
|
88
|
+
// post-merge reviews and the overlaps name and back to all (the `queue --role <id>`,
|
|
89
|
+
// `reviews --role <id>` and `diff --overlaps --role <id>` twins, one scope covering all three
|
|
90
|
+
// surfaces); Ctrl+N then steps through only the scoped files,
|
|
91
|
+
// and the scope survives a refresh. The queue's roles lead the cycle, the review-only roles
|
|
92
|
+
// follow, then the overlap holders that are in neither, and each kept merge and each kept
|
|
93
|
+
// review holds its whole-queue position.
|
|
25
94
|
let refreshGen = 0;
|
|
95
|
+
let base = "";
|
|
96
|
+
let lastOverlaps = null;
|
|
97
|
+
let lastQueue = null;
|
|
98
|
+
let lastReviews = [];
|
|
99
|
+
let rosterText = "";
|
|
100
|
+
let overlapIndex = -1;
|
|
101
|
+
let drillGen = 0;
|
|
102
|
+
let roleScope = null;
|
|
103
|
+
// The pane-local `--status` filter (the Ctrl+O twin of `tumwater reviews --status` and the
|
|
104
|
+
// dashboard State picker): null means all, otherwise one of waiting/failing/rejected. It
|
|
105
|
+
// composes with the Ctrl+G role scope in the review block's filter text but is surfaced only
|
|
106
|
+
// by that header, and it never moves the History pane's outcome filter (the Pending arm in
|
|
107
|
+
// tui-panes.ts is routed ahead of History's).
|
|
108
|
+
let reviewState = null;
|
|
109
|
+
let head = "";
|
|
110
|
+
// The Pending pane's row selection (null = list mode): the index into queueRows of the
|
|
111
|
+
// merge shown in full, cleared on every Ctrl+T cycle like selectedQueued. Each row pairs a
|
|
112
|
+
// kept entry with its 1-based whole-queue position (or the scope's kept positions when a
|
|
113
|
+
// Ctrl+G role scope is active), so a cancel addresses the entry `tumwater queue --cancel`
|
|
114
|
+
// numbers — the position the CLI's queue line printed.
|
|
115
|
+
let selectedQueue = null;
|
|
116
|
+
// The Pending pane's cached rows — the same whole-queue traversal renderMergeQueue painted,
|
|
117
|
+
// plus the post-merge review rows appended after them — so a selection addresses an entry's
|
|
118
|
+
// position without re-reading the queue on every frame.
|
|
119
|
+
let queueRows = [];
|
|
120
|
+
// The rendered detail of the selected row (the header, summary, and when present the
|
|
121
|
+
// entry's body and revision note); the pane body windows it in place of the queue list
|
|
122
|
+
// while a row is selected. Empty in list mode.
|
|
123
|
+
let queueDetail = "";
|
|
124
|
+
const compose = () => {
|
|
125
|
+
const block = lastOverlaps ? pendingOverlapsText(lastOverlaps, roleScope ?? undefined) : "";
|
|
126
|
+
base = head + (block ? "\n\n" + block : "");
|
|
127
|
+
};
|
|
128
|
+
const queueHead = () => {
|
|
129
|
+
if (lastQueue === null)
|
|
130
|
+
return "";
|
|
131
|
+
const inFlight = queueInFlight(session.root, lastQueue.entries, orchestratorAlive(session.root));
|
|
132
|
+
if (roleScope === null)
|
|
133
|
+
return renderMergeQueue(lastQueue, session.now(), inFlight);
|
|
134
|
+
const { view, scope } = scopeMergeQueue(lastQueue, [roleScope], (r) => r === roleScope);
|
|
135
|
+
return renderMergeQueue(view, session.now(), inFlight, scope);
|
|
136
|
+
};
|
|
137
|
+
// The one scoped traversal the review block's painting and the row selection both use, so
|
|
138
|
+
// the painted block and the selected row's accept position cannot drift: the whole list with
|
|
139
|
+
// no scope when neither the Ctrl+G role scope nor the Ctrl+O state filter is active, else
|
|
140
|
+
// `scopePostReviews`'s kept entries and scope (each kept review keeping its whole-queue
|
|
141
|
+
// position, the `tumwater reviews --role <id>` / `--status <state>` / composed twin). The
|
|
142
|
+
// whole-list path stays byte-identical so an unfiltered pane reads unchanged.
|
|
143
|
+
const scopedReviews = () => {
|
|
144
|
+
if (roleScope === null && reviewState === null)
|
|
145
|
+
return { entries: lastReviews };
|
|
146
|
+
const keep = (r) => (roleScope === null || r.role === roleScope) && (reviewState === null || postReviewState(r) === reviewState);
|
|
147
|
+
// The `--status <state>` filter text (or `--role <id> --status <state>` when composed with a
|
|
148
|
+
// Ctrl+G scope), passed only when a state filter is active: a role-only scope passes no filter
|
|
149
|
+
// so it keeps the `tumwater reviews --role <id>` twin's `no post-merge reviews for <id> …`
|
|
150
|
+
// no-match line (the CLI shares this path and passes no filter for `--role` alone).
|
|
151
|
+
const filterText = reviewState === null ? undefined : [roleScope !== null ? `--role ${roleScope}` : "", `--status ${reviewState}`].filter(Boolean).join(" ");
|
|
152
|
+
return scopePostReviews(lastReviews, roleScope === null ? [] : [roleScope], keep, filterText);
|
|
153
|
+
};
|
|
154
|
+
// The post-merge review block the pane paints under the merge queue and above the change
|
|
155
|
+
// roster: `tumwater reviews`' own renderer over the queue `readPostReviews` reads, so the
|
|
156
|
+
// TUI, the CLI, and the dashboard cannot drift on order or wording. Under a Ctrl+G scope it
|
|
157
|
+
// narrows to the scoped role's entries, each kept under its whole-queue position. Empty only
|
|
158
|
+
// when the whole queue is empty (no block, no header); a scope that keeps nothing still
|
|
159
|
+
// prints `renderPostReviews`'s no-match line — `no post-merge reviews for <id> …` for a
|
|
160
|
+
// role-only scope, `no post-merge reviews matching <filter> …` once a state filter joins.
|
|
161
|
+
const reviewsHead = () => {
|
|
162
|
+
if (lastReviews.length === 0)
|
|
163
|
+
return "";
|
|
164
|
+
const scoped = scopedReviews();
|
|
165
|
+
return renderPostReviews(scoped.entries, scoped.scope, session.now());
|
|
166
|
+
};
|
|
167
|
+
const queueAndReviews = () => {
|
|
168
|
+
const q = queueHead();
|
|
169
|
+
const r = reviewsHead();
|
|
170
|
+
return r === "" ? q : q + "\n\n" + r;
|
|
171
|
+
};
|
|
172
|
+
// The rendered detail of one selected row — the headline naming the entry with the position
|
|
173
|
+
// its kind's CLI command takes, then the summary and (for a merge) the body/revision/retry
|
|
174
|
+
// notes, so an operator deciding whether to cancel or accept sees what the entry is about
|
|
175
|
+
// before the second Ctrl+X/Ctrl+A. A review row never has a body or revision note: it names
|
|
176
|
+
// the accept action, the status suffix, and for a rejected entry why it cannot be accepted.
|
|
177
|
+
const rowDetail = (row) => {
|
|
178
|
+
if (row.kind === "merge") {
|
|
179
|
+
const e = row.entry;
|
|
180
|
+
const note = revisionNote(e);
|
|
181
|
+
const retry = retryNote(e, session.now());
|
|
182
|
+
const lines = [
|
|
183
|
+
`merge #${row.position} (Ctrl+X to cancel, Ctrl+V to top): ${roleLabel(e.role)} run #${e.run} · ${shortSha(e.sha)} · ${humanAgo(e.enqueuedAt, session.now())}`,
|
|
184
|
+
e.summary,
|
|
185
|
+
];
|
|
186
|
+
if (e.body)
|
|
187
|
+
lines.push("", e.body);
|
|
188
|
+
if (note)
|
|
189
|
+
lines.push("", note);
|
|
190
|
+
if (retry)
|
|
191
|
+
lines.push("", retry);
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
194
|
+
const e = row.entry;
|
|
195
|
+
const status = postReviewStatusLabel(e);
|
|
196
|
+
const lines = [
|
|
197
|
+
`review #${row.position} (Ctrl+A to accept): ${roleLabel(e.role)} ${shortSha(e.commit)} · ${squash(e.summary, 80)}` + (status === "" ? "" : ` · ${status}`),
|
|
198
|
+
];
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
};
|
|
201
|
+
// Resolve the selected row for a pending-pane action (Ctrl+X cancel, Ctrl+V top, Ctrl+A
|
|
202
|
+
// accept), or null when nothing is selected: a list-mode press just tells the operator how
|
|
203
|
+
// to select. Shared by the cancel, top, and accept handlers so the prompt and the row bound
|
|
204
|
+
// stay in one place.
|
|
205
|
+
const selectedRow = () => {
|
|
206
|
+
if (selectedQueue === null || queueRows.length === 0) {
|
|
207
|
+
session.flashMessage("select a merge with ↑↓ first");
|
|
208
|
+
session.requestRender();
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return queueRows[Math.min(selectedQueue, queueRows.length - 1)];
|
|
212
|
+
};
|
|
213
|
+
// True when the queue shifted under the selection (a concurrent drain or another cancel)
|
|
214
|
+
// moved this entry away from `row.position`: refuse rather than act on the wrong entry.
|
|
215
|
+
// Shared by the cancel and top handlers for the one "the merge queue changed" wording.
|
|
216
|
+
const queueShiftedAway = (row) => {
|
|
217
|
+
if (queuedMergeFiles(session.root)[row.position - 1]?.entry.sha !== row.entry.sha) {
|
|
218
|
+
session.flashMessage("the merge queue changed — try again");
|
|
219
|
+
session.requestRender();
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
};
|
|
224
|
+
// Guard the cancel/top actions against racing a merge that just entered the slot: the
|
|
225
|
+
// selected entry (or one its changes touch) is heading into the merge right now, so acting
|
|
226
|
+
// on it would race the drain. Flash the pane's "being merged" message and return true so
|
|
227
|
+
// the caller aborts; otherwise false.
|
|
228
|
+
const guardMergeInFlight = (row) => {
|
|
229
|
+
const inFlight = queueInFlight(session.root, lastQueue?.entries ?? [], orchestratorAlive(session.root));
|
|
230
|
+
if (inFlight && (inFlight.sha === row.entry.sha || mergeChanges(inFlight).some((c) => c.sha === row.entry.sha))) {
|
|
231
|
+
session.flashMessage(`merge #${row.position} is being merged right now — wait for it to land, then \`tumwater revert ${shortSha(row.entry.sha)}\``);
|
|
232
|
+
session.requestRender();
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
};
|
|
237
|
+
// Re-read the pane's rows after the queue or the Ctrl+G scope changed (the same traversal
|
|
238
|
+
// queueHead painted) and clamp the row selection to the new list — a selection past the new
|
|
239
|
+
// end lands on the last remaining entry, never on a stale role/position; an emptied queue
|
|
240
|
+
// clears it back to list mode. The detail is then rebuilt to match the clamped row.
|
|
241
|
+
const rebuildQueueRows = () => {
|
|
242
|
+
if (lastQueue === null) {
|
|
243
|
+
queueRows = [];
|
|
244
|
+
selectedQueue = null;
|
|
245
|
+
queueDetail = "";
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (roleScope === null) {
|
|
249
|
+
queueRows = lastQueue.entries.map((entry, i) => ({ kind: "merge", entry, position: i + 1 }));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const { view, scope } = scopeMergeQueue(lastQueue, [roleScope], (r) => r === roleScope);
|
|
253
|
+
queueRows = view.entries.map((entry, i) => ({ kind: "merge", entry, position: scope.positions[i] }));
|
|
254
|
+
}
|
|
255
|
+
// The review block follows the same scope the painted head used, so a review row's
|
|
256
|
+
// position is its 1-based whole-queue position — the one `tumwater reviews` and
|
|
257
|
+
// `tumwater reviews --accept <n>` number — whether unscoped (i + 1) or kept by the scope's
|
|
258
|
+
// positions. Reviews follow the merge rows in the pane, exactly as the traversal painted.
|
|
259
|
+
const reviews = scopedReviews();
|
|
260
|
+
if (reviews.scope) {
|
|
261
|
+
reviews.entries.forEach((entry, i) => queueRows.push({ kind: "review", entry, position: reviews.scope.positions[i] }));
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
reviews.entries.forEach((entry, i) => queueRows.push({ kind: "review", entry, position: i + 1 }));
|
|
265
|
+
}
|
|
266
|
+
if (selectedQueue !== null) {
|
|
267
|
+
if (queueRows.length === 0) {
|
|
268
|
+
selectedQueue = null;
|
|
269
|
+
queueDetail = "";
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
selectedQueue = Math.min(selectedQueue, queueRows.length - 1);
|
|
273
|
+
queueDetail = rowDetail(queueRows[selectedQueue]);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
26
277
|
const refresh = () => {
|
|
27
278
|
// Generation guard: each refresh bumps the counter, and a resolution paints only if it
|
|
28
279
|
// is the latest request's — Ctrl+R held twice, or a refresh raced with a re-entry,
|
|
29
280
|
// must not let an out-of-order older resolution overwrite the newer result.
|
|
30
281
|
const gen = ++refreshGen;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
282
|
+
overlapIndex = -1;
|
|
283
|
+
lastOverlaps = null;
|
|
284
|
+
++drillGen;
|
|
285
|
+
lastQueue = readMergeQueue(session.root);
|
|
286
|
+
lastReviews = readPostReviews(session.root);
|
|
287
|
+
rosterText = "";
|
|
288
|
+
rebuildQueueRows();
|
|
289
|
+
const queueText = queueAndReviews();
|
|
290
|
+
head = queueText;
|
|
291
|
+
session.paneCache = base = head;
|
|
292
|
+
void Promise.all([session.collectPending(session.root), session.collectOverlaps(session.root)]).then(([team, overlaps]) => {
|
|
35
293
|
if (gen !== refreshGen || session.view !== session.roleIds.length + 5)
|
|
36
294
|
return;
|
|
37
|
-
|
|
295
|
+
lastOverlaps = overlaps;
|
|
296
|
+
rosterText = "\n\n" + renderTeamChange(team);
|
|
297
|
+
head = queueAndReviews() + rosterText;
|
|
298
|
+
compose();
|
|
299
|
+
session.paneCache = base;
|
|
38
300
|
session.requestRender();
|
|
39
301
|
}, (err) => {
|
|
40
302
|
if (gen !== refreshGen || session.view !== session.roleIds.length + 5)
|
|
@@ -57,6 +319,263 @@ export function createPendingPane(session) {
|
|
|
57
319
|
session.requestRender();
|
|
58
320
|
return true;
|
|
59
321
|
}
|
|
322
|
+
if (key.ctrl && key.name === "g" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
323
|
+
const queueRoles = lastQueue ? mergeQueueRoles(lastQueue) : [];
|
|
324
|
+
const reviewRoles = postReviewRoles(lastReviews);
|
|
325
|
+
const holders = lastOverlaps?.state === "ready" ? overlapHolders(lastOverlaps) : [];
|
|
326
|
+
const roles = [
|
|
327
|
+
...queueRoles,
|
|
328
|
+
...reviewRoles.filter((r) => !queueRoles.includes(r)),
|
|
329
|
+
...holders.filter((h) => !queueRoles.includes(h) && !reviewRoles.includes(h)),
|
|
330
|
+
];
|
|
331
|
+
if (roles.length === 0) {
|
|
332
|
+
roleScope = null;
|
|
333
|
+
rebuildQueueRows();
|
|
334
|
+
head = queueAndReviews() + rosterText;
|
|
335
|
+
compose();
|
|
336
|
+
session.paneCache = base;
|
|
337
|
+
session.flashMessage("no queued merges, post-merge reviews or overlapping files to scope");
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
const slots = [null, ...roles];
|
|
341
|
+
const at = roleScope === null ? 0 : slots.indexOf(roleScope);
|
|
342
|
+
roleScope = slots[(at + 1) % slots.length] ?? null;
|
|
343
|
+
rebuildQueueRows();
|
|
344
|
+
overlapIndex = -1;
|
|
345
|
+
++drillGen;
|
|
346
|
+
head = queueAndReviews() + rosterText;
|
|
347
|
+
compose();
|
|
348
|
+
session.paneCache = base;
|
|
349
|
+
session.paneScroll = 0;
|
|
350
|
+
session.requestRender();
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
if (key.ctrl && key.name === "o" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
354
|
+
// Ctrl+O on the Pending pane cycles the post-merge review state filter through all,
|
|
355
|
+
// waiting, failing and rejected and back — the `tumwater reviews --status` twin and the
|
|
356
|
+
// dashboard Post-merge reviews State picker, meaning the full state vocabulary even when
|
|
357
|
+
// the queue holds none of a state (the renderer's `no post-merge reviews matching
|
|
358
|
+
// --status …` line is the empty-selection state). Each kept review keeps its whole-queue
|
|
359
|
+
// position, so Ctrl+A still accepts the right entry under any filter. The pane's own arm
|
|
360
|
+
// is routed ahead of History's in tui-panes.ts, so it never advances the History outcome
|
|
361
|
+
// filter; the Ctrl+G role scope composes with this filter in the block's header.
|
|
362
|
+
const slots = [null, ...POST_REVIEW_STATES];
|
|
363
|
+
const at = reviewState === null ? 0 : slots.indexOf(reviewState);
|
|
364
|
+
reviewState = slots[(at + 1) % slots.length] ?? null;
|
|
365
|
+
rebuildQueueRows();
|
|
366
|
+
overlapIndex = -1;
|
|
367
|
+
++drillGen;
|
|
368
|
+
head = queueAndReviews() + rosterText;
|
|
369
|
+
compose();
|
|
370
|
+
session.paneCache = base;
|
|
371
|
+
session.paneScroll = 0;
|
|
372
|
+
session.requestRender();
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
if (key.ctrl && key.name === "n" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
376
|
+
const overlaps = lastOverlaps && roleScope !== null ? scopeChangeOverlaps(lastOverlaps, [roleScope]) : lastOverlaps;
|
|
377
|
+
if (overlaps?.state !== "ready" || overlaps.overlaps.length === 0) {
|
|
378
|
+
session.flashMessage("no overlapping files to step through");
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
if (++overlapIndex >= overlaps.overlaps.length) {
|
|
382
|
+
overlapIndex = -1;
|
|
383
|
+
++drillGen;
|
|
384
|
+
session.paneCache = base;
|
|
385
|
+
session.paneScroll = 0;
|
|
386
|
+
session.requestRender();
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
const { path, roles } = overlaps.overlaps[overlapIndex];
|
|
390
|
+
const heading = overlapDrillHeading(overlapIndex, overlaps.overlaps.length, path);
|
|
391
|
+
session.paneCache = heading + "\n\nloading…";
|
|
392
|
+
session.paneScroll = 0;
|
|
393
|
+
session.requestRender();
|
|
394
|
+
const gen = ++drillGen;
|
|
395
|
+
const refresh0 = refreshGen;
|
|
396
|
+
const current = () => gen === drillGen && refresh0 === refreshGen && session.view === session.roleIds.length + 5;
|
|
397
|
+
void Promise.all(roles.map((r) => session.collectChange(session.root, r, { path }))).then((views) => {
|
|
398
|
+
if (!current())
|
|
399
|
+
return;
|
|
400
|
+
session.paneCache = heading + "\n\n" + renderRoleChanges({ mainBranch: overlaps.mainBranch, roles: views });
|
|
401
|
+
session.requestRender();
|
|
402
|
+
}, (err) => {
|
|
403
|
+
if (!current())
|
|
404
|
+
return;
|
|
405
|
+
session.flashError(err);
|
|
406
|
+
session.requestRender();
|
|
407
|
+
});
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
const arrow = arrowDir(key.name);
|
|
411
|
+
if (session.view === session.roleIds.length + 5 && arrow !== null && promptLineFree(ctx.es)) {
|
|
412
|
+
// In the Pending pane, up/down select a queued merge row in full (wrapping, the backlog
|
|
413
|
+
// pane's rule) instead of editing the prompt; an empty queue has nothing to select, so
|
|
414
|
+
// moveEntrySelection clears the selection. The selected detail shares paneScroll with the
|
|
415
|
+
// list, so a newly opened row must start at its head, or it renders scrolled into the
|
|
416
|
+
// middle of the previous row's body.
|
|
417
|
+
selectedQueue = moveEntrySelection(queueRows.length, selectedQueue, arrow);
|
|
418
|
+
const selRow = selectedQueue === null ? undefined : queueRows[selectedQueue];
|
|
419
|
+
queueDetail = selRow ? rowDetail(selRow) : "";
|
|
420
|
+
session.paneScroll = 0;
|
|
421
|
+
session.requestRender();
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (key.name === "escape" && session.view === session.roleIds.length + 5 && selectedQueue !== null && promptLineFree(ctx.es)) {
|
|
425
|
+
// Esc on the Pending pane returns it to list mode; the cached list is untouched (nothing
|
|
426
|
+
// was cancelled), so the pane shows the same renderMergeQueue it entered with. The shared
|
|
427
|
+
// paneScroll resets with the body switch, like a selection move.
|
|
428
|
+
selectedQueue = null;
|
|
429
|
+
queueDetail = "";
|
|
430
|
+
session.paneScroll = 0;
|
|
431
|
+
session.requestRender();
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
if (key.ctrl && key.name === "x" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
435
|
+
const row = selectedRow();
|
|
436
|
+
if (row === null)
|
|
437
|
+
return true;
|
|
438
|
+
if (row.kind === "review") {
|
|
439
|
+
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
440
|
+
session.requestRender();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
if (session.armedQueue?.sha === row.entry.sha && session.armedQueue?.position === row.position) {
|
|
444
|
+
// Second press on the same entry: re-read the queue, guard the in-flight race, then
|
|
445
|
+
// remove it through the CLI's own core (`tumwater queue --cancel <n>`), so the pane,
|
|
446
|
+
// the CLI, and the dashboard cancel cannot drift.
|
|
447
|
+
const confirm = async () => {
|
|
448
|
+
if (queueShiftedAway(row))
|
|
449
|
+
return;
|
|
450
|
+
if (guardMergeInFlight(row))
|
|
451
|
+
return;
|
|
452
|
+
const outcome = await cancelQueuedMerge(session.root, row.position);
|
|
453
|
+
if (outcome.status === "cancelled") {
|
|
454
|
+
selectedQueue = null;
|
|
455
|
+
queueDetail = "";
|
|
456
|
+
session.flashMessage(`cancelled queue entry ${row.position}: ${roleLabel(outcome.role)} ${shortSha(outcome.sha)} — ${outcome.summary}`);
|
|
457
|
+
refresh();
|
|
458
|
+
session.requestRender();
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
selectedQueue = null;
|
|
462
|
+
queueDetail = "";
|
|
463
|
+
session.flashMessage("that merge already left the queue");
|
|
464
|
+
refresh();
|
|
465
|
+
session.requestRender();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
void confirm();
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
session.queueArmed = { position: row.position, sha: row.entry.sha };
|
|
472
|
+
session.flashMessage(`press Ctrl+X again to cancel merge #${row.position} — any other key keeps it`);
|
|
473
|
+
session.requestRender();
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
if (key.ctrl && key.name === "a" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
478
|
+
// Ctrl+A on the Pending pane accepts the selected post-merge review unreviewed (the
|
|
479
|
+
// terminal twin of `tumwater reviews --accept <n>`), once selected and twice pressed
|
|
480
|
+
// like the queue's Ctrl+X cancel. A merge row names its own action instead; an unarmed
|
|
481
|
+
// press sets reviewArmed, and the second press re-reads the queue and settles the entry
|
|
482
|
+
// through the CLI's own core, so the pane and the CLI cannot drift.
|
|
483
|
+
const review = selectedRow();
|
|
484
|
+
if (review === null)
|
|
485
|
+
return true;
|
|
486
|
+
if (review.kind === "merge") {
|
|
487
|
+
session.flashMessage("that row is a queued merge — Ctrl+X cancels it");
|
|
488
|
+
session.requestRender();
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
if (session.armedReview?.position === review.position && session.armedReview?.commit === review.entry.commit) {
|
|
492
|
+
const confirm = async () => {
|
|
493
|
+
const entries = readPostReviews(session.root);
|
|
494
|
+
if (entries[review.position - 1]?.commit !== review.entry.commit) {
|
|
495
|
+
session.flashMessage("the review queue changed — try again");
|
|
496
|
+
refresh();
|
|
497
|
+
session.requestRender();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const outcome = acceptPostReview(session.root, review.position);
|
|
501
|
+
if (outcome.status === "accepted") {
|
|
502
|
+
selectedQueue = null;
|
|
503
|
+
queueDetail = "";
|
|
504
|
+
session.flashMessage(`accepted post-merge review of ${shortSha(outcome.commit)} unreviewed — ${outcome.summary}`);
|
|
505
|
+
refresh();
|
|
506
|
+
session.requestRender();
|
|
507
|
+
}
|
|
508
|
+
else if (outcome.status === "settled") {
|
|
509
|
+
session.flashMessage(`review #${review.position} already merged its fix on main — nothing to accept`);
|
|
510
|
+
refresh();
|
|
511
|
+
session.requestRender();
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
session.flashMessage("the review queue changed — try again");
|
|
515
|
+
refresh();
|
|
516
|
+
session.requestRender();
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
void confirm();
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
session.reviewArmed = { position: review.position, commit: review.entry.commit };
|
|
523
|
+
session.flashMessage(`press Ctrl+A again to accept review #${review.position} — any other key keeps it`);
|
|
524
|
+
session.requestRender();
|
|
525
|
+
}
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
if (key.ctrl && key.name === "v" && promptLineFree(ctx.es) && session.view === session.roleIds.length + 5) {
|
|
529
|
+
// Move the selected merge to the head of the queue — the pane's twin of `tumwater
|
|
530
|
+
// queue --top <n>`, through the same core the CLI and the dashboard use, so the three
|
|
531
|
+
// cannot drift on the rename or the already-first/out-of-range outcomes. A single
|
|
532
|
+
// keypress is enough because the move is reversible and non-destructive (unlike
|
|
533
|
+
// Ctrl+X's two-step cancel arm).
|
|
534
|
+
const row = selectedRow();
|
|
535
|
+
if (row === null)
|
|
536
|
+
return true;
|
|
537
|
+
if (row.kind === "review") {
|
|
538
|
+
session.flashMessage("that row is a post-merge review — Ctrl+A accepts it");
|
|
539
|
+
session.requestRender();
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
const top = async () => {
|
|
543
|
+
if (row.position === 1) {
|
|
544
|
+
session.flashMessage("merge #1 is already at the front");
|
|
545
|
+
session.requestRender();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (queueShiftedAway(row))
|
|
549
|
+
return;
|
|
550
|
+
if (guardMergeInFlight(row))
|
|
551
|
+
return;
|
|
552
|
+
const outcome = moveQueuedMergeToTop(session.root, row.position);
|
|
553
|
+
if (outcome.status === "topped") {
|
|
554
|
+
selectedQueue = null;
|
|
555
|
+
queueDetail = "";
|
|
556
|
+
session.flashMessage(`moved merge #${row.position} to the front: ${roleLabel(outcome.role)} ${shortSha(outcome.sha)} — ${outcome.summary}`);
|
|
557
|
+
refresh();
|
|
558
|
+
session.requestRender();
|
|
559
|
+
}
|
|
560
|
+
else if (outcome.status === "already-first") {
|
|
561
|
+
session.flashMessage(`merge #${row.position} is already at the front`);
|
|
562
|
+
refresh();
|
|
563
|
+
session.requestRender();
|
|
564
|
+
}
|
|
565
|
+
else if (outcome.status === "out-of-range") {
|
|
566
|
+
session.flashMessage("the merge queue changed — try again");
|
|
567
|
+
refresh();
|
|
568
|
+
session.requestRender();
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
session.flashMessage("that merge already left the queue");
|
|
572
|
+
refresh();
|
|
573
|
+
session.requestRender();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
void top();
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
60
579
|
return false;
|
|
61
580
|
};
|
|
62
581
|
return {
|
|
@@ -67,8 +586,13 @@ export function createPendingPane(session) {
|
|
|
67
586
|
refresh();
|
|
68
587
|
return true;
|
|
69
588
|
},
|
|
589
|
+
reset() {
|
|
590
|
+
// Leaving a view drops any queued-merge selection; re-entering starts at the list.
|
|
591
|
+
selectedQueue = null;
|
|
592
|
+
queueDetail = "";
|
|
593
|
+
},
|
|
70
594
|
state() {
|
|
71
|
-
return {};
|
|
595
|
+
return { selectedQueue, queueDetail };
|
|
72
596
|
},
|
|
73
597
|
};
|
|
74
598
|
}
|