tumwater 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +55 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +6 -4
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +404 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +281 -55
- package/dist/src/backlog/backlog-cli-view.js +255 -0
- package/dist/src/backlog/backlog-cli.js +91 -335
- package/dist/src/backlog/backlog-commit.js +20 -13
- package/dist/src/backlog/backlog-eligibility.js +193 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +264 -0
- package/dist/src/backlog/backlog-release.js +134 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-guardrails.js +136 -0
- package/dist/src/baseline/main-red.js +153 -36
- package/dist/src/baseline/main-watch.js +232 -0
- package/dist/src/brief/brief-edit.js +12 -12
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget-fallback-view.js +288 -0
- package/dist/src/budget/budget-wiring.js +158 -0
- package/dist/src/budget/budget.js +31 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +65 -3
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +181 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +183 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +291 -598
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +525 -0
- package/dist/src/cli/cli-start-summary.js +167 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +193 -237
- package/dist/src/cli/completion.js +169 -22
- package/dist/src/cli/config-commands.js +58 -26
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +258 -82
- package/dist/src/cli/log-commands.js +164 -85
- package/dist/src/cli/question-commands.js +211 -10
- package/dist/src/cli.js +173 -45
- package/dist/src/collections.js +56 -13
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +16 -1
- package/dist/src/config/config-fallbacks.js +94 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +169 -222
- package/dist/src/config/config-views.js +136 -54
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +164 -265
- package/dist/src/config/config.js +53 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +330 -0
- package/dist/src/doctor/doctor-backlog-plans.js +398 -0
- package/dist/src/doctor/doctor-backlog.js +249 -512
- package/dist/src/doctor/doctor-check-names.js +85 -0
- package/dist/src/doctor/doctor-checks.js +61 -593
- package/dist/src/doctor/doctor-claude-checks.js +142 -0
- package/dist/src/doctor/doctor-codex-checks.js +276 -0
- package/dist/src/doctor/doctor-contention-checks.js +506 -0
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +80 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +216 -537
- package/dist/src/doctor/doctor-notify.js +9 -5
- package/dist/src/doctor/doctor-opencode-checks.js +164 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-project-checks.js +397 -0
- package/dist/src/doctor/doctor.js +126 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +21 -12
- package/dist/src/events/event-format.js +185 -89
- package/dist/src/events/event-labels.js +11 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +52 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +80 -26
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +65 -60
- package/dist/src/failure/failure-render.js +29 -21
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +35 -35
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +88 -225
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +101 -143
- package/dist/src/gates/maintenance-quota.js +37 -30
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +89 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +54 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +43 -19
- package/dist/src/gui/gui-backlog-endpoints.js +156 -0
- package/dist/src/gui/gui-command.js +26 -13
- package/dist/src/gui/gui-config-commands.js +168 -80
- package/dist/src/gui/gui-endpoint-commands.js +5 -4
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +235 -13
- package/dist/src/gui/gui-prompt-commands.js +26 -20
- package/dist/src/gui/gui-report-endpoints.js +97 -0
- package/dist/src/gui/gui-server.js +26 -5
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +155 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +190 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +28 -17
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +58 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +45 -19
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +214 -0
- package/dist/src/merge/merge-queue-render.js +48 -11
- package/dist/src/merge/merge-queue.js +52 -15
- package/dist/src/merge/merge-requeue.js +103 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +27 -11
- package/dist/src/merge/merge-to-main.js +77 -26
- package/dist/src/merge/merge-top.js +83 -0
- package/dist/src/merge/merge-verify.js +75 -18
- package/dist/src/notes-cli.js +35 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +352 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +71 -370
- package/dist/src/orchestrator/orchestrator.js +129 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +245 -0
- package/dist/src/orchestrator/scheduling-skip.js +195 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +126 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +26 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +150 -217
- package/dist/src/prompt/notes-backlog.js +184 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +131 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +188 -124
- package/dist/src/report/report-fold.js +130 -54
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +136 -65
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +23 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +112 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +239 -0
- package/dist/src/review/protected-edits.js +24 -10
- package/dist/src/review/quarantine-render.js +65 -0
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +180 -401
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +127 -12
- package/dist/src/roles/role-guidance.js +65 -20
- package/dist/src/roles/role-labels.js +7 -5
- package/dist/src/roles/role-render.js +108 -56
- package/dist/src/roles/role-view.js +95 -56
- package/dist/src/roles/roles.js +33 -33
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/run/qa-coverage.js +125 -0
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +175 -0
- package/dist/src/run/run-detail-render.js +91 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +73 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +122 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +170 -83
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +105 -57
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +178 -0
- package/dist/src/run/stage-checks-shared.js +65 -0
- package/dist/src/run/stage-name-checks.js +204 -0
- package/dist/src/run/stage-path-checks.js +231 -0
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +216 -0
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +191 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +109 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +39 -18
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +236 -245
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +103 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +49 -17
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +43 -28
- package/dist/src/status/status-roles.js +50 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +7 -8
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +65 -18
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +60 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +78 -13
- package/dist/src/ui/badges.js +99 -38
- package/dist/src/ui/gui/gui-client-backlog.js +314 -0
- package/dist/src/ui/gui/gui-client-boot.js +89 -61
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +254 -0
- package/dist/src/ui/gui/gui-client-drawer.js +155 -289
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +437 -68
- package/dist/src/ui/gui/gui-client-labels.js +6 -3
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +60 -52
- package/dist/src/ui/gui/gui-client-pending.js +405 -24
- package/dist/src/ui/gui/gui-client-report-chart.js +492 -0
- package/dist/src/ui/gui/gui-client-report.js +105 -430
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +163 -5
- package/dist/src/ui/gui/gui-client-team.js +36 -465
- package/dist/src/ui/gui/gui-client.js +38 -26
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +10 -7
- package/dist/src/ui/gui/gui-styles.js +38 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +33 -32
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +364 -49
- package/dist/src/ui/status-render.js +109 -82
- package/dist/src/ui/team-alerts.js +728 -62
- package/dist/src/ui/tone.js +12 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +53 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +4 -12
- package/dist/src/ui/tui/tui-editor-backends.js +127 -0
- package/dist/src/ui/tui/tui-editor-brief.js +14 -36
- package/dist/src/ui/tui/tui-editor-budget.js +36 -39
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +25 -0
- package/dist/src/ui/tui/tui-editor-config.js +99 -0
- package/dist/src/ui/tui/tui-editor-entry.js +39 -64
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +46 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +117 -0
- package/dist/src/ui/tui/tui-editor-interval.js +29 -16
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +9 -29
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +95 -41
- package/dist/src/ui/tui/tui-editor-retire.js +5 -13
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +71 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +101 -34
- package/dist/src/ui/tui/tui-editor-slots.js +24 -0
- package/dist/src/ui/tui/tui-editors.js +44 -2
- package/dist/src/ui/tui/tui-frame.js +68 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +33 -9
- package/dist/src/ui/tui/tui-pane-activity.js +35 -19
- package/dist/src/ui/tui/tui-pane-backlog.js +198 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +168 -8
- package/dist/src/ui/tui/tui-pane-history.js +446 -82
- package/dist/src/ui/tui/tui-pane-pending.js +539 -15
- package/dist/src/ui/tui/tui-pane-queued.js +78 -57
- package/dist/src/ui/tui/tui-pane-report.js +54 -16
- package/dist/src/ui/tui/tui-pane-session.js +34 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +91 -28
- package/dist/src/ui/tui/tui-panes.js +153 -34
- package/dist/src/ui/tui/tui.js +18 -15
- package/dist/src/verdict/fix-claim.js +129 -33
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +77 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/leftover.js +0 -130
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/qa-coverage.js +0 -108
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* what the loop IS (catalog entry or custom, enabled, paused, tier), what it RUNS ON
|
|
1
|
+
/** One role's read-only inspection payload — the collector behind `tumwater role <id>`:
|
|
2
|
+
* what the role IS (catalog entry or custom, enabled, paused, tier), what it RUNS ON
|
|
4
3
|
* (resolved provider/model, the budget fallback pair), what it CARRIES (the instructions
|
|
5
|
-
* override and the role's own find text verbatim), and what its NEXT
|
|
4
|
+
* override and the role's own find text verbatim), and what its NEXT run's prompt will
|
|
6
5
|
* read like, assembled through the preview seam so nothing queued is consumed. Split out
|
|
7
6
|
* of cli.ts like backlogPayload and status/status-data.ts's snapshot so the collector is testable
|
|
8
7
|
* without the CLI and one renderer consumes exactly this shape. Every read degrades like
|
|
@@ -13,23 +12,26 @@ import { defaultConfig, enabledRoleIds, isCustomRole, knownRoleIds, loadConfigSa
|
|
|
13
12
|
import { DIRECTOR_ROLE, customRole, roleById, roleTier, unknownRoleMessage } from "./roles.js";
|
|
14
13
|
import { queuedRolePromptCount } from "../inbox/inbox.js";
|
|
15
14
|
import { pausedRoles } from "../team/team-state.js";
|
|
16
|
-
import {
|
|
17
|
-
import { baseRoleOf,
|
|
15
|
+
import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
16
|
+
import { baseRoleOf, runnerIds, runnerPaused } from "./runner-ids.js";
|
|
18
17
|
import { roleCapVerdicts } from "../gates/role-cap-gates.js";
|
|
19
18
|
import { yieldMultiplierFor } from "../scheduling/backoff.js";
|
|
20
19
|
import { quietHoursStatus } from "../scheduling/quiet-hours.js";
|
|
21
|
-
import {
|
|
22
|
-
import { configForRole, resolvedModelFields, reviewRunConfig, roleSeamTier, seamConcurrencyKey } from "../config/config-views.js";
|
|
20
|
+
import { assembleRunPrompt } from "../run/run-prompt.js";
|
|
21
|
+
import { configForRole, resolvedModelFields, reviewedAfterMerge, reviewRunConfig, roleSeamTier, seamConcurrencyKey } from "../config/config-views.js";
|
|
23
22
|
import { fallbackPair, modelFallbackView } from "../config/config-fallbacks.js";
|
|
24
23
|
import { fallbackModelPair, piModelsPath, seamContextWindow } from "../pi/pi-models.js";
|
|
25
24
|
import { readOpencodeCache } from "../agent/opencode/opencode-catalog.js";
|
|
26
25
|
import { formatModelSelector } from "../config/model-selector.js";
|
|
27
26
|
import { roleImageInfo } from "../agent/image-input.js";
|
|
27
|
+
import { readPiListedModels } from "../pi/pi-listed-models.js";
|
|
28
28
|
import { readImageProbeCache } from "../doctor/image-probe.js";
|
|
29
29
|
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
30
|
-
import {
|
|
30
|
+
import { readClaudeAccount } from "../agent/claude/claude-account.js";
|
|
31
|
+
import { readAccount, windowEntries } from "../agent/subscription-account.js";
|
|
31
32
|
import { CONFIG_BASENAME, configPath, roleNotesPath } from "../paths.js";
|
|
32
|
-
import {
|
|
33
|
+
import { coverageRows, readQaCoverage } from "../run/qa-coverage.js";
|
|
34
|
+
import { readTextOrNull, statOrNull } from "../files/files.js";
|
|
33
35
|
import { eventRole, eventUsage, readEvents } from "../events/event-read.js";
|
|
34
36
|
import { finiteNumber, objectOrEmpty } from "../files/json-object.js";
|
|
35
37
|
import { canonicalRoleId } from "./legacy-role-ids.js";
|
|
@@ -49,7 +51,7 @@ export function rawRoleField(root, roleId, field) {
|
|
|
49
51
|
return undefined;
|
|
50
52
|
return obj[field];
|
|
51
53
|
}
|
|
52
|
-
/** Collect one
|
|
54
|
+
/** Collect one role's inspection payload. `modelsPath` overrides pi's model definitions location
|
|
53
55
|
* — a test seam, like status/status-data.ts's snapshot — consulted only when a fallback pair is
|
|
54
56
|
* configured (the freeness verdict needs the definitions; nothing else does). */
|
|
55
57
|
export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now = new Date()) {
|
|
@@ -61,30 +63,37 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
61
63
|
const cfg = config ?? defaultConfig();
|
|
62
64
|
const catalog = roleById(role);
|
|
63
65
|
const customEntry = cfg.customRoles.find((c) => c.name === role);
|
|
64
|
-
// The director is inspectable like any
|
|
66
|
+
// The director is inspectable like any role but has no catalog entry: a pseudo-Role whose
|
|
65
67
|
// find text is empty (rolePayload nulls it below — the director's queued prompts ARE its
|
|
66
68
|
// work), so the unknown-role throw only fires for ids nothing can answer.
|
|
67
69
|
const resolved = catalog ??
|
|
68
70
|
(customEntry ? customRole(customEntry.name, customEntry.task)
|
|
69
71
|
: role === DIRECTOR_ROLE ? { id: DIRECTOR_ROLE, title: DIRECTOR_ROLE, find: "" }
|
|
70
72
|
: undefined);
|
|
71
|
-
|
|
73
|
+
// roleById maps any `feature-N` to its base whatever the config runs, so an instance id
|
|
74
|
+
// counts only when knownRoleIds (the ids the config runs) holds it.
|
|
75
|
+
if (!resolved || !knownRoleIds(cfg).includes(role)) {
|
|
72
76
|
// The one unknown-role answer (unknownRoleMessage): the CLI's main catch turns the throw
|
|
73
77
|
// into `tumwater: <message>` with exit 1, so the collector keeps the wording and the CLI
|
|
74
78
|
// stays a thin peel-parse-print layer.
|
|
75
79
|
throw new Error(unknownRoleMessage(role, knownRoleIds(cfg)));
|
|
76
80
|
}
|
|
77
81
|
const effective = configForRole(cfg, role);
|
|
78
|
-
// The
|
|
79
|
-
// the review gate's when the reviewer runs
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// The subscription backend whose billing is in play — the role's OWN seam when it routes
|
|
83
|
+
// to a non-pi backend, else the review gate's backend when the reviewer runs a non-pi
|
|
84
|
+
// backend (today's claude precedence generalized off the literal "claude", part 3c/9;
|
|
85
|
+
// PLANS.md "Mixed claude + pi fleets, part 3/4" gap 7): a pi author on a claude/codex
|
|
86
|
+
// reviewer still shows where its changes' spend lands. Read that backend's account; the
|
|
87
|
+
// account file is team-global, so either seam reads the same answer. Its subscription
|
|
88
|
+
// window's primary type rides along for the renderer's label.
|
|
89
|
+
const roleBackend = effective.agent !== undefined && effective.agent !== "pi" ? effective.agent : undefined;
|
|
90
|
+
const reviewAgent = reviewRunConfig(cfg).agent;
|
|
91
|
+
const reviewBackend = cfg.review.enabled && reviewAgent !== undefined && reviewAgent !== "pi" ? reviewAgent : undefined;
|
|
92
|
+
const billingBackend = roleBackend ?? reviewBackend;
|
|
93
|
+
const account = billingBackend !== undefined ? readAccount(root, billingBackend) : null;
|
|
85
94
|
const info = readOrchestratorInfo(root);
|
|
86
95
|
const chainStep = (orchestratorAlive(root, info) && info?.chain?.[roleSeamTier(cfg, role)]) || null;
|
|
87
|
-
const billingWindow = account?.billing === "subscription" ?
|
|
96
|
+
const billingWindow = account?.billing === "subscription" ? windowEntries(account)[0]?.type : undefined;
|
|
88
97
|
// pi's own saved default for a pi seam that resolves no model, read once beside the models
|
|
89
98
|
// catalog this collector already takes as a seam (piSettingsPath). The config's `thinking`
|
|
90
99
|
// wins over the default's because tumwater passes it to pi as `--thinking`, mirroring the
|
|
@@ -117,28 +126,34 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
117
126
|
// gate does) keeps them on one answer. OpenCode's `catalog` is threaded the way claude's
|
|
118
127
|
// `account` is: absent, an opencode pair reads NOT free.
|
|
119
128
|
const opencodeCatalog = readOpencodeCache(root);
|
|
120
|
-
|
|
129
|
+
// The claude account answer (6a/7), read the way the budget gate reads it — always, so the
|
|
130
|
+
// fallback price verdict sees the subscription even when claude is not this role's own seam
|
|
131
|
+
// (a pi role may still fall back to claude). The lookup returns it only for "claude" (part
|
|
132
|
+
// 2a/9) — no caller asks for another backend yet.
|
|
133
|
+
const claudeFallbackAccount = readClaudeAccount(root);
|
|
134
|
+
const accounts = (b) => (b === "claude" ? claudeFallbackAccount : null);
|
|
135
|
+
const freeFallback = declaredFallback ? fallbackModelPair(cfg, modelsPath, accounts, opencodeCatalog) : null;
|
|
121
136
|
const fallback = freeFallback ?? declaredFallback;
|
|
122
|
-
// One state read serves both the model-fallback episode and the next-
|
|
123
|
-
const state =
|
|
137
|
+
// One state read serves both the model-fallback episode and the next-run preview below.
|
|
138
|
+
const state = loadRunnerState(root, role);
|
|
124
139
|
const episode = state.modelFallback;
|
|
125
|
-
// The
|
|
140
|
+
// The role's recent run outcomes, read from the event log (readEvents) so the command
|
|
126
141
|
// answers with the team stopped and never touches live state. readEvents returns the log's
|
|
127
142
|
// newest events in file (chronological) order, so filtering to this role's run_end events
|
|
128
143
|
// and taking the last 8 yields the newest 8, reversed to newest-first. Durations and
|
|
129
144
|
// summaries ride along only when the event carried them (a torn or empty line drops them),
|
|
130
|
-
// and an empty result is omitted so a never-
|
|
131
|
-
// One event-log read serves both per-
|
|
145
|
+
// and an empty result is omitted so a never-run role carries no `recentOutcomes` at all.
|
|
146
|
+
// One event-log read serves both per-role facts — the recent outcomes and the spend sum — so
|
|
132
147
|
// the two cannot drift on what they cover: filtered to this role's run_end events once, then
|
|
133
148
|
// the outcome projection keeps the newest valid 8 while the usage fold sums the whole window.
|
|
134
149
|
const events = readEvents(root);
|
|
135
|
-
const
|
|
150
|
+
const runEnds = events.filter((ev) => ev.type === "run_end" && eventRole(ev) === role);
|
|
136
151
|
// The event log is read without a shape filter, so a torn or hand-edited run_end line can
|
|
137
152
|
// carry a non-numeric ts/durationMs or an empty result — the same foreign shape every other
|
|
138
153
|
// consumer guards with finiteNumber. A line with no usable ts or result is as unusable as
|
|
139
|
-
// an absent one (the same "a torn line carries no usable timestamp" rule
|
|
154
|
+
// an absent one (the same "a torn line carries no usable timestamp" rule eventRun/runStartMap
|
|
140
155
|
// apply), so it contributes no outcome; a non-numeric durationMs is dropped like a missing one.
|
|
141
|
-
const recentOutcomes =
|
|
156
|
+
const recentOutcomes = runEnds
|
|
142
157
|
.filter((ev) => {
|
|
143
158
|
return finiteNumber(ev.ts, null) !== null && typeof ev.result === "string" && ev.result !== "";
|
|
144
159
|
})
|
|
@@ -150,13 +165,13 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
150
165
|
...(typeof ev.summary === "string" && ev.summary !== "" ? { summary: ev.summary } : {}),
|
|
151
166
|
}))
|
|
152
167
|
.reverse();
|
|
153
|
-
// The
|
|
168
|
+
// The role's own spend over the same retained window, summed per-run_end through the shared
|
|
154
169
|
// eventUsage guard (a torn or hand-edited run_end line contributes nothing: a non-numeric
|
|
155
170
|
// field reads as 0, and a line without a usable ts is as unusable as an absent one, matching
|
|
156
|
-
// recentOutcomes's drop rule so a torn
|
|
157
|
-
// sums are 0, keeping a never-costly
|
|
171
|
+
// recentOutcomes's drop rule so a torn run never silently spends). Omitted when all three
|
|
172
|
+
// sums are 0, keeping a never-costly role's JSON unchanged (the report's zero-means-absent
|
|
158
173
|
// convention).
|
|
159
|
-
const usage =
|
|
174
|
+
const usage = runEnds.reduce((acc, ev) => {
|
|
160
175
|
if (finiteNumber(ev.ts, null) === null)
|
|
161
176
|
return acc;
|
|
162
177
|
const u = eventUsage(ev);
|
|
@@ -178,35 +193,44 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
178
193
|
// byte-identical when there is no override).
|
|
179
194
|
const rawInterval = rawRoleField(root, base, "minRunIntervalSeconds");
|
|
180
195
|
const minRunIntervalOverride = typeof rawInterval === "number" ? rawInterval : undefined;
|
|
181
|
-
// The raw provider/model/thinking the FILE holds (roles.<base>), distinct from
|
|
182
|
-
// provider/model/thinking fields configForRole merges with the top
|
|
183
|
-
// Model overrides editor prefills from these, empty when unset.
|
|
184
|
-
// the FILE (rawRoleField) like minRunIntervalOverride, so a
|
|
185
|
-
// level or a tier name carries no override. Each is spread
|
|
186
|
-
// with no overrides yields a payload byte-identical to
|
|
196
|
+
// The raw provider/model/thinking/backend the FILE holds (roles.<base>), distinct from
|
|
197
|
+
// the resolved provider/model/thinking/backend fields configForRole merges with the top
|
|
198
|
+
// level — the drawer's Model overrides editor prefills from these, empty when unset.
|
|
199
|
+
// Presence is judged against the FILE (rawRoleField) like minRunIntervalOverride, so a
|
|
200
|
+
// config that relies on the top level or a tier name carries no override. Each is spread
|
|
201
|
+
// only when a string, so a file with no overrides yields a payload byte-identical to
|
|
202
|
+
// today's.
|
|
187
203
|
const providerOverrideRaw = rawRoleField(root, base, "provider");
|
|
188
204
|
const modelOverrideRaw = rawRoleField(root, base, "model");
|
|
189
205
|
const thinkingOverrideRaw = rawRoleField(root, base, "thinking");
|
|
206
|
+
const backendOverrideRaw = rawRoleField(root, base, "backend");
|
|
190
207
|
const providerOverride = typeof providerOverrideRaw === "string" ? providerOverrideRaw : undefined;
|
|
191
208
|
const modelOverride = typeof modelOverrideRaw === "string" ? modelOverrideRaw : undefined;
|
|
192
209
|
const thinkingOverride = typeof thinkingOverrideRaw === "string" ? thinkingOverrideRaw : undefined;
|
|
210
|
+
const backendOverride = typeof backendOverrideRaw === "string" ? backendOverrideRaw : undefined;
|
|
193
211
|
const capUsd = cfg.maxDailyCostUsdPerRole?.[base];
|
|
194
212
|
let dailyCap;
|
|
195
213
|
if (role !== DIRECTOR_ROLE && typeof capUsd === "number" && Number.isFinite(capUsd) && capUsd > 0) {
|
|
196
|
-
const ids =
|
|
214
|
+
const ids = runnerIds(cfg).filter((id) => baseRoleOf(id) === base);
|
|
197
215
|
if (!ids.includes(role))
|
|
198
216
|
ids.push(role);
|
|
199
|
-
const runners = ids.map((id) => ({ role: id, state: id === role ? state :
|
|
217
|
+
const runners = ids.map((id) => ({ role: id, state: id === role ? state : loadRunnerState(root, id) }));
|
|
200
218
|
const verdict = roleCapVerdicts(runners, cfg.maxDailyCostUsdPerRole, now.getTime()).get(base);
|
|
201
219
|
if (verdict)
|
|
202
220
|
dailyCap = { capUsd, spentUsd: verdict.spentUsd, over: verdict.over, roleIds: verdict.roleIds };
|
|
203
221
|
}
|
|
204
|
-
// The role's
|
|
222
|
+
// The role's notes, read for display. The same degradation as every other read here: a
|
|
205
223
|
// missing, empty, or unreadable file is simply "no note yet". The director has none.
|
|
224
|
+
// Alongside the read, the same path is stat'd once for the note's save instant (mtimeMs),
|
|
225
|
+
// null when there is no note.
|
|
206
226
|
let note = null;
|
|
227
|
+
let noteSavedAt = null;
|
|
207
228
|
if (role !== DIRECTOR_ROLE) {
|
|
208
|
-
const
|
|
229
|
+
const notesPath = roleNotesPath(root, role);
|
|
230
|
+
const text = readTextOrNull(notesPath);
|
|
209
231
|
note = text !== null && text.trim() !== "" ? text : null;
|
|
232
|
+
if (note !== null)
|
|
233
|
+
noteSavedAt = statOrNull(notesPath)?.mtimeMs ?? null;
|
|
210
234
|
}
|
|
211
235
|
// The windows the scheduler applies (gates/gate-polls.ts), through the same parser and
|
|
212
236
|
// predicate; the director is exempt from both gates.
|
|
@@ -227,7 +251,7 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
227
251
|
title: resolved.title,
|
|
228
252
|
custom: isCustomRole(cfg, role),
|
|
229
253
|
enabled: enabledRoleIds(cfg).includes(role),
|
|
230
|
-
paused:
|
|
254
|
+
paused: runnerPaused(new Set(pausedRoles(root)), role),
|
|
231
255
|
tier: roleTier(role),
|
|
232
256
|
modelTier: roleSeamTier(cfg, role),
|
|
233
257
|
...resolvedModelFields(effective.provider, effective.model, effective.thinking),
|
|
@@ -245,46 +269,61 @@ export function rolePayload(root, typedRole, modelsPath = piModelsPath(), now =
|
|
|
245
269
|
fallback,
|
|
246
270
|
fallbackFree: freeFallback !== null,
|
|
247
271
|
modelFallback: modelFallbackView(episode, cfg, role),
|
|
248
|
-
...(account !== null ? { billing: account.billing } : {}),
|
|
272
|
+
...(account !== null ? { billing: account.billing, billingBackend } : {}),
|
|
249
273
|
...(billingWindow !== undefined ? { billingWindow } : {}),
|
|
250
274
|
chainStep,
|
|
251
|
-
...roleImageInfo(cfg, role, modelsPath, readImageProbeCache(root)),
|
|
275
|
+
...roleImageInfo(cfg, role, modelsPath, readImageProbeCache(root), readPiListedModels(root)),
|
|
252
276
|
minRunIntervalSeconds: effective.minRunIntervalSeconds,
|
|
253
277
|
...(minRunIntervalOverride !== undefined ? { minRunIntervalOverride } : {}),
|
|
254
278
|
...(providerOverride !== undefined ? { providerOverride } : {}),
|
|
255
279
|
...(modelOverride !== undefined ? { modelOverride } : {}),
|
|
256
280
|
...(thinkingOverride !== undefined ? { thinkingOverride } : {}),
|
|
281
|
+
...(backendOverride !== undefined ? { backendOverride } : {}),
|
|
257
282
|
// The configured parallel-runner count (roles.<base>.instances), present only when the
|
|
258
283
|
// config sets it — unlike minRunIntervalSeconds no catalog seed fills built-ins' counts,
|
|
259
284
|
// so the merged cfg tells presence exactly. Resolved through the base role like the
|
|
260
285
|
// instructions and min-run-interval override.
|
|
261
286
|
...(cfg.roles[base]?.instances !== undefined ? { instances: cfg.roles[base].instances } : {}),
|
|
287
|
+
// Whether the review gate reviews this role's commits after they merge — the role page's
|
|
288
|
+
// `- Review: after merge` line. Present only while review is enabled and review.afterMerge
|
|
289
|
+
// lists the role's base id, so a pre-merge-reviewed role or a gate-off config leaves the
|
|
290
|
+
// payload (and its render) unchanged.
|
|
291
|
+
...(cfg.review.enabled && reviewedAfterMerge(cfg, role) ? { reviewedAfterMerge: true } : {}),
|
|
292
|
+
// Whether the review gate is on at all (review.enabled !== false) — the gate toggle's
|
|
293
|
+
// presence, distinct from reviewedAfterMerge's membership, so a before-merge-reviewed role
|
|
294
|
+
// still gets the drawer's Review control while the gate runs.
|
|
295
|
+
...(cfg.review.enabled ? { reviewEnabled: true } : {}),
|
|
262
296
|
// The roles.<base>.instructions override verbatim — resolved through the base role like
|
|
263
297
|
// the cap and quiet hours, so an instance id shows (and can edit) the override its runs
|
|
264
|
-
// carry (
|
|
298
|
+
// carry (run-prompt reads config.roles[base]); null when unset.
|
|
265
299
|
instructions: cfg.roles[baseRoleOf(role)]?.instructions ?? null,
|
|
266
300
|
find: role === DIRECTOR_ROLE ? null : resolved.find,
|
|
267
301
|
notes: note,
|
|
302
|
+
noteSavedAt,
|
|
303
|
+
// The qa observer's flow-coverage ledger (plans/observer-roles.md 2/2) — each flow's last
|
|
304
|
+
// exercise and result in the prompt block's order (never-exercised first, then
|
|
305
|
+
// stalest-first). Present only for the canonical qa id, so every other role's --json
|
|
306
|
+
// payload and Markdown render stay byte-identical. The ledger read degrades to empty
|
|
307
|
+
// coverage on a missing or torn file exactly as readQaCoverage already does.
|
|
308
|
+
...(role === "qa" ? { flowCoverage: coverageRows(readQaCoverage(root), now.getTime()) } : {}),
|
|
268
309
|
inboxCount: queuedRolePromptCount(root, role),
|
|
269
310
|
// The resolved primary seam's declared context window in tokens, or null when it cannot
|
|
270
311
|
// be known (a non-pi seam, an unresolvable provider/model pair, or a model with no
|
|
271
312
|
// declared window) — the same seam `- Next run:` previews. Independent of the fallback.
|
|
272
313
|
contextWindow,
|
|
273
314
|
// The preview seam: the queued prompt (if any) is read, not consumed, so an inspection
|
|
274
|
-
// never costs the
|
|
275
|
-
nextPrompt:
|
|
315
|
+
// never costs the role its queued work — the property the run-prompt test pins.
|
|
316
|
+
nextPrompt: assembleRunPrompt({
|
|
276
317
|
root,
|
|
277
318
|
config: cfg,
|
|
278
319
|
role,
|
|
279
320
|
state,
|
|
280
321
|
preview: true,
|
|
281
|
-
// The compact prompt every run gets (loop-tick.ts), so the preview is the real prompt.
|
|
282
|
-
compact: true,
|
|
283
322
|
})?.prompt ?? null,
|
|
284
|
-
// The
|
|
285
|
-
// already loaded for the model-fallback episode and the next-
|
|
286
|
-
// read path exists and the collector stays one
|
|
287
|
-
// is omitted (JSON unchanged) until the
|
|
323
|
+
// The role's schedule — surfaced read-only from the same persisted state rolePayload
|
|
324
|
+
// already loaded for the model-fallback episode and the next-run preview, so no new
|
|
325
|
+
// read path exists and the collector stays one loadRunnerState per role. `lastRunEndedAt`
|
|
326
|
+
// is omitted (JSON unchanged) until the role has actually run.
|
|
288
327
|
nextRunAt: state.nextRunAt,
|
|
289
328
|
backoffSeconds: state.backoffSeconds,
|
|
290
329
|
yieldMultiplier: yieldMultiplierFor(state),
|
package/dist/src/roles/roles.js
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/** The role registries and classification helpers over the built-in role catalog. The catalog
|
|
3
|
-
* itself — each
|
|
2
|
+
* itself — each role's identity and its "find something to do" prompt text — lives in
|
|
4
3
|
* role-catalog.ts (split out 2026-10-01, organize); this module re-exports ROLES and Role so
|
|
5
4
|
* every importer keeps the single roles.js import path, and derives from them the scheduling
|
|
6
5
|
* registries (work/maintenance/observer tiers, deferrable and baseline-blocked sets) and the
|
|
7
|
-
* lookup helpers the CLI, GUI, and
|
|
6
|
+
* lookup helpers the CLI, GUI, and run prompt share. */
|
|
8
7
|
import { ROLES } from "./role-catalog.js";
|
|
9
|
-
import { baseRoleOf } from "./
|
|
8
|
+
import { baseRoleOf } from "./runner-ids.js";
|
|
10
9
|
import { typoSuffix } from "../text/suggest.js";
|
|
11
10
|
export { ROLES };
|
|
12
|
-
/** The director
|
|
11
|
+
/** The director role's id — a target that is not in ROLES (allRoleIds appends it) and is
|
|
13
12
|
* driven by operator prompts, not a find prompt: an un-targeted `tumwater prompt` queues
|
|
14
|
-
* there. It keeps running through a team-wide `pause` (which stops only the
|
|
13
|
+
* there. It keeps running through a team-wide `pause` (which stops only the roles);
|
|
15
14
|
* an un-targeted `stop` signals the whole orchestrator, director included. */
|
|
16
15
|
export const DIRECTOR_ROLE = "director";
|
|
17
|
-
/** The bugfix
|
|
18
|
-
* backlog (BUGS.md `## Open`) is empty — see
|
|
16
|
+
/** The bugfix role's id. It is the one work role that also defers like a maintenance role while its
|
|
17
|
+
* backlog (BUGS.md `## Open`) is empty — see deferRun in src/scheduling/scheduling.ts. With open
|
|
19
18
|
* bugs it never defers, but pollRunnerReasons stands it down while all of them are held. */
|
|
20
19
|
export const BUGFIX_ROLE = "bugfix";
|
|
21
20
|
/** Work-tier roles (need-based prioritization): they
|
|
22
|
-
* ship work — feature and bugfix merge code on main, plan feeds them — so their due
|
|
21
|
+
* ship work — feature and bugfix merge code on main, plan feeds them — so their due runs are
|
|
23
22
|
* never deferred and slot allocation always orders them ahead of maintenance. */
|
|
24
23
|
const WORK_ROLES = new Set(["feature", "bugfix", "plan"]);
|
|
25
24
|
/** Observer roles (plans/observer-roles.md 1/2): a role whose product is an observation, not a
|
|
26
25
|
* commit, and for which `no_change` means "checked, all well" rather than "found nothing to do".
|
|
27
|
-
* The idle ladder's premise — a
|
|
28
|
-
* not hold for these, so their no_change
|
|
26
|
+
* The idle ladder's premise — a role that keeps finding nothing stops burning model time — does
|
|
27
|
+
* not hold for these, so their no_change run schedules at `minRunIntervalSeconds` and leaves
|
|
29
28
|
* `backoffSeconds` at 0 (src/scheduling/backoff.ts). The error ladder still applies in full, and
|
|
30
29
|
* they are removed from DEFERRABLE_ROLES because their input (the running product for `qa`, the
|
|
31
|
-
* event log for `selfcheck`) is not a function of whether main moved. */
|
|
32
|
-
export const OBSERVER_ROLES = new Set(["qa", "selfcheck"]);
|
|
30
|
+
* event log for `selfcheck` and `feedback`) is not a function of whether main moved. */
|
|
31
|
+
export const OBSERVER_ROLES = new Set(["qa", "selfcheck", "feedback", "ci"]);
|
|
33
32
|
/** The built-in maintenance roles that author code on their own cadence — the eight members
|
|
34
33
|
* DEFERRABLE_ROLES and BASELINE_BLOCKED_ROLES share. Both sets list them because both charter
|
|
35
34
|
* the same roles (defer-until-needed scheduling, red-main blocking), so the list lives once
|
|
@@ -45,8 +44,8 @@ const CODE_MAINTENANCE_ROLES = [
|
|
|
45
44
|
"improve",
|
|
46
45
|
];
|
|
47
46
|
/** Maintenance-tier roles (need-based prioritization): exactly the ten built-ins whose due
|
|
48
|
-
*
|
|
49
|
-
* into main since their last
|
|
47
|
+
* runs are deferrable while no feature, bugfix, director, operator or human commit has merged
|
|
48
|
+
* into main since their last run and that run did nothing. Unknown/custom roles are
|
|
50
49
|
* deliberately NOT in this
|
|
51
50
|
* set — the harness cannot judge what an arbitrary custom role needs, so they never defer (they
|
|
52
51
|
* still sort into tier 1 for fairOrder via roleTier). Observers are excluded: an unmoved tree
|
|
@@ -56,12 +55,12 @@ export const DEFERRABLE_ROLES = new Set([
|
|
|
56
55
|
...CODE_MAINTENANCE_ROLES,
|
|
57
56
|
"steward",
|
|
58
57
|
]);
|
|
59
|
-
/** Does this role's recent yield scale its min-
|
|
58
|
+
/** Does this role's recent yield scale its min-run gap (yield-scaled clocks, PLANS.md)?
|
|
60
59
|
* The search/maintenance roles — the deferrable ten, the observers, and bugfix on its
|
|
61
60
|
* empty-backlog search duty (it is the one work role that defers like maintenance, so its
|
|
62
|
-
* idle clock stretches like one too; the open-bugs state is
|
|
63
|
-
* clock's: ten consecutive empty
|
|
64
|
-
* recorded). Never the work roles whose
|
|
61
|
+
* idle clock stretches like one too; the open-bugs state is deferRun's concern, not the
|
|
62
|
+
* clock's: ten consecutive empty runs are empty-yield evidence however many bugs are
|
|
63
|
+
* recorded). Never the work roles whose runs follow demand — feature, plan, director — a
|
|
65
64
|
* role with a queued prompt or a fresh wake bypasses the gap entirely (isEligible), and the
|
|
66
65
|
* multiplier is computed from counted results only (backoff.ts), so feature's errors
|
|
67
66
|
* and quiet kills never stretch anything. */
|
|
@@ -76,24 +75,25 @@ export function roleTier(role) {
|
|
|
76
75
|
return WORK_ROLES.has(baseRoleOf(role)) ? 0 : 1;
|
|
77
76
|
}
|
|
78
77
|
/** The roles blocked from starting an authoring run while main's own build/test suite is known
|
|
79
|
-
* red (the red-main baseline check
|
|
78
|
+
* red with no failing test named (the red-main baseline check; a red that names its failing tests
|
|
79
|
+
* blocks nothing, and these roles get a `<main-known-red>` note instead): every role whose diff
|
|
80
80
|
* can carry non-exempt (code) changes — on a red main such a diff is rejected deterministically
|
|
81
81
|
* by the gate's pre-check, so spending an authoring run on it is pure waste. Exempt: director
|
|
82
|
-
* (human prompts outrank autonomous gates, like the budget gate),
|
|
83
|
-
* — its
|
|
84
|
-
* existing pre-merge gate; blocking it would leave only
|
|
85
|
-
* plan/readme/steward/qa/selfcheck (markdown-only charter — their diffs are exempt
|
|
86
|
-
* pre-check via review.exemptPaths, so a red main does not block them). */
|
|
82
|
+
* (human prompts outrank autonomous gates, like the budget gate), ci (the designated healer
|
|
83
|
+
* — its run executes the suite per "leave the project working" and can fix a red main through the
|
|
84
|
+
* existing pre-merge gate; blocking it would leave only the ci role able to unblock the team), and
|
|
85
|
+
* plan/readme/steward/qa/selfcheck/feedback (markdown-only charter — their diffs are exempt
|
|
86
|
+
* from the build pre-check via review.exemptPaths, so a red main does not block them). */
|
|
87
87
|
export const BASELINE_BLOCKED_ROLES = new Set([
|
|
88
88
|
"feature",
|
|
89
89
|
...CODE_MAINTENANCE_ROLES,
|
|
90
90
|
]);
|
|
91
|
-
/** Is this
|
|
91
|
+
/** Is this role id blocked from authoring on a red main? True when its base role is in
|
|
92
92
|
* BASELINE_BLOCKED_ROLES, so `feature-2` is blocked exactly as `feature` is. */
|
|
93
93
|
export function baselineBlocked(role) {
|
|
94
94
|
return BASELINE_BLOCKED_ROLES.has(baseRoleOf(role));
|
|
95
95
|
}
|
|
96
|
-
/** Work ratio 4/4: which commit tier a
|
|
96
|
+
/** Work ratio 4/4: which commit tier a role's base role merges into, for the work vs maintenance
|
|
97
97
|
* split the report and the "Merged today" tile show and the maintenance allowance (Work ratio
|
|
98
98
|
* 1a/4, src/gates/maintenance-quota.ts) counts. The one home of the split. `work` is the
|
|
99
99
|
* shipping tier (feature, bugfix, director); `maintenance` is code upkeep plus readme. Every
|
|
@@ -121,17 +121,17 @@ export function roleById(id) {
|
|
|
121
121
|
* a did-you-mean suggestion when the typed id sits within edit distance 2 of a valid one —
|
|
122
122
|
* the same treatment unknownConfigKeyError gives config keys, so a mistyped `--role feautre`
|
|
123
123
|
* names its fix instead of only the valid list. parseRoleFlag, the operator commands, and
|
|
124
|
-
*
|
|
125
|
-
* the GUI, and a
|
|
124
|
+
* run-prompt's defensive runner path share it so the wording cannot drift between the CLI,
|
|
125
|
+
* the GUI, and a run's internal error. The hint is text/suggest.ts's typoSuffix —
|
|
126
126
|
* suggestClosest plus didYouMean composed once, the pairing every other unknown-X error
|
|
127
127
|
* renders through. */
|
|
128
128
|
export function unknownRoleMessage(role, validIds) {
|
|
129
129
|
return `unknown role: ${role} (valid ids: ${validIds.join(", ")})${typoSuffix(role, validIds)}`;
|
|
130
130
|
}
|
|
131
|
-
/** A user-defined
|
|
132
|
-
* role-specific find-something-to-do text, and the title is what identifies the
|
|
133
|
-
* its own prompt (`You are the "<name>"
|
|
134
|
-
* Custom
|
|
131
|
+
/** A user-defined role as a Role (plans/user-defined-loops.md): its task IS the
|
|
132
|
+
* role-specific find-something-to-do text, and the title is what identifies the role inside
|
|
133
|
+
* its own prompt (`You are the "<name>" role (user-defined role)`) and commit context.
|
|
134
|
+
* Custom roles run the `default` tier — the harness cannot judge what an arbitrary role's
|
|
135
135
|
* work needs, so it gets the team's ordinary model. */
|
|
136
136
|
export function customRole(name, task) {
|
|
137
137
|
return { id: name, title: "custom role", find: task, tier: "default" };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/** Runner-id arithmetic for parallel work instances (plans/parallel-work-instances.md, part 1/7). A
|
|
2
|
+
* base role may run several runner ids — `feature`, `feature-2`, `bugfix-3`, … — each keying
|
|
3
|
+
* its own persisted state, branch, refs, session and notes, while every lookup that is ABOUT the
|
|
4
|
+
* role (its catalog charter, model, tier, caps, scheduling class) resolves through the base role.
|
|
5
|
+
* With no instances configured, every runner id is bare, so `baseRoleOf` is the identity.
|
|
6
|
+
*
|
|
7
|
+
* Only `feature` and `bugfix` may gain instances; any other id is returned unchanged, including a
|
|
8
|
+
* custom role whose name merely resembles an instance. */
|
|
9
|
+
/** The base roles that may run more than one instance. */
|
|
10
|
+
export const INSTANCE_ROLES = new Set(["feature", "bugfix"]);
|
|
11
|
+
/** How many runner ids an instance role may hold per configured instance. `instances: N` limits the
|
|
12
|
+
* runs writing code at once; a runner whose change waits in the merge pipeline never runs
|
|
13
|
+
* (plans/merge-queue.md invariant 3), so tumwater keeps up to N × this many runner ids and the
|
|
14
|
+
* poll admits another while fewer than N are running (plans/fast-merge.md "Lever 1"). */
|
|
15
|
+
const RUNNERS_PER_INSTANCE = 2;
|
|
16
|
+
/** The instance-id shape: a base role, a dash, and an index of 2 or more (`-1` is not an
|
|
17
|
+
* instance — the bare id already means instance 1). The source string is also interpolated into
|
|
18
|
+
* the dashboard script (gui-client-labels.ts). */
|
|
19
|
+
export const INSTANCE_ID_SOURCE = "^(feature|bugfix)-([2-9]|[1-9][0-9]+)$";
|
|
20
|
+
const INSTANCE_RE = new RegExp(INSTANCE_ID_SOURCE);
|
|
21
|
+
/** The base role a runner id belongs to: `feature-2` → `feature`, `bugfix-10` → `bugfix`; any
|
|
22
|
+
* other id — a bare role, a custom role, `feature-1` — is returned unchanged. */
|
|
23
|
+
export function baseRoleOf(id) {
|
|
24
|
+
const m = INSTANCE_RE.exec(id);
|
|
25
|
+
return m ? m[1] : id;
|
|
26
|
+
}
|
|
27
|
+
/** The 1-based instance number of a runner id: 1 for a bare role or any non-instance id, N for
|
|
28
|
+
* `<role>-N`. */
|
|
29
|
+
export function instanceIndex(id) {
|
|
30
|
+
const m = INSTANCE_RE.exec(id);
|
|
31
|
+
return m ? Number(m[2]) : 1;
|
|
32
|
+
}
|
|
33
|
+
/** Whether a runner id is listed in a role scope, directly or through its base role — a scope
|
|
34
|
+
* naming `feature` takes `feature-2` too. One home for the instance-aware membership test
|
|
35
|
+
* shared by `logs --role`'s and the merge queue's filters and status snapshot scoping, so the
|
|
36
|
+
* three cannot drift on whether a bare role id takes its instances. */
|
|
37
|
+
export function roleInScope(id, scope) {
|
|
38
|
+
return scope.includes(id) || scope.includes(baseRoleOf(id));
|
|
39
|
+
}
|
|
40
|
+
/** The instance count configured for a runner id's base role (`roles.<base>.instances`, default
|
|
41
|
+
* 1). A caller tells a multi-instance base (whose unassigned runs stage claims for their
|
|
42
|
+
* siblings) from a single-runner one by this. */
|
|
43
|
+
export function configuredInstances(config, id) {
|
|
44
|
+
const role = config.roles[baseRoleOf(id)];
|
|
45
|
+
return role?.instances ?? 1;
|
|
46
|
+
}
|
|
47
|
+
/** How many runner ids a base role holds: its configured instance count, times
|
|
48
|
+
* RUNNERS_PER_INSTANCE once it runs more than one instance. A role left at one instance keeps its
|
|
49
|
+
* single bare runner, so its scheduling is exactly the single-runner one. */
|
|
50
|
+
function runnerCount(config, base) {
|
|
51
|
+
const count = config.roles[base]?.instances ?? 1;
|
|
52
|
+
return INSTANCE_ROLES.has(base) && count > 1 ? count * RUNNERS_PER_INSTANCE : count;
|
|
53
|
+
}
|
|
54
|
+
/** Is this runner id enabled? True when its base role is enabled in config and the id's instance
|
|
55
|
+
* index is within the role's runner count (runnerCount). */
|
|
56
|
+
export function runnerEnabled(config, id) {
|
|
57
|
+
const role = config.roles[baseRoleOf(id)];
|
|
58
|
+
if (role?.enabled !== true)
|
|
59
|
+
return false;
|
|
60
|
+
return instanceIndex(id) <= runnerCount(config, baseRoleOf(id));
|
|
61
|
+
}
|
|
62
|
+
/** Every runner id the config says should exist, in `config.roles` order: each enabled role's
|
|
63
|
+
* bare id, then `<id>-2`…`<id>-M` for the `INSTANCE_ROLES` at their runner count
|
|
64
|
+
* (runnerCount: twice the configured instances once there is more than one). Other roles and
|
|
65
|
+
* custom roles stay bare — only feature and bugfix may split. The one
|
|
66
|
+
* answer to "which runners should exist", so the orchestrator (part 5b/7) and live reload
|
|
67
|
+
* enumerate the same set. At the default instance count this returns exactly the bare ids
|
|
68
|
+
* `enabledRoleIds` returns. */
|
|
69
|
+
export function runnerIds(config) {
|
|
70
|
+
const ids = [];
|
|
71
|
+
for (const [id, role] of Object.entries(config.roles)) {
|
|
72
|
+
if (!role.enabled)
|
|
73
|
+
continue;
|
|
74
|
+
ids.push(id);
|
|
75
|
+
if (!INSTANCE_ROLES.has(id))
|
|
76
|
+
continue;
|
|
77
|
+
const count = runnerCount(config, id);
|
|
78
|
+
for (let i = 2; i <= count; i++)
|
|
79
|
+
ids.push(`${id}-${i}`);
|
|
80
|
+
}
|
|
81
|
+
return ids;
|
|
82
|
+
}
|
|
83
|
+
/** The pause-marker key for a runner id. Instance 1's runner id is the bare role id, which as a
|
|
84
|
+
* pause key would mean the whole role, so while the role runs several instances instance 1 pauses
|
|
85
|
+
* under `<role>-1`. `-1` is a pause key only, never a runner id. Every other id is its own key. */
|
|
86
|
+
export function instancePauseKey(config, runnerId) {
|
|
87
|
+
return INSTANCE_ROLES.has(runnerId) && configuredInstances(config, runnerId) > 1
|
|
88
|
+
? `${runnerId}-1`
|
|
89
|
+
: runnerId;
|
|
90
|
+
}
|
|
91
|
+
/** Does the per-role pause marker stop this runner id? True for its own id, its base role's id (a
|
|
92
|
+
* whole-role pause), or — for the bare id of an instance role — the `<role>-1` pause key. */
|
|
93
|
+
export function runnerPaused(paused, runnerId) {
|
|
94
|
+
return (paused.has(runnerId) ||
|
|
95
|
+
paused.has(baseRoleOf(runnerId)) ||
|
|
96
|
+
(INSTANCE_ROLES.has(runnerId) && paused.has(`${runnerId}-1`)));
|
|
97
|
+
}
|