tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The cluster-key rules shared by everything that groups failure messages into causes: the
|
|
2
2
|
* failure digest's collection (failure-data.ts) and the error-storm reducer
|
|
3
3
|
* (src/team/error-storm.ts) both count by these keys, so the normalization, the two
|
|
4
|
-
*
|
|
4
|
+
* run-timeout shapes and their pooling, and the grouping engine live here rather than
|
|
5
5
|
* inside either consumer. Pure string
|
|
6
6
|
* and grouping logic — no event reads, no clock. */
|
|
7
7
|
import { rankByCount } from "../collections.js";
|
|
@@ -76,34 +76,34 @@ export function normalizeClusterKey(message) {
|
|
|
76
76
|
.replace(/(?<!exited\s)\b\d+\b/g, "<n>");
|
|
77
77
|
return normalized.trim().slice(0, EXAMPLE_MAX);
|
|
78
78
|
}
|
|
79
|
-
/** normalizeClusterKey's rendering of the two
|
|
79
|
+
/** normalizeClusterKey's rendering of the two run-timeout errors src/agent/run-agent.ts emits —
|
|
80
80
|
* the plain kill and the still-making-progress variant (session and worktree edits preserved
|
|
81
81
|
* for resume, BUGS.md 2026-09-29). Both are one cause pointing at one knob (runTimeoutSeconds),
|
|
82
82
|
* so every consumer that counts by cluster key pools them under the plain one. */
|
|
83
|
-
export const
|
|
84
|
-
const
|
|
83
|
+
export const RUN_TIMEOUT_KEY = "timed out after <dur>";
|
|
84
|
+
const RUN_TIMEOUT_PROGRESSING_KEY = "timed out after <dur> while still making progress — session and worktree edits preserved for resume";
|
|
85
85
|
// The review run's rewording of the same cause (BUGS.md 2026-09-30): the reviewer never
|
|
86
86
|
// resumes, so its timeout names what actually happens — the commit is kept, the next
|
|
87
87
|
// attempt re-reviews from scratch. Same cause, same knob (review.timeoutSeconds), one row.
|
|
88
|
-
const
|
|
88
|
+
const RUN_TIMEOUT_PROGRESSING_REVIEW_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt reviews it from scratch";
|
|
89
89
|
// The review run's wording when a resumable session remains:
|
|
90
90
|
// the next attempt continues this review instead of re-reviewing. Continuing the timed-out
|
|
91
91
|
// session is a different cause from a from-scratch re-review, so this wording is its own
|
|
92
|
-
// cluster rather than joining the pooled plain
|
|
93
|
-
// (like
|
|
94
|
-
export const
|
|
92
|
+
// cluster rather than joining the pooled plain run-timeout row. Exported for its unit tests
|
|
93
|
+
// (like RUN_TIMEOUT_KEY), so a display or digest test can pin the mapping by name.
|
|
94
|
+
export const RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY = "timed out after <dur> while still making progress — the commit is kept; the next attempt continues this review";
|
|
95
95
|
/** The known progressing-timeout wordings and the key each pools under. The two review
|
|
96
96
|
* wordings name one cause on one knob (review.timeoutSeconds): a still-making-progress kill
|
|
97
|
-
* pools into the plain
|
|
97
|
+
* pools into the plain run-timeout row (a mixed team of plain and progressing kills is one
|
|
98
98
|
* cause's agent-hours, not two half-size rows the top-N cut can drop — the same pooling the
|
|
99
99
|
* error-storm reducer applies, src/team/error-storm.ts), and a resumed review's wording maps
|
|
100
100
|
* to itself, because continuing the timed-out session is a different cause from a from-scratch
|
|
101
101
|
* re-review. Enumerated so a future wording edit lands in
|
|
102
102
|
* this table rather than silently changing which row its timeouts count under. */
|
|
103
103
|
const TIMEOUT_POOL = new Map([
|
|
104
|
-
[
|
|
105
|
-
[
|
|
106
|
-
[
|
|
104
|
+
[RUN_TIMEOUT_PROGRESSING_KEY, RUN_TIMEOUT_KEY],
|
|
105
|
+
[RUN_TIMEOUT_PROGRESSING_REVIEW_KEY, RUN_TIMEOUT_KEY],
|
|
106
|
+
[RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY, RUN_TIMEOUT_PROGRESSING_REVIEW_RESUME_KEY],
|
|
107
107
|
]);
|
|
108
108
|
/** The cluster key a normalized error clusters under: the pooled wordings above map to their
|
|
109
109
|
* row, and every other cause stands as normalizeClusterKey rendered it. */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { assertReportSinceMs, eventWindowCovers, readWindowEvents } from "../events/event-window.js";
|
|
1
|
+
import { assertReportSinceMs, eventWindowCovers, readWindowEvents, sinceWindowTiming } from "../events/event-window.js";
|
|
2
2
|
import { eventDayKey, eventRole } from "../events/event-read.js";
|
|
3
|
-
import { baseRoleOf } from "../roles/
|
|
3
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
4
|
+
import { scopeBases } from "../report/report-fold.js";
|
|
4
5
|
import { timeAndSpend } from "./time-spend.js";
|
|
5
6
|
import { dayAt, dayKey, dayWindow, formatDate } from "../text/datetime.js";
|
|
6
7
|
import { describeStateChange, STATE_CHANGE_TOP, STATE_CHANGE_TYPES } from "./failure-state-change.js";
|
|
7
8
|
import { clusterMessages, errorTextOrPlaceholder, truncateExample } from "./failure-cluster.js";
|
|
8
9
|
import { addTo, getOrCreate, memoSetLru, rankByCount } from "../collections.js";
|
|
9
10
|
import { firstReason } from "../text/phrases.js";
|
|
10
|
-
import { sameStatIdentity, statOrNull } from "../files/files.js";
|
|
11
|
+
import { sameStatIdentity, statIdentityOrZero, statOrNull } from "../files/files.js";
|
|
11
12
|
import { nonNegativeNumber } from "../files/json-object.js";
|
|
12
13
|
import { eventsLogPath } from "../paths.js";
|
|
13
14
|
/** Caps that keep the digest bounded regardless of how bad the window was — the top-N
|
|
@@ -34,18 +35,18 @@ const MERGE_FAILURE_TOP = 10;
|
|
|
34
35
|
const NO_MERGE_REASON = "no reason recorded";
|
|
35
36
|
const MERGED_TOP = 10;
|
|
36
37
|
const SUMMARY_MAX = 100;
|
|
37
|
-
/** Keep only events that belong to
|
|
38
|
-
* (`feature-2`) into their group exactly as `report --role` and the per-role cap do.
|
|
39
|
-
* events match on their own
|
|
40
|
-
* streak breaker, its cap, its fallback) log with
|
|
38
|
+
/** Keep only events that belong to the scoped base roles, so a scoped digest aggregates instances
|
|
39
|
+
* (`feature-2`) into their group exactly as `report --role` and the per-role cap do. Role
|
|
40
|
+
* events match on their own role id; the role-scoped harness transitions (a role pause, its
|
|
41
|
+
* streak breaker, its cap, its fallback) log with source "harness" and name the affected role
|
|
41
42
|
* in `role`, so those match on that field too — otherwise a scoped digest would drop exactly
|
|
42
|
-
* the events that explain why the
|
|
43
|
+
* the events that explain why the role stopped. Applied to both partitions and the whole read,
|
|
43
44
|
* so every section derived from them narrows. */
|
|
44
|
-
function scopeToRole(events,
|
|
45
|
-
return events.filter((ev) => baseRoleOf(eventRole(ev))
|
|
45
|
+
function scopeToRole(events, bases) {
|
|
46
|
+
return events.filter((ev) => bases.includes(baseRoleOf(eventRole(ev))) ||
|
|
46
47
|
(STATE_CHANGE_TYPES.has(ev.type) &&
|
|
47
48
|
typeof ev.role === "string" &&
|
|
48
|
-
baseRoleOf(ev.role)
|
|
49
|
+
bases.includes(baseRoleOf(ev.role))));
|
|
49
50
|
}
|
|
50
51
|
/** Attach a section's window total (events the section owns, counted before any filter the
|
|
51
52
|
* clustering applies — for rejections, the reasons-less ones too) to the clustered result. */
|
|
@@ -65,10 +66,10 @@ function messageSection(events, type, top, urgent) {
|
|
|
65
66
|
}
|
|
66
67
|
/** The merge-failure section: each blocked or conflicted `merge_failed` clusters on
|
|
67
68
|
* (role, result, reason).
|
|
68
|
-
* `merge_failed` carries
|
|
69
|
-
* `warning` (a red merge check, a structure or fix-claim
|
|
70
|
-
* previous merge outcome; with none, the placeholder. The
|
|
71
|
-
* conflicted `merge_failed` in the window. */
|
|
69
|
+
* A `merge_failed` with a `reason` field uses it; an older one carries none, so the reason is
|
|
70
|
+
* the same role's nearest preceding `warning` (a red merge check, a structure or fix-claim
|
|
71
|
+
* block) logged after that role's previous merge outcome; with none, the placeholder. The
|
|
72
|
+
* total counts every blocked or conflicted `merge_failed` in the window. */
|
|
72
73
|
function mergeFailureSection(events) {
|
|
73
74
|
const ordered = [...events].sort((a, b) => a.ts - b.ts);
|
|
74
75
|
const lastWarning = new Map();
|
|
@@ -91,7 +92,8 @@ function mergeFailureSection(events) {
|
|
|
91
92
|
continue;
|
|
92
93
|
}
|
|
93
94
|
total++;
|
|
94
|
-
const
|
|
95
|
+
const own = typeof ev.reason === "string" ? ev.reason.trim() : "";
|
|
96
|
+
const reason = own !== "" ? own : (lastWarning.get(role) ?? NO_MERGE_REASON);
|
|
95
97
|
lastWarning.delete(role);
|
|
96
98
|
messages.push({ message: `${result} — ${reason}`, role, ts: ev.ts, keyPrefix: `${role}\u0000` });
|
|
97
99
|
}
|
|
@@ -102,8 +104,8 @@ function roleStats(events) {
|
|
|
102
104
|
const byRole = new Map();
|
|
103
105
|
const statsFor = (role) => getOrCreate(byRole, role, () => ({ runs: 0, errors: 0, quietKills: 0, rejections: 0 }));
|
|
104
106
|
for (const ev of events) {
|
|
105
|
-
//
|
|
106
|
-
// authoring
|
|
107
|
+
// Runs are run_end events; a rejection is now recorded by the merge slot, AFTER the
|
|
108
|
+
// authoring run has already ended `queued` (plans/merge-queue.md 3/5). review_rejected is
|
|
107
109
|
// the one event every reject path logs exactly once (src/review/review.ts), so it — not a
|
|
108
110
|
// run_end result no production path emits — is the rejection source.
|
|
109
111
|
if (ev.type === "run_end") {
|
|
@@ -121,7 +123,7 @@ function roleStats(events) {
|
|
|
121
123
|
return byRole;
|
|
122
124
|
}
|
|
123
125
|
/** Bounded memo of whole failure digests, keyed by root and day count. The dashboard's Failures
|
|
124
|
-
* tab, the TUI's Ctrl+T pane, and every
|
|
126
|
+
* tab, the TUI's Ctrl+T pane, and every selfcheck run re-request the same digest, and the
|
|
125
127
|
* collector re-reads and re-parses up to 2× the window's log bytes each time (~4 MB on this
|
|
126
128
|
* repo's own log, growing toward the rotation cap). An unchanged log and window cost one stat
|
|
127
129
|
* and return the prior result; any change — an append, a rotation, or a new local day —
|
|
@@ -131,10 +133,7 @@ const failureDigestCache = new Map();
|
|
|
131
133
|
const FAILURE_DIGEST_CACHE_MAX = 8;
|
|
132
134
|
/** The live log's identity, all zero when the file is missing (the empty-log case). */
|
|
133
135
|
function digestStat(root) {
|
|
134
|
-
|
|
135
|
-
return st
|
|
136
|
-
? { size: st.size, mtimeMs: st.mtimeMs, dev: st.dev, ino: st.ino }
|
|
137
|
-
: { size: 0, mtimeMs: 0, dev: 0, ino: 0 };
|
|
136
|
+
return statIdentityOrZero(statOrNull(eventsLogPath(root)));
|
|
138
137
|
}
|
|
139
138
|
/** Collect the digest over the last `days` local calendar days, reading a 2×-long window once
|
|
140
139
|
* and partitioning it in memory so the delta against the preceding equal window costs no
|
|
@@ -144,10 +143,10 @@ export function collectFailureReport(root, days, role) {
|
|
|
144
143
|
const now = new Date();
|
|
145
144
|
const { from, to } = dayWindow(days, now);
|
|
146
145
|
const priorFrom = formatDate(dayAt(days * 2 - 1, now));
|
|
147
|
-
const
|
|
146
|
+
const bases = scopeBases(role);
|
|
148
147
|
// The base role rides the key, so a scoped and a team digest of the same log cannot
|
|
149
148
|
// collide in the memo.
|
|
150
|
-
const key = `${root}\u0000${days}\u0000${
|
|
149
|
+
const key = `${root}\u0000${days}\u0000${bases?.join(",") ?? ""}`;
|
|
151
150
|
const stat = digestStat(root);
|
|
152
151
|
const cached = failureDigestCache.get(key);
|
|
153
152
|
if (cached?.from === from &&
|
|
@@ -177,29 +176,30 @@ export function collectFailureReport(root, days, role) {
|
|
|
177
176
|
* the window and the equal-length window immediately before it (the same current/prior split
|
|
178
177
|
* the day collector builds, so the Deltas table compares like with like). The read starts at the
|
|
179
178
|
* prior half's own day key, exactly as the day collector reads 2× its window; the cutoff instant
|
|
180
|
-
* for coverage is the prior half's start, which a day key alone cannot prove.
|
|
181
|
-
|
|
179
|
+
* for coverage is the prior half's start, which a day key alone cannot prove. With `untilMs` the
|
|
180
|
+
* window is the bounded `[now - sinceMs, now - untilMs)`; the prior window then has that
|
|
181
|
+
* window's own length, `sinceMs - untilMs`. */
|
|
182
|
+
export function collectFailureReportSince(root, sinceMs, role, untilMs) {
|
|
182
183
|
assertReportSinceMs(sinceMs);
|
|
183
|
-
const now =
|
|
184
|
-
const
|
|
185
|
-
const priorFrom = cutoff -
|
|
184
|
+
const { now, cutoff, end, slice } = sinceWindowTiming(sinceMs, untilMs);
|
|
185
|
+
const length = sinceMs - (untilMs ?? 0);
|
|
186
|
+
const priorFrom = cutoff - length;
|
|
186
187
|
const raw = readWindowEvents(root, dayKey(priorFrom));
|
|
187
188
|
const current = [];
|
|
188
189
|
const prior = [];
|
|
189
190
|
for (const ev of raw.events) {
|
|
190
191
|
if (typeof ev.ts !== "number")
|
|
191
192
|
continue;
|
|
192
|
-
if (ev.ts >= cutoff && ev.ts <= now)
|
|
193
|
+
if (ev.ts >= cutoff && (untilMs === undefined ? ev.ts <= now : ev.ts < end))
|
|
193
194
|
current.push(ev);
|
|
194
195
|
else if (ev.ts >= priorFrom && ev.ts < cutoff)
|
|
195
196
|
prior.push(ev);
|
|
196
197
|
}
|
|
197
198
|
return assembleFailureReport(current, prior, raw.events, eventWindowCovers(raw, priorFrom), raw.logState, {
|
|
198
|
-
days: Math.ceil(
|
|
199
|
+
days: Math.ceil(length / 86_400_000),
|
|
199
200
|
from: dayKey(cutoff),
|
|
200
|
-
to: dayKey(
|
|
201
|
-
|
|
202
|
-
fromIso: new Date(cutoff).toISOString(),
|
|
201
|
+
to: dayKey(end),
|
|
202
|
+
...slice,
|
|
203
203
|
}, role);
|
|
204
204
|
}
|
|
205
205
|
/** Assemble the digest's sections from the two event partitions, the full read, and the
|
|
@@ -210,16 +210,16 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
210
210
|
// Scope first when a role was named: every section below derives from these partitions, so
|
|
211
211
|
// narrowing them at the source scopes outcomes, deltas, clusters, merges, and time/spend at
|
|
212
212
|
// once. The unfiltered read's first event is kept for the retention line, so a scoped partial
|
|
213
|
-
// window still names when the log starts, not when the scoped role's own first
|
|
214
|
-
const
|
|
215
|
-
const current =
|
|
216
|
-
const prior =
|
|
217
|
-
const allEvents =
|
|
213
|
+
// window still names when the log starts, not when the scoped role's own first run does.
|
|
214
|
+
const bases = scopeBases(role);
|
|
215
|
+
const current = bases === undefined ? currentRaw : scopeToRole(currentRaw, bases);
|
|
216
|
+
const prior = bases === undefined ? priorRaw : scopeToRole(priorRaw, bases);
|
|
217
|
+
const allEvents = bases === undefined ? allEventsRaw : scopeToRole(allEventsRaw, bases);
|
|
218
218
|
const oldestRetained = allEventsRaw.length > 0 ? eventDayKey(allEventsRaw[0]) : null;
|
|
219
|
-
const
|
|
219
|
+
const runEvents = current.filter((ev) => ev.type === "run_end");
|
|
220
220
|
// Outcome table: one row per role, counts per result that occurred.
|
|
221
221
|
const outcomeMap = new Map();
|
|
222
|
-
for (const ev of
|
|
222
|
+
for (const ev of runEvents) {
|
|
223
223
|
const role = eventRole(ev);
|
|
224
224
|
const counts = getOrCreate(outcomeMap, role, () => ({}));
|
|
225
225
|
const result = ev.result;
|
|
@@ -231,12 +231,12 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
231
231
|
// (pinned by the tie test in test/failure-render.test.ts).
|
|
232
232
|
const outcomes = rankByCount([...outcomeMap.entries()].map(([role, counts]) => ({ role, counts })), (row) => total(row.counts), (row) => row.role);
|
|
233
233
|
// Time and spend: the same runs priced by wall-clock span and cost, per role × outcome
|
|
234
|
-
// class, plus the loss ranking that weighs causes by agent-hours rather than
|
|
235
|
-
const { timeSpend, lossCauses, lossCausesHidden } = timeAndSpend(
|
|
236
|
-
// Prompt-token telemetry: per role, the median prompt tokens a
|
|
234
|
+
// class, plus the loss ranking that weighs causes by agent-hours rather than run counts.
|
|
235
|
+
const { timeSpend, lossCauses, lossCausesHidden } = timeAndSpend(runEvents, allEvents);
|
|
236
|
+
// Prompt-token telemetry: per role, the median prompt tokens a run sent and how much of
|
|
237
237
|
// the prompt budget went out before the first edit. Only runs carrying the field count, so
|
|
238
238
|
// a pre-feature log leaves the section off entirely.
|
|
239
|
-
const promptStats = promptStatsFor(
|
|
239
|
+
const promptStats = promptStatsFor(runEvents);
|
|
240
240
|
// Deltas: current vs preceding window, per role.
|
|
241
241
|
const curStats = roleStats(current);
|
|
242
242
|
const prevStats = roleStats(prior);
|
|
@@ -260,8 +260,8 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
260
260
|
.sort((a, b) => b.runs - a.runs ||
|
|
261
261
|
b.prevRuns - a.prevRuns ||
|
|
262
262
|
a.role.localeCompare(b.role));
|
|
263
|
-
// Only runs that ENDED as errors carry a
|
|
264
|
-
// merge slot also writes, so a successful
|
|
263
|
+
// Only runs that ENDED as errors carry a run error: `state.lastError` is shared state the
|
|
264
|
+
// merge slot also writes, so a successful run's `run_end` can carry a leftover merge failure's
|
|
265
265
|
// text (BUGS.md 2026-09-21). `main_red` joins them because its cause is written for that result
|
|
266
266
|
// — the baseline gate's own text (src/baseline/main-red.ts), never a leftover — so the
|
|
267
267
|
// section itemizes the outcome that blocks every merge instead of leaving it a bare count
|
|
@@ -271,7 +271,7 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
271
271
|
// error-text test — so a malformed run_end that skipped the text can never be counted above
|
|
272
272
|
// but itemized nowhere: those events cluster under a placeholder cause, keeping the section's
|
|
273
273
|
// total equal to the tables it cross-checks against (BUGS.md 2026-09-30).
|
|
274
|
-
const errorEvents =
|
|
274
|
+
const errorEvents = runEvents.filter((ev) => ev.result === "error" || ev.result === "main_red");
|
|
275
275
|
const errors = section(clusterMessages(errorEvents.map((ev) => ({
|
|
276
276
|
message: errorTextOrPlaceholder(ev.error),
|
|
277
277
|
role: eventRole(ev),
|
|
@@ -281,22 +281,26 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
281
281
|
? { ...ev, message: ev.message.replace(OLD_MERGE_CHECK_WORDING, "merge check failed (kept for one retry)") }
|
|
282
282
|
: ev), "warning", WARNING_TOP, (m) => URGENT_WARNING.test(m));
|
|
283
283
|
// Rejections cluster on (role, reasons[0]) — the field the event feed renders — so two
|
|
284
|
-
// different rejection reasons from the same role stay separate rows
|
|
285
|
-
//
|
|
286
|
-
//
|
|
284
|
+
// different rejection reasons from the same role stay separate rows, and an already-landed
|
|
285
|
+
// one (ev.afterMerge === true) splits apart from a same-reason pre-merge one so the row names
|
|
286
|
+
// the post-merge class whose rejection stands recorded on main. The section's total counts
|
|
287
|
+
// every `review_rejected` in the window, reasons or not, so it equals the Deltas column and
|
|
288
|
+
// the render's remainder line cross-checks against it.
|
|
287
289
|
const rejectedEvents = current.filter((ev) => ev.type === "review_rejected");
|
|
290
|
+
const postMergeRejections = rejectedEvents.filter((ev) => ev.afterMerge === true).length;
|
|
288
291
|
const rejections = section(clusterMessages(rejectedEvents
|
|
289
292
|
.filter((ev) => Array.isArray(ev.reasons))
|
|
290
293
|
.map((ev) => {
|
|
291
294
|
const reasons = ev.reasons;
|
|
292
|
-
|
|
295
|
+
const message = ev.afterMerge === true ? `post-merge rejection: ${firstReason(reasons)}` : firstReason(reasons);
|
|
296
|
+
return { message, role: eventRole(ev), ts: ev.ts, keyPrefix: `${eventRole(ev)}\u0000` };
|
|
293
297
|
}), REJECTION_TOP), rejectedEvents.length);
|
|
294
298
|
const mergeFailures = mergeFailureSection(current);
|
|
295
299
|
// Merge review failures: a reviewer that could not return a parseable verdict (a dead
|
|
296
|
-
// backend, a transport error) fails closed and keeps the commit, but its authoring
|
|
300
|
+
// backend, a transport error) fails closed and keeps the commit, but its authoring run ends
|
|
297
301
|
// `queued`/`no_change` — so the failure lives on the `review_failed` event, never on
|
|
298
|
-
// `run_end.error`. Clustered on their own so the
|
|
299
|
-
// failing without misreading it as a
|
|
302
|
+
// `run_end.error`. Clustered on their own so the selfcheck role can see a gate that keeps
|
|
303
|
+
// failing without misreading it as a run error (BUGS.md 2026-09-21).
|
|
300
304
|
const reviewFailures = messageSection(current, "review_failed", REVIEW_FAILURE_TOP);
|
|
301
305
|
// The newest MERGED_TOP merges, newest first. The pre-slice count rides along, as it does for
|
|
302
306
|
// the state changes below, so the render can name what the cut left out — a busy window must
|
|
@@ -335,8 +339,8 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
335
339
|
const windowComplete = coversFullWindow;
|
|
336
340
|
return {
|
|
337
341
|
...window,
|
|
338
|
-
...(
|
|
339
|
-
runs:
|
|
342
|
+
...(bases === undefined ? {} : { role: bases.join(",") }),
|
|
343
|
+
runs: runEvents.length,
|
|
340
344
|
hasEvents,
|
|
341
345
|
emptyLog: !hasEvents && !coversFullWindow,
|
|
342
346
|
logState,
|
|
@@ -352,6 +356,7 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
352
356
|
warnings,
|
|
353
357
|
reviewFailures,
|
|
354
358
|
rejections,
|
|
359
|
+
postMergeRejections,
|
|
355
360
|
mergeFailures,
|
|
356
361
|
merged,
|
|
357
362
|
mergedTotal: allMerged.length,
|
|
@@ -365,9 +370,9 @@ function median(values) {
|
|
|
365
370
|
const mid = Math.floor(sorted.length / 2);
|
|
366
371
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
367
372
|
}
|
|
368
|
-
/** Per-role prompt-token stats from the window's run_end events.
|
|
373
|
+
/** Per-role prompt-token stats from the window's run_end events. Runs without a
|
|
369
374
|
* `promptTokens` reading contribute nothing (and skip a role entirely), so the section only
|
|
370
|
-
* appears once some
|
|
375
|
+
* appears once some run ran on a build that records the field. Both reads go through
|
|
371
376
|
* nonNegativeNumber like every other event-log number: a torn or hand-edited line whose
|
|
372
377
|
* `promptTokens` is `1e999` (JSON parses to Infinity) would otherwise land in the median and
|
|
373
378
|
* the `preEditSum / promptSum` ratio as a non-finite value no report can render. */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { agree, plural } from "../text/phrases.js";
|
|
2
2
|
import { shortSha, usd } from "../text/format.js";
|
|
3
|
-
import { dayKey, dayLabel,
|
|
3
|
+
import { boundedWindowLine, clockHm, dayKey, dayLabel, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
4
4
|
import { durationLabel } from "../cli/cli-args.js";
|
|
5
5
|
import { eventsRotationLabel } from "../events/events.js";
|
|
6
6
|
import { eventTypeLabel } from "../events/event-labels.js";
|
|
7
7
|
import { markdownTable } from "../text/markdown.js";
|
|
8
8
|
/** A cluster's role list shows at most this many names before a "+N more" remainder. */
|
|
9
9
|
const ROLES_SHOWN = 4;
|
|
10
|
-
/** Every `
|
|
11
|
-
* so adding a result to src/
|
|
10
|
+
/** Every `RunResult`, mapped to its display/column rank. Typed as a `Record<RunResult, …>`,
|
|
11
|
+
* so adding a result to src/run/run-outcome.ts and forgetting it here is a compile error — the
|
|
12
12
|
* vocabulary is closed by the type checker, not by a comment. Order runs from "made progress"
|
|
13
13
|
* through "did not" to the operator/shutdown outcomes. */
|
|
14
14
|
const RESULT_ORDER = {
|
|
@@ -27,7 +27,7 @@ const RESULT_ORDER = {
|
|
|
27
27
|
aborted: 12,
|
|
28
28
|
user_aborted: 13,
|
|
29
29
|
};
|
|
30
|
-
/** The digest table's columns: every
|
|
30
|
+
/** The digest table's columns: every RunResult that at least one role logged in the window, ranked
|
|
31
31
|
* by RESULT_ORDER (ties alphabetical), so the table's shape follows the window's evidence instead
|
|
32
32
|
* of the whole fixed vocabulary — a team with no errors gets no error column. */
|
|
33
33
|
function columns(outcomes) {
|
|
@@ -41,13 +41,13 @@ function columns(outcomes) {
|
|
|
41
41
|
/** A delta cell: `prev → cur`, with "—" on a side where the role had no runs (the "new role"
|
|
42
42
|
* case for the preceding window), so an absence is never rendered as an infinite increase.
|
|
43
43
|
* The displayed halves may be raw counts (stringified here) or pre-rendered rate strings —
|
|
44
|
-
* the
|
|
44
|
+
* the run-count halves exist only to decide the "—", not to be printed. */
|
|
45
45
|
function deltaCell(prevRuns, prev, runs, cur) {
|
|
46
46
|
return `${prevRuns === 0 ? "—" : prev} → ${runs === 0 ? "—" : cur}`;
|
|
47
47
|
}
|
|
48
48
|
/** The rejections column's cell. It cannot reuse deltaCell: a rejection is logged by the
|
|
49
49
|
* merge slot, so its authoring run_end can fall in the other window — a role with a
|
|
50
|
-
* rejection must show it even when its
|
|
50
|
+
* rejection must show it even when its run count is 0. Presence is judged on the rejection
|
|
51
51
|
* count itself (the role is "absent" from the column only with neither runs nor rejections). */
|
|
52
52
|
function rejectionCell(prevRuns, prevRejections, runs, rejections) {
|
|
53
53
|
const prev = prevRuns === 0 && prevRejections === 0 ? "—" : String(prevRejections);
|
|
@@ -86,7 +86,7 @@ function rate(errors, runs) {
|
|
|
86
86
|
return "—";
|
|
87
87
|
return percent(errors, runs);
|
|
88
88
|
}
|
|
89
|
-
/** One time-and-spend cell: `x.x h · $y.yy`, or "—" when the role ended no
|
|
89
|
+
/** One time-and-spend cell: `x.x h · $y.yy`, or "—" when the role ended no run on that
|
|
90
90
|
* class — an absence is not a zero cost, for the same reason the Deltas cells say "—".
|
|
91
91
|
* Hours carry one decimal: the digest prices agent-hours, and whole-hour rounding would
|
|
92
92
|
* erase the difference between ten 200-ms errors and one 30-minute timeout. */
|
|
@@ -103,13 +103,13 @@ function hoursPhrase(ms) {
|
|
|
103
103
|
}
|
|
104
104
|
/** Render the digest as bounded Markdown. Byte bound: for a given team the tables grow only with
|
|
105
105
|
* the number of configured roles (fixed by config), the RESULT_ORDER vocabulary (fixed by
|
|
106
|
-
* src/
|
|
106
|
+
* src/run/run-outcome.ts), and the time-and-spend section's LOSS_TOP loss-cause lines (fixed by
|
|
107
107
|
* the cut), the merge-failure section's MERGE_FAILURE_TOP clusters (src/failure/failure-data.ts),
|
|
108
108
|
* and every free string is capped — cluster examples at 120 chars (plus a `… (+N
|
|
109
109
|
* chars)` cut marker when truncated, so a marked cut can never read as a complete message —
|
|
110
110
|
* BUGS.md 2026-09-30), merged summaries at 100 under the same rule, a cluster's role list at 4
|
|
111
|
-
* names plus a remainder count, and any
|
|
112
|
-
* refuses longer custom-
|
|
111
|
+
* names plus a remainder count, and any role id sliced to 32 chars (config validation already
|
|
112
|
+
* refuses longer custom-role ids, so the slice is a guard rather than the real bound). Cluster
|
|
113
113
|
* counts are capped at top-N, and the Team state changes section is capped at STATE_CHANGE_TOP
|
|
114
114
|
* lines with each payload at STATE_CHANGE_MAX and each free field at STATE_CHANGE_FIELD_MAX; the
|
|
115
115
|
* merged list at MERGED_TOP lines, each capped section closing with one remainder line when its cut
|
|
@@ -124,9 +124,12 @@ export function renderFailureMarkdown(data) {
|
|
|
124
124
|
// The since shape prints the same sections as the day shape but labels its window as an
|
|
125
125
|
// instant span (the wording renderSinceReportMarkdown uses); the day shape keeps its
|
|
126
126
|
// day-window line. The empty branch follows the same split so neither claims the other's unit.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
const bounded = data.sinceMs !== undefined && data.untilMs !== undefined && data.toIso !== undefined;
|
|
128
|
+
lines.push(bounded
|
|
129
|
+
? `${boundedWindowLine(durationLabel(data.sinceMs), durationLabel(data.untilMs), data.fromIso, data.toIso)} · ${data.runs} runs`
|
|
130
|
+
: data.sinceMs !== undefined
|
|
131
|
+
? `${sinceWindowLine(durationLabel(data.sinceMs), data.fromIso)} · ${data.runs} runs`
|
|
132
|
+
: `${reportWindow(data.from, data.to, data.days, eventsRotationLabel())} · ${data.runs} runs`);
|
|
130
133
|
if (data.role !== undefined)
|
|
131
134
|
lines.push(`role: ${data.role}`);
|
|
132
135
|
if (data.logState === "unreadable")
|
|
@@ -134,9 +137,11 @@ export function renderFailureMarkdown(data) {
|
|
|
134
137
|
else if (data.emptyLog)
|
|
135
138
|
lines.push("no events retained");
|
|
136
139
|
else if (!data.hasEvents) {
|
|
137
|
-
lines.push(
|
|
138
|
-
? `no events
|
|
139
|
-
:
|
|
140
|
+
lines.push(bounded
|
|
141
|
+
? `no events from ${durationLabel(data.sinceMs)} to ${durationLabel(data.untilMs)} ago`
|
|
142
|
+
: data.sinceMs !== undefined
|
|
143
|
+
? `no events in the last ${durationLabel(data.sinceMs)}`
|
|
144
|
+
: `no events in the last ${dayLabel(data.days)}`);
|
|
140
145
|
}
|
|
141
146
|
else if (data.partial)
|
|
142
147
|
lines.push(`partial: retained log starts ${data.oldestEventDate ?? "?"}`);
|
|
@@ -216,7 +221,7 @@ export function renderFailureMarkdown(data) {
|
|
|
216
221
|
}
|
|
217
222
|
}
|
|
218
223
|
// Prompt-token telemetry: the per-role median prompt
|
|
219
|
-
// send and how much of the prompt budget goes out before a
|
|
224
|
+
// send and how much of the prompt budget goes out before a run's first edit. Omitted
|
|
220
225
|
// entirely when no run_end in the window carries the fields, so old logs and their golden
|
|
221
226
|
// digests render unchanged.
|
|
222
227
|
if (data.promptStats.length > 0) {
|
|
@@ -228,7 +233,7 @@ export function renderFailureMarkdown(data) {
|
|
|
228
233
|
}
|
|
229
234
|
}
|
|
230
235
|
lines.push("");
|
|
231
|
-
lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs) : dayLabel(data.days)}`);
|
|
236
|
+
lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs - (data.untilMs ?? 0)) : dayLabel(data.days)}`);
|
|
232
237
|
if (data.deltas.length === 0) {
|
|
233
238
|
lines.push("_no runs in either window_");
|
|
234
239
|
}
|
|
@@ -242,13 +247,16 @@ export function renderFailureMarkdown(data) {
|
|
|
242
247
|
])));
|
|
243
248
|
}
|
|
244
249
|
// The title restates the section's contract: its total now covers the Outcome table's error
|
|
245
|
-
// column AND its main_red cells (the baseline gate's own cause rides the
|
|
250
|
+
// column AND its main_red cells (the baseline gate's own cause rides the run, BUGS.md
|
|
246
251
|
// 2026-09-28), so an operator cross-checking counts against the tables above is not misled
|
|
247
252
|
// by a title that reads as the error column alone.
|
|
248
253
|
renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "run errors");
|
|
249
254
|
renderClusters(lines, "Top warning clusters", data.warnings, "warnings");
|
|
250
255
|
renderClusters(lines, "Top review failure clusters", data.reviewFailures, "review failures");
|
|
251
256
|
renderClusters(lines, "Top rejection clusters", data.rejections, "rejections");
|
|
257
|
+
if (data.postMergeRejections > 0) {
|
|
258
|
+
lines.push(`_${plural(data.postMergeRejections, "post-merge rejection", "post-merge rejections")} — the commit already reached main, so it stays there with the reviewer's recorded reasons (never re-reviewed — Forward, never back)_`);
|
|
259
|
+
}
|
|
252
260
|
renderClusters(lines, "Merge failures", data.mergeFailures, "merge failures");
|
|
253
261
|
lines.push("");
|
|
254
262
|
lines.push("## Merged in the window");
|
|
@@ -297,7 +305,7 @@ function roleList(roles) {
|
|
|
297
305
|
const rest = roles.length - ROLES_SHOWN;
|
|
298
306
|
return rest > 0 ? `${shown} +${rest} more` : shown;
|
|
299
307
|
}
|
|
300
|
-
/** Config validation caps custom-
|
|
308
|
+
/** Config validation caps custom-role ids at 32 chars; this slice keeps the render's byte bound
|
|
301
309
|
* true even for a hand-edited state file or a future catalog id. */
|
|
302
310
|
function roleCell(role) {
|
|
303
311
|
return role.slice(0, 32);
|
|
@@ -305,5 +313,5 @@ function roleCell(role) {
|
|
|
305
313
|
/** A transition line's local `MM-DD HH:MM` stamp — the year is redundant inside the window and
|
|
306
314
|
* the seconds add bytes without adding causality. */
|
|
307
315
|
function stateChangeStamp(ts) {
|
|
308
|
-
return `${monthDay(dayKey(ts))} ${
|
|
316
|
+
return `${monthDay(dayKey(ts))} ${clockHm(ts)}`;
|
|
309
317
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** Wording half of the failure digest's Team state changes section: which transition event types
|
|
3
2
|
* the digest replays and the bounded one-liner describing each. Split out of failure-data.ts (the
|
|
4
3
|
* same seam build/build-check-events.ts split from build/build-check.ts) so the per-event-type
|
|
@@ -7,15 +6,15 @@
|
|
|
7
6
|
* sliced here, so the digest's byte bound holds for any event shape; the render adds the timestamp
|
|
8
7
|
* and a roleCell-sliced role, so no unbounded field reaches the page. */
|
|
9
8
|
import { truncateExample } from "./failure-cluster.js";
|
|
10
|
-
import { cutSplitsSurrogatePair } from "../text/text.js";
|
|
9
|
+
import { cutSplitsSurrogatePair, isNonBlankString } from "../text/text.js";
|
|
11
10
|
import { finiteNumber, stringList } from "../files/json-object.js";
|
|
12
|
-
import {
|
|
11
|
+
import { orchestratorStopPhrase, backendKindPhrase, budgetPhrase, teamPausePhrase, teamResumePhrase, holdPhrase, plural, rolePausePhrase, roleResumePhrase, rolesPhrase, shortSpanPhrase, PAUSE_EXPIRED_SUFFIX } from "../text/phrases.js";
|
|
13
12
|
import { shortSha } from "../text/format.js";
|
|
14
|
-
import { deferredReasonText } from "../events/event-format.js";
|
|
13
|
+
import { deferredReasonText, untilClockSuffix } from "../events/event-format.js";
|
|
15
14
|
/** The transition events the digest replays: the decisions the harness made about itself (the
|
|
16
15
|
* cap/team gates and the team hold, the error-streak breaker's pause, the role's own daily
|
|
17
16
|
* cap, model fallbacks, live-config edits, self-hosted redeploys, need-based deferrals,
|
|
18
|
-
* orchestrator lifecycle). They are the evidence the
|
|
17
|
+
* orchestrator lifecycle). They are the evidence the selfcheck role's load-bearing
|
|
19
18
|
* rule asks it to judge — whether the harness's RESPONSE to a failure was wrong
|
|
20
19
|
* (plans/telemetry-role.md) — so they sit beside the outcomes rather than being dropped. */
|
|
21
20
|
export const STATE_CHANGE_TYPES = new Set([
|
|
@@ -101,20 +100,22 @@ export function describeStateChange(ev) {
|
|
|
101
100
|
text = `budget resumed (${budgetPhrase(ev.spentUsd, ev.capUsd)} today)`;
|
|
102
101
|
break;
|
|
103
102
|
case "team_paused":
|
|
104
|
-
text = teamPausePhrase();
|
|
103
|
+
text = teamPausePhrase() + untilClockSuffix(ev.until);
|
|
104
|
+
if (isNonBlankString(ev.reason))
|
|
105
|
+
text += `: ${field(ev.reason)}`;
|
|
105
106
|
break;
|
|
106
107
|
case "team_resumed":
|
|
107
|
-
text = teamResumePhrase();
|
|
108
|
+
text = teamResumePhrase() + (ev.expired === true ? PAUSE_EXPIRED_SUFFIX : "");
|
|
108
109
|
break;
|
|
109
110
|
case "role_paused":
|
|
110
|
-
text = rolePausePhrase(field(ev.role ?? "?"));
|
|
111
|
+
text = rolePausePhrase(field(ev.role ?? "?")) + untilClockSuffix(ev.until);
|
|
111
112
|
break;
|
|
112
113
|
case "role_resumed":
|
|
113
|
-
text = roleResumePhrase(field(ev.role ?? "?"));
|
|
114
|
+
text = roleResumePhrase(field(ev.role ?? "?")) + (ev.expired === true ? PAUSE_EXPIRED_SUFFIX : "");
|
|
114
115
|
break;
|
|
115
116
|
case "role_streak_paused":
|
|
116
117
|
// The error-streak breaker auto-paused the role; the digest names the depth so the
|
|
117
|
-
// operator can connect the pause to the failing
|
|
118
|
+
// operator can connect the pause to the failing runs the clusters above show.
|
|
118
119
|
text = `role ${field(ev.role ?? "?")} paused — ${field(ev.streak ?? "?")} runs failed in a row`;
|
|
119
120
|
break;
|
|
120
121
|
case "role_cap_paused":
|
|
@@ -178,7 +179,11 @@ export function describeStateChange(ev) {
|
|
|
178
179
|
break;
|
|
179
180
|
}
|
|
180
181
|
case "build_stale":
|
|
181
|
-
|
|
182
|
+
// The digest's 72-char line cap (§ STATE_CHANGE_MAX) cannot hold the full alert phrase
|
|
183
|
+
// "restarts after cooldown; work continues" beside the ahead-count prefix, so it says
|
|
184
|
+
// the same thing compactly: the restart waits while work continues (cooldown scope,
|
|
185
|
+
// PLANS.md 2026-10-23).
|
|
186
|
+
text = `update available (${plural(finiteNumber(ev.aheadCommits, 0), "commit")} behind) — restart waits; work continues`;
|
|
182
187
|
break;
|
|
183
188
|
case "restart_pending":
|
|
184
189
|
text = `restart pending — main ${shortSha(ev.head)} green`;
|
|
@@ -199,7 +204,7 @@ export function describeStateChange(ev) {
|
|
|
199
204
|
text = `tumwater started (pid ${field(ev.pid)}${ev.build ? `, build ${shortSha(ev.build)}` : ""})`;
|
|
200
205
|
break;
|
|
201
206
|
case "orchestrator_stop":
|
|
202
|
-
text =
|
|
207
|
+
text = orchestratorStopPhrase(ev.cause);
|
|
203
208
|
break;
|
|
204
209
|
case "supervisor_exit":
|
|
205
210
|
text = `tumwater down — restart ${field(ev.generation)} ${ev.signal ? `killed by ${field(ev.signal)}` : `exited ${field(ev.code)}`}${ev.reason ? `: ${field(ev.reason)}` : ""}`;
|