tumwater 0.2.0 → 0.3.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 +42 -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 +4 -3
- 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 +402 -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 +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -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 +254 -0
- package/dist/src/backlog/backlog-release.js +117 -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-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -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 +1 -2
- 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 +118 -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 +150 -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 +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- 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 +13 -1
- package/dist/src/config/config-fallbacks.js +93 -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 +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- 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 +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- 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 +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- 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 +77 -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 +36 -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 +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- 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 +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- 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 +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -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 +25 -9
- 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 +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- 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 +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -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 +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -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 +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -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 +116 -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 +18 -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 +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -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 +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- 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 +155 -360
- 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 +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -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} +108 -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} +108 -50
- 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} +91 -56
- 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 +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -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} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- 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 +43 -16
- 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 +28 -28
- package/dist/src/status/status-roles.js +39 -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 +5 -6
- 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 +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- 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 +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -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 +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- 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 +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -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 +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -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 +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -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 +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { addTo, rankCountEntries, sortByValueDesc } from "../collections.js";
|
|
2
2
|
import { compactTokens, usd } from "../text/format.js";
|
|
3
3
|
import { durationLabel } from "../cli/cli-args.js";
|
|
4
|
-
import { monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
4
|
+
import { boundedWindowLine, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
5
5
|
import { eventsRotationLabel } from "../events/events.js";
|
|
6
6
|
import { SPARSE_WINDOW_NOTE } from "../events/event-window.js";
|
|
7
7
|
import { shortSpanPhrase } from "../text/phrases.js";
|
|
8
8
|
import { markdownTable } from "../text/markdown.js";
|
|
9
|
-
import { baseRoleOf } from "../roles/
|
|
9
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
10
|
+
import { backendDisplayName } from "../agent/backend-traits.js";
|
|
10
11
|
/** Bar width for one day: up to 20 blocks scaled to the window's max tokensOut —
|
|
11
12
|
* round(20·v/max), min 1 when v > 0. */
|
|
12
13
|
function barWidth(v, max) {
|
|
@@ -37,18 +38,37 @@ function rankedRoleTotals(series, pick) {
|
|
|
37
38
|
function rankedRoleLine(label, pairs, fmt) {
|
|
38
39
|
return `**${label}:** ${pairs.length === 0 ? "-" : pairs.map(([r, n]) => `${r} — ${fmt(n)}`).join(" · ")}`;
|
|
39
40
|
}
|
|
40
|
-
/**
|
|
41
|
+
/** Push one "**Label:** …" ranked-role line, dropping roles with a non-positive value (zero
|
|
42
|
+
* count/spend) before rendering, so an all-zero window still renders "-" exactly like the
|
|
43
|
+
* unfiltered lines. fmt formats each role's number (default String for count lines; pass
|
|
44
|
+
* usd/compactTokens for spend/token lines). All the "X by role" lines share this filter-rule
|
|
45
|
+
* home so the zero-means-render-dash guard cannot drift between the --since and day renders. */
|
|
46
|
+
function pushRankedRoleLine(lines, label, pairs, fmt = String) {
|
|
47
|
+
lines.push(rankedRoleLine(label, pairs.filter(([, n]) => n > 0), fmt));
|
|
48
|
+
}
|
|
49
|
+
/** The per-day cameo line of the team day report — "MMM D <cell>" cells for each day that
|
|
41
50
|
* carries a value, joined under a bold title, or undefined when no day has one (zero means
|
|
42
|
-
* absent). The one home of that cameo rendering, shared by the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
|
|
51
|
+
* absent). The one home of that cameo rendering, shared by the per-day medians, the red-check
|
|
52
|
+
* count and the main-red wall-clock lines so their date+cell shape and title line cannot drift.
|
|
53
|
+
* Each caller supplies its own cell formatter (shortSpanPhrase for the medians, `×n` for the
|
|
54
|
+
* red-check count, durationLabel for the main-red span). */
|
|
55
|
+
function dayCameoLine(series, title, pick, fmt = shortSpanPhrase) {
|
|
46
56
|
const cells = series.flatMap((d) => {
|
|
47
57
|
const v = pick(d);
|
|
48
|
-
return v === undefined ? [] : [`${monthDay(d.date)} ${
|
|
58
|
+
return v === undefined ? [] : [`${monthDay(d.date)} ${fmt(v)}`];
|
|
49
59
|
});
|
|
50
60
|
return cells.length > 0 ? `**${title}:** ${cells.join(" · ")}` : undefined;
|
|
51
61
|
}
|
|
62
|
+
/** Push a day-cameo line onto `lines` only when some day carries a value — the “compute a
|
|
63
|
+
* cameo line, push it when present” boilerplate every per-day cell repeats. Renders via
|
|
64
|
+
* dayCameoLine (the one cameo shape), so the date+cell shape and title line cannot drift.
|
|
65
|
+
* Zero-means-absent: a day whose field is undefined renders no cell, and a series with no such
|
|
66
|
+
* day pushes no line. */
|
|
67
|
+
function pushDayCameoLine(lines, series, title, pick, fmt = shortSpanPhrase) {
|
|
68
|
+
const line = dayCameoLine(series, title, pick, fmt);
|
|
69
|
+
if (line !== undefined)
|
|
70
|
+
lines.push(line);
|
|
71
|
+
}
|
|
52
72
|
/** Push the `Cost by provider` and `Estimated by provider` lines of the per-provider spend
|
|
53
73
|
* breakdown (plans: per-provider spend), from the caller's already-ranked real- and notional-
|
|
54
74
|
* spend pairs. Role scoping is the caller's `role === undefined` gate — this helper only
|
|
@@ -80,13 +100,38 @@ function mergeShareLine(t) {
|
|
|
80
100
|
const failed = (t.mergeFailed ?? 0) > 0 ? ` (${t.mergeFailed} failed)` : "";
|
|
81
101
|
return `of which merge runs: ${t.mergeRuns} runs${failed} · ${compactTokens(t.mergeTokens)} tokens · ${usd(t.mergeCostUsd)} (reviewer + conflict resolution)`;
|
|
82
102
|
}
|
|
83
|
-
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* an
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
/** A float-robust "the per-backend notional sums account for the window's total" check: the two
|
|
104
|
+
* numbers fold in different orders (per-event vs per-backend-then-summed), so their float traces
|
|
105
|
+
* need not coincide exactly even when every subscription event is attributed — the gap lands
|
|
106
|
+
* around machine epsilon. A small relative slack absorbs that reordering noise, while a genuinely
|
|
107
|
+
* partial map (an unattributed subscription merge, or a legacy event with no agent) is off by a
|
|
108
|
+
* real, much larger amount and must fall back to the generic line. */
|
|
109
|
+
function notionalSumsCoverTotal(summed, total) {
|
|
110
|
+
return Math.abs(summed - total) <= 1e-9 * Math.max(1, Math.abs(total));
|
|
111
|
+
}
|
|
112
|
+
/** The notional-spend line(s) (plans/agent-backends.md part 6a, codex-backend.md part 3d/9):
|
|
113
|
+
* the API-equivalent cost of subscription runs folded into the window. Never part of the
|
|
114
|
+
* totals' costUsd, because a subscription run is exempt from the daily dollar cap; the caller
|
|
115
|
+
* prints it only when a subscription run actually folded in (zero means absent, like the merge
|
|
116
|
+
* line), so an API-only team's report is unchanged. When the window's notional spend fully
|
|
117
|
+
* decomposes into backends, one labeled line names each backend — including a single non-claude
|
|
118
|
+
* backend (a codex-only window), so a codex team's line never reads "claude". A claude-only
|
|
119
|
+
* account, an old-log window, or unattributed merge spend keeps one generic lowercase "claude
|
|
120
|
+
* subscription" line — byte-identical to the pre-codex render. */
|
|
121
|
+
function notionalLines(t) {
|
|
122
|
+
const total = t.notionalCostUsd ?? 0;
|
|
123
|
+
const byBackend = t.notionalByBackend ?? {};
|
|
124
|
+
const backends = Object.entries(byBackend).filter(([, c]) => c > 0);
|
|
125
|
+
const summed = backends.reduce((n, [, c]) => n + c, 0);
|
|
126
|
+
// A single fully-attributed claude account collapses to the generic line (its true or
|
|
127
|
+
// fallback backend is claude, and the capitalization keeps old/byte-identical output);
|
|
128
|
+
// every other fully-attributed window — multi-backend, or a single codex/other backend —
|
|
129
|
+
// names each backend on its own line.
|
|
130
|
+
const singleClaude = backends.length === 1 && backends[0]?.[0] === "claude";
|
|
131
|
+
if (notionalSumsCoverTotal(summed, total) && !singleClaude) {
|
|
132
|
+
return backends.map(([backend, c]) => `${backendDisplayName(backend)} subscription, API-equivalent: ${usd(c)} estimated`);
|
|
133
|
+
}
|
|
134
|
+
return [`claude subscription, API-equivalent: ${usd(total)} estimated`];
|
|
90
135
|
}
|
|
91
136
|
/** The scope note a `--role` report prints: merge runs belong to the merge slot and the
|
|
92
137
|
* day-granular backlog tallies (features done / bugs fixed / plans added / bugs filed) come
|
|
@@ -104,7 +149,7 @@ function pushReportSummary(lines, t, tail = "", scoped = false) {
|
|
|
104
149
|
if (t.mergeRuns > 0)
|
|
105
150
|
lines.push(mergeShareLine(t));
|
|
106
151
|
if ((t.notionalCostUsd ?? 0) > 0)
|
|
107
|
-
lines.push(
|
|
152
|
+
lines.push(...notionalLines(t));
|
|
108
153
|
if (scoped)
|
|
109
154
|
lines.push(ROLE_SCOPE_NOTE);
|
|
110
155
|
}
|
|
@@ -116,7 +161,9 @@ export function renderSinceReportMarkdown(data) {
|
|
|
116
161
|
const lines = [];
|
|
117
162
|
lines.push("# tumwater usage report");
|
|
118
163
|
lines.push("");
|
|
119
|
-
lines.push(
|
|
164
|
+
lines.push(data.untilMs !== undefined && data.toIso !== undefined
|
|
165
|
+
? boundedWindowLine(durationLabel(data.sinceMs), durationLabel(data.untilMs), data.fromIso, data.toIso)
|
|
166
|
+
: sinceWindowLine(durationLabel(data.sinceMs), data.fromIso));
|
|
120
167
|
if (data.role !== undefined)
|
|
121
168
|
lines.push(`role: ${data.role}`);
|
|
122
169
|
lines.push("");
|
|
@@ -131,10 +178,10 @@ export function renderSinceReportMarkdown(data) {
|
|
|
131
178
|
// omitted — an all-zero window renders "-" like the other lines. Output tokens are the
|
|
132
179
|
// resource the budget charges, so this is the line an operator reads to find which role's
|
|
133
180
|
// prompt loop burned the window's tokens.
|
|
134
|
-
lines
|
|
181
|
+
pushRankedRoleLine(lines, "Tokens by role", rankedRoleMap(data.tokensByRole), compactTokens);
|
|
135
182
|
// Same breakdown for spend: ranked by spend desc then name asc, zero-spend roles omitted —
|
|
136
|
-
// an all-zero window renders "-" like the
|
|
137
|
-
lines
|
|
183
|
+
// an all-zero window renders "-" like the runs line.
|
|
184
|
+
pushRankedRoleLine(lines, "Cost by role", rankedRoleMap(data.costByRole), usd);
|
|
138
185
|
// The same breakdown per PROVIDER (plans: per-provider spend), gated to the team report
|
|
139
186
|
// like its day sibling. SinceReport.costByProvider is always present (empty when none), so
|
|
140
187
|
// the zero-means-absent gate prints no line for a window with no provider spend.
|
|
@@ -150,19 +197,25 @@ export function renderSinceReportMarkdown(data) {
|
|
|
150
197
|
if (modelPairs.length > 0)
|
|
151
198
|
lines.push(rankedRoleLine("Runs by model", modelPairs, String));
|
|
152
199
|
}
|
|
200
|
+
// Main's red wall-clock over the window (main's own, not any role's — a role-scoped
|
|
201
|
+
// collector carries 0, so this never prints for a scoped window). Zero-absent: no line for
|
|
202
|
+
// a window main was never red.
|
|
203
|
+
if (data.mainRedMs > 0)
|
|
204
|
+
lines.push(`**Main red:** ${durationLabel(data.mainRedMs)}`);
|
|
153
205
|
// The same breakdown for review friction: rejections per role from review_rejected events;
|
|
154
206
|
// a window with none renders "-" like the other lines.
|
|
155
|
-
lines
|
|
156
|
-
lines
|
|
157
|
-
lines
|
|
207
|
+
pushRankedRoleLine(lines, "Rejections by role", rankedRoleMap(data.rejectionsByRole));
|
|
208
|
+
pushRankedRoleLine(lines, "Revisions by role", rankedRoleMap(data.revisionsByRole));
|
|
209
|
+
pushRankedRoleLine(lines, "Refusals by role", rankedRoleMap(data.refusalsByRole));
|
|
210
|
+
pushRankedRoleLine(lines, "Reverts by role", rankedRoleMap(data.revertsByRole));
|
|
158
211
|
// The same breakdown for outright failures: error-class run_ends per role under the
|
|
159
|
-
// digest's OUTCOME_CLASS — the
|
|
212
|
+
// digest's OUTCOME_CLASS — the runs that burned agent time and merged nothing without
|
|
160
213
|
// going through review, which rejections/revisions would miss entirely. A window with
|
|
161
214
|
// none renders "-" like the other lines.
|
|
162
|
-
lines
|
|
163
|
-
lines
|
|
164
|
-
lines
|
|
165
|
-
// Deferred maintenance
|
|
215
|
+
pushRankedRoleLine(lines, "Failed runs by role", rankedRoleMap(data.failedRunsByRole));
|
|
216
|
+
pushRankedRoleLine(lines, "Fallbacks by role", rankedRoleMap(data.fallbacksByRole));
|
|
217
|
+
pushRankedRoleLine(lines, "Image reroutes by role", rankedRoleMap(data.imageReroutesByRole));
|
|
218
|
+
// Deferred maintenance runs per reason code, the --since twin of the day render's line:
|
|
166
219
|
// how many due maintenance runs stood down and why (backlog / no-work / plans-full).
|
|
167
220
|
// Gated to the team report — a reason is not a role, so a role-scoped window omits it.
|
|
168
221
|
// Zero-means-absent: a window with no deferral prints no line.
|
|
@@ -195,7 +248,7 @@ export function renderSinceReportMarkdown(data) {
|
|
|
195
248
|
// Subscription spend is notional: its own line, only when the window has any — the same
|
|
196
249
|
// conditional/filtered `usd` form the day render's "Estimated by role" uses.
|
|
197
250
|
if (data.notionalByRole !== undefined) {
|
|
198
|
-
lines
|
|
251
|
+
pushRankedRoleLine(lines, "Estimated by role", rankedRoleMap(data.notionalByRole), usd);
|
|
199
252
|
}
|
|
200
253
|
lines.push("");
|
|
201
254
|
// Exactly one final line, chosen by the collector's coverage proof: either a hedged note
|
|
@@ -211,12 +264,12 @@ export function renderSinceReportMarkdown(data) {
|
|
|
211
264
|
lines.push("backlog tallies (features done / bugs fixed / plans added / bugs filed) need the day report (--days)");
|
|
212
265
|
return lines.join("\n");
|
|
213
266
|
}
|
|
214
|
-
/** Cost per merged commit by base role. Spend comes from costByRole (keyed by
|
|
267
|
+
/** Cost per merged commit by base role. Spend comes from costByRole (keyed by role id, folded to
|
|
215
268
|
* its base role so `feature-2` joins `feature`), commits from commitsByRole (already base-role
|
|
216
269
|
* keyed). `merged` is [role, cost per commit, commits] for roles with both, ranked by cost per
|
|
217
270
|
* commit desc then name asc; `idle` is roles that spent and merged nothing, ranked by spend.
|
|
218
271
|
* Merge spend (the reviewer's) and notional subscription spend stay out by default, matching
|
|
219
|
-
* `**Cost by role:**` (`pick` selects another per-
|
|
272
|
+
* `**Cost by role:**` (`pick` selects another per-role-id spend map, e.g. notionalByRole); a
|
|
220
273
|
* role with commits but no spend in the picked map appears in neither list. */
|
|
221
274
|
function costPerCommitPairs(series, pick = (d) => d.costByRole) {
|
|
222
275
|
const cost = {};
|
|
@@ -273,22 +326,22 @@ export function renderReportMarkdown(data) {
|
|
|
273
326
|
lines.push("");
|
|
274
327
|
const maxTokens = data.series.reduce((m, d) => Math.max(m, d.tokensOut), 0);
|
|
275
328
|
lines.push(...markdownTable(["day", "tokens out", "runs", "commits", "cost"], data.series.map((d) => {
|
|
276
|
-
const
|
|
329
|
+
const runs = Object.values(d.runsByRole).reduce((a, b) => a + b, 0);
|
|
277
330
|
const w = maxTokens > 0 ? barWidth(d.tokensOut, maxTokens) : 0;
|
|
278
331
|
const bar = w > 0 ? ` ${"█".repeat(w)}` : ""; // Zero days carry no bar (and no stray space).
|
|
279
|
-
return [monthDay(d.date), `${compactTokens(d.tokensOut)}${bar}`, String(
|
|
332
|
+
return [monthDay(d.date), `${compactTokens(d.tokensOut)}${bar}`, String(runs), String(d.commits), usd(d.costUsd)];
|
|
280
333
|
}), ["left", "right", "right", "right", "right"]));
|
|
281
334
|
lines.push("");
|
|
282
335
|
lines.push(rankedRoleLine("Runs by role", rankedRoleTotals(data.series, (d) => d.runsByRole), String));
|
|
283
336
|
// The same breakdown for output tokens (the resource the budget charges): window totals per
|
|
284
|
-
// role from tokensByRole, ranked like the
|
|
285
|
-
// all-zero window renders "-" like the
|
|
286
|
-
lines
|
|
337
|
+
// role from tokensByRole, ranked like the runs line. Zero-token roles are omitted; an
|
|
338
|
+
// all-zero window renders "-" like the runs line.
|
|
339
|
+
pushRankedRoleLine(lines, "Tokens by role", rankedRoleTotals(data.series, (d) => d.tokensByRole ?? {}), compactTokens);
|
|
287
340
|
// The same breakdown for spend: window totals per role from costByRole, ranked by spend
|
|
288
341
|
// desc then name asc — spend is what the operator acts on (an operator tuning per-role
|
|
289
|
-
// intervals wants the burning
|
|
290
|
-
// omitted; an all-zero window renders "-" like the
|
|
291
|
-
lines
|
|
342
|
+
// intervals wants the burning role first, not the busiest one). Zero-spend roles are
|
|
343
|
+
// omitted; an all-zero window renders "-" like the runs line.
|
|
344
|
+
pushRankedRoleLine(lines, "Cost by role", rankedRoleTotals(data.series, (d) => d.costByRole), usd);
|
|
292
345
|
// The same breakdown per PROVIDER (plans: per-provider spend): which seam of a mixed team
|
|
293
346
|
// spent what, straight from the events' providerCost maps (run_end's own runs and the
|
|
294
347
|
// merge's reviewer seam). Gated to the team report — a provider is not a role, so a
|
|
@@ -308,18 +361,19 @@ export function renderReportMarkdown(data) {
|
|
|
308
361
|
lines.push(rankedRoleLine("Runs by model", modelPairs, String));
|
|
309
362
|
}
|
|
310
363
|
// The same breakdown for review friction: window rejections per role from review_rejected
|
|
311
|
-
// events, ranked like the
|
|
312
|
-
lines
|
|
313
|
-
lines
|
|
314
|
-
lines
|
|
364
|
+
// events, ranked like the runs line; a window with none renders "-".
|
|
365
|
+
pushRankedRoleLine(lines, "Rejections by role", rankedRoleTotals(data.series, (d) => d.rejectionsByRole ?? {}));
|
|
366
|
+
pushRankedRoleLine(lines, "Revisions by role", rankedRoleTotals(data.series, (d) => d.revisionsByRole ?? {}));
|
|
367
|
+
pushRankedRoleLine(lines, "Refusals by role", rankedRoleTotals(data.series, (d) => d.refusalsByRole ?? {}));
|
|
368
|
+
pushRankedRoleLine(lines, "Reverts by role", rankedRoleTotals(data.series, (d) => d.revertsByRole ?? {}));
|
|
315
369
|
// The same breakdown for outright failures: error-class run_ends per role under the
|
|
316
|
-
// digest's OUTCOME_CLASS —
|
|
370
|
+
// digest's OUTCOME_CLASS — runs that burned agent time and merged nothing without going
|
|
317
371
|
// through review, which rejections/revisions would miss entirely. Zero-means-absent, so a
|
|
318
372
|
// failure-free window renders no line and an all-zero window renders "-" like the others.
|
|
319
|
-
lines
|
|
320
|
-
lines
|
|
321
|
-
lines
|
|
322
|
-
// Deferred maintenance
|
|
373
|
+
pushRankedRoleLine(lines, "Failed runs by role", rankedRoleTotals(data.series, (d) => d.failedRunsByRole ?? {}));
|
|
374
|
+
pushRankedRoleLine(lines, "Fallbacks by role", rankedRoleTotals(data.series, (d) => d.fallbacksByRole ?? {}));
|
|
375
|
+
pushRankedRoleLine(lines, "Image reroutes by role", rankedRoleTotals(data.series, (d) => d.imageReroutesByRole ?? {}));
|
|
376
|
+
// Deferred maintenance runs per reason code — how many due maintenance runs stood down
|
|
323
377
|
// and why, so an idle day reads as backlog-open / no-work / plans-full rather than blank,
|
|
324
378
|
// the per-role breakdown of the deferred maintenances. Gated to the team report — a
|
|
325
379
|
// reason is not a role (ROLE_SCOPE_NOTE covers the omission). Zero-means-absent: a window
|
|
@@ -352,29 +406,32 @@ export function renderReportMarkdown(data) {
|
|
|
352
406
|
lines.push(rankedRoleLine("Merge failures by reason", mergeFailureReasons, String));
|
|
353
407
|
// Per-day median passing check time, so a suite slowing over days shows; days without a
|
|
354
408
|
// check are left out, and no line prints when no day has one (zero means absent).
|
|
355
|
-
|
|
356
|
-
if (medians !== undefined)
|
|
357
|
-
lines.push(medians);
|
|
409
|
+
pushDayCameoLine(lines, data.series, "Merge check median", (d) => d.checkMedianMs);
|
|
358
410
|
// Per-day count of red full-suite checks, beside the passing medians above; a day whose
|
|
359
411
|
// suite went red before passing shows here. Zero-absent: no line when no day fails.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
412
|
+
pushDayCameoLine(lines, data.series, "Red merge checks", (d) => d.checkFailures, (n) => `×${n}`);
|
|
413
|
+
// Per-day main red wall-clock, beside the red-check counts above: how long each day main
|
|
414
|
+
// had no passing full check (the span every code role was held). Team-only (main's own
|
|
415
|
+
// red time, not any role's) and zero-absent: no line when no day was red.
|
|
416
|
+
pushDayCameoLine(lines, data.series, "Main red", (d) => d.mainRedMs, durationLabel);
|
|
417
|
+
pushDayCameoLine(lines, data.series, "Merge wait median", (d) => d.mergeWaitMedianMs);
|
|
418
|
+
// How long the merge slot itself held each change it landed, beside the merge-wait median:
|
|
419
|
+
// the queue backlog (merge wait minus slot time) apart from the slot's own review/check/
|
|
420
|
+
// rebase/merge work. Team-only (the slot is the team's), omitted when no day carries one.
|
|
421
|
+
pushDayCameoLine(lines, data.series, "Merge slot median", (d) => d.mergeSlotMedianMs);
|
|
422
|
+
pushDayCameoLine(lines, data.series, "Run duration median", (d) => d.runMedianMs);
|
|
423
|
+
// How long the review gate ran per day — how long the reviewer's verdict run(s) took —
|
|
424
|
+
// beside the run/merge-wait medians; pushDayCameoLine omits the line when no day carries one.
|
|
425
|
+
pushDayCameoLine(lines, data.series, "Review duration median", (d) => d.reviewMedianMs);
|
|
426
|
+
pushDayCameoLine(lines, data.series, "Provider wait median", (d) => d.providerWaitMedianMs);
|
|
427
|
+
pushDayCameoLine(lines, data.series, "Workspace wait median", (d) => d.slotWaitMedianMs);
|
|
428
|
+
pushDayCameoLine(lines, data.series, "Check wait median", (d) => d.checkWaitMedianMs);
|
|
372
429
|
}
|
|
373
430
|
lines.push(costPerCommitLine("Cost per commit", costPerCommitPairs(data.series)));
|
|
374
431
|
// Subscription spend is notional: its own two lines, only when the window has any.
|
|
375
432
|
if ((t.notionalCostUsd ?? 0) > 0) {
|
|
376
433
|
const notionalOf = (d) => d.notionalByRole ?? {};
|
|
377
|
-
lines
|
|
434
|
+
pushRankedRoleLine(lines, "Estimated by role", rankedRoleTotals(data.series, notionalOf), usd);
|
|
378
435
|
lines.push(costPerCommitLine("Estimated per commit", costPerCommitPairs(data.series, notionalOf)));
|
|
379
436
|
}
|
|
380
437
|
// Commits per backlog item is day-granular backlog data (features done + bugs fixed), so it
|
|
@@ -382,6 +439,18 @@ export function renderReportMarkdown(data) {
|
|
|
382
439
|
const items = t.featuresDone + t.bugsFixed;
|
|
383
440
|
const perItem = items > 0 ? ` · ${(t.commits / items).toFixed(1)} commits per backlog item` : "";
|
|
384
441
|
lines.push(`**Commits:** ${t.workCommits ?? 0} work / ${t.maintenanceCommits ?? 0} maintenance${perItem}`);
|
|
442
|
+
// How long each finished plan or bug waited from filing to done, per completion day — the
|
|
443
|
+
// median backlog age at which the day's items closed. Team-only (a lag has no role) and
|
|
444
|
+
// zero-means-absent: no line when no day carries one. Values are whole days from
|
|
445
|
+
// daysBetween, so a plain `${n}d` is exact where medianLine's shortSpanPhrase would print
|
|
446
|
+
// minutes.
|
|
447
|
+
if (data.role === undefined) {
|
|
448
|
+
const lags = data.series.flatMap((d) => d.backlogLagMedianDays === undefined
|
|
449
|
+
? []
|
|
450
|
+
: [`${monthDay(d.date)} ${d.backlogLagMedianDays}d`]);
|
|
451
|
+
if (lags.length > 0)
|
|
452
|
+
lines.push(`**Backlog lag median:** ${lags.join(" · ")}`);
|
|
453
|
+
}
|
|
385
454
|
// Where the bugfix work was hardest to confirm: the window's fixed bugs tallied by their
|
|
386
455
|
// `Validation gap` tag, so the team can see where its own test infrastructure was weakest
|
|
387
456
|
// (recorded first, consumed now). Team-only (a tag has no role) and zero-means-absent: no
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -6,28 +6,41 @@ import { collectReport, collectReportSince } from "./report-data.js";
|
|
|
6
6
|
import { collectFailureReport, collectFailureReportSince } from "../failure/failure-data.js";
|
|
7
7
|
import { renderFailureMarkdown } from "../failure/failure-render.js";
|
|
8
8
|
import { renderReportMarkdown, renderSinceReportMarkdown } from "./report-render.js";
|
|
9
|
-
import { say, sayJson, sayJsonOrRender } from "../cli/cli-output.js";
|
|
10
|
-
import {
|
|
9
|
+
import { fail, say, sayJson, sayJsonOrRender } from "../cli/cli-output.js";
|
|
10
|
+
import { followView } from "../cli/follow-view.js";
|
|
11
|
+
import { durationLabel, failRivalShapes, flagValue, parseCountFlag, parseRoleListScope, parseSinceFlag } from "../cli/cli-args.js";
|
|
12
|
+
import { hasFollowFlag } from "../cli/cli-flag-specs.js";
|
|
11
13
|
import { REPORT_DEFAULT_DAYS, REPORT_MAX_DAYS, REPORT_SINCE_MAX_MS } from "../events/event-window.js";
|
|
12
|
-
/** `tumwater report [--days <n>] [--failures] [--since <duration>]
|
|
14
|
+
/** `tumwater report [--days <n>] [--failures] [--since <duration>]
|
|
15
|
+
* [--until <duration>]`: parse the flags and
|
|
13
16
|
* print. Unknown-args rejection and the no-ready-repo-gate decision stay in cli.ts's case,
|
|
14
17
|
* like every other command's. */
|
|
15
|
-
export async function cmdReport(root, args) {
|
|
18
|
+
export async function cmdReport(root, args, seams = {}) {
|
|
16
19
|
// --since is handled before the day-shape reads: it is a rival shape (totals over a
|
|
17
20
|
// trailing window vs a series over whole days), not a modifier of either.
|
|
18
21
|
const ms = parseSinceFlag(args, "report --since", REPORT_SINCE_MAX_MS);
|
|
22
|
+
const untilMs = parseSinceFlag(args, "report --until", REPORT_SINCE_MAX_MS, "--until");
|
|
23
|
+
if (untilMs !== null && ms === null)
|
|
24
|
+
fail("report --until requires --since");
|
|
25
|
+
if (untilMs !== null && ms !== null && untilMs >= ms)
|
|
26
|
+
fail(`report --until (${durationLabel(untilMs)}) must be shorter than --since (${durationLabel(ms)})`);
|
|
19
27
|
// --role scopes both report shapes: the usage report (day and --since) and the failure
|
|
20
|
-
// digest, whose day and --since collectors take the same optional base
|
|
21
|
-
const role =
|
|
28
|
+
// digest, whose day and --since collectors take the same optional list of base roles.
|
|
29
|
+
const role = parseRoleListScope(root, args);
|
|
30
|
+
const follow = hasFollowFlag(args);
|
|
31
|
+
if (follow && args.includes("--json"))
|
|
32
|
+
fail("report --json prints one document; -f/--follow repaints a live view — drop one of them");
|
|
22
33
|
if (ms !== null) {
|
|
23
34
|
// --days is still a rival shape: it counts whole local days, while --since totals a trailing
|
|
24
35
|
// window. --failures is not a rival anymore — the digest has a windowed-since collector that
|
|
25
36
|
// reuses the day digest's own sections, so `report --failures --since <d>` dispatches it
|
|
26
|
-
// through the existing --json / render choice.
|
|
37
|
+
// through the existing --json / render choice; --until bounds its window's end.
|
|
38
|
+
if (follow)
|
|
39
|
+
failRivalShapes("report --since", "-f/--follow", 'follow means "from now on"; --since shows a bounded past window');
|
|
27
40
|
if (args.includes("--days"))
|
|
28
41
|
failRivalShapes("report --since", "--days", "--days counts whole local days; --since totals a trailing window");
|
|
29
42
|
if (args.includes("--failures")) {
|
|
30
|
-
const digest = collectFailureReportSince(root, ms, role ?? undefined);
|
|
43
|
+
const digest = collectFailureReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
|
|
31
44
|
if (args.includes("--json"))
|
|
32
45
|
sayJson(digest);
|
|
33
46
|
else
|
|
@@ -36,7 +49,7 @@ export async function cmdReport(root, args) {
|
|
|
36
49
|
}
|
|
37
50
|
// --json swaps the renderer for the collector's own payload, exactly as status --json
|
|
38
51
|
// does: bounds and cap checks above are shared, only the printing differs.
|
|
39
|
-
const since = collectReportSince(root, ms, role ?? undefined);
|
|
52
|
+
const since = collectReportSince(root, ms, role ?? undefined, untilMs ?? undefined);
|
|
40
53
|
sayJsonOrRender(args, since, renderSinceReportMarkdown);
|
|
41
54
|
return;
|
|
42
55
|
}
|
|
@@ -54,6 +67,10 @@ export async function cmdReport(root, args) {
|
|
|
54
67
|
// report --json and doctor --json precedent: the time-and-spend fold gave the digest a
|
|
55
68
|
// stable data shape, so the machine-readable form is the data itself, not a re-parse of the
|
|
56
69
|
// Markdown (the old "clustered narrative with no agreed shape" refusal is retired).
|
|
70
|
+
if (follow) {
|
|
71
|
+
await followView(() => args.includes("--failures") ? renderFailureMarkdown(collectFailureReport(root, days, role ?? undefined)) : renderReportMarkdown(collectReport(root, days, role ?? undefined)), seams);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
57
74
|
if (args.includes("--failures") && args.includes("--json")) {
|
|
58
75
|
sayJson(collectFailureReport(root, days, role ?? undefined));
|
|
59
76
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export function cappedRequestTimeouts(cfg,
|
|
1
|
+
export function cappedRequestTimeouts(cfg, runCapS, quietCapS) {
|
|
2
2
|
return {
|
|
3
|
-
runTimeoutSeconds: Math.min(cfg.runTimeoutSeconds,
|
|
3
|
+
runTimeoutSeconds: Math.min(cfg.runTimeoutSeconds, runCapS),
|
|
4
4
|
quietTimeoutSeconds: cfg.quietTimeoutSeconds > 0 ? Math.min(cfg.quietTimeoutSeconds, quietCapS) : quietCapS,
|
|
5
5
|
};
|
|
6
6
|
}
|
|
@@ -9,15 +9,24 @@
|
|
|
9
9
|
* repeat. Only the gate's positive flake record is trusted: a check-failure rejection is not
|
|
10
10
|
* evidence of flakiness — it is exactly the attribution this memory exists to avoid making
|
|
11
11
|
* twice. */
|
|
12
|
+
import { DAY_MS } from "../text/datetime.js";
|
|
13
|
+
import { TUMWATER_SOURCE, tryOrWarn } from "../events/events.js";
|
|
12
14
|
import { readEventsSince } from "../events/event-window.js";
|
|
13
15
|
import { increment } from "../collections.js";
|
|
14
16
|
import { normalizeClusterKey } from "../failure/failure-cluster.js";
|
|
17
|
+
import { FAILING_TESTS_MAX } from "../build/build-check-report.js";
|
|
18
|
+
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
19
|
+
import { isJsonObject } from "../files/json-object.js";
|
|
20
|
+
import { parseEntryDetails } from "../backlog/backlog-md.js";
|
|
21
|
+
import { readTextOrNull } from "../files/files.js";
|
|
22
|
+
import { quarantinePath } from "../paths.js";
|
|
23
|
+
import path from "node:path";
|
|
15
24
|
/** How far back the gate looks for its own flake warnings. One local day: the example in
|
|
16
25
|
* BUGS.md 2026-10-06 recorded a flake at 10:50 and rejected an unrelated change on the same
|
|
17
26
|
* headline at 15:57, and a day is short enough that a retired flake stops excusing failures
|
|
18
27
|
* while still covering a team's working span. Exported so the doctor's `flaky checks` check
|
|
19
28
|
* and the gate share the one window and cannot drift. */
|
|
20
|
-
export const KNOWN_FLAKE_WINDOW_MS =
|
|
29
|
+
export const KNOWN_FLAKE_WINDOW_MS = DAY_MS;
|
|
21
30
|
/** The exact prefix review-precheck.ts writes before a flake's failure headline. Exported so
|
|
22
31
|
* the writer and reader cannot drift over the separator. */
|
|
23
32
|
export const FLAKY_WARNING_PREFIX = "gate check failed then passed on retry — flaky: ";
|
|
@@ -63,3 +72,93 @@ export function isKnownFlake(root, headline) {
|
|
|
63
72
|
const { events } = readEventsSince(root, KNOWN_FLAKE_WINDOW_MS);
|
|
64
73
|
return flakeKeys(events, Date.now()).has(normalizeClusterKey(headline));
|
|
65
74
|
}
|
|
75
|
+
/** How many quarantined tests are kept; the oldest by lastSeen fall off. */
|
|
76
|
+
export const QUARANTINE_MAX = 100;
|
|
77
|
+
/** The quarantine list, a malformed file or entry reading as no data. */
|
|
78
|
+
export function loadQuarantine(root) {
|
|
79
|
+
const stored = readJsonFile(quarantinePath(root));
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const e of Array.isArray(stored?.tests) ? stored.tests : []) {
|
|
82
|
+
if (!isJsonObject(e) || typeof e.test !== "string")
|
|
83
|
+
continue;
|
|
84
|
+
const num = (v) => (typeof v === "number" ? v : 0);
|
|
85
|
+
out.push({
|
|
86
|
+
test: e.test,
|
|
87
|
+
firstSeen: num(e.firstSeen),
|
|
88
|
+
lastSeen: num(e.lastSeen),
|
|
89
|
+
count: num(e.count) || 1,
|
|
90
|
+
bug: typeof e.bug === "string" ? e.bug : "",
|
|
91
|
+
...(typeof e.tail === "string" ? { tail: e.tail } : {}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function saveQuarantine(root, entries) {
|
|
97
|
+
const kept = [...entries].sort((a, b) => b.lastSeen - a.lastSeen).slice(0, QUARANTINE_MAX);
|
|
98
|
+
writeJsonAtomic(quarantinePath(root), { tests: kept });
|
|
99
|
+
}
|
|
100
|
+
/** The quarantined test names. */
|
|
101
|
+
export function quarantinedTests(root) {
|
|
102
|
+
return loadQuarantine(root).map((e) => e.test);
|
|
103
|
+
}
|
|
104
|
+
/** The failing tests when every one is quarantined; null when none is named, any is outside the
|
|
105
|
+
* list, or the list may have been cut short (FAILING_TESTS_MAX). */
|
|
106
|
+
export function onlyQuarantined(root, failingTests) {
|
|
107
|
+
if (!failingTests || failingTests.length === 0 || failingTests.length >= FAILING_TESTS_MAX)
|
|
108
|
+
return null;
|
|
109
|
+
const known = new Set(quarantinedTests(root));
|
|
110
|
+
return failingTests.every((t) => known.has(t)) ? [...failingTests] : null;
|
|
111
|
+
}
|
|
112
|
+
/** Quarantine each test of a check that failed and then passed on the same tree. A test already
|
|
113
|
+
* listed counts another flake; `tail` is kept for the BUGS.md entry of a new one. Returns the
|
|
114
|
+
* newly quarantined tests. Nothing is recorded for an empty or possibly truncated list. A failed
|
|
115
|
+
* write warns and returns no tests: the callers run this after a re-run passed, so a lost record
|
|
116
|
+
* must not fail that check. */
|
|
117
|
+
export function quarantineTests(root, tests, tail) {
|
|
118
|
+
if (!tests || tests.length === 0 || tests.length >= FAILING_TESTS_MAX)
|
|
119
|
+
return [];
|
|
120
|
+
const entries = loadQuarantine(root);
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
const added = [];
|
|
123
|
+
for (const test of new Set(tests)) {
|
|
124
|
+
const have = entries.find((e) => e.test === test);
|
|
125
|
+
if (have) {
|
|
126
|
+
have.lastSeen = now;
|
|
127
|
+
have.count += 1;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const text = typeof tail === "string" ? tail : (tail ?? []).join("\n");
|
|
131
|
+
entries.push({ test, firstSeen: now, lastSeen: now, count: 1, bug: "", ...(text ? { tail: text } : {}) });
|
|
132
|
+
added.push(test);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!tryOrWarn(root, TUMWATER_SOURCE, "write quarantine.json", () => saveQuarantine(root, entries)))
|
|
136
|
+
return [];
|
|
137
|
+
return added;
|
|
138
|
+
}
|
|
139
|
+
/** Record the BUGS.md heading filed for each of `tests`, dropping their kept output. */
|
|
140
|
+
export function markQuarantineFiled(root, bugs) {
|
|
141
|
+
const entries = loadQuarantine(root);
|
|
142
|
+
for (const e of entries) {
|
|
143
|
+
const bug = bugs.get(e.test);
|
|
144
|
+
if (bug === undefined)
|
|
145
|
+
continue;
|
|
146
|
+
e.bug = bug;
|
|
147
|
+
delete e.tail;
|
|
148
|
+
}
|
|
149
|
+
saveQuarantine(root, entries);
|
|
150
|
+
}
|
|
151
|
+
/** Release the quarantined tests whose filed BUGS.md entry is no longer under `## Open`; a later
|
|
152
|
+
* flake of one puts it back. Returns the released tests. A missing BUGS.md releases nothing. */
|
|
153
|
+
export function releaseQuarantine(root) {
|
|
154
|
+
const entries = loadQuarantine(root);
|
|
155
|
+
const md = readTextOrNull(path.join(root, "BUGS.md"));
|
|
156
|
+
if (md === null || entries.length === 0)
|
|
157
|
+
return [];
|
|
158
|
+
const open = new Set(parseEntryDetails(md, "Open").map((e) => e.title));
|
|
159
|
+
const released = entries.filter((e) => e.bug !== "" && !open.has(e.bug));
|
|
160
|
+
if (released.length === 0)
|
|
161
|
+
return [];
|
|
162
|
+
saveQuarantine(root, entries.filter((e) => !released.includes(e)));
|
|
163
|
+
return released.map((e) => e.test);
|
|
164
|
+
}
|