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,29 +1,29 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** Collection half of `tumwater history`: read the event log and distill the last N completed
|
|
3
|
-
*
|
|
2
|
+
* runs into RunRow — one row per run, newest first, with the paired duration and the raw
|
|
4
3
|
* usage numbers. The Markdown/table rendering of this data lives in history.ts, a pure
|
|
5
4
|
* function of it; the split mirrors the usage report's (src/report/report-data.ts /
|
|
6
|
-
* src/report/report-render.ts) and keeps "what happened" (
|
|
5
|
+
* src/report/report-render.ts) and keeps "what happened" (run pairing, role filtering,
|
|
7
6
|
* scan windows) apart from "how it prints" (column widths, terminal display padding),
|
|
8
7
|
* which change for different reasons — and keeps core data collection out of the
|
|
9
8
|
* presentation layer, so a core consumer (as the GUI's /api/history already is) never
|
|
10
9
|
* forces a core→ui import. */
|
|
11
|
-
import { eventRole,
|
|
10
|
+
import { eventRole, eventRun, eventUsage, readEvents, runSpanMs, runStartMap, runStartModel, runStartModelMap } from "../events/event-read.js";
|
|
12
11
|
import { readEventsSinceJoined } from "../events/event-window.js";
|
|
13
|
-
import { formatTimestamp } from "../text/datetime.js";
|
|
12
|
+
import { formatTimestamp, HOUR_MS } from "../text/datetime.js";
|
|
14
13
|
import { eventTypeLabel } from "../events/event-labels.js";
|
|
15
14
|
import { roleLabel } from "../roles/role-labels.js";
|
|
16
|
-
import { squash, textOr } from "../text/text.js";
|
|
15
|
+
import { asList, squash, textOr } from "../text/text.js";
|
|
17
16
|
import { shortSpanPhrase } from "../text/phrases.js";
|
|
18
17
|
import { outcomeText, usageText } from "../events/event-format.js";
|
|
19
|
-
import { compareCodeUnits
|
|
18
|
+
import { compareCodeUnits } from "../collections.js";
|
|
19
|
+
import { bucketMergeEvents, resolveQueuedResult } from "./history-queued.js";
|
|
20
20
|
/** `history`'s default row count and ceiling. The default shows a working hour of a quiet
|
|
21
21
|
* team; the ceiling bounds how much log one ask re-reads — more rows only re-read more log
|
|
22
22
|
* with no added signal, so an explicit flag above it fails fast (the report's --days pattern). */
|
|
23
|
-
export const
|
|
24
|
-
export const
|
|
23
|
+
export const HISTORY_DEFAULT_RUNS = 20;
|
|
24
|
+
export const HISTORY_MAX_RUNS = 200;
|
|
25
25
|
/** The outcome classes `tumwater history --result` and the dashboard's History filter both
|
|
26
|
-
* speak: Changes (the
|
|
26
|
+
* speak: Changes (the run merged or queued work), No change (it did nothing on purpose, or
|
|
27
27
|
* the operator aborted it), Refused (a run that deliberately declined its entry), Problems
|
|
28
28
|
* (everything else, e.g. error/rejected). The one canonical
|
|
29
29
|
* rule; the browser's copy in gui-client-history.ts is pinned to it by
|
|
@@ -35,7 +35,7 @@ export const HISTORY_RESULT_FLAG_NAMES = { changes: "merged", none: "none", refu
|
|
|
35
35
|
export function parseHistoryResultClass(raw) {
|
|
36
36
|
return HISTORY_RESULT_CLASSES.find((c) => raw === c || raw === HISTORY_RESULT_FLAG_NAMES[c]) ?? null;
|
|
37
37
|
}
|
|
38
|
-
/** Which outcome class a
|
|
38
|
+
/** Which outcome class a run result falls in (the HISTORY_RESULT_CLASSES rule): `changed` and
|
|
39
39
|
* `queued` are `changes`; `no_change`, `skipped`, and `user_aborted` are `none`; `refused` is
|
|
40
40
|
* `refused`; every other result is `problems`. Pure; the canonical home the dashboard's
|
|
41
41
|
* client-side filter mirrors. */
|
|
@@ -48,16 +48,18 @@ export function historyClass(result) {
|
|
|
48
48
|
return "refused";
|
|
49
49
|
return "problems";
|
|
50
50
|
}
|
|
51
|
-
/** Tally rows
|
|
52
|
-
* order (not localeCompare) so the order is
|
|
53
|
-
|
|
51
|
+
/** Tally rows into groups by `keyOf`, each row counted once; the key lands in the group row's
|
|
52
|
+
* `role` field. Groups come back in plain string order (not localeCompare) so the order is
|
|
53
|
+
* deterministic; `total` sums every row. Pure. */
|
|
54
|
+
function tallyRows(rows, keyOf) {
|
|
54
55
|
const blank = (role) => ({ role, runs: 0, changes: 0, none: 0, refused: 0, problems: 0, tokens: 0, costUsd: 0, lastChangeTs: null });
|
|
55
|
-
const
|
|
56
|
+
const byKey = new Map();
|
|
56
57
|
const total = blank("total");
|
|
57
58
|
for (const row of rows) {
|
|
58
|
-
|
|
59
|
+
const key = keyOf(row);
|
|
60
|
+
let entry = byKey.get(key);
|
|
59
61
|
if (!entry)
|
|
60
|
-
|
|
62
|
+
byKey.set(key, (entry = blank(key)));
|
|
61
63
|
const cls = historyClass(row.result);
|
|
62
64
|
for (const t of [entry, total]) {
|
|
63
65
|
t.runs += 1;
|
|
@@ -70,52 +72,70 @@ export function summarizeTickRows(rows) {
|
|
|
70
72
|
t.lastChangeTs = Math.max(t.lastChangeTs ?? row.ts, row.ts);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
|
-
const
|
|
74
|
-
return {
|
|
75
|
+
const groups = [...byKey.values()].sort((a, b) => compareCodeUnits(a.role, b.role));
|
|
76
|
+
return { groups, total };
|
|
75
77
|
}
|
|
76
|
-
/**
|
|
78
|
+
/** Tally rows per role by historyClass, each row counted once. Roles come back in plain string
|
|
79
|
+
* order (not localeCompare) so the order is deterministic; `total` sums every role. Pure. */
|
|
80
|
+
export function summarizeRunRows(rows) {
|
|
81
|
+
const { groups, total } = tallyRows(rows, (r) => r.role);
|
|
82
|
+
return { roles: groups, total };
|
|
83
|
+
}
|
|
84
|
+
/** What `history --summary --by` accepts: the default per-role grouping or per-model. */
|
|
85
|
+
export const HISTORY_SUMMARY_GROUPS = ["role", "model"];
|
|
86
|
+
/** Sentinel group key for rows whose model is null; no model selector can equal it. */
|
|
87
|
+
const NO_MODEL_KEY = "\u0000";
|
|
88
|
+
/** summarizeRunRows grouped by the model each run started on instead of by role. Models come back
|
|
89
|
+
* in plain string order with the null bucket last; `total` equals summarizeRunRows's. Pure. */
|
|
90
|
+
export function summarizeRunRowsByModel(rows) {
|
|
91
|
+
const { groups, total } = tallyRows(rows, (r) => r.model ?? NO_MODEL_KEY);
|
|
92
|
+
const models = groups.map(({ role, ...counts }) => ({ model: role === NO_MODEL_KEY ? null : role, ...counts }));
|
|
93
|
+
models.sort((a, b) => (a.model === null ? 1 : b.model === null ? -1 : compareCodeUnits(a.model, b.model)));
|
|
94
|
+
return { models, total };
|
|
95
|
+
}
|
|
96
|
+
/** How much of a run's summary or error a row keeps. Long enough to name the work, short
|
|
77
97
|
* enough that the row stays one terminal line next to its other columns. */
|
|
78
98
|
const DETAIL_MAX = 72;
|
|
79
99
|
/** The hard ceiling on one ask's scan window (events, not rows): the growth loop below
|
|
80
100
|
* re-reads with a larger window only while the filtered rows fall short and the log may hold
|
|
81
101
|
* more, and this bounds the worst case — readTailText reads bytes proportional to the limit's
|
|
82
102
|
* line count, so the cap is also the largest byte read one command can cost. Comfortably
|
|
83
|
-
* above the dilution a whole role catalog can impose on
|
|
84
|
-
* the one ceiling every read-only view's single ask scans under:
|
|
85
|
-
*
|
|
103
|
+
* above the dilution a whole role catalog can impose on HISTORY_MAX_RUNS rows. Exported as
|
|
104
|
+
* the one ceiling every read-only view's single ask scans under: run-detail-data.ts's
|
|
105
|
+
* readRunDetail reads the same bound, so no ask can re-read more log than any other and the
|
|
86
106
|
* cap cannot drift per consumer. */
|
|
87
107
|
export const HISTORY_SCAN_MAX_EVENTS = 20_000;
|
|
88
108
|
/** How far behind a since-shaped scan's cutoff the queued-row join reads its evidence: the
|
|
89
|
-
* row set is ts-filtered to the cutoff, but a queued
|
|
90
|
-
* its `merge_queued` pin before it (the pin is logged during the
|
|
109
|
+
* row set is ts-filtered to the cutoff, but a queued run whose run SPANS the cutoff logged
|
|
110
|
+
* its `merge_queued` pin before it (the pin is logged during the run, the run_end after it)
|
|
91
111
|
* — so the join evidence needs its own read reaching further back. Six hours outlasts any
|
|
92
|
-
*
|
|
112
|
+
* run the harness runs (every stage inside a run is timeout-bounded); a run that somehow
|
|
93
113
|
* queued later than that keeps the raw label, the conservative fallback. The wider read shares
|
|
94
114
|
* one bounded backwards scan with the rows' own window (readEventsSinceJoined), and its
|
|
95
115
|
* day-keyed scan never reaches past its own window's first day. */
|
|
96
|
-
const
|
|
97
|
-
export function
|
|
116
|
+
const RUN_ROW_JOIN_BACKSLACK_MS = 6 * HOUR_MS;
|
|
117
|
+
export function runRowCells(row) {
|
|
98
118
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
role: roleLabel(row.role),
|
|
120
|
+
run: String(row.run),
|
|
101
121
|
result: eventTypeLabel(row.result),
|
|
102
122
|
model: row.model ?? "",
|
|
103
123
|
duration: row.durationMs === null ? "—" : shortSpanPhrase(row.durationMs),
|
|
104
124
|
usage: row.usage,
|
|
105
125
|
};
|
|
106
126
|
}
|
|
107
|
-
/** One
|
|
127
|
+
/** One run row's line: its timestamp, the six runRowCells values (the model cell between
|
|
108
128
|
* the result and the duration), then the free-text detail, joined by two spaces and stripped
|
|
109
129
|
* of trailing padding. This is the one home for the row's column order and separator;
|
|
110
130
|
* renderRow (history-render.ts) passes a per-column padding function to align the CLI table,
|
|
111
|
-
* while
|
|
131
|
+
* while runRowGrepHaystack below passes none. The run cell's "#" prefix rides outside the
|
|
112
132
|
* padding, so its column width measures the number alone. */
|
|
113
|
-
export function
|
|
114
|
-
const c =
|
|
133
|
+
export function runRowLine(row, pad = (_c, v) => v) {
|
|
134
|
+
const c = runRowCells(row);
|
|
115
135
|
return [
|
|
116
136
|
row.time,
|
|
117
|
-
pad("
|
|
118
|
-
`#${pad("
|
|
137
|
+
pad("role", c.role), // vocabulary-ok: column key, not shown
|
|
138
|
+
`#${pad("run", c.run)}`,
|
|
119
139
|
pad("result", c.result),
|
|
120
140
|
pad("model", c.model),
|
|
121
141
|
pad("duration", c.duration),
|
|
@@ -125,193 +145,58 @@ export function tickRowLine(row, pad = (_c, v) => v) {
|
|
|
125
145
|
.join(" ")
|
|
126
146
|
.trimEnd();
|
|
127
147
|
}
|
|
128
|
-
/** The `--grep` haystack for one rendered history row:
|
|
148
|
+
/** The `--grep` haystack for one rendered history row: runRowLine's canonical unpadded row
|
|
129
149
|
* line prefixed with the raw event type so stable ids (`run_end`) are greppable the way logs
|
|
130
150
|
* --grep's haystack carries `e.type`, and followed by the raw role id and result code, which
|
|
131
151
|
* the row shows as `feature #2` and `no change`, so a script's grep on the code still
|
|
132
152
|
* matches. Built per row because the filter runs before the widths of the rows actually shown
|
|
133
153
|
* are known, and shared by `tumwater history --grep` and the dashboard's
|
|
134
154
|
* /api/history?grep so the two cannot disagree on the rule. The row's cells and column order
|
|
135
|
-
* come from
|
|
136
|
-
export function
|
|
137
|
-
return `run_end ${
|
|
155
|
+
* come from runRowCells and runRowLine, the one home for each. */
|
|
156
|
+
export function runRowGrepHaystack(row) {
|
|
157
|
+
return `run_end ${runRowLine(row)} ${row.role} ${row.result}`;
|
|
138
158
|
}
|
|
139
|
-
/** The shared
|
|
159
|
+
/** The shared run-grep rule: a case-insensitive substring match over runRowGrepHaystack.
|
|
140
160
|
* Four call sites apply it — `tumwater history --grep`, /api/history's window and count
|
|
141
161
|
* shapes, and the TUI History search — so the three surfaces cannot disagree on what a match
|
|
142
162
|
* means. The pattern is lowercased once here, not per row. */
|
|
143
|
-
export function
|
|
163
|
+
export function runRowsMatchingGrep(rows, pattern) {
|
|
144
164
|
const needle = pattern.toLowerCase();
|
|
145
|
-
return rows.filter((r) =>
|
|
146
|
-
}
|
|
147
|
-
/** The merge bookkeeping resolveQueuedResult joins over: per loop, the `merge_queued` pins
|
|
148
|
-
* and the `merge_done`/`merge_failed` outcomes, each list oldest-first (the scan order). Exported
|
|
149
|
-
* as one helper with exactly two call sites — readTickRows' history-row join and the failure
|
|
150
|
-
* digest's time-and-spend fold (time-spend.ts) — so the two collectors cannot disagree about
|
|
151
|
-
* which events feed the join or in what order they sit: a third merge-bookkeeping event
|
|
152
|
-
* type, or a changed order rule, is edited once here. Callers bucket only the events they
|
|
153
|
-
* need; the digest folds its `review_rejected` reasons with its own pass.
|
|
154
|
-
* Roles or cutoffs are the caller's concern: pass a role-scoped or wider-read event list and
|
|
155
|
-
* the maps carry exactly that list's evidence. */
|
|
156
|
-
export function bucketMergeEvents(events) {
|
|
157
|
-
const mergeQueuedByLoop = groupBy(events.filter((e) => e.type === "merge_queued"), (e) => e.source);
|
|
158
|
-
const outcomeByLoop = groupBy(events.filter((e) => e.type === "merge_done" || e.type === "merge_failed"), (e) => e.source);
|
|
159
|
-
return { mergeQueuedByLoop, outcomeByLoop };
|
|
160
|
-
}
|
|
161
|
-
/** Resolve one queued tick row's eventual merge verdict: a "queued" run_end carries no
|
|
162
|
-
* result until its merge batch merges or fails, so the row joins its `end` run_end to the
|
|
163
|
-
* tick's merge_queued pin and that pin's merge_done/merge_failed outcome (both from the
|
|
164
|
-
* bucketMergeEvents maps, oldest-first per loop). The claim cursor `claimTop` (mutated in
|
|
165
|
-
* place) walks the pin list newest-first and hands each queued tick the newest unclaimed pin
|
|
166
|
-
* at or before its end, so several queued ticks cannot claim one pin.
|
|
167
|
-
*
|
|
168
|
-
* One change can take several merge attempts: a retriable result (RETRIABLE_MERGE_RESULTS:
|
|
169
|
-
* review_error, merge_conflict, merge_blocked) keeps the pin, and the role's next run — a
|
|
170
|
-
* leftover-recovery run that does no work of its own and ends in about a second — re-enqueues
|
|
171
|
-
* it (isRecoveryPin). The authoring run and the recovery runs resolve by different rules, so
|
|
172
|
-
* every attempt's verdict shows on exactly one row:
|
|
173
|
-
* - The run that authored the change (its pin is not a re-enqueue of a kept attempt) takes
|
|
174
|
-
* the change's FINAL verdict, not its first attempt's: a retriable failure that recovery
|
|
175
|
-
* later merges reads "changed" (BUGS.md 2026-10-10). While a re-enqueued attempt is still
|
|
176
|
-
* pending the row keeps the raw "queued" label — the change is waiting to merge again, and
|
|
177
|
-
* the failure before it already shows on the recovery run's row.
|
|
178
|
-
* - A recovery run takes the verdict of the attempt it re-enqueued — the retriable failure
|
|
179
|
-
* that kept the pin and made the run happen (recoveredAttemptVerdict). So a change that
|
|
180
|
-
* timed out in review seven times and was then rejected reads "rejected" once (the
|
|
181
|
-
* authoring row) and "review failed" seven times (the authoring attempt's and six
|
|
182
|
-
* recoveries' timeouts, each on the recovery row that followed it), not "rejected" on all
|
|
183
|
-
* eight rows.
|
|
184
|
-
* When main moved before a retry the verification's syncPinToMain rebased the ref
|
|
185
|
-
* (merge-core.ts), so the recovery pin carries the rebased sha while each attempt's own
|
|
186
|
-
* merge_done/merge_failed logs its entry's sha (merge-slot.ts's entry.sha): the attempts' shas
|
|
187
|
-
* differ, so the authoring row's walk follows the pin sequence (isRecoveryPin), not sha
|
|
188
|
-
* equality alone. It stops at a pin that is not a recovery re-enqueue — a fresh change after a
|
|
189
|
-
* conflict-cap discard — so the row never credits a different change's verdict (BUGS.md
|
|
190
|
-
* 2026-10-10). A recovery pin whose previous attempt shows no retriable verdict (a crash
|
|
191
|
-
* left the commit unpinned or the merge unfinished, or that attempt sits outside the read) is
|
|
192
|
-
* the change's first visible attempt, so its row takes the authoring rule.
|
|
193
|
-
*
|
|
194
|
-
* `joinStarts` maps loop#tick to its run_start's ts; when `floorTs` (a since-window's cutoff,
|
|
195
|
-
* null in the count view) sits after that start, the row's join evidence is not whole in the
|
|
196
|
-
* windowed read and the row keeps its raw "queued" label (null). A MISSING start needs no
|
|
197
|
-
* guard — it joins, because rotation drops a prefix, so every pin older than the lost start is
|
|
198
|
-
* lost with it and nothing can be mis-claimed (and a skipped tick has no start at all but is
|
|
199
|
-
* never queued). A merge with no outcome event in the join set also keeps the raw label: the
|
|
200
|
-
* conservative fallback for a verdict simply not visible here. Returns the verdict's result
|
|
201
|
-
* text plus the pin and outcome of the attempt that produced it (and that pin's sha), or null
|
|
202
|
-
* to keep the raw label. Pure apart from the cursor it owns. */
|
|
203
|
-
export function resolveQueuedResult(end, mergeQueuedByLoop, outcomeByLoop, claimTop, joinStarts, floorTs) {
|
|
204
|
-
if (floorTs !== null) {
|
|
205
|
-
const startTs = joinStarts.get(`${end.source}#${end.run}`);
|
|
206
|
-
if (startTs !== undefined && startTs < floorTs)
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
const queuedList = mergeQueuedByLoop.get(end.source);
|
|
210
|
-
if (!queuedList || queuedList.length === 0)
|
|
211
|
-
return null;
|
|
212
|
-
let top = claimTop.get(end.source) ?? queuedList.length - 1;
|
|
213
|
-
while (top >= 0 && queuedList[top].ts > end.ts)
|
|
214
|
-
top--;
|
|
215
|
-
if (top < 0)
|
|
216
|
-
return null;
|
|
217
|
-
claimTop.set(end.source, top - 1);
|
|
218
|
-
const outcomes = outcomeByLoop.get(end.source) ?? [];
|
|
219
|
-
// A recovery run's row: the attempt it re-enqueued, not the change's final verdict, so the
|
|
220
|
-
// final verdict shows once (on the authoring row) and each retriable failure once (here).
|
|
221
|
-
const recovered = recoveredAttemptVerdict(queuedList, top, outcomes);
|
|
222
|
-
if (recovered !== null)
|
|
223
|
-
return recovered;
|
|
224
|
-
// The authoring row: walk forward while the next pin is a recovery re-enqueue and resolve to
|
|
225
|
-
// the last attempt's verdict. `findLast` picks the newest same-sha outcome so an un-rebased
|
|
226
|
-
// retry (recovery re-enqueues the identical sha, logged by both pins) does not re-match the
|
|
227
|
-
// first attempt's outcome inside the recovery pin's walk. The returned pin/outcome ride the
|
|
228
|
-
// result so a caller correlating sibling evidence (the failure digest's review-failure
|
|
229
|
-
// message) can key on THIS attempt's position, not a sha a pre-review rebase may have moved
|
|
230
|
-
// (BUGS.md 2026-10-10).
|
|
231
|
-
let idx = top;
|
|
232
|
-
let resolved = null;
|
|
233
|
-
for (;;) {
|
|
234
|
-
const pin = queuedList[idx];
|
|
235
|
-
const outcome = outcomes.findLast((o) => o.ts >= pin.ts && textOr(o.commit, "") === textOr(pin.commit, ""));
|
|
236
|
-
// No outcome on the tick's own pin, or on a recovery pin the walk reached: that attempt is
|
|
237
|
-
// still pending, so the change is waiting to merge — keep the raw label. The previous
|
|
238
|
-
// attempt's retriable verdict already shows on the recovery run's row.
|
|
239
|
-
if (!outcome)
|
|
240
|
-
return null;
|
|
241
|
-
const verdict = attemptVerdict(pin, outcome);
|
|
242
|
-
if (verdict === null)
|
|
243
|
-
return resolved;
|
|
244
|
-
// A merge or a rejection ends the change (the pin is discarded, so no recovery pin can
|
|
245
|
-
// follow); any other verdict kept the pin, and the walk continues only if the next pin is
|
|
246
|
-
// the recovery re-enqueue that kept it. Otherwise this attempt's verdict is the final one
|
|
247
|
-
// visible.
|
|
248
|
-
if (endsChange(verdict.result) || !isRecoveryPin(queuedList[idx + 1]))
|
|
249
|
-
return verdict;
|
|
250
|
-
resolved = verdict;
|
|
251
|
-
idx += 1;
|
|
252
|
-
}
|
|
165
|
+
return rows.filter((r) => runRowGrepHaystack(r).toLowerCase().includes(needle));
|
|
253
166
|
}
|
|
254
|
-
/** The
|
|
255
|
-
*
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
167
|
+
/** The shared model filter: keeps the rows whose run_start named exactly one of `models`
|
|
168
|
+
* (case-sensitive, the strings `history --summary --by model` prints); a null-model row never
|
|
169
|
+
* matches. Four call sites apply it — `tumwater history --model`, /api/history?model, the TUI
|
|
170
|
+
* History model selector, and newestRunRow's `--last` model filter — so the surfaces cannot
|
|
171
|
+
* disagree on what a model selector matches. */
|
|
172
|
+
export function runRowsOnModels(rows, models) {
|
|
173
|
+
return rows.filter((r) => r.model !== null && models.includes(r.model));
|
|
259
174
|
}
|
|
260
|
-
/**
|
|
261
|
-
* re-enqueue can follow it. Every other verdict (the retriable ones) kept the pin. The one rule
|
|
262
|
-
* both directions of resolveQueuedResult's chain read — the authoring row's walk forward and a
|
|
263
|
-
* recovery row's step back — so the two cannot disagree on where a change ends. */
|
|
264
|
-
function endsChange(result) {
|
|
265
|
-
return result === "changed" || result === "rejected";
|
|
266
|
-
}
|
|
267
|
-
/** A recovery run's own row verdict: when the pin at `idx` is a recovery re-enqueue and the
|
|
268
|
-
* previous pin's attempt ended in a retriable verdict (the kept pin this run re-enqueued), that
|
|
269
|
-
* verdict, carrying the previous attempt's pin and outcome. The outcome is the newest one for
|
|
270
|
-
* the previous pin's sha logged between the two pins: recovery runs only after the previous
|
|
271
|
-
* attempt's outcome is written, and bounding at this pin keeps an un-rebased retry (both pins
|
|
272
|
-
* share a sha) from matching a LATER attempt's outcome. Null when the pin is not a recovery
|
|
273
|
-
* re-enqueue, or is one with no retriable attempt visible before it — a crash's unpinned or
|
|
274
|
-
* unfinished commit, or a previous pin outside the read — so the row takes the authoring rule. */
|
|
275
|
-
function recoveredAttemptVerdict(queuedList, idx, outcomes) {
|
|
276
|
-
const pin = queuedList[idx];
|
|
277
|
-
const prev = queuedList[idx - 1];
|
|
278
|
-
if (prev === undefined || !isRecoveryPin(pin))
|
|
279
|
-
return null;
|
|
280
|
-
const outcome = outcomes.findLast((o) => o.ts >= prev.ts && o.ts <= pin.ts && textOr(o.commit, "") === textOr(prev.commit, ""));
|
|
281
|
-
const verdict = outcome === undefined ? null : attemptVerdict(prev, outcome);
|
|
282
|
-
return verdict === null || endsChange(verdict.result) ? null : verdict;
|
|
283
|
-
}
|
|
284
|
-
/** True when a pin re-enqueues the previous attempt's kept pin rather than starting a fresh
|
|
285
|
-
* change. `recoverLeftover` (src/loop/leftover.ts) is the one `merge_queued` producer whose
|
|
286
|
-
* summary starts with this label, so a fresh change's pin after a conflict-cap discard ends
|
|
287
|
-
* `resolveQueuedResult`'s walk. */
|
|
288
|
-
function isRecoveryPin(pin) {
|
|
289
|
-
return pin !== undefined && textOr(pin.summary, "").startsWith("recovered leftover work from ");
|
|
290
|
-
}
|
|
291
|
-
/** The tick rows for the last `limit` completed ticks in `events` (oldest-first, as
|
|
175
|
+
/** The run rows for the last `limit` completed runs in `events` (oldest-first, as
|
|
292
176
|
* readEvents returns them), filtered to `role` when given, newest first. Pure: the CLI, the
|
|
293
177
|
* GUI, and the tests share this collector, and none of them writes anything. */
|
|
294
|
-
export function
|
|
295
|
-
const
|
|
178
|
+
export function runRows(events, limit, role, join) {
|
|
179
|
+
const wanted = role === null ? null : new Set(asList(role));
|
|
180
|
+
const scoped = wanted === null ? events : events.filter((e) => wanted.has(e.source));
|
|
296
181
|
// The start pairing is the shared helper (events.ts), so history's dash-on-unpaired rule
|
|
297
|
-
// and the digest's fold cannot drift into different notions of a
|
|
298
|
-
const starts =
|
|
299
|
-
const models =
|
|
300
|
-
// The merge bookkeeping resolveQueuedResult joins over: per
|
|
182
|
+
// and the digest's fold cannot drift into different notions of a run's span.
|
|
183
|
+
const starts = runStartMap(scoped);
|
|
184
|
+
const models = runStartModelMap(scoped);
|
|
185
|
+
// The merge bookkeeping resolveQueuedResult joins over: per role, the merge_queued pins
|
|
301
186
|
// (oldest first) and the merge_done/merge_failed outcomes (oldest first). Both ride the role
|
|
302
|
-
// filter with their
|
|
303
|
-
// A since-shaped scan hands a WIDER join read instead (
|
|
304
|
-
// the pins of
|
|
187
|
+
// filter with their run, so the scoped events hold them exactly when they hold the row.
|
|
188
|
+
// A since-shaped scan hands a WIDER join read instead (readRunRowsSince): its cutoff cut
|
|
189
|
+
// the pins of runs that span it, so the maps read the unfiltered-by-cutoff evidence and
|
|
305
190
|
// resolveQueuedResult guards each claim on the row's run_start being whole in that read.
|
|
306
191
|
const joinScoped = join
|
|
307
|
-
?
|
|
192
|
+
? wanted === null
|
|
308
193
|
? join.events
|
|
309
|
-
: join.events.filter((e) => e.source
|
|
194
|
+
: join.events.filter((e) => wanted.has(e.source))
|
|
310
195
|
: scoped;
|
|
311
|
-
const joinStarts = join ?
|
|
312
|
-
const joinModels = join ?
|
|
196
|
+
const joinStarts = join ? runStartMap(joinScoped) : starts;
|
|
197
|
+
const joinModels = join ? runStartModelMap(joinScoped) : models;
|
|
313
198
|
const floorTs = join ? join.floorTs : null;
|
|
314
|
-
const {
|
|
199
|
+
const { mergeQueuedByRunner, outcomeByRunner } = bucketMergeEvents(joinScoped);
|
|
315
200
|
const claimTop = new Map();
|
|
316
201
|
const rows = [];
|
|
317
202
|
for (let i = scoped.length - 1; i >= 0 && rows.length < limit; i--) {
|
|
@@ -320,7 +205,7 @@ export function tickRows(events, limit, role, join) {
|
|
|
320
205
|
continue;
|
|
321
206
|
const rawResult = outcomeText(e);
|
|
322
207
|
const result = rawResult === "queued"
|
|
323
|
-
? (resolveQueuedResult(e,
|
|
208
|
+
? (resolveQueuedResult(e, mergeQueuedByRunner, outcomeByRunner, claimTop, joinStarts, floorTs)
|
|
324
209
|
?.result ?? rawResult)
|
|
325
210
|
: rawResult;
|
|
326
211
|
const usage = eventUsage(e);
|
|
@@ -330,38 +215,38 @@ export function tickRows(events, limit, role, join) {
|
|
|
330
215
|
tokens: usage.tokens,
|
|
331
216
|
costUsd: usage.costUsd,
|
|
332
217
|
role: eventRole(e),
|
|
333
|
-
run:
|
|
218
|
+
run: eventRun(e),
|
|
334
219
|
result,
|
|
335
|
-
model:
|
|
336
|
-
durationMs:
|
|
220
|
+
model: runStartModel(e, join ? joinModels : models),
|
|
221
|
+
durationMs: runSpanMs(e, starts),
|
|
337
222
|
usage: usageText(e),
|
|
338
223
|
detail: squash(textOr(e.summary, textOr(e.error, "")), DETAIL_MAX),
|
|
339
224
|
});
|
|
340
225
|
}
|
|
341
226
|
return rows;
|
|
342
227
|
}
|
|
343
|
-
/** The last `limit` completed
|
|
228
|
+
/** The last `limit` completed runs read from root's event log, filtered to `role` when
|
|
344
229
|
* given (newest first) — the scan cmdHistory and the GUI's handleHistory share, so the two
|
|
345
230
|
* surfaces cannot drift. A window twice the ask plus slack: run_start lines and unrelated
|
|
346
|
-
* events interleave with the run_end rows scanned for, and a skipped
|
|
231
|
+
* events interleave with the run_end rows scanned for, and a skipped run's end rides a start
|
|
347
232
|
* that may sit outside any smaller window. Two shortfalls grow the window (and re-read) while
|
|
348
233
|
* the scan filled it — `events.length >= window` means the log may hold older events — under
|
|
349
234
|
* the same HISTORY_SCAN_MAX_EVENTS bound so one ask cannot scan without end:
|
|
350
|
-
* - Rows fall short. A role filter dilutes the window (every other
|
|
351
|
-
* too), so a quiet role in a busy team must still get its last `limit`
|
|
352
|
-
* retained log holds them — never a bare "no
|
|
235
|
+
* - Rows fall short. A role filter dilutes the window (every other role's events occupy it
|
|
236
|
+
* too), so a quiet role in a busy team must still get its last `limit` runs whenever the
|
|
237
|
+
* retained log holds them — never a bare "no runs yet" for a role whose runs sit just
|
|
353
238
|
* past the first window.
|
|
354
|
-
* - A row's run_start went unpaired. The window boundary can cut inside a
|
|
239
|
+
* - A row's run_start went unpaired. The window boundary can cut inside a run's own event
|
|
355
240
|
* block: every run_end of the ask sits within the window but the oldest one's run_start
|
|
356
241
|
* sits just before it, so the row renders a dash though the log holds the start. Every
|
|
357
|
-
* run_end the harness logs follows its run_start (
|
|
242
|
+
* run_end the harness logs follows its run_start (run() logs the start before runOnce,
|
|
358
243
|
* skipped results included), so an unpaired start is either just past the window or lost
|
|
359
244
|
* to rotation — growing re-pairs the former, and the latter only costs the bounded scan.
|
|
360
245
|
* (A queued row's merge verdict needs no growth: the merge_done/merge_failed event sits
|
|
361
246
|
* after its run_end, so any tail window holding the row holds the verdict too — or there
|
|
362
247
|
* is none yet, the genuinely-pending case that keeps the raw label.) */
|
|
363
|
-
export function
|
|
364
|
-
const key = `${root}\u0000${role ?? ""}\u0000${limit}`;
|
|
248
|
+
export function readRunRows(root, limit, role) {
|
|
249
|
+
const key = `${root}\u0000${typeof role === "string" || role === null ? role ?? "" : role.join(",")}\u0000${limit}`;
|
|
365
250
|
// A repeated ask's steady state starts where the last one ended, not back at the base window: the
|
|
366
251
|
// GUI drawer's history panel re-issues this ask every second while events flow, and each poll's
|
|
367
252
|
// ladder re-parsed every intermediate window (base → ×4 → …) before the one that satisfied
|
|
@@ -371,47 +256,50 @@ export function readTickRows(root, limit, role) {
|
|
|
371
256
|
// (events only accumulate), and if it no longer does — a rotation shrank the live log, or the
|
|
372
257
|
// ask itself changed — the loop below keeps growing exactly as before. In-memory only; a stale
|
|
373
258
|
// note costs one larger first read, never a wrong row.
|
|
374
|
-
let window =
|
|
259
|
+
let window = runRowWindowMemo.get(key) ?? limit * 2 + 50;
|
|
375
260
|
let events = readEvents(root, window);
|
|
376
|
-
let rows =
|
|
261
|
+
let rows = runRows(events, limit, role);
|
|
377
262
|
while ((rows.length < limit || rows.some((r) => r.durationMs === null)) &&
|
|
378
263
|
events.length >= window &&
|
|
379
264
|
window < HISTORY_SCAN_MAX_EVENTS) {
|
|
380
265
|
window = Math.min(window * 4, HISTORY_SCAN_MAX_EVENTS);
|
|
381
266
|
events = readEvents(root, window);
|
|
382
|
-
rows =
|
|
267
|
+
rows = runRows(events, limit, role);
|
|
383
268
|
}
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
269
|
+
if (runRowWindowMemo.size >= RUN_ROW_WINDOW_MEMO_MAX)
|
|
270
|
+
runRowWindowMemo.clear();
|
|
271
|
+
runRowWindowMemo.set(key, window);
|
|
387
272
|
return rows;
|
|
388
273
|
}
|
|
389
|
-
/** Per (root, role, limit): the window
|
|
274
|
+
/** Per (root, role, limit): the window readRunRows' last ask ended on — the first window that
|
|
390
275
|
* satisfied it, or the cap when the ladder ran out (the next poll then skips the ladder's
|
|
391
276
|
* proven-too-small windows, the same steady-state note mainCheckForPoll's grewFull carries).
|
|
392
277
|
* Bounded so many short-lived roots in tests cannot grow it unbounded. */
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
/** The window-shaped sibling of
|
|
278
|
+
const runRowWindowMemo = new Map();
|
|
279
|
+
const RUN_ROW_WINDOW_MEMO_MAX = 256;
|
|
280
|
+
/** The window-shaped sibling of readRunRows: the completed runs of the last `sinceMs`, not
|
|
396
281
|
* the last N — the collector behind `history --since`. The window read itself (cutoff, the
|
|
397
282
|
* rotation-spanning day-keyed read with its at-most-one-day over-read, the ts filter, and the
|
|
398
283
|
* exact coverage predicate cmdLogs consults) is event-window.ts's readEventsSinceJoined — one
|
|
399
284
|
* backwards scan serves both this view and the wider join read, with the narrow view's events
|
|
400
285
|
* and coverage derived from the wide scan, so neither windowed surface can claim coverage the
|
|
401
286
|
* other would hedge or key its read differently. The rows read the cutoff-filtered events, but
|
|
402
|
-
* their queued-row join reads WIDER evidence (
|
|
403
|
-
*
|
|
287
|
+
* their queued-row join reads WIDER evidence (RUN_ROW_JOIN_BACKSLACK_MS): a queued run whose
|
|
288
|
+
* run spans the cutoff logged its merge_queued pin before the cutoff, and without the wider
|
|
404
289
|
* read the row would either keep "Queued to land" after its change merged or misclaim an older
|
|
405
|
-
*
|
|
290
|
+
* run's pin (BUGS.md 2026-09-30). The rows reuse the pure runRows with limit =
|
|
406
291
|
* events.length, so pairing, role filtering, and the dash-on-unpaired rule stay exactly the
|
|
407
|
-
* count view's.
|
|
408
|
-
|
|
292
|
+
* count view's. `untilMs`, when given, ends the window that long ago. Pure over root: reads the
|
|
293
|
+
* event log, writes nothing. */
|
|
294
|
+
export function readRunRowsSince(root, sinceMs, role, untilMs = null) {
|
|
409
295
|
// One backwards scan serves both the rows' window and the wider join read (see
|
|
410
296
|
// readEventsSinceJoined): two readEventsSince calls re-scanned and re-parsed the same
|
|
411
297
|
// log bytes — a whole duplicate window scan per `history --since`.
|
|
412
|
-
const { events, join, covered } = readEventsSinceJoined(root, sinceMs,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
298
|
+
const { cutoff, events, join, covered } = readEventsSinceJoined(root, sinceMs, RUN_ROW_JOIN_BACKSLACK_MS);
|
|
299
|
+
const rows = runRows(events, events.length, role, { events: join.events, floorTs: join.cutoff });
|
|
300
|
+
// `untilMs` ends the window that long ago, on the same `now` as the lower edge (cutoff +
|
|
301
|
+
// sinceMs - untilMs). It filters rows, not events, so the queued-row join and the run_start
|
|
302
|
+
// pairing read the same evidence as without it; `covered` is the far edge's and unchanged.
|
|
303
|
+
const upperTs = untilMs === null ? null : cutoff + sinceMs - untilMs;
|
|
304
|
+
return { rows: upperTs === null ? rows : rows.filter((r) => r.ts < upperTs), covered };
|
|
417
305
|
}
|