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,12 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
/** The dashboard's Pending view, browser-side: every loop's unmerged change as /api/diff
|
|
1
|
+
/** The dashboard's Pending view, browser-side: every role's unmerged change as /api/diff
|
|
3
2
|
* serves it (the same document `tumwater diff` prints from the shared change collectors). The
|
|
4
3
|
* roster shows each role's branch, state, commits ahead of main with their subjects, and its
|
|
5
|
-
* uncommitted-file count; a row opens that
|
|
4
|
+
* uncommitted-file count; a row opens that role's drawer, whose Pending change section fetches
|
|
6
5
|
* the full patch (`/api/diff?role=<id>`). It refetches when the view opens and when its
|
|
7
|
-
* Refresh button is pressed — never on a timer of its own.
|
|
8
|
-
*
|
|
6
|
+
* Refresh button is pressed — never on a timer of its own. It also paints the overlapping-files
|
|
7
|
+
* card from `/api/diff?overlaps=1`, with Role and Folder pickers that narrow it client-side,
|
|
8
|
+
* and the Merge queue card from the status payload, with a Role picker that keeps only the
|
|
9
|
+
* chosen role's waiting merges under their whole-queue positions (the tumwater queue --role
|
|
10
|
+
* twin) and a per-row Move to top / two-click Cancel pair on every non-first entry, and the
|
|
11
|
+
* Post-merge reviews card from the status payload, with a Role picker that keeps only
|
|
12
|
+
* the chosen role's entries (`tumwater reviews --role` twin). Spliced
|
|
13
|
+
* into gui-client.ts's script, reaching its helpers (esc, getJson, icon,
|
|
14
|
+
* errorPanel) through that concatenation. */
|
|
9
15
|
export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
16
|
+
let pendingOverlaps = null;
|
|
17
|
+
let pendingOverlapOpen = null;
|
|
18
|
+
let pendingOverlapRole = "";
|
|
19
|
+
let pendingOverlapDir = "";
|
|
20
|
+
let pendingQueueRole = "";
|
|
21
|
+
let pendingReviewRole = "";
|
|
22
|
+
let pendingReviewState = "";
|
|
23
|
+
let pendingCancelSha = "";
|
|
24
|
+
// The Merge queue card's per-row cancel, armed by a first click and fired by a second within
|
|
25
|
+
// 5 s — the same confirmSlot two-click factory the drawer's note-clear/entry-cancel use, so a
|
|
26
|
+
// stray click cannot remove an entry. Repaints the card (an armed row reads Confirm cancel).
|
|
27
|
+
const queueCancelSlot = confirmSlot(() => renderPendingQueue(lastStatus));
|
|
28
|
+
let pendingAcceptCommit = "";
|
|
29
|
+
// The Post-merge reviews card's per-row accept, armed by a first click and fired by a second
|
|
30
|
+
// within 5 s — the same confirmSlot two-click factory, so a stray click cannot accept an
|
|
31
|
+
// entry unreviewed. Repaints the card (an armed row reads Confirm accept).
|
|
32
|
+
const reviewAcceptSlot = confirmSlot(() => renderPendingReviews(lastStatus));
|
|
10
33
|
// pending-table:start
|
|
11
34
|
// One role's state cell: the collector's three states, with "ready" split into pending vs
|
|
12
35
|
// idle work. Never colors a degraded state as an active one.
|
|
@@ -30,7 +53,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
30
53
|
return "<span class='num' title='" + esc(files.join(", ")) + "'>" + esc(plural(files.length, "file")) + "</span>";
|
|
31
54
|
}
|
|
32
55
|
// The Pending roster: one clickable row per known role (the collector's config order), each
|
|
33
|
-
// opening that
|
|
56
|
+
// opening that role's drawer. Pure — the view paints what this returns.
|
|
34
57
|
function pendingTableHtml(team) {
|
|
35
58
|
if (!team || !Array.isArray(team.roles) || team.roles.length === 0)
|
|
36
59
|
return "<div class='empty'><strong>No roles yet</strong>Roles appear here once the team is configured.</div>";
|
|
@@ -44,37 +67,268 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
44
67
|
"<td>" + pendingAheadHtml(r) + "</td>" +
|
|
45
68
|
"<td class='num'>" + pendingDirtyHtml(r) + "</td></tr>",
|
|
46
69
|
).join("");
|
|
47
|
-
return "<div class='table-wrap'><table class='table pending'><thead><tr><th class='c-
|
|
70
|
+
return "<div class='table-wrap'><table class='table pending'><thead><tr><th class='c-role'>Role</th><th>Branch</th>" +
|
|
48
71
|
"<th>State</th><th>Ahead of " + esc(team.mainBranch) + "</th><th class='c-dirty num'>Uncommitted</th></tr></thead><tbody>" + rows + "</tbody></table></div>";
|
|
49
72
|
}
|
|
73
|
+
// One queued merge's line: the 1-based whole-queue position, role, run, short sha, age, an
|
|
74
|
+
// optional merge label, and summary. Shared by the Merge queue card and the role drawer so
|
|
75
|
+
// the two cannot drift; a malformed entry renders stand-ins rather than undefined/NaN.
|
|
76
|
+
function queueEntryLineHtml(e, position, now, label, cancel, top) {
|
|
77
|
+
const sec = Number.isFinite(e.enqueuedAt) ? Math.max(0, Math.round((now - e.enqueuedAt) / 1000)) : 0;
|
|
78
|
+
const run = Number.isFinite(e.run) ? e.run : "?";
|
|
79
|
+
const note = Number.isFinite(e.revisionRound) ? " · revision " + e.revisionRound + (Number.isFinite(e.priorObjections) && e.priorObjections > 0 ? " · " + plural(e.priorObjections, "prior objection") : "") : "";
|
|
80
|
+
const retry = typeof e.retry === "string" ? " · re-queued after " + e.retry + (Number.isFinite(e.retryCount) && e.retryCount > 1 ? " · ×" + e.retryCount : "") + (Number.isFinite(e.notBefore) && e.notBefore > now ? " · eligible in " + humanSeconds(Math.max(0, Math.round((e.notBefore - now) / 1000))) : "") : "";
|
|
81
|
+
let line = "<div class='mono clamp1' style='padding:3px 0'>" + position + ". " + esc(e.role ? roleLabel(e.role) : "?") +
|
|
82
|
+
" run #" + esc(run) + " · " + esc(shortSha(e.sha)) + " · " + esc(humanSeconds(sec)) +
|
|
83
|
+
" ago" + (label && typeof label === "string" ? " · [" + esc(label) + "]" : "") +
|
|
84
|
+
(e.highFriction === true ? " · high friction" : "") +
|
|
85
|
+
retry +
|
|
86
|
+
note +
|
|
87
|
+
" · " + esc(e.summary || "—") + "</div>";
|
|
88
|
+
// A reorder button (Move to top) on every non-first row of the Merge queue card, before
|
|
89
|
+
// the two-click cancel: the move is reversible and non-destructive, so a single click is
|
|
90
|
+
// enough (unlike Cancel's two-click arm). The drawer (this role's merges) passes no top,
|
|
91
|
+
// so its rows carry no reorder.
|
|
92
|
+
if (top && position > 1 && typeof e.sha === "string") {
|
|
93
|
+
line = "<button type='button' class='btn btn-sm' data-top='" + esc(e.sha) + "'>Move to top</button>" + line;
|
|
94
|
+
}
|
|
95
|
+
// A two-click cancel: armed by the first click, fired by the second within 5 s (the
|
|
96
|
+
// confirmSlot guard), so a stray click cannot remove an entry. A malformed entry carries
|
|
97
|
+
// no sha, so it renders no button.
|
|
98
|
+
if (cancel && typeof cancel.sha === "string") {
|
|
99
|
+
return line + "<button type='button' class='btn btn-sm' data-cancel='" + esc(cancel.sha) + "'>" + (cancel.armed ? "Confirm cancel" : "Cancel") + "</button>";
|
|
100
|
+
}
|
|
101
|
+
return line;
|
|
102
|
+
}
|
|
103
|
+
// The todo/queued merge entries of a status merge queue document, or [] when the document
|
|
104
|
+
// is null/absent or its entries are missing or malformed — so callers never guard the shape.
|
|
105
|
+
function mergeQueueEntries(mergeQueue) {
|
|
106
|
+
return mergeQueue && Array.isArray(mergeQueue.entries) ? mergeQueue.entries : [];
|
|
107
|
+
}
|
|
108
|
+
// The distinct role names a list of queue/review entries mentions (each entry's role when
|
|
109
|
+
// present; null/undefined entries yield nothing), as a Set for .has() picker checks. Every
|
|
110
|
+
// role picker here — the Merge queue card, the Post-merge reviews card, and the drawer's
|
|
111
|
+
// section — derives it, so one definition of "the roles present in this list" cannot drift.
|
|
112
|
+
// Sort into a checked array where a stable picker order is wanted:
|
|
113
|
+
// [...presentRoles(e)].sort().
|
|
114
|
+
function presentRoles(entries) {
|
|
115
|
+
return new Set(entries.map((e) => e && e.role).filter((r) => typeof r === "string"));
|
|
116
|
+
}
|
|
117
|
+
// The role drawer's Merge queue section: only this role's waiting merges, each under its
|
|
118
|
+
// whole-queue position (labels run parallel to the whole queue). "" when it has none.
|
|
119
|
+
function drawerQueueHtml(mergeQueue, role, now, labels) {
|
|
120
|
+
const entries = mergeQueueEntries(mergeQueue);
|
|
121
|
+
let kept = 0;
|
|
122
|
+
const lines = entries.map((e, i) => {
|
|
123
|
+
if (!e || e.role !== role) return "";
|
|
124
|
+
kept++;
|
|
125
|
+
return queueEntryLineHtml(e, i + 1, now, Array.isArray(labels) ? labels[i] : undefined);
|
|
126
|
+
}).join("");
|
|
127
|
+
if (!kept) return "";
|
|
128
|
+
return "<section><div class='sec-head'><h3>Merge queue</h3><span class='muted' style='font-size:13px'>" +
|
|
129
|
+
esc(kept + " of " + entries.length + " waiting") + "</span></div><div style='padding:4px 0'>" + lines + "</div></section>";
|
|
130
|
+
}
|
|
50
131
|
// The Merge queue card's body: every queued merge in execution order (oldest first), one
|
|
51
|
-
// line each mirroring tumwater queue's renderMergeQueue — 1-based position,
|
|
132
|
+
// line each mirroring tumwater queue's renderMergeQueue — 1-based position, role, run,
|
|
52
133
|
// short sha, age, summary — so the page and the CLI cannot drift. Painted from the status
|
|
53
134
|
// payload the page already polls; depth counts the unreadable queue files the status reader
|
|
54
135
|
// skips, so a positive depth with no readable list states the count instead of inventing rows.
|
|
55
136
|
// Pure: the age comes from the passed now, and a malformed entry renders a stand-in rather
|
|
56
|
-
// than undefined/NaN. Null/absent/zero-depth reads as empty.
|
|
57
|
-
|
|
137
|
+
// than undefined/NaN. Null/absent/zero-depth reads as empty. Its Role picker keeps only the
|
|
138
|
+
// chosen role's waiting merges, each under its position in the whole queue (the
|
|
139
|
+
// tumwater queue --role twin); "All roles" (the default) shows every line.
|
|
140
|
+
function pendingQueueHtml(mergeQueue, now, labels, role = "", cancelArmedSha = "") {
|
|
58
141
|
const depth = mergeQueue && Number.isFinite(mergeQueue.depth) ? mergeQueue.depth : 0;
|
|
59
|
-
const entries =
|
|
142
|
+
const entries = mergeQueueEntries(mergeQueue);
|
|
143
|
+
const roles = [...presentRoles(entries)].sort();
|
|
144
|
+
const active = role && roles.includes(role) ? role : "";
|
|
145
|
+
let head = "<div class='card-head'><h2>Merge queue</h2></div>";
|
|
60
146
|
let body;
|
|
61
147
|
if (depth === 0) {
|
|
62
148
|
body = "<div class='empty'>Merge queue empty</div>";
|
|
63
149
|
} else if (entries.length === 0) {
|
|
64
150
|
body = "<div class='empty'>merge queue: " + esc(depth) + " waiting</div>";
|
|
65
151
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
152
|
+
if (roles.length) {
|
|
153
|
+
head = "<div class='card-head'><h2>Merge queue</h2><label><span class='sr-only'>Role</span><select class='field' id='pendingqueuerole'><option value=''>All roles</option>" +
|
|
154
|
+
roles.map((id) => "<option value='" + esc(id) + "'" + (id === active ? " selected" : "") + ">" + esc(roleLabel(id)) + "</option>").join("") + "</select></label></div>";
|
|
155
|
+
}
|
|
156
|
+
body = "<div style='padding:10px 16px 12px'>" + entries.map((e, i) => ({ e, i })).filter(({ e }) => !active || (e && e.role === active)).map(({ e, i }) =>
|
|
157
|
+
queueEntryLineHtml(e, i + 1, now, Array.isArray(labels) ? labels[i] : undefined, { sha: e && e.sha, armed: e && e.sha === cancelArmedSha }, true)).join("") + "</div>";
|
|
158
|
+
}
|
|
159
|
+
return head + body;
|
|
160
|
+
}
|
|
161
|
+
// The Overlapping files card: one "path: role, role" line per file two or more pending changes
|
|
162
|
+
// touch (tumwater diff --overlaps). Pure; a malformed document reads as no overlaps. Its Role
|
|
163
|
+
// picker keeps only the lines the chosen role holds (diff --overlaps --role), each line still
|
|
164
|
+
// naming every holder and keeping its index in the unfiltered list. Its Folder picker keeps only
|
|
165
|
+
// the lines at or under the chosen directory (diff --overlaps --path).
|
|
166
|
+
// The expanded detail under one overlap line: each holding role's path-scoped change, or its
|
|
167
|
+
// loading / error row. Pure.
|
|
168
|
+
function overlapDetailHtml(rows) {
|
|
169
|
+
return (Array.isArray(rows) ? rows : []).map((r) => {
|
|
170
|
+
let body;
|
|
171
|
+
if (r.state === "loading") body = "<p class='muted'>Loading…</p>";
|
|
172
|
+
else if (r.state === "error") body = "<p class='muted'>" + esc(r.message || "Pending change unavailable") + "</p>";
|
|
173
|
+
else body = drawerChangeHtml(r.view);
|
|
174
|
+
return "<h3 class='mono'>" + esc(roleLabel(r.role)) + "</h3>" + body;
|
|
175
|
+
}).join("");
|
|
176
|
+
}
|
|
177
|
+
// The sorted, de-duplicated role ids any overlap names; empty for a malformed document.
|
|
178
|
+
function pendingOverlapHolders(view) {
|
|
179
|
+
if (!view || typeof view !== "object" || !Array.isArray(view.overlaps)) return [];
|
|
180
|
+
const ids = new Set();
|
|
181
|
+
for (const o of view.overlaps) {
|
|
182
|
+
if (o && Array.isArray(o.roles)) for (const id of o.roles) if (typeof id === "string") ids.add(id);
|
|
76
183
|
}
|
|
77
|
-
return
|
|
184
|
+
return [...ids].sort();
|
|
185
|
+
}
|
|
186
|
+
// The sorted, de-duplicated directories holding any overlap path; empty for a malformed
|
|
187
|
+
// document. A trailing-slash path (an untracked directory) also adds itself.
|
|
188
|
+
function pendingOverlapDirs(view) {
|
|
189
|
+
if (!view || typeof view !== "object" || !Array.isArray(view.overlaps)) return [];
|
|
190
|
+
const dirs = new Set();
|
|
191
|
+
for (const o of view.overlaps) {
|
|
192
|
+
if (!o || typeof o.path !== "string") continue;
|
|
193
|
+
const dir = o.path.endsWith("/");
|
|
194
|
+
const parts = (dir ? o.path.slice(0, -1) : o.path).split("/");
|
|
195
|
+
const n = dir ? parts.length : parts.length - 1;
|
|
196
|
+
for (let k = 1; k <= n; k++) dirs.add(parts.slice(0, k).join("/"));
|
|
197
|
+
}
|
|
198
|
+
return [...dirs].sort();
|
|
199
|
+
}
|
|
200
|
+
function pendingOverlapsHtml(view, open, role, dir) {
|
|
201
|
+
const ok = view && typeof view === "object";
|
|
202
|
+
let body;
|
|
203
|
+
let head = "<div class='card-head'><h2>Overlapping files</h2></div>";
|
|
204
|
+
if (ok && view.state === "no-base") {
|
|
205
|
+
body = "<div class='empty'>Main branch " + esc(view.mainBranch) + " does not exist</div>";
|
|
206
|
+
} else if (!ok || !Array.isArray(view.overlaps) || view.overlaps.length === 0) {
|
|
207
|
+
body = "<div class='empty'>No file is touched by two pending changes</div>";
|
|
208
|
+
} else {
|
|
209
|
+
const holders = pendingOverlapHolders(view);
|
|
210
|
+
const active = role && holders.includes(role) ? role : "";
|
|
211
|
+
const dirs = pendingOverlapDirs(view);
|
|
212
|
+
const activeDir = dir && dirs.includes(dir) ? dir : "";
|
|
213
|
+
head = "<div class='card-head'><h2>Overlapping files</h2><label><span class='sr-only'>Role</span><select class='field' id='pendingoverlaprole'><option value=''>All roles</option>" +
|
|
214
|
+
holders.map((id) => "<option value='" + esc(id) + "'" + (id === active ? " selected" : "") + ">" + esc(roleLabel(id)) + "</option>").join("") + "</select></label>" +
|
|
215
|
+
(dirs.length ? "<label><span class='sr-only'>Folder</span><select class='field' id='pendingoverlapdir'><option value=''>All folders</option>" +
|
|
216
|
+
dirs.map((d) => "<option value='" + esc(d) + "'" + (d === activeDir ? " selected" : "") + ">" + esc(d + "/") + "</option>").join("") + "</select></label>" : "") + "</div>";
|
|
217
|
+
const lines = view.overlaps.map((o, i) => {
|
|
218
|
+
const roles = o && Array.isArray(o.roles) ? o.roles : [];
|
|
219
|
+
if (active && !roles.includes(active)) return "";
|
|
220
|
+
const path = o && typeof o.path === "string" ? o.path : "?";
|
|
221
|
+
if (activeDir && !path.startsWith(activeDir + "/")) return "";
|
|
222
|
+
const line = "<button type='button' class='li-open mono clamp1' data-overlap='" + i + "'>" + esc(path) + ": " + esc(roles.map(roleLabel).join(", ")) + "</button>";
|
|
223
|
+
return open && open.path === path ? line + "<div class='overlap-detail'>" + overlapDetailHtml(open.rows) + "</div>" : line;
|
|
224
|
+
}).join("");
|
|
225
|
+
body = lines ? "<div style='padding:10px 16px 12px'>" + lines + "</div>" : "<div class='empty'>No overlapping file matches the filters</div>";
|
|
226
|
+
}
|
|
227
|
+
return head + body;
|
|
228
|
+
}
|
|
229
|
+
// The Post-merge reviews card: every well-formed entry in .tumwater/state/post-review.json
|
|
230
|
+
// in FIFO order, one line each mirroring 'tumwater reviews' renderPostReviews — role, short
|
|
231
|
+
// commit, summary, and the status suffix from the browser twin of postReviewStatusLabel — so
|
|
232
|
+
// the page and the CLI cannot drift on order or wording. Painted from the status payload the
|
|
233
|
+
// page already polls. Empty (or a missing/malformed list) shows the empty line. A malformed
|
|
234
|
+
// entry renders a stand-in rather than undefined/NaN.
|
|
235
|
+
// The status suffix — the browser twin of postReviewStatusLabel
|
|
236
|
+
// (src/review/post-merge-review-render.ts):
|
|
237
|
+
// the reviewer's recorded rejected reasons when 'rejected' is set, else the
|
|
238
|
+
// verdict-less failed-review-run count when positive, else "". Hand-kept copy because the
|
|
239
|
+
// script cannot import the server renderer; pinned by the client test against the CLI wording.
|
|
240
|
+
const squashText = (s, max) => {
|
|
241
|
+
const t = String(s).replace(/\s+/g, " ").trim();
|
|
242
|
+
if (t.length <= max) return t;
|
|
243
|
+
return t.slice(0, max - 1).trimEnd() + "…";
|
|
244
|
+
};
|
|
245
|
+
// The states the Post-merge reviews card's State picker lists, in the order 'tumwater reviews
|
|
246
|
+
// --status' names them — the browser twin of POST_REVIEW_STATES
|
|
247
|
+
// (src/review/post-merge-review-render.ts), so the page and the CLI cannot drift on what a
|
|
248
|
+
// state means. Hand-kept because the inline script cannot import the server renderer.
|
|
249
|
+
const POST_REVIEW_STATE_ORDER = ["waiting", "failing", "rejected", "fixed"];
|
|
250
|
+
// One post-merge review entry's state — the browser twin of the server's 'postReviewState'
|
|
251
|
+
// (src/review/post-merge-review-render.ts), literal in its 'rejected' branch: 'fixed' when
|
|
252
|
+
// the repair already merged (the terminal Forward-never-back 1a/6 record — naming both shas,
|
|
253
|
+
// it is not pending anything), 'rejected' when 'rejected' is set (an empty array still
|
|
254
|
+
// counts; statusPayload's entries come from readPostReviews, which only ever stores an array
|
|
255
|
+
// or leaves it off), else 'failing' when the review runs keep ending without a verdict
|
|
256
|
+
// ('failures' positive), else 'waiting'. The precedence matches postReviewStatusLabel and
|
|
257
|
+
// 'tumwater reviews --status'; pinned against the server predicate by test so the browser and
|
|
258
|
+
// the server cannot disagree on what fixed/rejected/failing/waiting mean.
|
|
259
|
+
function postReviewState(e) {
|
|
260
|
+
if (e && e.fixed !== undefined) return "fixed";
|
|
261
|
+
if (e && e.rejected !== undefined) return "rejected";
|
|
262
|
+
if (e && typeof e.failures === "number" && e.failures > 0) return "failing";
|
|
263
|
+
return "waiting";
|
|
264
|
+
}
|
|
265
|
+
function postReviewStatusLabel(e) {
|
|
266
|
+
switch (postReviewState(e)) {
|
|
267
|
+
case "fixed":
|
|
268
|
+
return "[fixed — the repair " + shortSha(e.fixed) + " already merged on main]";
|
|
269
|
+
case "rejected":
|
|
270
|
+
return "[rejected — " + e.rejected.map((r) => squashText(r, 60)).join("; ") + "]";
|
|
271
|
+
case "failing":
|
|
272
|
+
if (e && typeof e.lastFailure === "string" && e.lastFailure.length > 0) {
|
|
273
|
+
return "[" + plural(e.failures, "failed review run") + " — last: " + squashText(e.lastFailure, 80) + "]";
|
|
274
|
+
}
|
|
275
|
+
return "[" + plural(e.failures, "failed review run") + "]";
|
|
276
|
+
default:
|
|
277
|
+
return "";
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function postReviewsHtml(entries, armed = "", role = "", state = "", now = 0) {
|
|
281
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
282
|
+
return "<div class='card-head'><h2>Post-merge reviews</h2></div><div class='empty'>No merged commit is waiting for a post-merge review</div>";
|
|
283
|
+
}
|
|
284
|
+
const roles = [...presentRoles(entries)].sort();
|
|
285
|
+
const active = role && roles.includes(role) ? role : "";
|
|
286
|
+
const present = new Set(entries.map(postReviewState));
|
|
287
|
+
const states = POST_REVIEW_STATE_ORDER.filter((s) => present.has(s));
|
|
288
|
+
const activeState = state && states.includes(state) ? state : "";
|
|
289
|
+
let head = "<div class='card-head'><h2>Post-merge reviews</h2></div>";
|
|
290
|
+
if (roles.length) {
|
|
291
|
+
head = "<div class='card-head'><h2>Post-merge reviews</h2><label><span class='sr-only'>Role</span><select class='field' id='pendingreviewrole'><option value=''>All roles</option>" +
|
|
292
|
+
roles.map((id) => "<option value='" + esc(id) + "'" + (id === active ? " selected" : "") + ">" + esc(roleLabel(id)) + "</option>").join("") + "</select></label>" +
|
|
293
|
+
(states.length ? "<label><span class='sr-only'>State</span><select class='field' id='pendingreviewstate'><option value=''>All states</option>" +
|
|
294
|
+
states.map((s) => "<option value='" + s + "'" + (s === activeState ? " selected" : "") + ">" + s + "</option>").join("") + "</select></label>" : "") + "</div>";
|
|
295
|
+
}
|
|
296
|
+
const kept = entries.filter((e) => (!active || (e && e.role === active)) && (!activeState || postReviewState(e) === activeState));
|
|
297
|
+
const lines = kept.map((e) => {
|
|
298
|
+
const label = postReviewStatusLabel(e);
|
|
299
|
+
const age = typeof e.enqueuedAt === "number" && Number.isFinite(now)
|
|
300
|
+
? " · " + esc(humanSeconds(Math.max(0, Math.round((now - e.enqueuedAt) / 1000)))) + " ago"
|
|
301
|
+
: "";
|
|
302
|
+
let line = "<div class='queue-entry mono clamp1'>" + esc(e.role ? roleLabel(e.role) : "?") + " " + esc(shortSha(e.commit)) +
|
|
303
|
+
age +
|
|
304
|
+
" · " + esc(e.summary || "—") +
|
|
305
|
+
(label ? " · <span class='mono'>" + esc(label) + "</span>" : "") + "</div>";
|
|
306
|
+
// A two-click Accept on every well-formed entry not already settled fixed (the server
|
|
307
|
+
// refuses a fixed entry, so it renders no button): armed by the first
|
|
308
|
+
// click, fired by the second within 5 s (the confirmSlot guard), so a stray click cannot
|
|
309
|
+
// accept an entry unreviewed. Threaded the way pendingQueueHtml threads cancelArmedSha.
|
|
310
|
+
if (e && e.fixed === undefined && typeof e.commit === "string") {
|
|
311
|
+
line += "<button type='button' class='btn btn-sm' data-accept='" + esc(e.commit) + "'>" + (armed === e.commit ? "Confirm accept" : "Accept") + "</button>";
|
|
312
|
+
}
|
|
313
|
+
return line;
|
|
314
|
+
}).join("");
|
|
315
|
+
if (kept.length === 0) {
|
|
316
|
+
return head + "<div class='queue-head'>post-merge reviews (0 of " + entries.length + "):</div><div class='empty'>No post-merge review matches the filters</div>";
|
|
317
|
+
}
|
|
318
|
+
return head +
|
|
319
|
+
"<div class='queue-head'>post-merge reviews (" + esc(active || activeState ? kept.length + " of " + entries.length : entries.length) + "):</div>" + lines;
|
|
320
|
+
}
|
|
321
|
+
// Paint the Post-merge reviews card from the polled status payload — the dashboard twin of
|
|
322
|
+
// 'tumwater reviews', driven by the raw postReviews list statusPayload already carries.
|
|
323
|
+
function renderPendingReviews(status) {
|
|
324
|
+
const card = $("pendingreviews");
|
|
325
|
+
if (!card) return;
|
|
326
|
+
const entries = status && Array.isArray(status.postReviews) ? status.postReviews : [];
|
|
327
|
+
const roles = presentRoles(entries);
|
|
328
|
+
if (!roles.has(pendingReviewRole)) pendingReviewRole = "";
|
|
329
|
+
const states = new Set(entries.map(postReviewState));
|
|
330
|
+
if (!states.has(pendingReviewState)) pendingReviewState = "";
|
|
331
|
+
card.innerHTML = postReviewsHtml(entries, reviewAcceptSlot.armed(pendingAcceptCommit) ? pendingAcceptCommit : "", pendingReviewRole, pendingReviewState, Date.now());
|
|
78
332
|
}
|
|
79
333
|
// pending-table:end
|
|
80
334
|
// Paint the Merge queue card from the polled status payload, at paint-time age; no fetch of
|
|
@@ -82,19 +336,145 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
82
336
|
function renderPendingQueue(status) {
|
|
83
337
|
const card = $("pendingqueue");
|
|
84
338
|
if (!card) return;
|
|
85
|
-
|
|
339
|
+
const entries = mergeQueueEntries(status && status.mergeQueue);
|
|
340
|
+
const roles = presentRoles(entries);
|
|
341
|
+
if (!roles.has(pendingQueueRole)) pendingQueueRole = "";
|
|
342
|
+
card.innerHTML = pendingQueueHtml(status && status.mergeQueue, Date.now(), status && status.mergeQueueLabels, pendingQueueRole, queueCancelSlot.armed(pendingCancelSha) ? pendingCancelSha : "");
|
|
343
|
+
}
|
|
344
|
+
function paintPendingOverlaps() {
|
|
345
|
+
const card = $("pendingoverlaps");
|
|
346
|
+
if (card) card.innerHTML = pendingOverlapsHtml(pendingOverlaps, pendingOverlapOpen, pendingOverlapRole, pendingOverlapDir);
|
|
347
|
+
}
|
|
348
|
+
// Toggle one overlap line's detail: fetch each holding role's change scoped to the path. A
|
|
349
|
+
// reply is dropped once another line (or a Refresh) replaced the open detail.
|
|
350
|
+
async function togglePendingOverlap(index) {
|
|
351
|
+
const o = pendingOverlaps && Array.isArray(pendingOverlaps.overlaps) ? pendingOverlaps.overlaps[index] : null;
|
|
352
|
+
if (!o || typeof o.path !== "string") return;
|
|
353
|
+
const path = o.path;
|
|
354
|
+
if (pendingOverlapOpen && pendingOverlapOpen.path === path) {
|
|
355
|
+
pendingOverlapOpen = null;
|
|
356
|
+
paintPendingOverlaps();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const roles = Array.isArray(o.roles) ? o.roles : [];
|
|
360
|
+
const open = { path, rows: roles.map((role) => ({ role, state: "loading" })) };
|
|
361
|
+
pendingOverlapOpen = open;
|
|
362
|
+
paintPendingOverlaps();
|
|
363
|
+
await Promise.all(roles.map(async (role, k) => {
|
|
364
|
+
let row;
|
|
365
|
+
try {
|
|
366
|
+
const view = await getJson("/api/diff?role=" + encodeURIComponent(role) + "&path=" + encodeURIComponent(path));
|
|
367
|
+
row = { role, state: "ok", view };
|
|
368
|
+
} catch (err) {
|
|
369
|
+
row = { role, state: "error", message: errMessage(err, "Pending change unavailable") };
|
|
370
|
+
}
|
|
371
|
+
if (pendingOverlapOpen !== open) return;
|
|
372
|
+
open.rows[k] = row;
|
|
373
|
+
paintPendingOverlaps();
|
|
374
|
+
}));
|
|
86
375
|
}
|
|
87
376
|
function buildPendingView() {
|
|
88
377
|
if (!buildOnce("pending", "<div class='view-head'><div><h1>Pending</h1><p>Every role's unmerged change — its branch, commits ahead of main, and uncommitted files. Open a row for the full diff.</p></div>" +
|
|
89
378
|
"<div class='toolbar'><button type='button' class='btn btn-sm' id='pendingrefresh'>" + icon("refresh") + "Refresh</button></div></div>" +
|
|
90
379
|
"<div class='card' id='pendingqueue'><div class='empty'>Loading…</div></div>" +
|
|
380
|
+
"<div class='card' id='pendingreviews'><div class='empty'>Loading…</div></div>" +
|
|
381
|
+
"<div class='card' id='pendingoverlaps'><div class='empty'>Loading…</div></div>" +
|
|
91
382
|
"<div class='card' id='pendingbody'><div class='empty'>Loading…</div></div>")) return;
|
|
92
383
|
$("pendingrefresh").addEventListener("click", () => fetchPending());
|
|
384
|
+
// A Merge queue row's Cancel: the first click on an unarmed sha arms it (repainting the
|
|
385
|
+
// button as Confirm cancel), a second on the armed sha within 5 s posts /api/queue-cancel
|
|
386
|
+
// then clears the arm and refetches so the removed row disappears at once.
|
|
387
|
+
$("pendingqueue").addEventListener("click", async (e) => {
|
|
388
|
+
// A Merge queue row's Move to top: a single click posts /api/queue-top by sha, moving
|
|
389
|
+
// that entry to the head of the queue and refetching (the move is reversible and
|
|
390
|
+
// non-destructive, so it needs no two-step arm).
|
|
391
|
+
const t = e.target.closest("[data-top]");
|
|
392
|
+
if (t) {
|
|
393
|
+
const sha = t.getAttribute("data-top");
|
|
394
|
+
if (sha) {
|
|
395
|
+
await postAction("/api/queue-top", { sha }, (d) => d && d.status === "topped" ? "moved the merge of " + shortSha(d.commit) + " to the front" : d && d.status === "already-first" ? "that merge is already at the front" : "that merge already left the queue");
|
|
396
|
+
fetchPending();
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const b = e.target.closest("[data-cancel]");
|
|
401
|
+
if (!b) return;
|
|
402
|
+
const sha = b.getAttribute("data-cancel");
|
|
403
|
+
if (!sha) return;
|
|
404
|
+
if (queueCancelSlot.armed(sha)) {
|
|
405
|
+
await postAction("/api/queue-cancel", { sha }, (d) => d && d.status === "cancelled" ? "cancelled the merge of " + shortSha(d.commit) : "that merge already left the queue");
|
|
406
|
+
pendingCancelSha = "";
|
|
407
|
+
queueCancelSlot.disarm();
|
|
408
|
+
fetchPending();
|
|
409
|
+
} else {
|
|
410
|
+
pendingCancelSha = sha;
|
|
411
|
+
queueCancelSlot.arm(sha);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// A Post-merge reviews row's Accept: the first click on an unarmed commit arms it
|
|
415
|
+
// (repainting that button as Confirm accept), a second on the armed commit within 5 s posts
|
|
416
|
+
// /api/post-review-accept then clears the arm and refetches so the removed row disappears.
|
|
417
|
+
$("pendingreviews").addEventListener("click", async (e) => {
|
|
418
|
+
const b = e.target.closest("[data-accept]");
|
|
419
|
+
if (!b) return;
|
|
420
|
+
const commit = b.getAttribute("data-accept");
|
|
421
|
+
if (!commit) return;
|
|
422
|
+
if (reviewAcceptSlot.armed(commit)) {
|
|
423
|
+
await postAction("/api/post-review-accept", { sha: commit }, (d) => d && d.status === "accepted" ? "accepted the post-merge review of " + shortSha(d.commit) + " unreviewed — " + d.summary : "that review already left the queue");
|
|
424
|
+
pendingAcceptCommit = "";
|
|
425
|
+
reviewAcceptSlot.disarm();
|
|
426
|
+
fetchPending();
|
|
427
|
+
} else {
|
|
428
|
+
pendingAcceptCommit = commit;
|
|
429
|
+
reviewAcceptSlot.arm(commit);
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
$("pendingqueue").addEventListener("change", (e) => {
|
|
433
|
+
if (e.target.id === "pendingqueuerole") {
|
|
434
|
+
pendingQueueRole = e.target.value;
|
|
435
|
+
renderPendingQueue(lastStatus);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
$("pendingreviews").addEventListener("change", (e) => {
|
|
439
|
+
if (e.target.id === "pendingreviewrole") {
|
|
440
|
+
pendingReviewRole = e.target.value;
|
|
441
|
+
renderPendingReviews(lastStatus);
|
|
442
|
+
} else if (e.target.id === "pendingreviewstate") {
|
|
443
|
+
pendingReviewState = e.target.value;
|
|
444
|
+
renderPendingReviews(lastStatus);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
$("pendingoverlaps").addEventListener("click", (e) => {
|
|
448
|
+
const b = e.target.closest("[data-overlap]");
|
|
449
|
+
if (b) togglePendingOverlap(Number(b.dataset.overlap));
|
|
450
|
+
});
|
|
451
|
+
$("pendingoverlaps").addEventListener("change", (e) => {
|
|
452
|
+
if (e.target.id === "pendingoverlapdir") pendingOverlapDir = e.target.value;
|
|
453
|
+
else if (e.target.id === "pendingoverlaprole") pendingOverlapRole = e.target.value;
|
|
454
|
+
else return;
|
|
455
|
+
pendingOverlapOpen = null;
|
|
456
|
+
paintPendingOverlaps();
|
|
457
|
+
});
|
|
93
458
|
}
|
|
94
459
|
// Load the team-wide change roster. Called when the view opens and by the Refresh button;
|
|
95
|
-
// no timer of its own, and the roster only changes when a
|
|
460
|
+
// no timer of its own, and the roster only changes when a role merges or edits its worktree.
|
|
461
|
+
// Load the overlapping-files card on its own, so neither read can blank the other's card.
|
|
462
|
+
async function fetchPendingOverlaps() {
|
|
463
|
+
pendingOverlapOpen = null;
|
|
464
|
+
try {
|
|
465
|
+
pendingOverlaps = await getJson("/api/diff?overlaps=1");
|
|
466
|
+
} catch (err) {
|
|
467
|
+
const card = $("pendingoverlaps");
|
|
468
|
+
if (card) card.innerHTML = errorPanel("Overlaps unavailable", err);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (!pendingOverlapHolders(pendingOverlaps).includes(pendingOverlapRole)) pendingOverlapRole = "";
|
|
472
|
+
if (!pendingOverlapDirs(pendingOverlaps).includes(pendingOverlapDir)) pendingOverlapDir = "";
|
|
473
|
+
paintPendingOverlaps();
|
|
474
|
+
}
|
|
96
475
|
async function fetchPending() {
|
|
97
476
|
buildPendingView();
|
|
477
|
+
fetchPendingOverlaps();
|
|
98
478
|
try {
|
|
99
479
|
pendingTeam = await getJson("/api/diff");
|
|
100
480
|
} catch (err) {
|
|
@@ -104,6 +484,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
104
484
|
}
|
|
105
485
|
renderPending();
|
|
106
486
|
renderPendingQueue(lastStatus);
|
|
487
|
+
renderPendingReviews(lastStatus);
|
|
107
488
|
}
|
|
108
489
|
function renderPending() {
|
|
109
490
|
const body = $("pendingbody");
|