tumwater 0.1.3 → 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 +77 -28
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +23 -0
- package/dist/src/agent/agent-backend.js +9 -7
- package/dist/src/agent/backend-traits.js +77 -0
- package/dist/src/agent/claude/claude-account.js +81 -161
- package/dist/src/agent/claude/claude-args.js +19 -19
- package/dist/src/agent/claude/claude-backend.js +58 -48
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +11 -19
- 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 +112 -0
- package/dist/src/agent/notes-shim.js +40 -0
- package/dist/src/agent/opencode/opencode-args.js +60 -0
- package/dist/src/agent/opencode/opencode-backend.js +258 -0
- package/dist/src/agent/opencode/opencode-catalog.js +210 -0
- package/dist/src/agent/opencode/opencode-config.js +193 -0
- package/dist/src/agent/opencode/opencode-env.js +35 -0
- package/dist/src/agent/opencode/opencode-session-id.js +36 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +16 -33
- package/dist/src/agent/run-agent.js +475 -381
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +142 -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 +487 -0
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +384 -0
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +354 -132
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +132 -22
- 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 +89 -11
- package/dist/src/backlog/backlog-structure.js +271 -125
- package/dist/src/backlog/backlog-write.js +121 -471
- package/dist/src/backlog/backlog.js +129 -17
- package/dist/src/backlog/question-answer.js +345 -0
- package/dist/src/baseline/main-baseline.js +157 -101
- 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 +164 -55
- 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 +41 -0
- package/dist/src/{brief.js → brief/brief.js} +38 -33
- package/dist/src/budget/budget.js +55 -28
- package/dist/src/budget/fallback-breaker.js +31 -31
- package/dist/src/build/build-check-detect.js +25 -16
- package/dist/src/build/build-check-events.js +87 -22
- package/dist/src/build/build-check-report.js +66 -27
- package/dist/src/build/build-check-scoped.js +64 -68
- package/dist/src/build/build-check.js +17 -15
- package/dist/src/build/build-info.js +3 -3
- package/dist/src/build/build-stage.js +31 -35
- package/dist/src/build/dep-install.js +21 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +131 -37
- package/dist/src/change/change-render.js +56 -14
- package/dist/src/cli/brief-command.js +9 -8
- package/dist/src/cli/cli-args.js +133 -28
- package/dist/src/cli/cli-command-args.js +82 -21
- package/dist/src/cli/cli-editor.js +15 -2
- package/dist/src/cli/cli-flag-specs.js +180 -50
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +14 -11
- package/dist/src/cli/cli-query-commands.js +325 -224
- package/dist/src/cli/cli-repo-gate.js +12 -2
- 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-start.js +453 -0
- package/dist/src/cli/completion.js +202 -17
- package/dist/src/cli/config-commands.js +51 -25
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +413 -157
- package/dist/src/cli/log-commands.js +234 -62
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +215 -231
- package/dist/src/cli.js +154 -49
- package/dist/src/collections.js +84 -18
- package/dist/src/concurrency/check-permit.js +35 -19
- package/dist/src/concurrency/lock.js +22 -17
- package/dist/src/concurrency/provider-lease.js +105 -0
- package/dist/src/concurrency/semaphore.js +344 -46
- package/dist/src/config/config-aliases.js +105 -0
- package/dist/src/config/config-editable-keys.js +16 -4
- package/dist/src/config/config-fallbacks.js +416 -0
- package/dist/src/config/config-field-checks.js +58 -19
- package/dist/src/config/config-keys.js +182 -0
- package/dist/src/config/config-live.js +113 -22
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +58 -39
- 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 +201 -175
- package/dist/src/config/config-views.js +202 -346
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +219 -391
- package/dist/src/config/config.js +102 -81
- package/dist/src/config/model-selector.js +44 -18
- 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 +191 -161
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +229 -305
- 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 +181 -104
- package/dist/src/doctor/doctor-install.js +31 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +496 -270
- package/dist/src/doctor/doctor-notify.js +5 -7
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +189 -69
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +145 -50
- package/dist/src/doctor/image-probe.js +257 -0
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +60 -0
- package/dist/src/events/event-format.js +363 -155
- package/dist/src/events/event-labels.js +123 -0
- package/dist/src/events/event-legacy.js +164 -0
- package/dist/src/events/event-read.js +74 -38
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +83 -27
- package/dist/src/events/events.js +47 -26
- package/dist/src/events/notify.js +85 -35
- package/dist/src/failure/failure-cluster.js +23 -20
- package/dist/src/failure/failure-data.js +140 -92
- package/dist/src/failure/failure-render.js +97 -78
- package/dist/src/failure/failure-state-change.js +45 -33
- package/dist/src/failure/time-spend.js +166 -82
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +129 -41
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +45 -9
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +2 -2
- package/dist/src/gates/bootstrap-gates.js +8 -8
- package/dist/src/gates/budget-gates.js +361 -52
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +201 -103
- package/dist/src/gates/maintenance-quota.js +34 -37
- package/dist/src/gates/pause-gates.js +33 -15
- package/dist/src/gates/readiness.js +8 -3
- package/dist/src/gates/role-cap-gates.js +23 -28
- package/dist/src/gates/startup-gate.js +154 -48
- package/dist/src/gates/streak-gate.js +67 -41
- package/dist/src/git/commit-message.js +80 -48
- package/dist/src/git/git-diff.js +29 -23
- package/dist/src/git/git-run.js +87 -46
- package/dist/src/git/git.js +114 -92
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +18 -9
- package/dist/src/git/worktree-pool.js +150 -49
- package/dist/src/git/worktree-use.js +14 -20
- package/dist/src/git/worktree.js +108 -51
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +88 -21
- package/dist/src/gui/gui-backlog-commands.js +297 -0
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +9 -14
- package/dist/src/gui/gui-config-commands.js +462 -0
- package/dist/src/gui/gui-endpoint-commands.js +17 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +13 -331
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +240 -0
- package/dist/src/gui/gui-prompt-commands.js +231 -0
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +33 -9
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +166 -203
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +96 -0
- package/dist/src/history/history.js +206 -103
- package/dist/src/inbox/inbox-attachments.js +94 -39
- package/dist/src/inbox/inbox-cancel.js +16 -15
- package/dist/src/inbox/inbox-edit.js +10 -10
- package/dist/src/inbox/inbox-submit.js +54 -14
- package/dist/src/inbox/inbox-top.js +99 -0
- package/dist/src/inbox/inbox.js +49 -44
- package/dist/src/inbox/pending-prompt.js +27 -27
- package/dist/src/inbox/prompt-commands.js +154 -100
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +15 -14
- package/dist/src/init/init.js +35 -29
- package/dist/src/merge/backlog-conflicts.js +335 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +100 -93
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +209 -0
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +183 -110
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +6 -5
- package/dist/src/merge/merge-drain.js +351 -0
- package/dist/src/merge/merge-git.js +403 -0
- package/dist/src/merge/merge-pipeline.js +111 -0
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +113 -0
- package/dist/src/merge/merge-queue.js +224 -0
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +385 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +97 -71
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +151 -82
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +214 -0
- package/dist/src/notes-cli.js +112 -0
- package/dist/src/operator/operator-commands.js +268 -89
- package/dist/src/operator/operator-intent.js +130 -82
- package/dist/src/operator/operator-requests.js +82 -87
- package/dist/src/operator/retire.js +49 -31
- package/dist/src/operator/revert.js +179 -0
- package/dist/src/orchestrator/orchestrator-launch.js +116 -48
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +78 -348
- package/dist/src/orchestrator/orchestrator.js +173 -361
- package/dist/src/orchestrator/retention.js +66 -15
- 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 +52 -0
- package/dist/src/orchestrator/startup-migrations.js +44 -0
- package/dist/src/paths.js +228 -75
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +30 -5
- package/dist/src/pi/pi-backend.js +19 -27
- package/dist/src/pi/pi-bin.js +64 -67
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +163 -42
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +137 -113
- package/dist/src/pi/pi-watchdogs.js +20 -20
- package/dist/src/pi-extension/bounded-output.js +25 -8
- package/dist/src/pi-extension/context-budget.js +5 -3
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +36 -0
- package/dist/src/pi-extension/full-output.js +5 -12
- package/dist/src/pi-extension/strip-thinking.js +79 -0
- package/dist/src/pi-extension/trim-system-prompt.js +37 -0
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +11 -11
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +48 -9
- package/dist/src/process/run-marker.js +42 -7
- package/dist/src/process/supervisor.js +47 -35
- package/dist/src/prompt/dialect.js +46 -11
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +201 -188
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -1
- package/dist/src/prompt/prompt-followup.js +43 -29
- package/dist/src/prompt/prompt.js +255 -172
- package/dist/src/redeploy/redeploy-policy.js +29 -24
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +7 -6
- package/dist/src/redeploy/redeployer.js +78 -70
- package/dist/src/redeploy/self-reload.js +3 -2
- package/dist/src/report/report-data.js +374 -285
- package/dist/src/report/report-fold.js +430 -0
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +321 -61
- 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 +133 -14
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +7 -7
- package/dist/src/review/review-followup.js +21 -9
- package/dist/src/review/review-precheck.js +77 -33
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +16 -2
- package/dist/src/review/review.js +172 -356
- package/dist/src/review/secret-leaks.js +147 -0
- package/dist/src/review/suite-rerun.js +3 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +135 -0
- package/dist/src/roles/role-catalog.js +172 -137
- package/dist/src/roles/role-guidance.js +112 -44
- package/dist/src/roles/role-labels.js +9 -0
- package/dist/src/roles/role-render.js +221 -28
- package/dist/src/roles/role-view.js +244 -33
- package/dist/src/roles/roles.js +37 -35
- 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 +10 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +166 -136
- 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/run/run-finalize.js +154 -0
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -6
- package/dist/src/run/run-prompt.js +304 -0
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +80 -35
- package/dist/src/run/run-stage.js +295 -0
- package/dist/src/run/run-timing.js +198 -0
- package/dist/src/run/run-usage.js +180 -0
- package/dist/src/run/run-verdict.js +301 -0
- 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/runner/leftover.js +185 -0
- package/dist/src/{loop → runner}/model-fallback.js +13 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +10 -10
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/runner/runner-pi.js +433 -0
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +34 -28
- package/dist/src/runner/runner-run.js +625 -0
- package/dist/src/runner/runner-state-load.js +268 -0
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/runner/runner.js +350 -0
- package/dist/src/scheduling/backoff.js +64 -54
- package/dist/src/scheduling/claims.js +46 -26
- package/dist/src/scheduling/once-round.js +30 -30
- package/dist/src/scheduling/quiet-hours.js +26 -34
- package/dist/src/scheduling/scheduling.js +87 -74
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +123 -0
- package/dist/src/status/status-data.js +106 -191
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +26 -0
- package/dist/src/status/status-polls.js +70 -59
- package/dist/src/status/status-roles.js +131 -0
- package/dist/src/{fleet → team}/error-storm.js +16 -16
- package/dist/src/{fleet → team}/failure-spread.js +10 -10
- package/dist/src/{fleet → team}/orchestrator-info.js +97 -25
- package/dist/src/{fleet → team}/reclaim.js +9 -9
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +60 -26
- package/dist/src/team/team-polls.js +299 -0
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +47 -40
- package/dist/src/text/datetime.js +102 -16
- package/dist/src/text/format.js +49 -3
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +89 -54
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +68 -14
- package/dist/src/ui/badges.js +165 -93
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +221 -42
- package/dist/src/ui/gui/gui-client-composer.js +105 -27
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +226 -130
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +492 -68
- package/dist/src/ui/gui/gui-client-labels.js +23 -0
- package/dist/src/ui/gui/gui-client-markdown.js +1 -1
- package/dist/src/ui/gui/gui-client-model.js +84 -76
- package/dist/src/ui/gui/gui-client-operator.js +112 -78
- package/dist/src/ui/gui/gui-client-pending.js +248 -41
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +122 -179
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +118 -76
- package/dist/src/ui/gui/gui-client-settings.js +180 -20
- package/dist/src/ui/gui/gui-client-team.js +252 -0
- package/dist/src/ui/gui/gui-client.js +48 -40
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +25 -24
- package/dist/src/ui/gui/gui-styles.js +46 -38
- package/dist/src/ui/progress-data.js +11 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +40 -40
- package/dist/src/ui/status-model.js +276 -194
- package/dist/src/ui/status-payload.js +227 -62
- package/dist/src/ui/status-render.js +196 -108
- package/dist/src/ui/team-alerts.js +532 -0
- package/dist/src/ui/tone.js +11 -11
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -19
- package/dist/src/ui/transcript.js +85 -38
- package/dist/src/ui/tui/tui-backlog.js +132 -21
- package/dist/src/ui/tui/tui-editor-answer.js +93 -0
- package/dist/src/ui/tui/tui-editor-attach.js +105 -0
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +105 -0
- package/dist/src/ui/tui/tui-editor-budget.js +159 -0
- 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-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +219 -0
- 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 +124 -0
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +112 -0
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +255 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +90 -0
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +94 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +170 -0
- package/dist/src/ui/tui/tui-editor-retire.js +81 -0
- 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 +85 -0
- package/dist/src/ui/tui/tui-editor-session.js +123 -0
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +185 -0
- package/dist/src/ui/tui/tui-frame.js +107 -58
- package/dist/src/ui/tui/tui-history.js +16 -12
- package/dist/src/ui/tui/tui-input.js +56 -60
- package/dist/src/ui/tui/tui-keys.js +106 -1825
- package/dist/src/ui/tui/tui-pane-activity.js +102 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +269 -0
- package/dist/src/ui/tui/tui-pane-brief.js +29 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +174 -0
- package/dist/src/ui/tui/tui-pane-history.js +636 -0
- package/dist/src/ui/tui/tui-pane-pending.js +369 -0
- package/dist/src/ui/tui/tui-pane-queued.js +159 -0
- package/dist/src/ui/tui/tui-pane-report.js +154 -0
- package/dist/src/ui/tui/tui-pane-session.js +120 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +224 -0
- package/dist/src/ui/tui/tui-pane.js +125 -44
- package/dist/src/ui/tui/tui-panes.js +364 -0
- package/dist/src/ui/tui/tui.js +52 -34
- package/dist/src/verdict/fix-claim.js +135 -62
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +80 -24
- package/dist/src/verdict/reply-contract.js +51 -28
- package/dist/src/version.js +2 -2
- package/package.json +5 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/brief-edit.js +0 -42
- package/dist/src/cli/cli-run.js +0 -469
- package/dist/src/fleet/fleet-polls.js +0 -207
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-check-failures.js +0 -141
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-git.js +0 -190
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -56
- package/dist/src/landing/landing-queue.js +0 -149
- package/dist/src/landing/landing-slot.js +0 -256
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/loop/leftover.js +0 -129
- package/dist/src/loop/loop-pi.js +0 -370
- package/dist/src/loop/loop-state.js +0 -204
- package/dist/src/loop/loop-tick.js +0 -545
- package/dist/src/loop/loop.js +0 -315
- package/dist/src/loop/revision.js +0 -61
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/roles/loop-ids.js +0 -60
- package/dist/src/tick/stage-check.js +0 -302
- package/dist/src/tick/telemetry-digest.js +0 -24
- package/dist/src/tick/tick-detail-data.js +0 -107
- package/dist/src/tick/tick-detail.js +0 -150
- package/dist/src/tick/tick-finalize.js +0 -135
- package/dist/src/tick/tick-prompt.js +0 -201
- package/dist/src/tick/tick-stage.js +0 -223
- package/dist/src/tick/tick-timing.js +0 -195
- package/dist/src/tick/tick-usage.js +0 -142
- package/dist/src/tick/tick-verdict.js +0 -201
- package/dist/src/ui/fleet-alerts.js +0 -174
- package/dist/src/ui/gui/gui-client-fleet.js +0 -576
- package/dist/src/ui/gui/gui-client-sound.js +0 -52
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { COMMIT_IDENT, git, gitTry, runGit } from "../git/git-run.js";
|
|
4
|
-
import { writeBytesAtomic } from "../files/files.js";
|
|
5
|
-
import { currentBranch, gitLines, headOf } from "../git/git.js";
|
|
6
|
-
import { unquotePorcelainPath } from "../git/git-diff.js";
|
|
7
|
-
import { CONFIG_BASENAME, configPath, primaryWorktreeLockPath } from "../paths.js";
|
|
8
|
-
import { withStateLockAsync } from "../concurrency/lock.js";
|
|
9
|
-
/** Git plumbing for the landing flow: rebasing a worktree branch onto main, inspecting and
|
|
10
|
-
* finishing a conflicted rebase, and fast-forwarding main — the mechanics, with no landing policy
|
|
11
|
-
* of their own. Landing policy (lock → rebase → verify → ff, conflict resolution, batch ff)
|
|
12
|
-
* lives in landing-merge.ts on top of these. Moved here from landing-merge.ts: the cluster started
|
|
13
|
-
* as landing-merge.ts-only helpers (organize tick 78, moving them out of git/git.ts), but the
|
|
14
|
-
* lander and the batch lander grew to call them directly, leaving landing-merge.ts exporting
|
|
15
|
-
* generic rebase/ff plumbing that had nothing to do with merging. */
|
|
16
|
-
/** Paths currently in conflict (unmerged) in the worktree. C-quoted names are decoded to
|
|
17
|
-
* real paths — a non-ASCII conflicted file arrives as `"h\303\251llo.ts"` (core.quotePath is on
|
|
18
|
-
* by default), and undecoded it does not exist on disk: hasConflictMarkers could never read
|
|
19
|
-
* it, so an unresolved conflict in such a file passed the marker check and continueRebase
|
|
20
|
-
* committed its markers to main. */
|
|
21
|
-
export async function conflictedFiles(wt) {
|
|
22
|
-
const out = await gitTry(wt, "diff", "--name-only", "--diff-filter=U");
|
|
23
|
-
return gitLines(out).map(unquotePorcelainPath);
|
|
24
|
-
}
|
|
25
|
-
/** How many main commits the resolver prompt lists, and how many body lines it keeps per
|
|
26
|
-
* commit: enough to explain a deliberate removal without filling the window. */
|
|
27
|
-
const MAX_INTENT_COMMITS = 15;
|
|
28
|
-
const MAX_INTENT_BODY_LINES = 20;
|
|
29
|
-
function capIntentBody(body) {
|
|
30
|
-
const lines = body.split("\n");
|
|
31
|
-
if (lines.length <= MAX_INTENT_BODY_LINES)
|
|
32
|
-
return body;
|
|
33
|
-
const kept = lines.slice(0, MAX_INTENT_BODY_LINES).join("\n");
|
|
34
|
-
return `${kept}\n… (${lines.length - MAX_INTENT_BODY_LINES} more lines)`;
|
|
35
|
-
}
|
|
36
|
-
/** The commits on `mainBranch` since `since` (the change's merge-base with main) that touched
|
|
37
|
-
* any of `files`, newest first. resolveConflict threads this into the resolver prompt so pi
|
|
38
|
-
* reads what main deliberately changed instead of guessing from the markers. The list is
|
|
39
|
-
* capped at MAX_INTENT_COMMITS and each body at MAX_INTENT_BODY_LINES; `omitted` counts the
|
|
40
|
-
* commits the cap dropped so the prompt can say so. An empty result is the normal case of a
|
|
41
|
-
* fork point with no later main commits touching the files. */
|
|
42
|
-
export async function mainCommitsTouching(wt, since, mainBranch, files) {
|
|
43
|
-
if (files.length === 0)
|
|
44
|
-
return { commits: [], omitted: 0 };
|
|
45
|
-
const out = await gitTry(wt, "log", "--format=%h%x1f%s%x1f%b%x1e", `${since}..${mainBranch}`, "--", ...files);
|
|
46
|
-
const parsed = (out ?? "")
|
|
47
|
-
.split("\x1e")
|
|
48
|
-
.map((record) => record.trim())
|
|
49
|
-
.filter(Boolean)
|
|
50
|
-
.map((record) => {
|
|
51
|
-
const [sha = "", subject = "", ...rest] = record.split("\x1f");
|
|
52
|
-
return { sha, subject, body: capIntentBody(rest.join("\x1f").trim()) };
|
|
53
|
-
});
|
|
54
|
-
return {
|
|
55
|
-
commits: parsed.slice(0, MAX_INTENT_COMMITS),
|
|
56
|
-
omitted: Math.max(0, parsed.length - MAX_INTENT_COMMITS),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/** Attempt to rebase the worktree branch onto main and classify the outcome WITHOUT
|
|
60
|
-
* cleaning up: "rebased" (including already up to date), "conflict" (the rebase stopped on
|
|
61
|
-
* unmerged paths, which remain in the worktree for a resolver), or "other" (any other
|
|
62
|
-
* failure). Shared by the two rebase wrappers below, which differ only in cleanup policy.
|
|
63
|
-
* Rebase — not merge — keeps main's history linear: each tick lands as its own commit on top
|
|
64
|
-
* of whatever main holds. */
|
|
65
|
-
async function attemptRebase(wt, mainBranch) {
|
|
66
|
-
try {
|
|
67
|
-
// The -c ident is needed for the rewritten committer identity. diff3 carries the base
|
|
68
|
-
// section of every conflict, which the deterministic backlog resolver reads
|
|
69
|
-
// (plans/parallel-work-instances.md, part 3/7); rebaseOntoMain aborts on conflict, so the
|
|
70
|
-
// marker style only matters to its leave-conflicts sibling.
|
|
71
|
-
await runGit(wt, [...COMMIT_IDENT, "-c", "merge.conflictStyle=diff3", "rebase", mainBranch]);
|
|
72
|
-
return "rebased";
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return (await conflictedFiles(wt)).length > 0 ? "conflict" : "other";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/** Rebase the worktree branch onto main (main may have advanced during the tick).
|
|
79
|
-
* Returns false and aborts the rebase on conflict. */
|
|
80
|
-
export async function rebaseOntoMain(wt, mainBranch) {
|
|
81
|
-
const state = await attemptRebase(wt, mainBranch);
|
|
82
|
-
if (state !== "rebased")
|
|
83
|
-
await gitTry(wt, "rebase", "--abort");
|
|
84
|
-
return state === "rebased";
|
|
85
|
-
}
|
|
86
|
-
/** Rebase the worktree branch onto main, leaving conflict markers in place for a
|
|
87
|
-
* resolver to work on. "clean" = rebased (or already up to date); "conflict" = the rebase
|
|
88
|
-
* stopped on conflicts and the worktree holds them mid-rebase; "failed" = anything else
|
|
89
|
-
* (aborted and cleaned up). */
|
|
90
|
-
export async function rebaseOntoMainLeaveConflicts(wt, mainBranch) {
|
|
91
|
-
const state = await attemptRebase(wt, mainBranch);
|
|
92
|
-
if (state === "other")
|
|
93
|
-
await gitTry(wt, "rebase", "--abort");
|
|
94
|
-
return state === "rebased" ? "clean" : state === "conflict" ? "conflict" : "failed";
|
|
95
|
-
}
|
|
96
|
-
/** True if any of the given files still contains a git conflict marker.
|
|
97
|
-
* A deleted file counts as resolved (the resolver chose the deletion). */
|
|
98
|
-
export function hasConflictMarkers(wt, files) {
|
|
99
|
-
// Only start/end markers are checked: every real conflict block carries them, while a bare
|
|
100
|
-
// `=======` line is legitimate content (a markdown setext or RST underline of exactly seven
|
|
101
|
-
// characters), and flagging it would reject clean resolutions forever. A resolver that
|
|
102
|
-
// leaves only a separator line behind is treated as resolved; its stray line is content the
|
|
103
|
-
// project's own tests can catch.
|
|
104
|
-
const marker = /^(<{7}|>{7})( |$)/m;
|
|
105
|
-
return files.some((f) => {
|
|
106
|
-
const p = path.join(wt, f);
|
|
107
|
-
try {
|
|
108
|
-
return marker.test(fs.readFileSync(p, "utf8"));
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
/** The reason naming every changed file that still carries a conflict marker, or undefined when
|
|
116
|
-
* none do — the one home of the wording, shared by the stage check's pre-queue finding
|
|
117
|
-
* (stage-check.ts) and the review gate's hard rejection (review.ts), so the two surfaces cannot
|
|
118
|
-
* name the same leftover marker differently. Committing or landing one would put raw `<<<<<<<`
|
|
119
|
-
* lines on main (PLANS.md "Robust conflict landing, part 2/2"). Builds on hasConflictMarkers
|
|
120
|
-
* above, the shared definition of what a marker is. */
|
|
121
|
-
export function conflictMarkerReason(wt, files) {
|
|
122
|
-
const marked = files.filter((f) => hasConflictMarkers(wt, [f]));
|
|
123
|
-
return marked.length > 0 ? `conflict markers remain in: ${marked.join(", ")}` : undefined;
|
|
124
|
-
}
|
|
125
|
-
/** Conclude an in-progress rebase with everything in the worktree as the resolution.
|
|
126
|
-
* GIT_EDITOR=true so `rebase --continue` can never block on a commit-message prompt. A
|
|
127
|
-
* resolution that leaves no unique content (the branch's change was fully superseded by
|
|
128
|
-
* main) is skipped automatically by git, finishing the rebase cleanly. Throws when the
|
|
129
|
-
* rebase stops again — e.g. on a second conflict from a later commit the resolution run added; the caller aborts and reports merge_conflict. */
|
|
130
|
-
export async function continueRebase(wt) {
|
|
131
|
-
await git(wt, "add", "-A");
|
|
132
|
-
await runGit(wt, [...COMMIT_IDENT, "rebase", "--continue"], { GIT_EDITOR: "true" });
|
|
133
|
-
return headOf(wt, "HEAD");
|
|
134
|
-
}
|
|
135
|
-
/** The live config's bytes, when the landing about to fast-forward `ref` would delete it
|
|
136
|
-
* (plans/portability.md §4b/7): the config file exists, is tracked in the current index (and
|
|
137
|
-
* HEAD — a staged-or-dirty config makes `git merge --ff-only` refuse below, so the two agree
|
|
138
|
-
* on every merge that can succeed), and is absent from the incoming tree. Anything else —
|
|
139
|
-
* already untracked, already absent, present in `ref` — needs no preserve step. */
|
|
140
|
-
export async function configBytesToPreserve(root, ref) {
|
|
141
|
-
const cfg = configPath(root);
|
|
142
|
-
if (!fs.existsSync(cfg))
|
|
143
|
-
return null;
|
|
144
|
-
if ((await gitTry(root, "ls-files", "--error-unmatch", CONFIG_BASENAME)) === null)
|
|
145
|
-
return null;
|
|
146
|
-
if ((await gitTry(root, "cat-file", "-e", `${ref}:${CONFIG_BASENAME}`)) !== null)
|
|
147
|
-
return null;
|
|
148
|
-
return fs.readFileSync(cfg);
|
|
149
|
-
}
|
|
150
|
-
/** The preserve step's write-back, restore-only-when-absent: write the saved bytes only when
|
|
151
|
-
* the live config is absent at write-back time. The merge deletes the file mid-window, so a
|
|
152
|
-
* config request applied in that window (3/7's applyConfigRequest runs outside the merge lock)
|
|
153
|
-
* recreates it — the newer bytes win (latest instruction wins), which also makes the step
|
|
154
|
-
* idempotent. Called only after a successful merge: a refused merge leaves the file untouched.
|
|
155
|
-
* Written through writeBytesAtomic (tmp+rename), like every other tumwater.json writer, so a
|
|
156
|
-
* crash mid-write cannot leave the live config torn for the ~2 s config poll or the next
|
|
157
|
-
* generation's startup load. */
|
|
158
|
-
export function restoreConfigBytes(root, saved) {
|
|
159
|
-
if (!saved)
|
|
160
|
-
return;
|
|
161
|
-
const cfg = configPath(root);
|
|
162
|
-
if (fs.existsSync(cfg))
|
|
163
|
-
return;
|
|
164
|
-
writeBytesAtomic(cfg, saved);
|
|
165
|
-
}
|
|
166
|
-
/** Fast-forward main to `ref`, without touching any remote. Callers pass the worktree's
|
|
167
|
-
* post-rebase HEAD — a bare sha, which both arms accept (`merge --ff-only <sha>` and
|
|
168
|
-
* `push . <sha>:<main>`). Uses a working-tree merge when the primary checkout is on main (so its
|
|
169
|
-
* files update), otherwise a local ref push. Returns true on success. The working-tree arm
|
|
170
|
-
* preserves the live config across a landing that untracks it (plans/portability.md §4b/7):
|
|
171
|
-
* without the write-back, the commit that removes the tracked config would delete the fleet's
|
|
172
|
-
* running config out from under it and the next config poll would fall back to defaults. */
|
|
173
|
-
export async function ffMainTo(root, ref, mainBranch) {
|
|
174
|
-
const primaryBranch = await currentBranch(root);
|
|
175
|
-
if (primaryBranch === mainBranch) {
|
|
176
|
-
// The working-tree arm rewrites the same tracked files the CLI backlog writers read and
|
|
177
|
-
// rewrite, so it shares their primary-worktree lock (paths.ts): without it a `tumwater bug`
|
|
178
|
-
// that read before this merge can rename its stale whole-file write over the landed change.
|
|
179
|
-
// The ref-push arm below touches no working tree and needs no lock; the save/merge/restore
|
|
180
|
-
// stays one short section, never held across a build re-check.
|
|
181
|
-
return withStateLockAsync(primaryWorktreeLockPath(root), async () => {
|
|
182
|
-
const saved = await configBytesToPreserve(root, ref);
|
|
183
|
-
if ((await gitTry(root, "merge", "--ff-only", ref)) === null)
|
|
184
|
-
return false;
|
|
185
|
-
restoreConfigBytes(root, saved);
|
|
186
|
-
return true;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
return (await gitTry(root, "push", ".", `${ref}:${mainBranch}`)) !== null;
|
|
190
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { runOnAbort } from "../process/process.js";
|
|
2
|
-
import { warnEventBestEffort } from "../events/events.js";
|
|
3
|
-
import { errorMessage } from "../text/text.js";
|
|
4
|
-
import { deleteRef } from "../git/git.js";
|
|
5
|
-
import { removeLandingChange, writeLandingOutcome } from "./landing-slot.js";
|
|
6
|
-
import { landingRefName, rejectedRefName } from "../paths.js";
|
|
7
|
-
/** A fresh pipeline: nothing in flight anywhere — no vet running, no approved change waiting,
|
|
8
|
-
* no merge holding main. The orchestrator builds one per fleet start (orchestrator.ts). */
|
|
9
|
-
export function newLandingPipeline() {
|
|
10
|
-
return { vetting: new Map(), vetted: new Map(), merge: null };
|
|
11
|
-
}
|
|
12
|
-
/** Every landing task in flight — each vet holding its permit, and the merge — for the shutdown
|
|
13
|
-
* and restart waits. A parked vet is not one of them: it has started nothing, and a shutdown
|
|
14
|
-
* (its controller) or a closed start gate (at its grant) settles it without a run. */
|
|
15
|
-
export function landingTasks(p) {
|
|
16
|
-
return [...[...p.vetting.values()].filter((v) => !v.parked), ...(p.merge ? [p.merge] : [])];
|
|
17
|
-
}
|
|
18
|
-
/** Everything `tumwater abort --role` can reach: every task in flight, plus each vetted change
|
|
19
|
-
* waiting with no task of its own. A parked vet is out of reach, as a plainly queued entry
|
|
20
|
-
* always was: its change has not started landing. */
|
|
21
|
-
export function abortableLandings(p) {
|
|
22
|
-
return [...landingTasks(p), ...p.vetted.values()];
|
|
23
|
-
}
|
|
24
|
-
/** Wire harness shutdown to one task's own controller — through the shared register/abort
|
|
25
|
-
* race helper, so an already-fired signal aborts at once. Returns the detach function. The
|
|
26
|
-
* fleet shutdown signal outlives every landing, so each task that settles while the signal is
|
|
27
|
-
* still live must detach in its own finally; without that, the one long-lived signal
|
|
28
|
-
* accumulates a listener (and the retained controller behind it) for every landing the fleet
|
|
29
|
-
* ever runs, growing without bound until the shutdown it was waiting for. */
|
|
30
|
-
export function abortOnShutdown(signal, controller) {
|
|
31
|
-
return runOnAbort(signal, () => controller.abort());
|
|
32
|
-
}
|
|
33
|
-
/** Wrap a landing task's stored promise so it can never reject: the tasks are fire-and-forget
|
|
34
|
-
* during normal operation (only the shutdown/restart drain awaits them), and an unexpected
|
|
35
|
-
* throw out of their bookkeeping — an unwritable state file, event feed, or marker while
|
|
36
|
-
* settling an outcome — would otherwise surface as an unhandled rejection and take the
|
|
37
|
-
* orchestrator down. The throw is reported best-effort; the task's own finally has already
|
|
38
|
-
* released its slot and controller, and the surviving queue entry and pin recover on the next
|
|
39
|
-
* poll. */
|
|
40
|
-
export function guardLandingTask(root, role, promise) {
|
|
41
|
-
return promise.catch((err) => {
|
|
42
|
-
warnEventBestEffort(root, "harness", `landing task for ${role} could not settle: ${errorMessage(err)}`);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/** Take a permit at `tier` unless `signal` fires first: resolves to its release, or to null
|
|
46
|
-
* when the signal won — a grant that arrives after that is handed straight back (a hop, never
|
|
47
|
-
* a leak), so a parked vet that a shutdown settles holds nothing. */
|
|
48
|
-
export async function acquireUnlessAborted(semaphore, tier, signal) {
|
|
49
|
-
if (signal.aborted)
|
|
50
|
-
return null;
|
|
51
|
-
const granted = semaphore.acquire(tier).then(() => true);
|
|
52
|
-
const stopped = new Promise((resolve) => signal.addEventListener("abort", () => resolve(false), { once: true }));
|
|
53
|
-
if (await Promise.race([granted, stopped]))
|
|
54
|
-
return () => semaphore.release();
|
|
55
|
-
void granted.then(() => semaphore.release());
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
/** The roles the pipeline holds right now — vetting (parked or not), vetted, or merging. */
|
|
59
|
-
export function liveRoles(p) {
|
|
60
|
-
return new Set([...p.vetting.keys(), ...p.vetted.keys(), ...(p.merge?.roles ?? [])]);
|
|
61
|
-
}
|
|
62
|
-
/** Discard the pinned landing refs of every role in a deliberately-aborted landing.
|
|
63
|
-
* `tumwater abort --role` throws the committed work away, and the pin is what would otherwise
|
|
64
|
-
* recover it, so the ref must go; a shutdown abort leaves `userAborted` unset and every ref
|
|
65
|
-
* survives for recovery. Shared by every task's settling code so their discard semantics
|
|
66
|
-
* cannot drift. A ref that is already gone is not an error. */
|
|
67
|
-
export async function discardPinnedRefs(root, roles) {
|
|
68
|
-
for (const role of roles) {
|
|
69
|
-
try {
|
|
70
|
-
await deleteRef(root, landingRefName(role));
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
/* already gone */
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/** Settle a landing queue entry that reached a terminal outcome — the one home of the
|
|
78
|
-
* write-plus-drop pairing every settled landing goes through: the result lands in the role's
|
|
79
|
-
* state and the landed/land_failed event (writeLandingOutcome), then the change's marker record
|
|
80
|
-
* is dropped (removeLandingChange — landing-slot leaves the marker to the caller). Shared by
|
|
81
|
-
* settleAbortedVetted, the vet's final-verdict branch, and the merge's per-change write-back, so
|
|
82
|
-
* no settled entry can keep a stale marker or lose its outcome write. The pinned-ref discard
|
|
83
|
-
* stays with the callers: only a user abort throws work away, and the merge discards its whole
|
|
84
|
-
* stack at once rather than change by change. */
|
|
85
|
-
export async function settleLandingOutcome(root, entry, state, result, durationMs, usage, file) {
|
|
86
|
-
writeLandingOutcome(root, entry, state, result, durationMs, usage, file);
|
|
87
|
-
// A revision that lands ends the rejected change's life: drop the rejected ref (plans/revise-
|
|
88
|
-
// rejected.md). A rejection repoints or deletes it in landing-core.ts, so this is the only
|
|
89
|
-
// ref cleanup a landed revision needs here. Guarded on the entry's revisionRound, not just a
|
|
90
|
-
// `changed` result: a fresh change can land while a different rejection's revision is still
|
|
91
|
-
// pending, and its landing must leave that pending revision's ref alone.
|
|
92
|
-
if (result === "changed" && entry.revisionRound !== undefined) {
|
|
93
|
-
await deleteRef(root, rejectedRefName(entry.role));
|
|
94
|
-
}
|
|
95
|
-
removeLandingChange(root, entry.role);
|
|
96
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/** The renderer behind `tumwater queue` — the operator-facing text for the land queue view the
|
|
2
|
-
* reader gathers (landing-queue.ts's readLandingQueue, beside it): one line per queued landing
|
|
3
|
-
* in execution order, plus the count of queue files the reader skipped. Pure function of the
|
|
4
|
-
* collected view and a `now` instant — no I/O, no clock reads — so the age it prints can be
|
|
5
|
-
* pinned by a test. */
|
|
6
|
-
import { humanSeconds, secondsSince } from "../text/datetime.js";
|
|
7
|
-
import { shortSha } from "../text/format.js";
|
|
8
|
-
import { plural } from "../text/phrases.js";
|
|
9
|
-
import { landingChanges, LANDING_STAGE_LABELS } from "./landing-slot.js";
|
|
10
|
-
/** The bracketed-label text for one queue entry, in the wording the loop rows use (status-model's
|
|
11
|
-
* loopPhase): `landing <elapsed> · <stage>` while a vet or the merge holds the change,
|
|
12
|
-
* `vetted, awaiting merge` once vetted, and "" when the marker is absent, names no record for
|
|
13
|
-
* `sha`, or the record is `done`. Reads the marker through landingChanges, so an older-generation
|
|
14
|
-
* single-change marker counts as one `landing` record. */
|
|
15
|
-
export function queueEntryLandingLabel(sha, inFlight, now) {
|
|
16
|
-
if (!inFlight)
|
|
17
|
-
return "";
|
|
18
|
-
const change = landingChanges(inFlight).find((c) => c.sha === sha);
|
|
19
|
-
if (!change || change.status === "done")
|
|
20
|
-
return "";
|
|
21
|
-
if (change.status === "vetted")
|
|
22
|
-
return "vetted, awaiting merge";
|
|
23
|
-
let label = "landing";
|
|
24
|
-
if (change.startedAt)
|
|
25
|
-
label += ` ${humanSeconds(secondsSince(change.startedAt, now))}`;
|
|
26
|
-
if (change.stage)
|
|
27
|
-
label += ` · ${LANDING_STAGE_LABELS[change.stage]}`;
|
|
28
|
-
return label;
|
|
29
|
-
}
|
|
30
|
-
/** Render the queue as the text `tumwater queue` prints: a `land queue (<depth>):` header then
|
|
31
|
-
* one 1-based line per readable entry, oldest first, carrying the loop, tick number, short
|
|
32
|
-
* pinned sha, age, and summary. An empty queue (no entries and nothing skipped) prints
|
|
33
|
-
* `land queue empty`; a non-zero skip count appends one `(+N unreadable queue file(s) skipped)`
|
|
34
|
-
* line so a torn or foreign file the reader dropped is visible rather than silent. An entry the
|
|
35
|
-
* optional in-flight marker has a live record for carries ` · [<label>]` after its age
|
|
36
|
-
* (queueEntryLandingLabel). */
|
|
37
|
-
export function renderLandingQueue(view, now, inFlight) {
|
|
38
|
-
const depth = view.entries.length + view.unreadable;
|
|
39
|
-
const lines = [];
|
|
40
|
-
if (depth === 0) {
|
|
41
|
-
lines.push("land queue empty");
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
lines.push(`land queue (${depth}):`);
|
|
45
|
-
view.entries.forEach((e, i) => {
|
|
46
|
-
const age = humanSeconds(secondsSince(e.enqueuedAt, now));
|
|
47
|
-
const label = queueEntryLandingLabel(e.sha, inFlight, now);
|
|
48
|
-
const tag = label ? ` · [${label}]` : "";
|
|
49
|
-
lines.push(` ${i + 1}. ${e.role} tick #${e.tick} · ${shortSha(e.sha)} · ${age} ago${tag} · ${e.summary}`);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
if (view.unreadable > 0) {
|
|
53
|
-
lines.push(` (+${plural(view.unreadable, "unreadable queue file")} skipped)`);
|
|
54
|
-
}
|
|
55
|
-
return lines.join("\n");
|
|
56
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { listQueueFiles, queueFileName, removeQueueFile } from "../files/file-queue.js";
|
|
3
|
-
import { isJsonObject, nonNegativeNumber } from "../files/json-object.js";
|
|
4
|
-
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
5
|
-
import { cachedByStat } from "../files/stat-cache.js";
|
|
6
|
-
import { landQueueDir } from "../paths.js";
|
|
7
|
-
let seq = 0;
|
|
8
|
-
/** Append one landing to the queue as a single timestamped file (creating the queue dir if
|
|
9
|
-
* needed). The filename orders entries across processes by wall-clock time; the per-process
|
|
10
|
-
* counter and pid break ties within one process. Written atomically (tmp + rename): the
|
|
11
|
-
* enqueue runs while the process can be hard-killed mid-write (the tick deadline's
|
|
12
|
-
* process-group kill, a shutdown), and a torn entry the crash left behind is not always
|
|
13
|
-
* recoverable — the drain repairs only an unreadable HEAD (staleHeadFile), so a torn file
|
|
14
|
-
* that sorts after a healthy entry would sit in the queue dir unread forever. */
|
|
15
|
-
export function enqueueLanding(root, entry) {
|
|
16
|
-
const dir = landQueueDir(root);
|
|
17
|
-
const name = queueFileName(entry.enqueuedAt, seq++, ".json");
|
|
18
|
-
writeJsonAtomic(path.join(dir, name), entry);
|
|
19
|
-
}
|
|
20
|
-
/** Every queue file in execution order (oldest first) — the same filename sort queuedLandings,
|
|
21
|
-
* headLanding, and the orchestrator's per-poll queued-roles set read. A missing queue dir
|
|
22
|
-
* reads as an empty queue. */
|
|
23
|
-
function queueFiles(root) {
|
|
24
|
-
return listQueueFiles(landQueueDir(root), ".json");
|
|
25
|
-
}
|
|
26
|
-
/** Number of landings currently queued — a directory listing only; no file content is read.
|
|
27
|
-
* A missing queue dir reads as 0, like every other reader here. */
|
|
28
|
-
export function queueDepth(root) {
|
|
29
|
-
return queueFiles(root).length;
|
|
30
|
-
}
|
|
31
|
-
// Per-poll entry-content cache (stat-cache.cachedByStat): dashboard polls must not re-parse
|
|
32
|
-
// every queued entry — each file is written once by enqueueLanding and only deleted on drop,
|
|
33
|
-
// the same write-once discipline the inbox caches for prompts.
|
|
34
|
-
const entryCache = new Map();
|
|
35
|
-
/** True when `v` carries every field a LandingEntry requires, with the right type. The land
|
|
36
|
-
* queue is one JSON object per file, so a file that parses to anything else — a scalar, an
|
|
37
|
-
* array, an object missing a required field — is foreign or torn and reads as no entry.
|
|
38
|
-
* Checking only `role` and `sha` left the `as LandingEntry` cast unsound: a foreign file that
|
|
39
|
-
* happened to carry those two fields was handed downstream with `tick` and `summary`
|
|
40
|
-
* undefined, which surface as bogus session names (`tumwater-<role>-undefined-review`) and
|
|
41
|
-
* `undefined` in the reviewer's prompt. Optional fields are checked only when present.
|
|
42
|
-
*
|
|
43
|
-
* The three numeric fields go through nonNegativeNumber, not a bare `typeof === "number"`:
|
|
44
|
-
* JSON's `1e999` parses to Infinity, so a hand-edited file could carry a non-finite tick or
|
|
45
|
-
* enqueuedAt past a typeof check, and both feed plumbing downstream (session names, queue
|
|
46
|
-
* ordering, the status payload). */
|
|
47
|
-
function isPriorReview(v) {
|
|
48
|
-
if (!isJsonObject(v))
|
|
49
|
-
return false;
|
|
50
|
-
return (typeof v.sha === "string" &&
|
|
51
|
-
Array.isArray(v.reasons) &&
|
|
52
|
-
v.reasons.every((r) => typeof r === "string"));
|
|
53
|
-
}
|
|
54
|
-
function isLandingEntry(v) {
|
|
55
|
-
if (!isJsonObject(v))
|
|
56
|
-
return false;
|
|
57
|
-
const e = v;
|
|
58
|
-
return (typeof e.role === "string" &&
|
|
59
|
-
typeof e.sha === "string" &&
|
|
60
|
-
nonNegativeNumber(e.tick, null) !== null &&
|
|
61
|
-
typeof e.summary === "string" &&
|
|
62
|
-
nonNegativeNumber(e.enqueuedAt, null) !== null &&
|
|
63
|
-
(e.body === undefined || typeof e.body === "string") &&
|
|
64
|
-
(e.highFriction === undefined || typeof e.highFriction === "boolean") &&
|
|
65
|
-
(e.revisionRound === undefined || nonNegativeNumber(e.revisionRound, null) !== null) &&
|
|
66
|
-
(e.priorReview === undefined || isPriorReview(e.priorReview)));
|
|
67
|
-
}
|
|
68
|
-
/** Read one queue file through the stat-keyed cache: null when it vanishes mid-listing (a
|
|
69
|
-
* concurrent drop — the inbox.ts race policy), is unreadable, or fails to parse or to carry
|
|
70
|
-
* every required LandingEntry field (a foreign or torn file is skipped, never thrown on). A
|
|
71
|
-
* shallow copy out: the entry's fields are all scalars and callers treat the result as
|
|
72
|
-
* read-only. */
|
|
73
|
-
function readEntry(file) {
|
|
74
|
-
return cachedByStat(entryCache, file, file, () => {
|
|
75
|
-
// readJsonFile is the shared "missing or torn reads as no data" policy; a foreign shape
|
|
76
|
-
// is still rejected by isLandingEntry.
|
|
77
|
-
const parsed = readJsonFile(file);
|
|
78
|
-
return isLandingEntry(parsed) ? parsed : null;
|
|
79
|
-
}, (e) => ({ ...e }));
|
|
80
|
-
}
|
|
81
|
-
/** Every queued landing in execution order (oldest first) paired with the file to drop on
|
|
82
|
-
* completion. The batch drain (merge queue 5/5) reads this slice because `dropLanding` needs
|
|
83
|
-
* the file, which the bare-entry readers never return. A missing queue directory reads as an
|
|
84
|
-
* empty queue. */
|
|
85
|
-
export function queuedLandingFiles(root) {
|
|
86
|
-
const out = [];
|
|
87
|
-
for (const file of queueFiles(root)) {
|
|
88
|
-
const entry = readEntry(file);
|
|
89
|
-
if (entry)
|
|
90
|
-
out.push({ entry, file });
|
|
91
|
-
}
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
|
-
/** All queued landings in execution order (oldest first). A missing queue directory reads as
|
|
95
|
-
* an empty queue. */
|
|
96
|
-
export function queuedLandings(root) {
|
|
97
|
-
return queuedLandingFiles(root).map((q) => q.entry);
|
|
98
|
-
}
|
|
99
|
-
/** The roles currently holding a queued landing — the maintenance quota's and the permit
|
|
100
|
-
* reserve's "already in flight" input, and the scheduler's per-poll merge-queue interlock.
|
|
101
|
-
* A missing queue directory reads as an empty set, like every reader here. */
|
|
102
|
-
export function queuedLandingRoles(root) {
|
|
103
|
-
return new Set(queuedLandingFiles(root).map((q) => q.entry.role));
|
|
104
|
-
}
|
|
105
|
-
/** Read every queue file once through the stat-keyed cache, collecting the readable entries
|
|
106
|
-
* (filename order) and counting the rest — the torn/foreign file policy readEntry already
|
|
107
|
-
* applies, surfacing the skip count instead of hiding it. */
|
|
108
|
-
export function readLandingQueue(root) {
|
|
109
|
-
const entries = [];
|
|
110
|
-
let unreadable = 0;
|
|
111
|
-
for (const file of queueFiles(root)) {
|
|
112
|
-
const entry = readEntry(file);
|
|
113
|
-
if (entry)
|
|
114
|
-
entries.push(entry);
|
|
115
|
-
else
|
|
116
|
-
unreadable++;
|
|
117
|
-
}
|
|
118
|
-
return { entries, unreadable };
|
|
119
|
-
}
|
|
120
|
-
/** The head of the queue — its oldest entry — plus the file to drop on completion, or null when
|
|
121
|
-
* the queue is empty or its oldest file is a vanished/torn entry. */
|
|
122
|
-
export function headLanding(root) {
|
|
123
|
-
const file = queueFiles(root)[0];
|
|
124
|
-
if (!file)
|
|
125
|
-
return null;
|
|
126
|
-
const entry = readEntry(file);
|
|
127
|
-
return entry ? { entry, file } : null;
|
|
128
|
-
}
|
|
129
|
-
/** The oldest queue file when the queue is non-empty but its head is unreadable — torn or
|
|
130
|
-
* foreign (a partial write an older build left, a hand-placed file) — i.e. when headLanding
|
|
131
|
-
* reads null while the queue holds files; null when the queue is empty or its head is healthy.
|
|
132
|
-
* The drain drops this file with a warning so the queue can drain: headLanding reads null for
|
|
133
|
-
* it forever and nothing else will remove it, stranding every live entry behind it (each
|
|
134
|
-
* author's interlock, the per-poll queued-roles set, pins its ticks along with them). The
|
|
135
|
-
* crashed entry's commit, if any, still lives in its landing ref — next-tick leftover recovery
|
|
136
|
-
* re-lands it — so the drop loses nothing recoverable from the file (BUGS.md 2026-09-16). */
|
|
137
|
-
export function staleHeadFile(root) {
|
|
138
|
-
const file = queueFiles(root)[0];
|
|
139
|
-
if (!file)
|
|
140
|
-
return null;
|
|
141
|
-
return readEntry(file) === null ? file : null;
|
|
142
|
-
}
|
|
143
|
-
/** Remove one queued landing's file after its landing outcome — terminal or not (a
|
|
144
|
-
* non-terminal outcome keeps the landing ref; the retry is the role's next fresh tick, not a
|
|
145
|
-
* queue re-drain). ENOENT is a no-op: a concurrent drop between listing and removal is a
|
|
146
|
-
* normal race (the inbox.ts policy), not an error. */
|
|
147
|
-
export function dropLanding(file) {
|
|
148
|
-
removeQueueFile(file);
|
|
149
|
-
}
|