tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { textOr } from "../text/text.js";
|
|
2
|
+
import { groupBy } from "../collections.js";
|
|
3
|
+
/** The merge bookkeeping resolveQueuedResult joins over: per role, the `merge_queued` pins
|
|
4
|
+
* and the `merge_done`/`merge_failed` outcomes, each list oldest-first (the scan order). Exported
|
|
5
|
+
* as one helper with exactly three call sites — readRunRows' history-row join, the failure
|
|
6
|
+
* digest's time-and-spend fold (time-spend.ts), and run-detail-data.ts — so the collectors
|
|
7
|
+
* cannot disagree about which events feed the join or in what order they sit: a fourth
|
|
8
|
+
* merge-bookkeeping event
|
|
9
|
+
* type, or a changed order rule, is edited once here. Callers bucket only the events they
|
|
10
|
+
* need; the digest folds its `review_rejected` reasons with its own pass.
|
|
11
|
+
* Roles or cutoffs are the caller's concern: pass a role-scoped or wider-read event list and
|
|
12
|
+
* the maps carry exactly that list's evidence. */
|
|
13
|
+
export function bucketMergeEvents(events) {
|
|
14
|
+
const mergeQueuedByRunner = groupBy(events.filter((e) => e.type === "merge_queued"), (e) => e.source);
|
|
15
|
+
const outcomeByRunner = groupBy(events.filter((e) => e.type === "merge_done" || e.type === "merge_failed"), (e) => e.source);
|
|
16
|
+
return { mergeQueuedByRunner, outcomeByRunner };
|
|
17
|
+
}
|
|
18
|
+
/** Resolve one queued run row's eventual merge verdict: a "queued" run_end carries no
|
|
19
|
+
* result until its merge batch merges or fails, so the row joins its `end` run_end to the
|
|
20
|
+
* run's merge_queued pin and that pin's merge_done/merge_failed outcome (both from the
|
|
21
|
+
* bucketMergeEvents maps, oldest-first per role). The claim cursor `claimTop` (mutated in
|
|
22
|
+
* place) walks the pin list newest-first and hands each queued run the newest unclaimed pin
|
|
23
|
+
* at or before its end, so several queued runs cannot claim one pin.
|
|
24
|
+
*
|
|
25
|
+
* One change can take several merge attempts: a retriable result (RETRIABLE_MERGE_RESULTS:
|
|
26
|
+
* review_error, merge_conflict, merge_blocked) keeps the pin, and the role's next run — a
|
|
27
|
+
* leftover-recovery run that does no work of its own and ends in about a second — re-enqueues
|
|
28
|
+
* it (isRecoveryPin). The authoring run and the recovery runs resolve by different rules, so
|
|
29
|
+
* every attempt's verdict shows on exactly one row:
|
|
30
|
+
* - The run that authored the change (its pin is not a re-enqueue of a kept attempt) takes
|
|
31
|
+
* the change's FINAL verdict, not its first attempt's: a retriable failure that recovery
|
|
32
|
+
* later merges reads "changed" (BUGS.md 2026-10-10). While a re-enqueued attempt is still
|
|
33
|
+
* pending the row keeps the raw "queued" label — the change is waiting to merge again, and
|
|
34
|
+
* the failure before it already shows on the recovery run's row.
|
|
35
|
+
* - A recovery run takes the verdict of the attempt it re-enqueued — the retriable failure
|
|
36
|
+
* that kept the pin and made the run happen (recoveredAttemptVerdict). So a change that
|
|
37
|
+
* timed out in review seven times and was then rejected reads "rejected" once (the
|
|
38
|
+
* authoring row) and "review failed" seven times (the authoring attempt's and six
|
|
39
|
+
* recoveries' timeouts, each on the recovery row that followed it), not "rejected" on all
|
|
40
|
+
* eight rows.
|
|
41
|
+
* When main moved before a retry the verification's syncPinToMain rebased the ref
|
|
42
|
+
* (merge-core.ts), so the recovery pin carries the rebased sha while each attempt's own
|
|
43
|
+
* merge_done/merge_failed logs its entry's sha (merge-slot.ts's entry.sha): the attempts' shas
|
|
44
|
+
* differ, so the authoring row's walk follows the pin sequence (isRecoveryPin), not sha
|
|
45
|
+
* equality alone. It stops at a pin that is not a recovery re-enqueue — a fresh change after a
|
|
46
|
+
* conflict-cap discard — so the row never credits a different change's verdict (BUGS.md
|
|
47
|
+
* 2026-10-10). A recovery pin whose previous attempt shows no retriable verdict (a crash
|
|
48
|
+
* left the commit unpinned or the merge unfinished, or that attempt sits outside the read) is
|
|
49
|
+
* the change's first visible attempt, so its row takes the authoring rule.
|
|
50
|
+
*
|
|
51
|
+
* `joinStarts` maps role#run to its run_start's ts; when `floorTs` (a since-window's cutoff,
|
|
52
|
+
* null in the count view) sits after that start, the row's join evidence is not whole in the
|
|
53
|
+
* windowed read and the row keeps its raw "queued" label (null). A MISSING start needs no
|
|
54
|
+
* guard — it joins, because rotation drops a prefix, so every pin older than the lost start is
|
|
55
|
+
* lost with it and nothing can be mis-claimed (and a skipped run has no start at all but is
|
|
56
|
+
* never queued). A merge with no outcome event in the join set also keeps the raw label: the
|
|
57
|
+
* conservative fallback for a verdict simply not visible here. Returns the verdict's result
|
|
58
|
+
* text plus the pin and outcome of the attempt that produced it (and that pin's sha), or null
|
|
59
|
+
* to keep the raw label. Pure apart from the cursor it owns. */
|
|
60
|
+
export function resolveQueuedResult(end, mergeQueuedByRunner, outcomeByRunner, claimTop, joinStarts, floorTs) {
|
|
61
|
+
// Interlock relief (plans/interlock-relief.md): a held run's commit stayed on the branch and
|
|
62
|
+
// enqueued nothing, so its queued label names the held commit, not a merge this run made.
|
|
63
|
+
// Returning null keeps that row's raw queued label and, crucially, leaves the earlier change's
|
|
64
|
+
// merge pins unclaimed, so the verdict that belongs to the queued entry is not credited to the
|
|
65
|
+
// held run. One guard covers all three call sites (history-data.ts, time-spend.ts,
|
|
66
|
+
// run-detail-data.ts).
|
|
67
|
+
if (end.heldOnBranch === true)
|
|
68
|
+
return null;
|
|
69
|
+
if (floorTs !== null) {
|
|
70
|
+
const startTs = joinStarts.get(`${end.source}#${end.run}`);
|
|
71
|
+
if (startTs !== undefined && startTs < floorTs)
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const queuedList = mergeQueuedByRunner.get(end.source);
|
|
75
|
+
if (!queuedList || queuedList.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
let top = claimTop.get(end.source) ?? queuedList.length - 1;
|
|
78
|
+
while (top >= 0 && queuedList[top].ts > end.ts)
|
|
79
|
+
top--;
|
|
80
|
+
if (top < 0)
|
|
81
|
+
return null;
|
|
82
|
+
claimTop.set(end.source, top - 1);
|
|
83
|
+
const outcomes = outcomeByRunner.get(end.source) ?? [];
|
|
84
|
+
// A recovery run's row: the attempt it re-enqueued, not the change's final verdict, so the
|
|
85
|
+
// final verdict shows once (on the authoring row) and each retriable failure once (here).
|
|
86
|
+
const recovered = recoveredAttemptVerdict(queuedList, top, outcomes);
|
|
87
|
+
if (recovered !== null)
|
|
88
|
+
return recovered;
|
|
89
|
+
// The authoring row: walk forward while the next pin is a recovery re-enqueue and resolve to
|
|
90
|
+
// the last attempt's verdict. `findLast` picks the newest same-sha outcome so an un-rebased
|
|
91
|
+
// retry (recovery re-enqueues the identical sha, logged by both pins) does not re-match the
|
|
92
|
+
// first attempt's outcome inside the recovery pin's walk. The returned pin/outcome ride the
|
|
93
|
+
// result so a caller correlating sibling evidence (the failure digest's review-failure
|
|
94
|
+
// message) can key on THIS attempt's position, not a sha a pre-review rebase may have moved
|
|
95
|
+
// (BUGS.md 2026-10-10).
|
|
96
|
+
let idx = top;
|
|
97
|
+
let resolved = null;
|
|
98
|
+
for (;;) {
|
|
99
|
+
const pin = queuedList[idx];
|
|
100
|
+
const outcome = outcomes.findLast((o) => o.ts >= pin.ts && textOr(o.commit, "") === textOr(pin.commit, ""));
|
|
101
|
+
// No outcome on the run's own pin, or on a recovery pin the walk reached: that attempt is
|
|
102
|
+
// still pending, so the change is waiting to merge — keep the raw label. The previous
|
|
103
|
+
// attempt's retriable verdict already shows on the recovery run's row.
|
|
104
|
+
if (!outcome)
|
|
105
|
+
return null;
|
|
106
|
+
const verdict = attemptVerdict(pin, outcome);
|
|
107
|
+
if (verdict === null)
|
|
108
|
+
return resolved;
|
|
109
|
+
// A merge or a rejection ends the change (the pin is discarded, so no recovery pin can
|
|
110
|
+
// follow); any other verdict kept the pin, and the walk continues only if the next pin is
|
|
111
|
+
// the recovery re-enqueue that kept it. Otherwise this attempt's verdict is the final one
|
|
112
|
+
// visible.
|
|
113
|
+
if (endsChange(verdict.result) || !isRecoveryPin(queuedList[idx + 1]))
|
|
114
|
+
return verdict;
|
|
115
|
+
resolved = verdict;
|
|
116
|
+
idx += 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** The verdict one attempt's outcome carries, or null for a torn outcome with no result (a
|
|
120
|
+
* merge_failed whose result is missing or not text). A merge_done with no result is a merge. */
|
|
121
|
+
function attemptVerdict(pin, outcome) {
|
|
122
|
+
const result = textOr(outcome.result, outcome.type === "merge_done" ? "changed" : "");
|
|
123
|
+
return result ? { result, sha: textOr(pin.commit, ""), pin, outcome } : null;
|
|
124
|
+
}
|
|
125
|
+
/** True when a verdict ends its change: a merge or a rejection discards the pin, so no recovery
|
|
126
|
+
* re-enqueue can follow it. Every other verdict (the retriable ones) kept the pin. The one rule
|
|
127
|
+
* both directions of resolveQueuedResult's chain read — the authoring row's walk forward and a
|
|
128
|
+
* recovery row's step back — so the two cannot disagree on where a change ends. */
|
|
129
|
+
function endsChange(result) {
|
|
130
|
+
return result === "changed" || result === "rejected";
|
|
131
|
+
}
|
|
132
|
+
/** A recovery run's own row verdict: when the pin at `idx` is a recovery re-enqueue and the
|
|
133
|
+
* previous pin's attempt ended in a retriable verdict (the kept pin this run re-enqueued), that
|
|
134
|
+
* verdict, carrying the previous attempt's pin and outcome. The outcome is the newest one for
|
|
135
|
+
* the previous pin's sha logged between the two pins: recovery runs only after the previous
|
|
136
|
+
* attempt's outcome is written, and bounding at this pin keeps an un-rebased retry (both pins
|
|
137
|
+
* share a sha) from matching a LATER attempt's outcome. Null when the pin is not a recovery
|
|
138
|
+
* re-enqueue, or is one with no retriable attempt visible before it — a crash's unpinned or
|
|
139
|
+
* unfinished commit, or a previous pin outside the read — so the row takes the authoring rule. */
|
|
140
|
+
function recoveredAttemptVerdict(queuedList, idx, outcomes) {
|
|
141
|
+
const pin = queuedList[idx];
|
|
142
|
+
const prev = queuedList[idx - 1];
|
|
143
|
+
if (prev === undefined || !isRecoveryPin(pin))
|
|
144
|
+
return null;
|
|
145
|
+
const outcome = outcomes.findLast((o) => o.ts >= prev.ts && o.ts <= pin.ts && textOr(o.commit, "") === textOr(prev.commit, ""));
|
|
146
|
+
const verdict = outcome === undefined ? null : attemptVerdict(prev, outcome);
|
|
147
|
+
return verdict === null || endsChange(verdict.result) ? null : verdict;
|
|
148
|
+
}
|
|
149
|
+
/** True when a pin re-enqueues the previous attempt's kept pin rather than starting a fresh
|
|
150
|
+
* change. `recoverLeftover` (src/runner/leftover.ts) is the one `merge_queued` producer whose
|
|
151
|
+
* summary starts with this label, so a fresh change's pin after a conflict-cap discard ends
|
|
152
|
+
* `resolveQueuedResult`'s walk. */
|
|
153
|
+
function isRecoveryPin(pin) {
|
|
154
|
+
return pin !== undefined && textOr(pin.summary, "").startsWith("recovered leftover work from ");
|
|
155
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/** Rendering half of `tumwater history`: format a `TickRow[]`/summary — collected by
|
|
1
|
+
/** Rendering half of `tumwater history`: format a `RunRow[]`/summary — collected by
|
|
3
2
|
* history-data.ts — into the aligned table or `--summary` text `cmdHistory` (history.ts)
|
|
4
3
|
* prints. The sibling observing view report does the same (src/report/report.ts prints
|
|
5
4
|
* src/report/report-render.ts's Markdown), and this split keeps the same separation
|
|
@@ -10,16 +9,16 @@ import { formatTimestamp, humanSeconds } from "../text/datetime.js";
|
|
|
10
9
|
import { roleLabel } from "../roles/role-labels.js";
|
|
11
10
|
import { displayWidth, padToWidth } from "../text/text-width.js";
|
|
12
11
|
import { compactTokens, usd } from "../text/format.js";
|
|
13
|
-
import {
|
|
12
|
+
import { runRowCells, runRowLine } from "./history-data.js";
|
|
14
13
|
/** Each column's width from its unpadded cell. Text cells measure terminal display columns
|
|
15
|
-
* (displayWidth), not UTF-16 code units: a
|
|
14
|
+
* (displayWidth), not UTF-16 code units: a role name holding CJK or emoji renders two columns
|
|
16
15
|
* per code point, and a code-unit padEnd lets that row's later columns drift right of its
|
|
17
|
-
* ASCII neighbors. The
|
|
16
|
+
* ASCII neighbors. The run cell measures before its "#" prefix is added (the prefix rides
|
|
18
17
|
* outside the pad), and duration is ASCII, so both take plain length. */
|
|
19
18
|
function widthsOf(cells) {
|
|
20
19
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
role: displayWidth(cells.role),
|
|
21
|
+
run: cells.run.length,
|
|
23
22
|
result: displayWidth(cells.result),
|
|
24
23
|
model: displayWidth(cells.model),
|
|
25
24
|
duration: cells.duration.length,
|
|
@@ -29,11 +28,11 @@ function widthsOf(cells) {
|
|
|
29
28
|
/** The per-column maxima over every row the table shows — the widths derive from the rows
|
|
30
29
|
* actually shown, so a single-row table has no padding gap. */
|
|
31
30
|
export function widestWidths(rows) {
|
|
32
|
-
const widths = {
|
|
31
|
+
const widths = { role: 0, run: 0, result: 0, model: 0, duration: 0, usage: 0 };
|
|
33
32
|
for (const row of rows) {
|
|
34
|
-
const w = widthsOf(
|
|
35
|
-
widths.
|
|
36
|
-
widths.
|
|
33
|
+
const w = widthsOf(runRowCells(row));
|
|
34
|
+
widths.role = Math.max(widths.role, w.role);
|
|
35
|
+
widths.run = Math.max(widths.run, w.run);
|
|
37
36
|
widths.result = Math.max(widths.result, w.result);
|
|
38
37
|
widths.model = Math.max(widths.model, w.model);
|
|
39
38
|
widths.duration = Math.max(widths.duration, w.duration);
|
|
@@ -41,22 +40,43 @@ export function widestWidths(rows) {
|
|
|
41
40
|
}
|
|
42
41
|
return widths;
|
|
43
42
|
}
|
|
43
|
+
/** The column-wise maximum of two width sets: the running widths of a follow, which only grow. */
|
|
44
|
+
export function widenWidths(a, b) {
|
|
45
|
+
return {
|
|
46
|
+
role: Math.max(a.role, b.role),
|
|
47
|
+
run: Math.max(a.run, b.run),
|
|
48
|
+
result: Math.max(a.result, b.result),
|
|
49
|
+
model: Math.max(a.model, b.model),
|
|
50
|
+
duration: Math.max(a.duration, b.duration),
|
|
51
|
+
usage: Math.max(a.usage, b.usage),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
44
54
|
/** One aligned table line: fixed-width columns over the row set, then the free-text detail.
|
|
45
55
|
* Every cell is padded to its column's width even when empty — usage is the one cell that can
|
|
46
|
-
* be empty (a
|
|
47
|
-
* misaligning that row against its neighbors;
|
|
48
|
-
* row's column order rides history-data.ts's
|
|
56
|
+
* be empty (a run with neither tokens nor cost), and dropping it would pull the detail left,
|
|
57
|
+
* misaligning that row against its neighbors; runRowLine strips only the trailing pad. The
|
|
58
|
+
* row's column order rides history-data.ts's runRowLine, shared with the grep haystack so the
|
|
49
59
|
* two cannot disagree about which field sits where. */
|
|
50
60
|
export function renderRow(row, widths) {
|
|
51
|
-
return
|
|
61
|
+
return runRowLine(row, (column, value) => padToWidth(value, widths[column]));
|
|
52
62
|
}
|
|
53
|
-
/** The `--summary` text: a header, one line per
|
|
63
|
+
/** The `--summary` text: a header, one line per role, then the total line. The role column pads to
|
|
54
64
|
* display width; each number column right-aligns under its header; the `time` column is the sum of
|
|
55
65
|
* each role's run spans, `—` when none of the role's rows carried a span; the last column,
|
|
56
66
|
* `last change`, is the newest change's timestamp (`—` when none) and is left-aligned, unpadded. */
|
|
57
67
|
export function renderSummary(summary) {
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
return renderSummaryTable("role", [...summary.roles, summary.total].map((row) => ({ label: roleLabel(row.role), row })));
|
|
69
|
+
}
|
|
70
|
+
/** The `--summary --by model` text: renderSummary's table with one line per model (`(unknown)` for
|
|
71
|
+
* the null bucket), then the total line. */
|
|
72
|
+
export function renderModelSummary(summary) {
|
|
73
|
+
return renderSummaryTable("model", [
|
|
74
|
+
...summary.models.map((row) => ({ label: row.model ?? "(unknown)", row })),
|
|
75
|
+
{ label: "total", row: summary.total },
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
function renderSummaryTable(header, all) {
|
|
79
|
+
const roleWidth = Math.max(...[header, ...all.map((r) => r.label)].map(displayWidth));
|
|
60
80
|
const cols = ["runs", "changes", "none", "refused", "problems", "tokens", "cost", "time"];
|
|
61
81
|
const cell = (r, c) => c === "tokens"
|
|
62
82
|
? compactTokens(r.tokens)
|
|
@@ -67,10 +87,10 @@ export function renderSummary(summary) {
|
|
|
67
87
|
? "—"
|
|
68
88
|
: humanSeconds(r.durationMs / 1000)
|
|
69
89
|
: String(c === "runs" ? r.runs : r[c]);
|
|
70
|
-
const widths = cols.map((c) => Math.max(c.length, ...all.map((r) => cell(r, c).length)));
|
|
71
|
-
const line = (
|
|
90
|
+
const widths = cols.map((c) => Math.max(c.length, ...all.map((r) => cell(r.row, c).length)));
|
|
91
|
+
const line = (role, cells, lastChange) => [padToWidth(role, roleWidth), ...cells.map((c, i) => c.padStart(widths[i] ?? 0)), lastChange].join(" ").trimEnd();
|
|
72
92
|
return [
|
|
73
|
-
line(
|
|
74
|
-
...all.map((
|
|
93
|
+
line(header, [...cols], "last change"),
|
|
94
|
+
...all.map(({ label, row }) => line(label, cols.map((c) => cell(row, c)), row.lastChangeTs === null ? "—" : formatTimestamp(row.lastChangeTs))),
|
|
75
95
|
].join("\n");
|
|
76
96
|
}
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { renderRow, renderSummary, widestWidths } from "./history-render.js";
|
|
1
|
+
import { fail, say, sayJson, sayJsonLine } from "../cli/cli-output.js";
|
|
2
|
+
import { durationLabel, failRivalShapes, failUntilShorterThanSince, flagValue, parseCountFlag, parseGrepFlag, parseRoleListScope, parseSinceFlag, rejectFlagLookingValue } from "../cli/cli-args.js";
|
|
3
|
+
import { HISTORY_DEFAULT_RUNS, HISTORY_MAX_RUNS, HISTORY_RESULT_FLAG_NAMES, HISTORY_SUMMARY_GROUPS, historyClass, parseHistoryResultClass, readRunRows, readRunRowsSince, runRows, summarizeRunRows, summarizeRunRowsByModel, runRowsMatchingGrep, runRowsOnModels } from "./history-data.js";
|
|
4
|
+
import { renderModelSummary, renderRow, renderSummary, widenWidths, widestWidths } from "./history-render.js";
|
|
6
5
|
import { LOGS_SINCE_MAX_MS, SPARSE_WINDOW_NOTE } from "../events/event-window.js";
|
|
7
|
-
import {
|
|
6
|
+
import { followUntilParentGone } from "../cli/log-commands.js";
|
|
7
|
+
import { hasFollowFlag } from "../cli/cli-flag-specs.js";
|
|
8
|
+
import { parseEventLine, readEventsTailWithEnd } from "../events/event-read.js";
|
|
9
|
+
import { eventsLogPath } from "../paths.js";
|
|
10
|
+
import { gotSuffix, splitCommaList, splitCommaListUnique } from "../text/text.js";
|
|
11
|
+
import { trimToLast } from "../collections.js";
|
|
8
12
|
import { typoSuffix } from "../text/suggest.js";
|
|
9
|
-
/**
|
|
13
|
+
/** Parse a `--model` value: a comma list of model selectors, deduplicated in first-seen order.
|
|
14
|
+
* A missing, empty or all-commas value fails with HISTORY_MODEL_VALUE_ERROR; selectors have no
|
|
15
|
+
* closed vocabulary, so none is rejected as unknown. Shared by the listing and the one-run
|
|
16
|
+
* trail's `--last --model`. */
|
|
17
|
+
export function parseHistoryModelList(raw) {
|
|
18
|
+
if (raw === undefined || raw === "")
|
|
19
|
+
fail(HISTORY_MODEL_VALUE_ERROR);
|
|
20
|
+
const pieces = splitCommaListUnique(raw);
|
|
21
|
+
if (pieces.length === 0)
|
|
22
|
+
fail(HISTORY_MODEL_VALUE_ERROR);
|
|
23
|
+
return pieces;
|
|
24
|
+
}
|
|
25
|
+
/** `tumwater history [--role <id>[,<id>...]] [-n N]`: one row per completed run, newest first. The
|
|
10
26
|
* observing half beside cmdLogs (log-commands.ts): read-only over the event log, stdout only —
|
|
11
|
-
* every datum rides the `run_end` events src/
|
|
27
|
+
* every datum rides the `run_end` events src/run/run-finalize.ts already writes, so this
|
|
12
28
|
* is a rendering of the existing record, not a new one. The rows themselves are collected
|
|
13
29
|
* by history-data.ts (beside src/report/report-data.ts and src/failure/failure-data.ts), and
|
|
14
30
|
* the aligned table/summary text they print is rendered by history-render.ts (beside
|
|
@@ -23,14 +39,46 @@ export const HISTORY_GREP_VALUE_ERROR = "history --grep needs a pattern";
|
|
|
23
39
|
* same wording. Its sibling of HISTORY_GREP_VALUE_ERROR: each names the command that printed
|
|
24
40
|
* it. */
|
|
25
41
|
export const HISTORY_RESULT_VALUE_ERROR = "history --result needs a value";
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
/** The missing-value error cmdHistory prints for a valueless, empty or comma-only `--model`,
|
|
43
|
+
* exported so cli.ts's rejectUnknownArgs spec for --model fails a trailing `history --model`
|
|
44
|
+
* with the same wording. Its sibling of HISTORY_RESULT_VALUE_ERROR. */
|
|
45
|
+
export const HISTORY_MODEL_VALUE_ERROR = "history --model needs a model selector";
|
|
46
|
+
/** The missing-value error for a valueless or empty `--by`, exported for cli.ts's flag spec. */
|
|
47
|
+
export const HISTORY_BY_VALUE_ERROR = "history --by needs role or model";
|
|
48
|
+
/** How many trailing events `history -f` keeps: the seed read and the rolling buffer a finished
|
|
49
|
+
* run's start is paired from. */
|
|
50
|
+
const HISTORY_FOLLOW_BUFFER_EVENTS = 2000;
|
|
51
|
+
/** Parse a `--result` value: a comma list of outcome classes (the spellings
|
|
52
|
+
* HISTORY_RESULT_FLAG_NAMES prints, plus the hidden `changes` alias), deduplicated in first-seen
|
|
53
|
+
* order. A missing, empty or all-commas value fails with HISTORY_RESULT_VALUE_ERROR; the first
|
|
54
|
+
* unknown piece fails naming the accepted classes (gotSuffix plus a did-you-mean hint). Shared by
|
|
55
|
+
* the listing and the one-run trail's `--last --result`. */
|
|
56
|
+
export function parseHistoryResultList(raw) {
|
|
57
|
+
if (raw === undefined || raw === "")
|
|
58
|
+
fail(HISTORY_RESULT_VALUE_ERROR);
|
|
59
|
+
const pieces = splitCommaList(raw);
|
|
60
|
+
if (pieces.length === 0)
|
|
61
|
+
fail(HISTORY_RESULT_VALUE_ERROR);
|
|
62
|
+
const parsed = [];
|
|
63
|
+
for (const piece of pieces) {
|
|
64
|
+
const cls = parseHistoryResultClass(piece);
|
|
65
|
+
if (cls === null)
|
|
66
|
+
fail(`history --result must be one of ${Object.values(HISTORY_RESULT_FLAG_NAMES).join(", ")}${gotSuffix(piece)}${typoSuffix(piece, Object.values(HISTORY_RESULT_FLAG_NAMES))}`);
|
|
67
|
+
if (!parsed.includes(cls))
|
|
68
|
+
parsed.push(cls);
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
/** `tumwater history [--role <id>[,<id>...]] [-n N] [-f] [--since <duration>]
|
|
73
|
+
* [--until <duration>] [--grep <text>] [--result <class>] [--model <selector>[,...]]`: print the
|
|
74
|
+
* last N completed runs, newest first — or,
|
|
75
|
+
* with `--since`, the runs of a bounded past window over the same record, the same shape
|
|
76
|
+
* `logs --since` and `report --since` speak. `--grep` keeps rows whose rendered line matches,
|
|
77
|
+
* `--result` narrows to the listed outcome classes (changes/none/problems, the dashboard History
|
|
78
|
+
* filter's buckets) and `--model` keeps runs that started on the listed models; all compose
|
|
79
|
+
* with --role, -n, and --since. Read-only: stdout only, no state file created — a missing or empty
|
|
32
80
|
* event log prints `no runs yet` and exits 0. */
|
|
33
|
-
export async function cmdHistory(root, args) {
|
|
81
|
+
export async function cmdHistory(root, args, seams = {}) {
|
|
34
82
|
// `--grep <text>` filters the row set case-insensitively against the WYSIWYG row line (plus a
|
|
35
83
|
// `run_end` prefix, the same haystack rule logs --grep applies to its rendered lines), so
|
|
36
84
|
// both observing views answer substring questions the same way. The flag scan itself — the
|
|
@@ -40,7 +88,7 @@ export async function cmdHistory(root, args) {
|
|
|
40
88
|
// --grep, rest is args.
|
|
41
89
|
const { rest, pattern: grepPattern } = parseGrepFlag(args, HISTORY_GREP_VALUE_ERROR);
|
|
42
90
|
const nRaw = flagValue(rest, "-n");
|
|
43
|
-
// `--since <duration>` is the window-shaped view over the same
|
|
91
|
+
// `--since <duration>` is the window-shaped view over the same run record the -n view
|
|
44
92
|
// dumps: a rival shape to -n, so they fail together naming both (the same rule and wording
|
|
45
93
|
// shape logs uses). It reuses the logs --since cap (same log, same cap — no third cap
|
|
46
94
|
// value) through parseSinceFlag, so the error wordings cannot drift. `--role` composes with
|
|
@@ -48,66 +96,108 @@ export async function cmdHistory(root, args) {
|
|
|
48
96
|
// against.
|
|
49
97
|
let rows;
|
|
50
98
|
let covered = true;
|
|
99
|
+
const follow = hasFollowFlag(rest);
|
|
100
|
+
if (follow) {
|
|
101
|
+
if (rest.includes("--since"))
|
|
102
|
+
failRivalShapes("history --since", "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
|
|
103
|
+
if (rest.includes("--summary"))
|
|
104
|
+
failRivalShapes("history --summary", "-f/--follow", "a summary tallies a bounded set; -f follows from now on");
|
|
105
|
+
}
|
|
106
|
+
const followTail = follow ? readEventsTailWithEnd(root, HISTORY_FOLLOW_BUFFER_EVENTS) : null;
|
|
51
107
|
const sinceMs = parseSinceFlag(rest, "history --since", LOGS_SINCE_MAX_MS);
|
|
52
|
-
|
|
108
|
+
const untilMs = parseSinceFlag(rest, "history --until", LOGS_SINCE_MAX_MS, "--until");
|
|
109
|
+
failUntilShorterThanSince("history", untilMs, sinceMs);
|
|
110
|
+
if (followTail !== null) {
|
|
111
|
+
const limit = nRaw !== null ? parseCountFlag("-n", nRaw, HISTORY_MAX_RUNS) : HISTORY_DEFAULT_RUNS;
|
|
112
|
+
// The seed goes through readRunRows, which widens its window until it finds `limit` rows;
|
|
113
|
+
// the tail buffer only feeds the live follow.
|
|
114
|
+
rows = readRunRows(root, limit, parseRoleListScope(root, rest));
|
|
115
|
+
}
|
|
116
|
+
else if (sinceMs !== null) {
|
|
53
117
|
if (nRaw !== null)
|
|
54
118
|
failRivalShapes("history --since", "-n", "a count and a window are rival shapes");
|
|
55
|
-
const role =
|
|
56
|
-
const windowed =
|
|
119
|
+
const role = parseRoleListScope(root, rest);
|
|
120
|
+
const windowed = readRunRowsSince(root, sinceMs, role, untilMs);
|
|
57
121
|
rows = windowed.rows;
|
|
58
122
|
covered = windowed.covered;
|
|
59
123
|
}
|
|
60
124
|
else {
|
|
61
125
|
// parseCountFlag's max caps the scan window: a typo'd "999999" must not build a
|
|
62
|
-
//
|
|
63
|
-
const limit = nRaw !== null ? parseCountFlag("-n", nRaw,
|
|
126
|
+
// HISTORY_MAX_RUNS-sized slice scan from a huge limit.
|
|
127
|
+
const limit = nRaw !== null ? parseCountFlag("-n", nRaw, HISTORY_MAX_RUNS) : HISTORY_DEFAULT_RUNS;
|
|
64
128
|
// The config is read (through loadConfigCached, never throwing) only when --role is present:
|
|
65
129
|
// a read-only view must not refuse a transiently broken tumwater.json.
|
|
66
|
-
const role =
|
|
67
|
-
rows =
|
|
130
|
+
const role = parseRoleListScope(root, rest);
|
|
131
|
+
rows = readRunRows(root, limit, role);
|
|
68
132
|
}
|
|
69
133
|
// The filter runs on the collected rows, before rendering and before --json serialization:
|
|
70
134
|
// the same rows the table would print, filtered the same way in both forms (the logs --grep
|
|
71
135
|
// rule: -n bounds the scanned window, not the printed rows).
|
|
72
136
|
if (grepPattern !== null)
|
|
73
|
-
rows =
|
|
137
|
+
rows = runRowsMatchingGrep(rows, grepPattern);
|
|
74
138
|
// The set --grep admitted, saved so the empty-state wording blames the filter that actually
|
|
75
139
|
// emptied it: if the result filter below removes rows grep matched, naming the pattern would
|
|
76
140
|
// be false (`no runs matching "widget"` when feature #7 does match). The GUI draws the same
|
|
77
141
|
// line between a search and its outcome filter.
|
|
78
142
|
const afterGrep = rows.length;
|
|
79
|
-
// `--result <class
|
|
80
|
-
// rule the dashboard's History filter applies (shared from
|
|
81
|
-
// composes with --role, -n, --since, and --grep, so no
|
|
82
|
-
// value fails with its own wording, and an unknown
|
|
83
|
-
// shape the other flag validators use, plus a
|
|
84
|
-
//
|
|
85
|
-
// over the same rows both the table
|
|
143
|
+
// `--result <class>[,<class>...]` narrows to the listed outcome buckets, reading the same
|
|
144
|
+
// canonical historyClass rule the dashboard's History filter applies (shared from
|
|
145
|
+
// history-data.ts, not copied). It composes with --role, -n, --since, and --grep, so no
|
|
146
|
+
// rival-shape rule: a missing or empty value fails with its own wording, and an unknown
|
|
147
|
+
// class names the accepted ones (the gotSuffix shape the other flag validators use, plus a
|
|
148
|
+
// did-you-mean hint for a near-miss of a real result — the same typoSuffix treatment
|
|
149
|
+
// `logs --type` gives its codes). Applied after --grep, over the same rows both the table
|
|
86
150
|
// and --json serialize.
|
|
87
|
-
const resultRaw = flagValue(rest, "--result");
|
|
88
|
-
let
|
|
151
|
+
const resultRaw = rejectFlagLookingValue("history --result", flagValue(rest, "--result"));
|
|
152
|
+
let resultClasses = null;
|
|
89
153
|
if (resultRaw !== null) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
154
|
+
const wanted = parseHistoryResultList(resultRaw);
|
|
155
|
+
resultClasses = wanted;
|
|
156
|
+
rows = rows.filter((r) => wanted.includes(historyClass(r.result)));
|
|
157
|
+
}
|
|
158
|
+
// `--model <selector>[,...]` keeps the rows whose run_start named exactly one of the listed
|
|
159
|
+
// selectors (case-sensitive, the strings `--summary --by model` prints); a null-model row never
|
|
160
|
+
// matches. An unknown selector is not an error: models have no closed vocabulary and an old log
|
|
161
|
+
// holds models the config has since dropped, so it simply keeps no rows.
|
|
162
|
+
const afterResult = rows.length;
|
|
163
|
+
const modelRaw = rejectFlagLookingValue("history --model", flagValue(rest, "--model"));
|
|
164
|
+
let models = null;
|
|
165
|
+
if (modelRaw !== null) {
|
|
166
|
+
const pieces = parseHistoryModelList(modelRaw);
|
|
167
|
+
models = pieces;
|
|
168
|
+
rows = runRowsOnModels(rows, pieces);
|
|
169
|
+
}
|
|
170
|
+
if (followTail !== null) {
|
|
171
|
+
await followHistory(root, rest, rows, followTail, (r) => rowKept(r, grepPattern, resultClasses, models), seams, rest.includes("--json"));
|
|
172
|
+
return;
|
|
96
173
|
}
|
|
97
174
|
// --json swaps the renderer for the collector's own payload, exactly as status --json and
|
|
98
175
|
// report --json: the same rows the table prints, each with ts/tokens/costUsd kept raw. A
|
|
99
176
|
// JSON document even when the log is empty ({"rows":[]} — never the prose `no runs yet`,
|
|
100
177
|
// the report --json precedent: the flag's output must be parseable in every exit-0 case).
|
|
178
|
+
// `--by role|model` picks how --summary groups its lines (role by default).
|
|
179
|
+
const byRaw = flagValue(rest, "--by");
|
|
180
|
+
let byModel = false;
|
|
181
|
+
if (rest.includes("--by")) {
|
|
182
|
+
if (!byRaw)
|
|
183
|
+
return fail(HISTORY_BY_VALUE_ERROR);
|
|
184
|
+
if (!HISTORY_SUMMARY_GROUPS.includes(byRaw)) {
|
|
185
|
+
fail(`history --by must be one of ${HISTORY_SUMMARY_GROUPS.join(", ")}${gotSuffix(byRaw)}${typoSuffix(byRaw, [...HISTORY_SUMMARY_GROUPS])}`);
|
|
186
|
+
}
|
|
187
|
+
if (!rest.includes("--summary"))
|
|
188
|
+
fail("history --by needs --summary (it picks how the summary groups runs)");
|
|
189
|
+
byModel = byRaw === "model";
|
|
190
|
+
}
|
|
101
191
|
const summary = rest.includes("--summary") && rows.length > 0;
|
|
102
192
|
if (rest.includes("--json")) {
|
|
103
|
-
sayJson(summary ?
|
|
193
|
+
sayJson(summary ? (byModel ? summarizeRunRowsByModel(rows) : summarizeRunRows(rows)) : { rows });
|
|
104
194
|
return;
|
|
105
195
|
}
|
|
106
196
|
if (rows.length === 0) {
|
|
107
197
|
// A grep that matched nothing must not borrow the empty-log prose: `no runs yet` and
|
|
108
198
|
// `no runs in 2h` are statements about the LOG and the WINDOW, and both are false when
|
|
109
199
|
// rows were scanned and filtered out — the operator would read a quiet team where the
|
|
110
|
-
// truth is `the
|
|
200
|
+
// truth is `the runs are there, none match`. The logs --grep rule (its `no events
|
|
111
201
|
// matching "<pattern>"`, src/cli/log-commands.ts) names the pattern instead; --json stays
|
|
112
202
|
// a bare {"rows":[]} document above, never prose in either form.
|
|
113
203
|
//
|
|
@@ -115,17 +205,21 @@ export async function cmdHistory(root, args) {
|
|
|
115
205
|
// empty log or window scanned no rows, so the result filter removed nothing and the
|
|
116
206
|
// empty-log/window prose is the true one — the dashboard's History view draws the same
|
|
117
207
|
// line. A grep that admitted nothing keeps its own wording regardless of --result.
|
|
118
|
-
say(
|
|
119
|
-
? `no runs
|
|
120
|
-
:
|
|
121
|
-
? `no runs
|
|
122
|
-
:
|
|
123
|
-
?
|
|
124
|
-
:
|
|
208
|
+
say(models !== null && afterResult > 0
|
|
209
|
+
? `no runs on model "${models.join(",")}"`
|
|
210
|
+
: resultClasses !== null && afterGrep > 0
|
|
211
|
+
? `no runs with result "${resultClasses.join(",")}"`
|
|
212
|
+
: grepPattern !== null
|
|
213
|
+
? `no runs matching "${grepPattern}"`
|
|
214
|
+
: sinceMs === null
|
|
215
|
+
? "no runs yet"
|
|
216
|
+
: untilMs === null
|
|
217
|
+
? `no runs in ${durationLabel(sinceMs)}`
|
|
218
|
+
: `no runs between ${durationLabel(sinceMs)} and ${durationLabel(untilMs)} ago`);
|
|
125
219
|
return;
|
|
126
220
|
}
|
|
127
221
|
if (summary)
|
|
128
|
-
say(renderSummary(
|
|
222
|
+
say(byModel ? renderModelSummary(summarizeRunRowsByModel(rows)) : renderSummary(summarizeRunRows(rows)));
|
|
129
223
|
else {
|
|
130
224
|
const widths = widestWidths(rows);
|
|
131
225
|
say(rows.map((r) => renderRow(r, widths)).join("\n"));
|
|
@@ -137,3 +231,50 @@ export async function cmdHistory(root, args) {
|
|
|
137
231
|
if (!covered)
|
|
138
232
|
say(SPARSE_WINDOW_NOTE);
|
|
139
233
|
}
|
|
234
|
+
/** The row filters `--grep`, `--result` and `--model` apply, as one predicate for followed rows. */
|
|
235
|
+
function rowKept(row, grep, classes, models) {
|
|
236
|
+
if (grep !== null && runRowsMatchingGrep([row], grep).length === 0)
|
|
237
|
+
return false;
|
|
238
|
+
if (classes !== null && !classes.includes(historyClass(row.result)))
|
|
239
|
+
return false;
|
|
240
|
+
return models === null || runRowsOnModels([row], models).length > 0;
|
|
241
|
+
}
|
|
242
|
+
/** `history -f`: print the seed rows oldest first, then one row per `run_end` as it lands. A run
|
|
243
|
+
* whose change waits to merge prints `queued` and no second line when the merge resolves. With
|
|
244
|
+
* `json`, each row is one `sayJsonLine` object (the `history --json` row shape), nothing else. */
|
|
245
|
+
async function followHistory(root, rest, seed, tail, keep, seams, json) {
|
|
246
|
+
const ordered = [...seed].reverse();
|
|
247
|
+
let widths = widestWidths(ordered);
|
|
248
|
+
if (json)
|
|
249
|
+
for (const r of ordered)
|
|
250
|
+
sayJsonLine(r);
|
|
251
|
+
else if (ordered.length > 0)
|
|
252
|
+
say(ordered.map((r) => renderRow(r, widths)).join("\n"));
|
|
253
|
+
const seen = new Set(seed.map((r) => `${r.role}#${r.run}`));
|
|
254
|
+
const buffer = [...tail.events];
|
|
255
|
+
const role = parseRoleListScope(root, rest);
|
|
256
|
+
await followUntilParentGone(eventsLogPath(root), tail.coveredEnd, (lines) => {
|
|
257
|
+
for (const line of lines.filter(Boolean)) {
|
|
258
|
+
const e = parseEventLine(line);
|
|
259
|
+
if (!e)
|
|
260
|
+
continue;
|
|
261
|
+
buffer.push(e);
|
|
262
|
+
trimToLast(buffer, HISTORY_FOLLOW_BUFFER_EVENTS);
|
|
263
|
+
if (e.type !== "run_end")
|
|
264
|
+
continue;
|
|
265
|
+
const row = runRows(buffer, 1, role)[0];
|
|
266
|
+
if (!row)
|
|
267
|
+
continue;
|
|
268
|
+
const key = `${row.role}#${row.run}`;
|
|
269
|
+
if (seen.has(key) || !keep(row))
|
|
270
|
+
continue;
|
|
271
|
+
seen.add(key);
|
|
272
|
+
if (json) {
|
|
273
|
+
sayJsonLine(row);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
widths = widenWidths(widths, widestWidths([row]));
|
|
277
|
+
say(renderRow(row, widths));
|
|
278
|
+
}
|
|
279
|
+
}, seams);
|
|
280
|
+
}
|