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,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),
|
|
@@ -293,10 +293,10 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
293
293
|
}), REJECTION_TOP), rejectedEvents.length);
|
|
294
294
|
const mergeFailures = mergeFailureSection(current);
|
|
295
295
|
// 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
|
|
296
|
+
// backend, a transport error) fails closed and keeps the commit, but its authoring run ends
|
|
297
297
|
// `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
|
|
298
|
+
// `run_end.error`. Clustered on their own so the selfcheck role can see a gate that keeps
|
|
299
|
+
// failing without misreading it as a run error (BUGS.md 2026-09-21).
|
|
300
300
|
const reviewFailures = messageSection(current, "review_failed", REVIEW_FAILURE_TOP);
|
|
301
301
|
// The newest MERGED_TOP merges, newest first. The pre-slice count rides along, as it does for
|
|
302
302
|
// the state changes below, so the render can name what the cut left out — a busy window must
|
|
@@ -335,8 +335,8 @@ function assembleFailureReport(currentRaw, priorRaw, allEventsRaw, coversFullWin
|
|
|
335
335
|
const windowComplete = coversFullWindow;
|
|
336
336
|
return {
|
|
337
337
|
...window,
|
|
338
|
-
...(
|
|
339
|
-
runs:
|
|
338
|
+
...(bases === undefined ? {} : { role: bases.join(",") }),
|
|
339
|
+
runs: runEvents.length,
|
|
340
340
|
hasEvents,
|
|
341
341
|
emptyLog: !hasEvents && !coversFullWindow,
|
|
342
342
|
logState,
|
|
@@ -365,9 +365,9 @@ function median(values) {
|
|
|
365
365
|
const mid = Math.floor(sorted.length / 2);
|
|
366
366
|
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
367
367
|
}
|
|
368
|
-
/** Per-role prompt-token stats from the window's run_end events.
|
|
368
|
+
/** Per-role prompt-token stats from the window's run_end events. Runs without a
|
|
369
369
|
* `promptTokens` reading contribute nothing (and skip a role entirely), so the section only
|
|
370
|
-
* appears once some
|
|
370
|
+
* appears once some run ran on a build that records the field. Both reads go through
|
|
371
371
|
* nonNegativeNumber like every other event-log number: a torn or hand-edited line whose
|
|
372
372
|
* `promptTokens` is `1e999` (JSON parses to Infinity) would otherwise land in the median and
|
|
373
373
|
* 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, formatTime, monthDay, reportWindow, sinceWindowLine } from "../text/datetime.js";
|
|
3
|
+
import { boundedWindowLine, dayKey, dayLabel, formatTime, 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 ?? "?"}`);
|
|
@@ -174,8 +179,9 @@ export function renderFailureMarkdown(data) {
|
|
|
174
179
|
// The same runs priced by what they cost, so a 30-minute timeout outranks ten 200-ms
|
|
175
180
|
// errors that the Outcome table above weighs equally. Two parts: the role × class table of
|
|
176
181
|
// hours and dollars, then the top loss causes by time (an error cluster, a role's
|
|
177
|
-
// no_change total — the quiet loss no cluster names, a review-rejected authoring span,
|
|
178
|
-
// merge-conflict/merge-blocked authoring span
|
|
182
|
+
// no_change total — the quiet loss no cluster names, a review-rejected authoring span, a
|
|
183
|
+
// merge-conflict/merge-blocked authoring span, or a change the main watch reverted off
|
|
184
|
+
// main). Grows only with the role catalog
|
|
179
185
|
// (fixed by config) and the LOSS_TOP cut, so the byte bound argued above holds.
|
|
180
186
|
lines.push("");
|
|
181
187
|
lines.push("## Time and spend by outcome");
|
|
@@ -204,7 +210,9 @@ export function renderFailureMarkdown(data) {
|
|
|
204
210
|
? `refused on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
205
211
|
: c.kind === "merge-conflict" || c.kind === "merge-blocked"
|
|
206
212
|
? `${c.kind} authoring on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
207
|
-
:
|
|
213
|
+
: c.kind === "reverted"
|
|
214
|
+
? `reverted off main on ${roleCell(c.roles[0] ?? "?")}${c.example ? ` — ${c.example}` : ""}`
|
|
215
|
+
: `${c.example} (${roleList(c.roles)})`;
|
|
208
216
|
lines.push(`- ${hoursPhrase(c.ms)} · ${usd(c.costUsd)} — ${plural(c.runs, "run")}: ${cause}`);
|
|
209
217
|
}
|
|
210
218
|
// Mark the top-5 cut the way the cluster sections mark theirs: a capped ranking that
|
|
@@ -216,7 +224,7 @@ export function renderFailureMarkdown(data) {
|
|
|
216
224
|
}
|
|
217
225
|
}
|
|
218
226
|
// Prompt-token telemetry: the per-role median prompt
|
|
219
|
-
// send and how much of the prompt budget goes out before a
|
|
227
|
+
// send and how much of the prompt budget goes out before a run's first edit. Omitted
|
|
220
228
|
// entirely when no run_end in the window carries the fields, so old logs and their golden
|
|
221
229
|
// digests render unchanged.
|
|
222
230
|
if (data.promptStats.length > 0) {
|
|
@@ -228,7 +236,7 @@ export function renderFailureMarkdown(data) {
|
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
lines.push("");
|
|
231
|
-
lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs) : dayLabel(data.days)}`);
|
|
239
|
+
lines.push(`## Deltas vs the preceding ${data.sinceMs !== undefined ? durationLabel(data.sinceMs - (data.untilMs ?? 0)) : dayLabel(data.days)}`);
|
|
232
240
|
if (data.deltas.length === 0) {
|
|
233
241
|
lines.push("_no runs in either window_");
|
|
234
242
|
}
|
|
@@ -242,7 +250,7 @@ export function renderFailureMarkdown(data) {
|
|
|
242
250
|
])));
|
|
243
251
|
}
|
|
244
252
|
// 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
|
|
253
|
+
// column AND its main_red cells (the baseline gate's own cause rides the run, BUGS.md
|
|
246
254
|
// 2026-09-28), so an operator cross-checking counts against the tables above is not misled
|
|
247
255
|
// by a title that reads as the error column alone.
|
|
248
256
|
renderClusters(lines, "Top error clusters (red-main causes included)", data.errors, "run errors");
|
|
@@ -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);
|
|
@@ -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)}` : ""}`;
|