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,18 +1,39 @@
|
|
|
1
|
-
import { commitAll, foldCommitsSince, headOf } from "../git/git.js";
|
|
1
|
+
import { commitAll, foldCommitsSince, headOf, isDirty } from "../git/git.js";
|
|
2
2
|
import { changedFiles } from "../git/git-diff.js";
|
|
3
|
-
import { buildCommitMessage, commitTrailer, extractCommitBody, extractSummary, summaryOverCap, fallbackSummary, formatCommitBody,
|
|
3
|
+
import { buildCommitMessage, commitTrailer, extractCommitBody, extractSummary, summaryOverCap, fallbackSummary, formatCommitBody, appendSelfCheckNotes, stampedSubject, } from "../git/commit-message.js";
|
|
4
4
|
import { logEventBestEffort } from "../events/events.js";
|
|
5
5
|
import { enqueueMerge, entryRevisionFields } from "../merge/merge-queue.js";
|
|
6
6
|
import { plural } from "../text/phrases.js";
|
|
7
|
+
import { errorMessage, squash } from "../text/text.js";
|
|
7
8
|
import { recordFlow } from "./qa-coverage.js";
|
|
8
|
-
import {
|
|
9
|
+
import { recordFiled } from "./feedback-filings.js";
|
|
10
|
+
import { INSTANCE_ROLES, baseRoleOf, configuredInstances } from "../roles/runner-ids.js";
|
|
9
11
|
import { stagedMovedEntries } from "../scheduling/claims.js";
|
|
10
|
-
import { entryKey } from "../backlog/backlog-
|
|
11
|
-
import { assignedMovedFinding, heldMovedFinding } from "./stage-
|
|
12
|
+
import { entryKey } from "../backlog/backlog-parts.js";
|
|
13
|
+
import { assignedMovedFinding, heldMovedFinding } from "./stage-backlog-checks.js";
|
|
12
14
|
import { archiveNewFinishedEntries } from "../backlog/backlog-archive.js";
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/** The most findings one self-check outcome event carries. */
|
|
16
|
+
const SELF_CHECK_LOGGED_FINDINGS = 12;
|
|
17
|
+
/** The cap on each logged finding's text. */
|
|
18
|
+
const SELF_CHECK_FINDING_CHARS = 300;
|
|
19
|
+
/** The findings as the outcome event records them: capped in count and in each text's length. */
|
|
20
|
+
function findingDigest(findings) {
|
|
21
|
+
return findings.slice(0, SELF_CHECK_LOGGED_FINDINGS).map((f) => squash(f, SELF_CHECK_FINDING_CHARS));
|
|
22
|
+
}
|
|
23
|
+
/** The self-check's outcome warning. It bypasses `ctx.warn` because it carries fields beyond a
|
|
24
|
+
* message (the finding texts, and those still open); the event shape is otherwise the same. */
|
|
25
|
+
function logSelfCheckOutcome(ctx, message, findings, remaining = []) {
|
|
26
|
+
logEventBestEffort(ctx.root, {
|
|
27
|
+
source: ctx.role,
|
|
28
|
+
type: "warning",
|
|
29
|
+
message,
|
|
30
|
+
findings: findingDigest(findings),
|
|
31
|
+
...(remaining.length > 0 ? { open: findingDigest(remaining) } : {}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Fold commits an agent run made since `base` into the run's tree, keeping the harness commit
|
|
35
|
+
* the run's only one (INV-H1); warns when any were folded. The one home of the fold-and-warn
|
|
36
|
+
* policy and its wording, shared by the two fold sites — the authoring run (runner-run.ts) and
|
|
16
37
|
* this module's follow-up turns — so they cannot drift. */
|
|
17
38
|
export async function foldAgentCommitsSince(ctx, wt, base) {
|
|
18
39
|
const folded = await foldCommitsSince(wt, base);
|
|
@@ -20,24 +41,24 @@ export async function foldAgentCommitsSince(ctx, wt, base) {
|
|
|
20
41
|
ctx.warn(`agent made ${plural(folded, "commit")} during the run; folded into tumwater's commit`);
|
|
21
42
|
}
|
|
22
43
|
/** Stage a changed worktree for the merge queue: derive the commit message from the reply's
|
|
23
|
-
* SUMMARY block (one bounded follow-up turn when the run left none), flag high-friction
|
|
44
|
+
* SUMMARY block (one bounded follow-up turn when the run left none), flag high-friction runs,
|
|
24
45
|
* commit, pin the commit by the role's merge ref, and enqueue the merge. Extracted from
|
|
25
|
-
*
|
|
26
|
-
* the commit-staging layer — the only place a fresh
|
|
27
|
-
* Returns the
|
|
46
|
+
* RoleRunner.runOnce so the run's own method stays the outcome classifier and this module is
|
|
47
|
+
* the commit-staging layer — the only place a fresh run's work becomes a queued merge.
|
|
48
|
+
* Returns the run's outcome; a failed pin or an aborted follow-up returns an error/aborted
|
|
28
49
|
* outcome without anything queued. */
|
|
29
|
-
export async function
|
|
50
|
+
export async function stageRunMerge(ctx) {
|
|
30
51
|
const s = ctx.state;
|
|
31
|
-
// A revision
|
|
52
|
+
// A revision run carries the round it applied on its context (runner-run.ts): it rides onto the
|
|
32
53
|
// queued merge so a rejection of this change records the NEXT round (plans/revise-rejected.md).
|
|
33
|
-
// Undefined for a fresh change — including a user-request
|
|
34
|
-
// untouched — so only the
|
|
54
|
+
// Undefined for a fresh change — including a user-request run that leaves a pending revision
|
|
55
|
+
// untouched — so only the run that actually held the rejected diff is labeled a revision.
|
|
35
56
|
const revisionRound = ctx.revisionRound;
|
|
36
57
|
// A revision merge carries the review that rejected the change (plans/revise-rejected.md
|
|
37
58
|
// part 2/2): the prior head, its numbered objections, and each objection's persistence count
|
|
38
59
|
// ride the queued entry so the re-review can check each one against the interdiff and the next
|
|
39
|
-
// rejection can resolve a `(prior N)` restatement (BUGS.md 2026-10-14). Only a
|
|
40
|
-
// actually held the rejected diff attaches it; a fresh change — including a user-request
|
|
60
|
+
// rejection can resolve a `(prior N)` restatement (BUGS.md 2026-10-14). Only a run that
|
|
61
|
+
// actually held the rejected diff attaches it; a fresh change — including a user-request run
|
|
41
62
|
// that leaves a pending revision untouched — and a recovery merge carry none.
|
|
42
63
|
const priorReview = revisionRound !== undefined && s.lastReview?.head
|
|
43
64
|
? {
|
|
@@ -52,18 +73,22 @@ export async function stageTickMerge(ctx) {
|
|
|
52
73
|
// only if that too yields nothing is the subject derived from the changed paths.
|
|
53
74
|
let summary = extractSummary(ctx.finalText);
|
|
54
75
|
let body = extractCommitBody(ctx.finalText);
|
|
76
|
+
// True when no reply named the change, so the subject below is derived from its paths.
|
|
77
|
+
let summaryDerived = false;
|
|
55
78
|
if (summary === null) {
|
|
56
79
|
const base = await headOf(ctx.wt, "HEAD");
|
|
57
80
|
const followUp = await ctx.requestSummary(ctx.wt);
|
|
58
81
|
await foldAgentCommitsSince(ctx, ctx.wt, base);
|
|
59
82
|
if (followUp?.aborted)
|
|
60
|
-
return ctx.
|
|
83
|
+
return ctx.finishAbortedRun();
|
|
61
84
|
if (followUp) {
|
|
62
85
|
summary = extractSummary(followUp.finalText);
|
|
63
86
|
body = body ?? extractCommitBody(followUp.finalText);
|
|
64
87
|
}
|
|
65
|
-
if (summary === null)
|
|
88
|
+
if (summary === null) {
|
|
66
89
|
summary = fallbackSummary(await changedFiles(ctx.wt), ctx.role, s.runs);
|
|
90
|
+
summaryDerived = true;
|
|
91
|
+
}
|
|
67
92
|
ctx.warn(`reply had no SUMMARY line — ` +
|
|
68
93
|
(followUp && extractSummary(followUp.finalText) !== null
|
|
69
94
|
? "recovered it with a follow-up turn"
|
|
@@ -82,7 +107,7 @@ export async function stageTickMerge(ctx) {
|
|
|
82
107
|
const followUp = await ctx.requestSummary(ctx.wt, over);
|
|
83
108
|
await foldAgentCommitsSince(ctx, ctx.wt, base);
|
|
84
109
|
if (followUp?.aborted)
|
|
85
|
-
return ctx.
|
|
110
|
+
return ctx.finishAbortedRun();
|
|
86
111
|
const short = followUp && summaryOverCap(followUp.finalText) === null ? extractSummary(followUp.finalText) : null;
|
|
87
112
|
if (short !== null) {
|
|
88
113
|
summary = short;
|
|
@@ -99,11 +124,11 @@ export async function stageTickMerge(ctx) {
|
|
|
99
124
|
// author's own session gets to fix them; the gate still has the final say, and the change commits
|
|
100
125
|
// and queues either way.
|
|
101
126
|
// Claims made at staging (plans/parallel-work-instances.md "Claims", part 4/7): an
|
|
102
|
-
// unassigned
|
|
127
|
+
// unassigned run of a MULTI-instance base whose diff moved a backlog entry out of its
|
|
103
128
|
// actionable section records the claim so a sibling is not assigned it while this change
|
|
104
129
|
// merges. A single-runner base has no sibling to hold the entry from, so it stages nothing
|
|
105
|
-
// and is unchanged by claims. An assigned
|
|
106
|
-
// so the one fix-up turn can put it back. So does an unassigned
|
|
130
|
+
// and is unchanged by claims. An assigned run that moved a DIFFERENT entry gets a finding,
|
|
131
|
+
// so the one fix-up turn can put it back. So does an unassigned run that moved an entry a
|
|
107
132
|
// sibling holds (the poll's heldElsewhere, which its prompt listed as off-limits): that entry
|
|
108
133
|
// is never claimed a second time, and the fix-up turn is asked to drop the duplicate work
|
|
109
134
|
// (BUGS.md 2026-10-12). An unreadable file or git hiccup yields no move and no finding.
|
|
@@ -119,7 +144,7 @@ export async function stageTickMerge(ctx) {
|
|
|
119
144
|
const first = claimable[0];
|
|
120
145
|
s.claim = { file: first.file, key: first.key, title: first.title, at: Date.now(), source: "staged" };
|
|
121
146
|
// The assignment is durable in state before this event; an unwritable feed must not reject
|
|
122
|
-
// the
|
|
147
|
+
// the run and make a claim that did take effect look unapplied (logEventBestEffort).
|
|
123
148
|
logEventBestEffort(ctx.root, {
|
|
124
149
|
source: ctx.role,
|
|
125
150
|
type: "claim",
|
|
@@ -138,50 +163,59 @@ export async function stageTickMerge(ctx) {
|
|
|
138
163
|
if (findings.length > 0) {
|
|
139
164
|
const before = findings.length;
|
|
140
165
|
const fixBase = await headOf(ctx.wt, "HEAD");
|
|
141
|
-
const fixUp = await ctx.requestStageFix(ctx.wt, findings
|
|
166
|
+
const fixUp = await ctx.requestStageFix(ctx.wt, findings, {
|
|
167
|
+
summary: summaryDerived ? null : summary,
|
|
168
|
+
body: body ? formatCommitBody(body) : "",
|
|
169
|
+
});
|
|
142
170
|
await foldAgentCommitsSince(ctx, ctx.wt, fixBase);
|
|
143
171
|
if (fixUp?.aborted)
|
|
144
|
-
return ctx.
|
|
172
|
+
return ctx.finishAbortedRun();
|
|
145
173
|
if (fixUp) {
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
174
|
+
// The fix-up reply answers the findings, not the change, so it never replaces the
|
|
175
|
+
// change's description (BUGS.md 2026-10-21): its SUMMARY is taken only over a subject
|
|
176
|
+
// derived from the paths, and its RISK/VERIFIED are appended as self-check notes.
|
|
177
|
+
const fixSummary = summaryDerived ? extractSummary(fixUp.finalText) : null;
|
|
178
|
+
if (fixSummary !== null)
|
|
179
|
+
summary = fixSummary;
|
|
180
|
+
body = appendSelfCheckNotes(body, extractCommitBody(fixUp.finalText));
|
|
150
181
|
}
|
|
151
182
|
// A fix-up turn may resolve its findings by reverting the whole change. With nothing left
|
|
152
|
-
// in the worktree there is nothing to commit or queue: end the
|
|
183
|
+
// in the worktree there is nothing to commit or queue: end the run no_change here instead
|
|
153
184
|
// of handing a clean tree to commitAll. The worktree is left clean and no pin is taken.
|
|
154
|
-
|
|
155
|
-
|
|
185
|
+
// isDirty, not changedFiles: a `git status` that fails must throw, not read as clean — on
|
|
186
|
+
// 2026-10-21 a spawn failure here ended bugfix-2's 17-minute run no_change and released
|
|
187
|
+
// its edits unpinned; thrown, the run ends unsettled and its next run gets them back.
|
|
188
|
+
if (!(await isDirty(ctx.wt))) {
|
|
189
|
+
logSelfCheckOutcome(ctx, "stage self-check: the follow-up turn left no change; nothing to land", findings);
|
|
156
190
|
return { result: "no_change" };
|
|
157
191
|
}
|
|
158
192
|
const remaining = await ctx.stageCheck(ctx.wt);
|
|
159
|
-
ctx
|
|
193
|
+
logSelfCheckOutcome(ctx, remaining.length === 0
|
|
160
194
|
? `stage self-check: ${plural(before, "finding")} — fixed by the follow-up turn`
|
|
161
|
-
: `stage self-check: ${plural(before, "finding")}; ${remaining.length} still open, queued for the gate
|
|
195
|
+
: `stage self-check: ${plural(before, "finding")}; ${remaining.length} still open, queued for the gate`, findings, remaining);
|
|
162
196
|
}
|
|
163
|
-
// Friction as a signal (plans/refusal-and-thrash.md): a changed
|
|
197
|
+
// Friction as a signal (plans/refusal-and-thrash.md): a changed run that burned BOTH more
|
|
164
198
|
// than thrashTurns turns and thrashMinutes of wall clock is flagged high-friction —
|
|
165
199
|
// difficulty suggests the work may not fit, so it goes to review marked and leaves a warning
|
|
166
200
|
// event. Requiring both (not either) keeps the absolute turn count from measuring model
|
|
167
201
|
// speed: a fast model emits 40+ turns in a few minutes, which is ordinary work, not
|
|
168
|
-
// difficulty (BUGS.md 2026-09-19). Measured over this
|
|
202
|
+
// difficulty (BUGS.md 2026-09-19). Measured over this run's main authoring run (a transient
|
|
169
203
|
// retry included via runAuthoringPi), like the trailer; conflict-resolution runs happen later
|
|
170
204
|
// inside merge().
|
|
171
205
|
const minutes = (Date.now() - ctx.piStartedAt) / 60_000;
|
|
172
|
-
// Friction is measured over this
|
|
173
|
-
// run into
|
|
206
|
+
// Friction is measured over this run's authoring runs only — the review gate folds its
|
|
207
|
+
// run into runTurns AFTER the commit, so every friction artifact (flag, warning event,
|
|
174
208
|
// trailer line, final summary) reads this pre-gate snapshot instead of the live counter.
|
|
175
|
-
const authoringTurns = ctx.
|
|
209
|
+
const authoringTurns = ctx.runTurns;
|
|
176
210
|
const highFriction = authoringTurns > ctx.config.thrashTurns && minutes > ctx.config.thrashMinutes;
|
|
177
211
|
if (highFriction) {
|
|
178
212
|
ctx.warn(`high-friction run: ${authoringTurns} turns in ${Math.round(minutes)} min ` +
|
|
179
213
|
`(thresholds: ${ctx.config.thrashTurns} turns / ${ctx.config.thrashMinutes} min)`);
|
|
180
214
|
}
|
|
181
|
-
// The trailer is harness-stamped truth: turns and peak ctx over this
|
|
215
|
+
// The trailer is harness-stamped truth: turns and peak ctx over this run's pre-commit
|
|
182
216
|
// runs only (conflict-resolution and review runs fold after the commit). A high-friction
|
|
183
|
-
//
|
|
184
|
-
const message = buildCommitMessage(stampedSubject(baseRoleOf(ctx.role), summary), body, commitTrailer(ctx.role, s.runs, authoringTurns, s.peakContextTokens, highFriction ? minutes : undefined, revisionRound));
|
|
217
|
+
// run appends its Friction line here — both values are already computed above.
|
|
218
|
+
const message = buildCommitMessage(stampedSubject(baseRoleOf(ctx.role), summary), body, commitTrailer(ctx.role, s.runs, authoringTurns, s.peakContextTokens, highFriction ? minutes : undefined, revisionRound, ctx.modelLine));
|
|
185
219
|
// The backlog archive (plans/backlog-archive.md): an entry this change pasted under PLANS.md's
|
|
186
220
|
// ## Done or BUGS.md's ## Fixed moves to the top of PLANS-DONE.md or BUGS-FIXED.md now, after
|
|
187
221
|
// the self-check and its fix-up turn worked on the tree the author wrote and before the
|
|
@@ -193,19 +227,19 @@ export async function stageTickMerge(ctx) {
|
|
|
193
227
|
// Pin the commit by its merge ref BEFORE freeing the worktree (invariant 4), then hand it
|
|
194
228
|
// to the merge queue: from here on the review gate and the rebase run in a verification's
|
|
195
229
|
// leased pool slot, never in this worktree (plans/merge-queue.md 2/5). A failed pin defers to
|
|
196
|
-
// next-
|
|
230
|
+
// next-run recovery — merge without a pin would lose the ref lifecycle this whole flow depends
|
|
197
231
|
// on. The reviewer checks the author's claimed WHY/VERIFIED against the actual diff; no diff
|
|
198
232
|
// reaches main unreviewed.
|
|
199
233
|
if (!(await ctx.pinAndReset(ctx.wt, commit))) {
|
|
200
234
|
s.lastError = "failed to pin the merge ref; left for the next run's recovery";
|
|
201
235
|
return { result: "error", summary: s.lastError };
|
|
202
236
|
}
|
|
203
|
-
// The commit is pinned and the worktree is free: enqueue the merge and END the
|
|
237
|
+
// The commit is pinned and the worktree is free: enqueue the merge and END the run — the
|
|
204
238
|
// orchestrator drains the queue on its single merge slot, outside the author semaphore, so
|
|
205
239
|
// this slot is free the moment the work is committed (plans/merge-queue.md 3/5). The merge
|
|
206
240
|
// runs the same gate + merge flow through runMergePi/foldMergeUsage on this same state
|
|
207
241
|
// object — recording the commit count, the outcome, and the reviewer spend into it — and logs
|
|
208
|
-
// merge_done/merge_failed; a non-terminal outcome keeps the pin for next-
|
|
242
|
+
// merge_done/merge_failed; a non-terminal outcome keeps the pin for next-run leftover recovery.
|
|
209
243
|
// `commits` was NOT incremented above: it counts merged changes only.
|
|
210
244
|
enqueueMerge(ctx.root, {
|
|
211
245
|
role: ctx.role,
|
|
@@ -224,14 +258,38 @@ export async function stageTickMerge(ctx) {
|
|
|
224
258
|
if (revisionRound !== undefined)
|
|
225
259
|
s.revision = undefined;
|
|
226
260
|
// The merge sits in the durable queue above and the commit is pinned; the event is
|
|
227
|
-
// observability, so an unwritable feed must not reject a
|
|
261
|
+
// observability, so an unwritable feed must not reject a run whose work is already queued.
|
|
228
262
|
logEventBestEffort(ctx.root, { source: ctx.role, type: "merge_queued", commit, summary });
|
|
229
263
|
// The flag's durable record is the Friction trailer line stamped on the commit above;
|
|
230
264
|
// lastSummary and the run_end event carry it too for dashboards and logs.
|
|
231
265
|
const finalSummary = highFriction
|
|
232
266
|
? `${summary} (high friction: ${authoringTurns} turns / ${Math.round(minutes)}m)`
|
|
233
267
|
: summary;
|
|
234
|
-
if (ctx.flow)
|
|
235
|
-
|
|
268
|
+
if (ctx.flow) {
|
|
269
|
+
// The merge is already queued and the ledger only orders the qa menu, so a failed write
|
|
270
|
+
// warns rather than failing the run.
|
|
271
|
+
try {
|
|
272
|
+
recordFlow(ctx.root, ctx.flow.flow, ctx.flow.result, ctx.flow.result === "bug" ? summary : undefined);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
ctx.warn(`qa flow "${ctx.flow.flow}" not recorded: ${errorMessage(err)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// A feedback run that somehow changed the tree still records its filing, exactly as the
|
|
279
|
+
// qa flow does above — a filing is a result independent of the run leaving files behind.
|
|
280
|
+
if (ctx.filed) {
|
|
281
|
+
logEventBestEffort(ctx.root, {
|
|
282
|
+
source: ctx.role,
|
|
283
|
+
type: "issue_filed",
|
|
284
|
+
issue: ctx.filed.issue,
|
|
285
|
+
title: ctx.filed.title,
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
recordFiled(ctx.root, ctx.filed.issue, ctx.filed.title);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
ctx.warn(`feedback filing #${ctx.filed.issue} not recorded: ${errorMessage(err)}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
236
294
|
return { result: "queued", summary: finalSummary, commit, highFriction };
|
|
237
295
|
}
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
import { BUILD_CHECK_TIMEOUT_MS } from "../build/build-check-detect.js";
|
|
3
|
-
import {
|
|
2
|
+
import { warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
4
3
|
import { removeQuiet } from "../files/files.js";
|
|
5
4
|
import { mergeStatePath } from "../paths.js";
|
|
6
5
|
import { runOnAbort } from "../process/process.js";
|
|
7
|
-
/** The orchestrator's
|
|
6
|
+
/** The orchestrator's run-timing and scheduling seams (src/orchestrator/orchestrator.ts
|
|
8
7
|
* keeps the poll loop itself — the control flow that calls these). Each is exported as a
|
|
9
|
-
* unit-test seam (test/orchestrator-seams.test.ts): timing a semaphore-gated
|
|
8
|
+
* unit-test seam (test/orchestrator-seams.test.ts): timing a semaphore-gated run, an
|
|
10
9
|
* abort-interruptible sleep, and the restart hand-off's bounded wait on the in-flight merge.
|
|
11
10
|
* Split out of orchestrator.ts — which had grown into both the poll loop and the timing helpers
|
|
12
11
|
* it schedules with — so the loop reads as control flow over these named steps. The team-health
|
|
13
12
|
* polls (the team hold, the error-storm warning, the failure-spread alarm) are their own family
|
|
14
13
|
* in src/team/team-polls.ts. */
|
|
15
|
-
/** How many completed role-
|
|
14
|
+
/** How many completed role-run samples the p75 needs before it is trusted as the drain window.
|
|
16
15
|
* Below this the orchestrator reports no p75 and poll keeps the cold-start constant. */
|
|
17
16
|
const DRAIN_P75_MIN_SAMPLES = 10;
|
|
18
|
-
/** The p75 of completed role-
|
|
19
|
-
* trust. The orchestrator's half of the adaptive drain window (BUGS.md 2026-09-18): a
|
|
17
|
+
/** The p75 of completed role-run durations (ms), or null when there are too few samples to
|
|
18
|
+
* trust. The orchestrator's half of the adaptive drain window (BUGS.md 2026-09-18): a run that
|
|
20
19
|
* finishes inside it is waited for, a longer one is aborted resumably. The samples come from
|
|
21
|
-
*
|
|
20
|
+
* runTimedRoleRun above; the consumer is the redeploy policy's InFlightCounts.roleRunP75Ms.
|
|
22
21
|
* Exported for its unit test; the p75 is the statistic the bug's expected fix names. */
|
|
23
|
-
export function
|
|
22
|
+
export function p75RunDurationMs(durations) {
|
|
24
23
|
if (durations.length < DRAIN_P75_MIN_SAMPLES)
|
|
25
24
|
return null;
|
|
26
25
|
const sorted = [...durations].sort((a, b) => a - b);
|
|
@@ -29,8 +28,8 @@ export function p75TickDurationMs(durations) {
|
|
|
29
28
|
/** How long a restart's hand-off waits on an in-flight merge, per phase: one window for it to
|
|
30
29
|
* finish on its own, then — having aborted it — one more for the step it was in to end
|
|
31
30
|
* (awaitMergeForHandoff; BUGS.md 2026-09-23). By the time a restart reaches its `finally` the new
|
|
32
|
-
* build is already in dist/ and every role
|
|
33
|
-
* is still running: each minute spent on it is a minute nothing else
|
|
31
|
+
* build is already in dist/ and every role run is done or aborted, so the merge is all the team
|
|
32
|
+
* is still running: each minute spent on it is a minute nothing else runs. A merge's unbounded
|
|
34
33
|
* part is model work — its reviewer run (and, until 2026-09-24, a gate build-fix run: the
|
|
35
34
|
* 2026-09-23 one held the slot for 4 h 35 m and that day's hand-off sat through 97 minutes of it).
|
|
36
35
|
* Its deterministic part is bounded: at most one build check (BUILD_CHECK_TIMEOUT_MS) plus git
|
|
@@ -38,35 +37,35 @@ export function p75TickDurationMs(durations) {
|
|
|
38
37
|
* last check and fast-forward — merge, and gives an aborted merge's current step (a check, which
|
|
39
38
|
* no abort reaches) time to end, so the hand-off does not leave a check running in a merger
|
|
40
39
|
* worktree the next generation is about to reset. Not the drain's window: that is the p75 of whole
|
|
41
|
-
* role
|
|
40
|
+
* role runs (over an hour on this team) — the very lag this bounds. */
|
|
42
41
|
export const HANDOFF_MERGE_WINDOW_MS = BUILD_CHECK_TIMEOUT_MS + 60_000;
|
|
43
|
-
/** Run one role
|
|
42
|
+
/** Run one role run under the concurrency semaphore and return how long the run itself ran,
|
|
44
43
|
* in ms — null when it never started (the harness is already stopping, or `held`) or was cut
|
|
45
44
|
* off by an abort. This is the restart drain's p75 sample (BUGS.md 2026-09-18). The clock starts
|
|
46
|
-
* only once the permit is granted, so time a
|
|
47
|
-
* counted as work: the drain waits on
|
|
45
|
+
* only once the permit is granted, so time a run spends parked in the semaphore queue is not
|
|
46
|
+
* counted as work: the drain waits on runs that are already running, and folding queue wait
|
|
48
47
|
* into the window would overstate how long they have left (the `run_start`..`run_end` span
|
|
49
|
-
* the window was sized against excludes it too). Aborted
|
|
50
|
-
* cut-off lengths cannot drag the window down. So do
|
|
48
|
+
* the window was sized against excludes it too). Aborted runs return null so their short
|
|
49
|
+
* cut-off lengths cannot drag the window down. So do runs that ended with nothing to show
|
|
51
50
|
* (`no_change`, `skipped`): a `main moved` re-check runs the model and still finishes in
|
|
52
51
|
* seconds, and sampling it equally let a team's window collapse to the re-check scale and
|
|
53
52
|
* cut real work off minutes into its run (BUGS.md 2026-09-30) — only a work-bearing outcome
|
|
54
53
|
* says how long the work the drain waits on takes. `now` is a test seam.
|
|
55
54
|
*
|
|
56
|
-
* `held` is the team-wide hold on new
|
|
57
|
-
* moment a reserved
|
|
55
|
+
* `held` is the team-wide hold on new runs (today the restart drain's), re-checked at the one
|
|
56
|
+
* moment a reserved run actually begins: when its permit is granted. A run scheduled before
|
|
58
57
|
* the hold may have parked in the semaphore queue long before it; checking the hold only at
|
|
59
58
|
* scheduling let every such waiter start a fresh pi run mid-drain as slots freed — the very
|
|
60
|
-
*
|
|
61
|
-
* calling `
|
|
59
|
+
* runs the restart then aborted (BUGS.md 2026-09-23). A held run releases its permit without
|
|
60
|
+
* calling `run`, so it writes no state and logs no run_start/run_end; the caller hands its
|
|
62
61
|
* reservation back. */
|
|
63
|
-
export async function
|
|
62
|
+
export async function runTimedRoleRun(signal, acquire, release, run, now = Date.now, held = () => false) {
|
|
64
63
|
await acquire();
|
|
65
64
|
try {
|
|
66
65
|
if (signal.aborted || held())
|
|
67
66
|
return null;
|
|
68
67
|
const startedAt = now();
|
|
69
|
-
const outcome = await
|
|
68
|
+
const outcome = await run();
|
|
70
69
|
if (outcome.result === "aborted" || outcome.result === "user_aborted")
|
|
71
70
|
return null;
|
|
72
71
|
if (outcome.result === "no_change" || outcome.result === "skipped")
|
|
@@ -81,7 +80,7 @@ export async function runTimedRoleTick(signal, acquire, release, tick, now = Dat
|
|
|
81
80
|
* abort) is prompt instead of waiting out the current poll cycle. The listener is removed
|
|
82
81
|
* on either exit path so long-running orchestrators don't accumulate one per poll, and an
|
|
83
82
|
* already-aborted signal returns synchronously through runOnAbort. Exported as a unit-test
|
|
84
|
-
* seam, like
|
|
83
|
+
* seam, like runTimedRoleRun above. */
|
|
85
84
|
export function sleepInterruptible(ms, signal) {
|
|
86
85
|
return new Promise((resolve) => {
|
|
87
86
|
const onAbort = () => {
|
|
@@ -126,7 +125,7 @@ function settlesWithin(promise, ms, after) {
|
|
|
126
125
|
* swap by 97 minutes, in silence. The wait is announced as it starts (one warning naming the
|
|
127
126
|
* merge's roles), so the feed says why `restarting onto build …` is not yet followed by
|
|
128
127
|
* `orchestrator stopped`. Past `windowMs` the merge is stopped the way the drain gives up on role
|
|
129
|
-
*
|
|
128
|
+
* runs: `abort` fires the harness's internal stop, which kills its pi runs at once and makes it
|
|
130
129
|
* stop at its next step boundary (merge-core.ts, merge-batch.ts, and merge-stack.ts check the
|
|
131
130
|
* signal before every gate, every approved merge, and each of a stack's check attempts), and a
|
|
132
131
|
* warning names what was still awaited. An aborted merge records `aborted` like any shutdown
|
|
@@ -134,24 +133,25 @@ function settlesWithin(promise, ms, after) {
|
|
|
134
133
|
* new build. One still running a window after the abort (a step wedged past its own bound) is left
|
|
135
134
|
* behind: its 4/5 marker is cleared here, since this process exits the moment the hand-off returns,
|
|
136
135
|
* and its entries and pins survive exactly as a crash leaves them — the next generation's first
|
|
137
|
-
* drain re-merges them.
|
|
136
|
+
* drain re-merges them. Its announcements are best-effort: an events feed that cannot be written
|
|
137
|
+
* does not shorten the wait. Exported as a unit-test seam, like runTimedRoleRun. */
|
|
138
138
|
export async function awaitMergeForHandoff(root, merge, windowMs, abort, after = realAfterDelay) {
|
|
139
139
|
const roles = merge.roles.join(", ");
|
|
140
140
|
const deadline = `${windowMs / 1000}s`;
|
|
141
|
-
|
|
141
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `restart waiting on the merge in progress for ${roles} (deadline ${deadline})`);
|
|
142
142
|
if (await settlesWithin(merge.promise, windowMs, after))
|
|
143
143
|
return "finished";
|
|
144
144
|
abort();
|
|
145
|
-
|
|
145
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `restart: the merge for ${roles} outlived its ${deadline} deadline — aborted; its pinned commits survive for the next restart`);
|
|
146
146
|
if (await settlesWithin(merge.promise, windowMs, after))
|
|
147
147
|
return "aborted";
|
|
148
148
|
removeQuiet(mergeStatePath(root));
|
|
149
|
-
|
|
149
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `restart: the aborted merge for ${roles} was still running ${deadline} later — restarting without it; its queue entries and pinned commits survive for the next restart`);
|
|
150
150
|
return "abandoned";
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
153
153
|
* Drain the orchestrator's in-flight work at shutdown: wait out the reserved role and director
|
|
154
|
-
*
|
|
154
|
+
* runs, and await the merge pipeline's tasks beside them — and how depends on what comes
|
|
155
155
|
* next. On an operator stop the harness signal has already aborted the merge — its pi runs die
|
|
156
156
|
* and it stops at its next step boundary, ending "aborted" with its ref kept and its entry
|
|
157
157
|
* dropped for next-start recovery — and it is waited out: returning early would remove
|
|
@@ -159,15 +159,16 @@ export async function awaitMergeForHandoff(root, merge, windowMs, abort, after =
|
|
|
159
159
|
* concurrent merger (a second Ctrl+C still forces the exit). On a restart the caller exits for
|
|
160
160
|
* the next generation the moment this returns, and a merge is not bounded by one reviewer run
|
|
161
161
|
* — a merge's check follows the verifications' — so the wait is a bounded hand-off that
|
|
162
|
-
* announces itself (awaitMergeForHandoff; BUGS.md 2026-09-23).
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
162
|
+
* announces itself (awaitMergeForHandoff; BUGS.md 2026-09-23). The announcements are best-effort:
|
|
163
|
+
* an events feed that cannot be written does not shorten the wait. "The merge" is every merge task
|
|
164
|
+
* at once — each verification holding a permit, and the merge — waited on (and, past the
|
|
165
|
+
* hand-off's window, aborted) together under the roles of them all. The approved changes waiting
|
|
166
|
+
* for the merge have no task, and a verification still parked for its permit has started nothing (a
|
|
166
167
|
* shutdown settles it at once; a restart's closed start gate at its grant): all of them keep their
|
|
167
168
|
* entries and pins for the next start.
|
|
168
169
|
*/
|
|
169
170
|
export async function drainInFlightWork(root, roleInFlight, directorInFlight, inFlight, restart, handoffWindowMs, abort, after = realAfterDelay) {
|
|
170
|
-
const
|
|
171
|
+
const runs = Promise.allSettled([...roleInFlight, ...directorInFlight]);
|
|
171
172
|
const merge = inFlight.length === 0
|
|
172
173
|
? null
|
|
173
174
|
: {
|
|
@@ -179,18 +180,19 @@ export async function drainInFlightWork(root, roleInFlight, directorInFlight, in
|
|
|
179
180
|
// wires the harness signal to its controller, and a conflict-resolution run watches the
|
|
180
181
|
// harness signal alone (runMergePi). Nothing else it reaches can start work here — the
|
|
181
182
|
// director has finished, permit holders were aborted at the restart, and a parked waiter
|
|
182
|
-
// meets the closed start gate (
|
|
183
|
+
// meets the closed start gate (runStartHeld) whenever it is granted a permit.
|
|
183
184
|
const outcome = await awaitMergeForHandoff(root, merge, handoffWindowMs, abort, after);
|
|
184
185
|
// An abandoned merge still holds its permit, so a waiter parked behind it would never be
|
|
185
|
-
// granted one and never settle. The
|
|
186
|
+
// granted one and never settle. The runs have had both windows beside the hand-off, so
|
|
186
187
|
// whatever is still reserved then started nothing (or is wedged like the merge): the
|
|
187
188
|
// hand-off goes ahead without it too.
|
|
188
189
|
if (outcome !== "abandoned")
|
|
189
|
-
await
|
|
190
|
+
await runs;
|
|
190
191
|
}
|
|
191
192
|
else {
|
|
192
|
-
if (merge)
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
if (merge) {
|
|
194
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `shutdown waiting on the merge in progress for ${merge.roles.join(", ")}`);
|
|
195
|
+
}
|
|
196
|
+
await Promise.allSettled([runs, merge?.promise]);
|
|
195
197
|
}
|
|
196
198
|
}
|