tumwater 0.1.2 → 0.2.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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- 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 +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- 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 +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { legacyMigrateLandQueueDir } from "../merge/merge-queue.js";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { deleteRefIfAt, refSha, refsUnder, setRef } from "../git/git.js";
|
|
4
|
+
import { mergeRefName, legacyLandingStatePath } from "../paths.js";
|
|
5
|
+
import { migrateLegacyRoleIds } from "../roles/legacy-role-migration.js";
|
|
6
|
+
import { registerStartupMigration } from "./startup-migrations.js";
|
|
7
|
+
/** The one place that fixes startup-migration step order. Callers import the runners from here,
|
|
8
|
+
* so the steps are registered before anything can run. Add a step by appending one
|
|
9
|
+
* `registerStartupMigration(...)` call at the end. */
|
|
10
|
+
registerStartupMigration({ name: "mergeQueueDir", run: async (root) => legacyMigrateLandQueueDir(root) });
|
|
11
|
+
registerStartupMigration({
|
|
12
|
+
name: "mergingMarker",
|
|
13
|
+
run: async (root) => {
|
|
14
|
+
try {
|
|
15
|
+
fs.unlinkSync(legacyLandingStatePath(root));
|
|
16
|
+
return ["removed the old landing.json marker"]; // vocabulary-ok: names the legacy file
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err.code === "ENOENT")
|
|
20
|
+
return [];
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
registerStartupMigration({
|
|
26
|
+
name: "mergeRefs",
|
|
27
|
+
run: async (root) => {
|
|
28
|
+
const legacyPrefix = "refs/tumwater/landing/"; // vocabulary-ok: legacy name read from old refs or commits
|
|
29
|
+
const notes = [];
|
|
30
|
+
for (const { ref, sha } of await refsUnder(root, legacyPrefix)) {
|
|
31
|
+
const role = ref.slice(legacyPrefix.length);
|
|
32
|
+
const target = mergeRefName(role);
|
|
33
|
+
const existing = await refSha(root, target);
|
|
34
|
+
if (existing === null) {
|
|
35
|
+
if (!(await setRef(root, target, sha)))
|
|
36
|
+
continue; // keep the legacy pin; the next start retries
|
|
37
|
+
await deleteRefIfAt(root, ref, sha);
|
|
38
|
+
notes.push(`moved ${ref} to ${target}`);
|
|
39
|
+
}
|
|
40
|
+
else if (existing === sha) {
|
|
41
|
+
await deleteRefIfAt(root, ref, sha);
|
|
42
|
+
notes.push(`removed ${ref}: ${target} already names the same commit`);
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
notes.push(`kept ${ref} and ${target}: they name different commits`);
|
|
46
|
+
}
|
|
47
|
+
return notes;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
registerStartupMigration({ name: "legacyRoleIds", run: (root) => migrateLegacyRoleIds(root) });
|
|
51
|
+
export { runStartupMigrations, runStartupMigrationsIfIdle } from "./startup-migrations.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logEvent, TUMWATER_SOURCE, warnEventBestEffort } from "../events/events.js";
|
|
2
|
+
import { orchestratorAlive } from "../team/orchestrator-info.js";
|
|
3
|
+
import { errorMessage } from "../text/text.js";
|
|
4
|
+
const registry = [];
|
|
5
|
+
/** Append one step to the registry; a duplicate name throws. */
|
|
6
|
+
export function registerStartupMigration(step) {
|
|
7
|
+
if (registry.some((s) => s.name === step.name))
|
|
8
|
+
throw new Error(`startup migration "${step.name}" is already registered`);
|
|
9
|
+
registry.push(step);
|
|
10
|
+
}
|
|
11
|
+
/** Run every registered step in registration order. A step that moved something logs one
|
|
12
|
+
* `state_migrated` event; a step that throws logs one warning, the rest still run, and the failed
|
|
13
|
+
* step retries at the next start. A second call moves and logs nothing. */
|
|
14
|
+
export async function runStartupMigrations(root) {
|
|
15
|
+
for (const step of registry) {
|
|
16
|
+
try {
|
|
17
|
+
const notes = await step.run(root);
|
|
18
|
+
if (notes.length > 0) {
|
|
19
|
+
logEvent(root, {
|
|
20
|
+
source: TUMWATER_SOURCE,
|
|
21
|
+
type: "state_migrated",
|
|
22
|
+
step: step.name,
|
|
23
|
+
moved: notes.length,
|
|
24
|
+
detail: notes.join("; "),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `startup migration ${step.name} failed: ${errorMessage(err)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The CLI-reader form: runs the steps only when no orchestrator is alive for `root`, so a CLI
|
|
34
|
+
* process never moves files under a running team, which may still be the old build. A live team
|
|
35
|
+
* has already migrated at its own start. */
|
|
36
|
+
export async function runStartupMigrationsIfIdle(root) {
|
|
37
|
+
if (orchestratorAlive(root))
|
|
38
|
+
return;
|
|
39
|
+
await runStartupMigrations(root);
|
|
40
|
+
}
|
package/dist/src/paths.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { DIRECTOR_ROLE } from "./roles/roles.js";
|
|
3
4
|
/** All harness runtime state lives under <repo>/.tumwater (gitignored). */
|
|
@@ -17,13 +18,13 @@ export function briefCandidates(root) {
|
|
|
17
18
|
function tumwaterDir(root) {
|
|
18
19
|
return path.join(root, STATE_DIR);
|
|
19
20
|
}
|
|
20
|
-
/** The repo's tumwater.json — the
|
|
21
|
+
/** The repo's tumwater.json — the team config (config.ts). */
|
|
21
22
|
export function configPath(root) {
|
|
22
23
|
return path.join(root, CONFIG_BASENAME);
|
|
23
24
|
}
|
|
24
25
|
/** The cross-process lock serializing read-modify-write updates of tumwater.json
|
|
25
26
|
* (writeConfigMutation and applyConfigRequest in config/config-write.ts): the CLI's
|
|
26
|
-
* `tumwater config set`, a standalone `tumwater gui` server, and the running
|
|
27
|
+
* `tumwater config set`, a standalone `tumwater gui` server, and the running team are separate
|
|
27
28
|
* processes that can rewrite one config in the same instant, and without serialization each
|
|
28
29
|
* one's whole-file write silently drops the others' change. A lock directory owned by the
|
|
29
30
|
* shared mkdir-and-pid mutex (withSyncLock, src/concurrency/lock.ts), which creates and removes
|
|
@@ -33,10 +34,11 @@ export function configLockPath(root) {
|
|
|
33
34
|
}
|
|
34
35
|
/** The cross-process lock serializing writes to the primary checkout's tracked backlog files:
|
|
35
36
|
* `tumwater bug`/`plan` (backlog-write.ts's appendEntry) and `tumwater questions answer`
|
|
36
|
-
* (question-
|
|
37
|
-
*
|
|
37
|
+
* (backlog/question-answer.ts's tryAnswerQuestion) each read a whole backlog file and rewrite
|
|
38
|
+
* it, while a merge's working-tree fast-forward (merge-git.ts's ffMainTo) updates those
|
|
39
|
+
* same files in
|
|
38
40
|
* the same checkout by `git merge --ff-only`. Without a shared lock the CLI's stale read can
|
|
39
|
-
* overwrite the
|
|
41
|
+
* overwrite the merged change, and vice versa. Same mkdir-and-pid mutex as configLockPath, under
|
|
40
42
|
* .tumwater/state so it never enters the tracked tree; its critical sections are one file read
|
|
41
43
|
* plus one atomic rename, short enough to exclude a merge without stalling the build re-check. */
|
|
42
44
|
export function primaryWorktreeLockPath(root) {
|
|
@@ -54,7 +56,7 @@ export function exampleConfigPath(root) {
|
|
|
54
56
|
export function configRequestPath(wt) {
|
|
55
57
|
return path.join(wt, ".tumwater-config-request.json");
|
|
56
58
|
}
|
|
57
|
-
/** The directory every harness worktree lives under — role,
|
|
59
|
+
/** The directory every harness worktree lives under — role, merger and mirror alike. The
|
|
58
60
|
* single home of the tumwaterDir + worktrees pair: every worktree path helper below joins
|
|
59
61
|
* its leaf through this, so the layout moves in one place. */
|
|
60
62
|
export function worktreesDir(root) {
|
|
@@ -91,13 +93,19 @@ export function worktreeUseLockPath(root) {
|
|
|
91
93
|
return path.join(tumwaterDir(root), "state", "worktree-use.lock");
|
|
92
94
|
}
|
|
93
95
|
/** The orchestrator's own info file (its pid, for liveness checks —
|
|
94
|
-
*
|
|
96
|
+
* team/orchestrator-info.ts). */
|
|
95
97
|
export function orchestratorStatePath(root) {
|
|
96
98
|
return path.join(tumwaterDir(root), "state", "orchestrator.json");
|
|
97
99
|
}
|
|
100
|
+
/** The doctor's image-probe verdict cache (src/doctor/image-probe.ts): how each claimed
|
|
101
|
+
* image-input model actually answered a live probe, keyed agent/provider/model. Runtime state
|
|
102
|
+
* under .tumwater/state/, never committed. */
|
|
103
|
+
export function imageProbesPath(root) {
|
|
104
|
+
return path.join(tumwaterDir(root), "state", "image-probes.json");
|
|
105
|
+
}
|
|
98
106
|
/** The `qa` flow-coverage ledger (tick/qa-coverage.ts): which flow each observer tick exercised
|
|
99
107
|
* and how it went. Runtime state under .tumwater/state/, never committed — a passing check
|
|
100
|
-
* must leave the repo untouched, or every cheap pass would move main and wake the
|
|
108
|
+
* must leave the repo untouched, or every cheap pass would move main and wake the team. */
|
|
101
109
|
export function qaCoveragePath(root) {
|
|
102
110
|
return path.join(tumwaterDir(root), "state", "qa-coverage.json");
|
|
103
111
|
}
|
|
@@ -108,12 +116,17 @@ export function qaCoveragePath(root) {
|
|
|
108
116
|
export function roleNotesPath(root, role) {
|
|
109
117
|
return path.join(tumwaterDir(root), "state", "notes", `${role}.md`);
|
|
110
118
|
}
|
|
111
|
-
/** The in-flight
|
|
112
|
-
* writes it (
|
|
119
|
+
/** The in-flight merge marker (merge queue 4/5): the orchestrator's drain task
|
|
120
|
+
* writes it (merge-slot.ts, merge queue 4/5) and removes it after every outcome — the observers
|
|
113
121
|
* (`status`, TUI, GUI) are separate processes that cannot see the drain's in-memory promise,
|
|
114
122
|
* but they can read this file (plans/merge-queue.md). */
|
|
115
|
-
export function
|
|
116
|
-
return path.join(tumwaterDir(root), "state", "
|
|
123
|
+
export function mergeStatePath(root) {
|
|
124
|
+
return path.join(tumwaterDir(root), "state", "merging.json");
|
|
125
|
+
}
|
|
126
|
+
/** The merge marker's old file, deleted by the startup migration (the marker is display-only,
|
|
127
|
+
* so a previous generation's is stale and is not read). */
|
|
128
|
+
export function legacyLandingStatePath(root) {
|
|
129
|
+
return path.join(tumwaterDir(root), "state", "landing.json"); // vocabulary-ok: legacy path read from old layouts
|
|
117
130
|
}
|
|
118
131
|
/** The last completed auto-restart's timestamp (epoch ms) — unlike orchestrator.json it must
|
|
119
132
|
* survive the process exit that IS the restart, so it lives in its own file and is never removed
|
|
@@ -121,17 +134,18 @@ export function landingStatePath(root) {
|
|
|
121
134
|
export function autoRestartStampPath(root) {
|
|
122
135
|
return path.join(tumwaterDir(root), "state", "auto-restart.json");
|
|
123
136
|
}
|
|
124
|
-
/** The green main-baseline verdicts the
|
|
125
|
-
* persistBaselineVerdicts): SHAs whose suite passed, so a new generation does not re-verify
|
|
137
|
+
/** The green main-baseline verdicts the team keeps across restarts (baseline/main-baseline.ts's
|
|
138
|
+
* persistBaselineVerdicts): SHAs whose suite passed, so a new generation does not re-verify
|
|
139
|
+
* them. */
|
|
126
140
|
export function baselineVerdictsPath(root) {
|
|
127
141
|
return path.join(tumwaterDir(root), "state", "baseline-verdicts.json");
|
|
128
142
|
}
|
|
129
|
-
/** Marker file `tumwater reset-counters` drops for a running
|
|
143
|
+
/** Marker file `tumwater reset-counters` drops for a running team to consume (it must also
|
|
130
144
|
* zero the runners' in-memory counters, or their next save resurrects the old values). */
|
|
131
145
|
export function resetRequestPath(root) {
|
|
132
146
|
return path.join(tumwaterDir(root), "reset-counters.json");
|
|
133
147
|
}
|
|
134
|
-
/** Marker file `tumwater wake [--role <id>]` drops for a running
|
|
148
|
+
/** Marker file `tumwater wake [--role <id>]` drops for a running team to consume: like the
|
|
135
149
|
* reset-counters marker it must name its targets because it can affect many loops, and like it
|
|
136
150
|
* it must reach the runners' IN-MEMORY schedule — the poll loop reads eligibility from the
|
|
137
151
|
* in-memory state, so clearing only the on-disk file would not wake anything. */
|
|
@@ -145,43 +159,49 @@ export function scheduledWakeDir(root) {
|
|
|
145
159
|
return path.join(tumwaterDir(root), "wakes");
|
|
146
160
|
}
|
|
147
161
|
/** Marker file the dashboard's restart button (the build-stale alert's refresh icon) drops for
|
|
148
|
-
* a running
|
|
162
|
+
* a running team to consume: force the pending self-redeploy past its cooldown now. Presence
|
|
149
163
|
* is the request; the content is just `{ at }` — like the abort markers, one untyped flag. */
|
|
150
164
|
export function restartRequestPath(root) {
|
|
151
165
|
return path.join(tumwaterDir(root), "restart.json");
|
|
152
166
|
}
|
|
153
|
-
/** Marker file `tumwater reclaim` drops for a running
|
|
167
|
+
/** Marker file `tumwater reclaim` drops for a running team to consume: presence means "run
|
|
154
168
|
* one manual reclaim pass over every candidate." Like the restart marker, presence is the
|
|
155
|
-
* request and the content is just `{ at }` — one untyped flag. A
|
|
169
|
+
* request and the content is just `{ at }` — one untyped flag. A team that is not running
|
|
156
170
|
* cannot consume it, so `cmdReclaim` runs the pass in-process instead. */
|
|
157
171
|
export function reclaimRequestPath(root) {
|
|
158
172
|
return path.join(tumwaterDir(root), "reclaim.json");
|
|
159
173
|
}
|
|
160
|
-
/** Per-role marker file `tumwater abort --role <id>` drops for a running
|
|
174
|
+
/** Per-role marker file `tumwater abort --role <id>` drops for a running team to consume:
|
|
161
175
|
* kill that loop's in-flight tick. One file per role (presence = pending request; content is
|
|
162
176
|
* just `{ at }`) keeps consumption race-free and needs no parsing — unlike the single shared
|
|
163
177
|
* reset-counters marker, which must name its targets because it affects many loops. */
|
|
164
178
|
export function abortRequestPath(root, role) {
|
|
165
179
|
return path.join(tumwaterDir(root), `abort-${role}.json`);
|
|
166
180
|
}
|
|
167
|
-
/** Persistent operator-intent marker for `tumwater pause`: its presence means "the
|
|
181
|
+
/** Persistent operator-intent marker for `tumwater pause`: its presence means "the team is
|
|
168
182
|
* paused" until `resume` removes it — unlike the abort/reset markers above, which are one-shot
|
|
169
183
|
* requests consumed on pickup. Content is `{ at }` (the pause timestamp), read for display
|
|
170
184
|
* only, never required. */
|
|
171
185
|
export function pausedPath(root) {
|
|
172
186
|
return path.join(tumwaterDir(root), "paused.json");
|
|
173
187
|
}
|
|
188
|
+
/** Operator-intent marker for `tumwater stop --in <duration>`: `{ at, until }` in epoch ms. The
|
|
189
|
+
* running team's watch consumes it when `until` passes; a fresh `tumwater run` clears a
|
|
190
|
+
* stale one. */
|
|
191
|
+
export function scheduledStopPath(root) {
|
|
192
|
+
return path.join(tumwaterDir(root), "stop-at.json");
|
|
193
|
+
}
|
|
174
194
|
/** Persistent operator-intent marker for `tumwater pause --role <id>` / `resume --role <id>`:
|
|
175
195
|
* the set of individually paused roles, `{ roles: string[], at: number }` — the same
|
|
176
|
-
* presence-means-paused persistence as the
|
|
196
|
+
* presence-means-paused persistence as the team marker above, so pausing a role before
|
|
177
197
|
* startup starts it paused. Under state/ beside the orchestrator info file: unlike the
|
|
178
|
-
*
|
|
198
|
+
* team marker it is structured state several writers rewrite (the orchestrator never does,
|
|
179
199
|
* but the shape is atomic-json state, not a presence flag). */
|
|
180
200
|
export function pausedRolesPath(root) {
|
|
181
201
|
return path.join(tumwaterDir(root), "state", "paused-roles.json");
|
|
182
202
|
}
|
|
183
203
|
/** The cross-process lock serializing read-modify-write updates of the paused-roles marker
|
|
184
|
-
* (pauseRole/resumeRole in
|
|
204
|
+
* (pauseRole/resumeRole in team/team-state.ts): the CLI and the GUI server are separate processes
|
|
185
205
|
* that can toggle different roles in the same instant, and without serialization the last
|
|
186
206
|
* writer's whole-set overwrite silently drops the other's pause. A lock directory owned by the
|
|
187
207
|
* shared mkdir-and-pid mutex (withSyncLock, src/concurrency/lock.ts), which creates and removes it;
|
|
@@ -231,6 +251,66 @@ export function sessionsRootDir(root) {
|
|
|
231
251
|
export function sessionDir(root, role) {
|
|
232
252
|
return path.join(sessionsRootDir(root), role);
|
|
233
253
|
}
|
|
254
|
+
/** `sessionDir`'s real team root — the nearest ancestor holding a `.tumwater` directory — or
|
|
255
|
+
* undefined when none does. run-agent installs the notes shim from THIS (never from teamRootOf,
|
|
256
|
+
* whose no-ancestor fallback can be a shared directory): writing `.tumwater` at the fallback
|
|
257
|
+
* would mint a phantom harness root every other suite on the host then found — BUGS.md
|
|
258
|
+
* 2026-10-18's `$TMPDIR/.tumwater/bin/tumwater-notes`. */
|
|
259
|
+
export function nearestTumwaterRoot(sessionDir) {
|
|
260
|
+
let dir = sessionDir;
|
|
261
|
+
for (let i = 0; i < 64; i += 1) {
|
|
262
|
+
let tumwater;
|
|
263
|
+
try {
|
|
264
|
+
tumwater = fs.statSync(path.join(dir, ".tumwater"));
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
tumwater = undefined;
|
|
268
|
+
}
|
|
269
|
+
if (tumwater?.isDirectory())
|
|
270
|
+
return dir;
|
|
271
|
+
const parent = path.dirname(dir);
|
|
272
|
+
if (parent === dir)
|
|
273
|
+
break;
|
|
274
|
+
dir = parent;
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
/** The team root that owns `sessionDir`: the `.tumwater` directory that contains it. A run's
|
|
279
|
+
* session dir is always `<root>/.tumwater/sessions/…`, so the first ancestor holding a
|
|
280
|
+
* `.tumwater` directory is the root; opencodeDbPath reads back to that same `.tumwater` for
|
|
281
|
+
* the run's shared database. The no-ancestor fallback — a directory two levels up — is
|
|
282
|
+
* unreachable in a harness run and used only by OpenCode's harness-only read-back lookups
|
|
283
|
+
* (opencodeDbPath, loadConfigSafe), which always see a real root. Never use this to decide
|
|
284
|
+
* where to WRITE a `.tumwater`: the fallback can be a shared directory, and creating `.tumwater`
|
|
285
|
+
* there mints a phantom root (BUGS.md 2026-10-18); run-agent's shim install uses
|
|
286
|
+
* nearestTumwaterRoot instead. Moved here from the opencode adapter (plans/notes-command.md) so
|
|
287
|
+
* run-agent's shim install — which every backend's run needs — shares the one lookup instead of
|
|
288
|
+
* the opencode adapter keeping it private. */
|
|
289
|
+
export function teamRootOf(sessionDir) {
|
|
290
|
+
return nearestTumwaterRoot(sessionDir) ?? path.dirname(path.dirname(sessionDir));
|
|
291
|
+
}
|
|
292
|
+
/** The directory the notes shim lands in, under a team root: `<root>/.tumwater/bin`.
|
|
293
|
+
* run-agent installs the `tumwater-notes` command there (src/agent/notes-shim.ts) and
|
|
294
|
+
* run-marker's agentBaseEnv prepends it to every run's PATH, so a run on any backend finds
|
|
295
|
+
* the command (plans/notes-command.md "Reaching it from a run"). */
|
|
296
|
+
export function notesShimDir(root) {
|
|
297
|
+
return path.join(tumwaterDir(root), "bin");
|
|
298
|
+
}
|
|
299
|
+
/** The shared OpenCode session database for a team root (plans/opencode-backend.md §4): one
|
|
300
|
+
* file per root, under the same `.tumwater` the run's session dir lives in, so the 6.5 MB
|
|
301
|
+
* models catalog is downloaded once and six concurrent runs on one file work. The opencode
|
|
302
|
+
* adapter derives `root` from a run's session dir (the `.tumwater` that contains it) and
|
|
303
|
+
* points `OPENCODE_DB` here. */
|
|
304
|
+
export function opencodeDbPath(root) {
|
|
305
|
+
return path.join(tumwaterDir(root), "opencode", "opencode.db");
|
|
306
|
+
}
|
|
307
|
+
/** tumwater's cached models.dev catalog for OpenCode (plans/opencode-backend.md §7; part
|
|
308
|
+
* 6a/7 opencode-catalog.ts): the providers the config routes through OpenCode, priced from
|
|
309
|
+
* models.dev's `api.json` at most once a day. Runtime state under .tumwater/state/, never
|
|
310
|
+
* committed. */
|
|
311
|
+
export function opencodeModelsPath(root) {
|
|
312
|
+
return path.join(tumwaterDir(root), "state", "opencode-models.json");
|
|
313
|
+
}
|
|
234
314
|
/** Session dir for a role's review-gate runs. Kept OUTSIDE the role's own session dir on
|
|
235
315
|
* purpose: hasResumableSession (the resume-after-shutdown guard) must only ever see the
|
|
236
316
|
* AUTHOR's sessions — a leftover reviewer session would make an interrupted tick "resume"
|
|
@@ -256,15 +336,20 @@ export function inboxDir(root) {
|
|
|
256
336
|
export function roleInboxDir(root, role) {
|
|
257
337
|
return role === DIRECTOR_ROLE ? inboxDir(root) : path.join(inboxDir(root), role);
|
|
258
338
|
}
|
|
259
|
-
/** The durable
|
|
260
|
-
* as one JSON file for the orchestrator's
|
|
339
|
+
/** The durable merge queue (src/merge/merge-queue.ts): a changed tick's pinned commit waits here
|
|
340
|
+
* as one JSON file for the orchestrator's merge pipeline (plans/merge-queue.md 3/5, land-queue
|
|
261
341
|
* speed 2c). Like the inbox it is a directory of timestamped files —
|
|
262
342
|
* a crash between enqueue and drop loses nothing, and `tumwater status` can read it without the
|
|
263
343
|
* scheduler. */
|
|
264
|
-
export function
|
|
265
|
-
return path.join(tumwaterDir(root), "
|
|
344
|
+
export function mergeQueueDir(root) {
|
|
345
|
+
return path.join(tumwaterDir(root), "merge-queue");
|
|
346
|
+
}
|
|
347
|
+
/** The merge queue's old directory, still read by observers until the startup migration
|
|
348
|
+
* (src/orchestrator/startup-migration-steps.ts) moves its files into `mergeQueueDir`. */
|
|
349
|
+
export function legacyLandQueueDir(root) {
|
|
350
|
+
return path.join(tumwaterDir(root), "land-queue"); // vocabulary-ok: legacy path read from old layouts
|
|
266
351
|
}
|
|
267
|
-
/** The lock dir serializing merges to main (
|
|
352
|
+
/** The lock dir serializing merges to main (merge-to-main.ts; doctor checks it). */
|
|
268
353
|
export function mergeLockDir(root) {
|
|
269
354
|
return path.join(tumwaterDir(root), "merge.lock");
|
|
270
355
|
}
|
|
@@ -284,19 +369,19 @@ export function gateMainWorktreePath(root) {
|
|
|
284
369
|
}
|
|
285
370
|
/** Detached worktree pinned at the RUNNING build's own SHA for the redeployer's urgency
|
|
286
371
|
* carve-out (redeploy.ts's buildRed): the baseline check that decides whether the build the
|
|
287
|
-
*
|
|
372
|
+
* team is executing is red runs here — its own checkout, not the redeploy mirror above, so a
|
|
288
373
|
* witness check at an old SHA can never repoint the mirror a green check or compile is using.
|
|
289
374
|
* The leading underscore follows the _main convention, so it can never collide with a role. */
|
|
290
375
|
export function witnessWorktreePath(root) {
|
|
291
376
|
return path.join(worktreesDir(root), "_build");
|
|
292
377
|
}
|
|
293
378
|
/** One slot of the worktree pool (plans/worktree-pool.md): a fixed `.tumwater/worktrees/_slot-<n>`
|
|
294
|
-
* checkout that ticks and
|
|
379
|
+
* checkout that ticks and verifies lease and switch commits in place, instead of one `_land-<role>`
|
|
295
380
|
* per role. The leading underscore keeps it from colliding with a role worktree. */
|
|
296
381
|
export function slotWorktreePath(root, n) {
|
|
297
382
|
return path.join(worktreesDir(root), `_slot-${n}`);
|
|
298
383
|
}
|
|
299
|
-
/** The worktree pool's dedicated checkout for the
|
|
384
|
+
/** The worktree pool's dedicated checkout for the merge drain's merge-side work
|
|
300
385
|
* (plans/worktree-pool.md, "Layout"): a single change, a stack and its bisect, the conflict
|
|
301
386
|
* resolver and the post-resolve re-review. Kept out of the pool so a permit-waiting lease can
|
|
302
387
|
* never deadlock behind the merge lock, and the leading underscore keeps it from colliding with
|
|
@@ -316,15 +401,21 @@ export function slotsStatePath(root) {
|
|
|
316
401
|
export function slotsLockPath(root) {
|
|
317
402
|
return path.join(tumwaterDir(root), "state", "slots.lock");
|
|
318
403
|
}
|
|
319
|
-
/** The ref pinning a role's committed-but-
|
|
404
|
+
/** The ref pinning a role's committed-but-not yet merged sha (plans/merge-queue.md invariant 4):
|
|
320
405
|
* written right after the tick's commit, before its branch resets to main, so the reset cannot
|
|
321
|
-
* orphan the work. Deleted on every terminal
|
|
322
|
-
export function
|
|
323
|
-
return `refs/tumwater/
|
|
406
|
+
* orphan the work. Deleted on every terminal merge outcome; kept until re-merged otherwise. */
|
|
407
|
+
export function mergeRefName(role) {
|
|
408
|
+
return `refs/tumwater/merge/${role}`;
|
|
409
|
+
}
|
|
410
|
+
/** The pre-rename pin name (`refs/tumwater/landing/<role>`): the startup step moves these to
|
|
411
|
+
* mergeRefName, and retire still reads and deletes it for a team stopped before its first
|
|
412
|
+
* start on this build. */
|
|
413
|
+
export function legacyLandingRefName(role) {
|
|
414
|
+
return `refs/tumwater/landing/${role}`; // vocabulary-ok: legacy name read from old refs or commits
|
|
324
415
|
}
|
|
325
416
|
/** The ref naming a role's rejected change while a revision is due (plans/revise-rejected.md):
|
|
326
417
|
* written at the gate's rejection, read by the author's next tick to re-apply the rejected diff,
|
|
327
|
-
* and deleted once the revision
|
|
418
|
+
* and deleted once the revision merges, is dropped, conflicts, or exhausts its rounds. Keeping
|
|
328
419
|
* the head reachable here stops git gc from collecting the one commit the revision re-applies. */
|
|
329
420
|
export function rejectedRefName(role) {
|
|
330
421
|
return `refs/tumwater/rejected/${role}`;
|
package/dist/src/pi/pi-args.js
CHANGED
|
@@ -8,11 +8,27 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
* location so staged builds (.tumwater/build/<sha>) load their own copies: bounded-output caps
|
|
9
9
|
* oversized tool results, context-shake then reclaims old bulky results at 70% of the window,
|
|
10
10
|
* context-budget appends its context-usage note to the (already bounded) result that crosses a
|
|
11
|
-
* threshold,
|
|
12
|
-
*
|
|
11
|
+
* threshold, strip-thinking drops the model's replayed thinking from each request on a
|
|
12
|
+
* small-window chat-completions model, and trim-system-prompt drops pi's docs section from the
|
|
13
|
+
* system prompt. Notes ride the `tumwater-notes` command on
|
|
14
|
+
* every backend from part 2/4 on, so the role-notes extension is no longer bundled. */
|
|
13
15
|
export function bundledExtensionPaths() {
|
|
14
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
"bounded-output.js",
|
|
18
|
+
"context-shake.js",
|
|
19
|
+
"context-budget.js",
|
|
20
|
+
"strip-thinking.js",
|
|
21
|
+
"trim-system-prompt.js",
|
|
22
|
+
].map((file) => fileURLToPath(new URL(`../pi-extension/${file}`, import.meta.url)));
|
|
15
23
|
}
|
|
24
|
+
/** pi 1.2.0's built-in extensions other than `mcp`, read from pi's own `dist/extensions/index.js`.
|
|
25
|
+
* Team runs skip pi's auto-discovery with `--no-extensions`, then re-enable exactly these with an
|
|
26
|
+
* explicit `-e builtin:` — so an installed pi package, a `~/.pi/agent/extensions` file, a target
|
|
27
|
+
* repo's own `.pi/extensions`, and pi's built-in MCP extension (which reads `~/.pi/agent/mcp.json`)
|
|
28
|
+
* can never smuggle an operator MCP server (e.g. funes) into a team run, while behavior is
|
|
29
|
+
* otherwise unchanged. A pi bump that renames or adds a built-in is a visible diff here; doctor
|
|
30
|
+
* prints the same names so a package meant for the team does not vanish silently. */
|
|
31
|
+
export const PI_KEPT_BUILTINS = ["llama.cpp", "codemode", "tool-search"];
|
|
16
32
|
/** Build the pi argv for one tick: the harness's own flags, the bundled extensions, then the
|
|
17
33
|
* user's config.piArgs (so a user flag still wins). */
|
|
18
34
|
export function piArgs(opts) {
|
|
@@ -33,6 +49,13 @@ export function piArgs(opts) {
|
|
|
33
49
|
// tumwater pi extension") and tell the model how full its window is at fixed thresholds.
|
|
34
50
|
// Loaded before config.piArgs so a user flag still wins.
|
|
35
51
|
if (path.basename(opts.agentBin ?? "pi") === "pi") {
|
|
52
|
+
// Disable pi's discovered extensions (packages, ~/.pi/agent/extensions, a repo's own
|
|
53
|
+
// .pi/extensions, and the built-in MCP reader for ~/.pi/agent/mcp.json), then re-enable only
|
|
54
|
+
// the named built-ins so operator MCP servers stay out of team runs. Kept before tumwater's
|
|
55
|
+
// own `-e` extensions; config.piArgs still wins (appended last).
|
|
56
|
+
args.push("--no-extensions");
|
|
57
|
+
for (const builtin of PI_KEPT_BUILTINS)
|
|
58
|
+
args.push("-e", `builtin:${builtin}`);
|
|
36
59
|
for (const ext of bundledExtensionPaths())
|
|
37
60
|
args.push("-e", ext);
|
|
38
61
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* backend-neutral. The claude adapter registers alongside it in src/agent/run-agent.ts. */
|
|
6
6
|
import { piArgs } from "./pi-args.js";
|
|
7
7
|
import { resolveBackendBin } from "./pi-bin.js";
|
|
8
|
-
import { agentBaseEnv } from "../process/run-marker.js";
|
|
9
|
-
import { isPermanentConfigError, matchBackendFailure } from "./pi-
|
|
8
|
+
import { agentBaseEnv, withNotesPath } from "../process/run-marker.js";
|
|
9
|
+
import { isPermanentConfigError, matchBackendFailure } from "./pi-failure.js";
|
|
10
10
|
import { hasResumableSession, sessionResumable } from "../agent/session-ledger.js";
|
|
11
11
|
/** pi crashing on malformed JSON, as Node's JSON.parse phrases it on pi's stderr — five ticks in
|
|
12
12
|
* the first 18 days died this way (one of them 2 h 39 m of director work on a fresh steering
|
|
@@ -18,13 +18,6 @@ export const TRANSIENT_PI_CRASH = /Unexpected end of JSON input|is not valid JSO
|
|
|
18
18
|
* mispricing of BUGS.md 2026-10-06. Matched against the child's stderr at exit; exported for
|
|
19
19
|
* tests. */
|
|
20
20
|
export const MODEL_FALLBACK_CLONE = /not found for provider[^\n]*Using custom model id/i;
|
|
21
|
-
/** `base` plus TUMWATER_NOTES_PATH when this run carries a notebook. Kept out of runMarkerEnv so
|
|
22
|
-
* the two environment concerns — cross-group attribution and the notebook — stay separate. */
|
|
23
|
-
function withNotesPath(base, notesPath) {
|
|
24
|
-
if (!notesPath)
|
|
25
|
-
return base;
|
|
26
|
-
return { ...base, TUMWATER_NOTES_PATH: notesPath };
|
|
27
|
-
}
|
|
28
21
|
/** pi's identity translator: every native line is already a canonical line. */
|
|
29
22
|
const identityTranslator = {
|
|
30
23
|
translate: (line) => [line],
|
|
@@ -39,20 +32,19 @@ export const piBackend = {
|
|
|
39
32
|
return resolveBackendBin(config, this);
|
|
40
33
|
},
|
|
41
34
|
invocation(run) {
|
|
42
|
-
// The prompt rides
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return { args, env };
|
|
35
|
+
// The prompt rides on stdin, never argv: pi --print reads a piped stdin to EOF as the first
|
|
36
|
+
// message (trimmed), and the runner writes and closes it. As the last argv element it sat
|
|
37
|
+
// in every pi's `ps` command line, so one agent's `pkill -f "npm run test"` matched — and
|
|
38
|
+
// SIGTERMed — every pi run on the host whose prompt mentions the check (BUGS.md 2026-10-18).
|
|
39
|
+
const args = piArgs({
|
|
40
|
+
config: run.config,
|
|
41
|
+
sessionDir: run.sessionDir,
|
|
42
|
+
sessionName: run.sessionName,
|
|
43
|
+
continueSession: run.continueSession,
|
|
44
|
+
agentBin: run.agentBin,
|
|
45
|
+
});
|
|
46
|
+
const env = withNotesPath(agentBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
47
|
+
return { args, stdin: run.prompt, env };
|
|
56
48
|
},
|
|
57
49
|
translator() {
|
|
58
50
|
return identityTranslator;
|
package/dist/src/pi/pi-bin.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { resolvedAgentConfig } from "../config/config-views.js";
|
|
4
4
|
import { envPath, findOnPath } from "../files/files.js";
|
|
5
5
|
/** Which agent binary the harness spawns and where that value came from — the per-agent
|
|
6
|
-
* resolution `TUMWATER_<ID>_BIN` → `
|
|
6
|
+
* resolution `TUMWATER_<ID>_BIN` → `backends.<id>.bin` (pi's legacy `agentBin` alias) → the
|
|
7
7
|
* backends' defaults (plans/agent-backends.md "Config"; plans/portability.md §5/7), the
|
|
8
8
|
* executability rule the preflight askers share, and the missing-binary wording they quote.
|
|
9
9
|
* The spawn (src/agent/run-agent.ts) resolves through each backend's resolveBin; the startup
|
|
@@ -19,7 +19,7 @@ const PI_MISSING_MESSAGE = "pi not found on PATH — install it (https://github.
|
|
|
19
19
|
* report. Exported so src/agent/run-agent.ts's spawn errors and doctor's checks quote the same
|
|
20
20
|
* string. `binEnv` is the environment variable a backend reads (pi's TUMWATER_PI_BIN by
|
|
21
21
|
* default; claude's adapter passes TUMWATER_CLAUDE_BIN), and `configKey` is the tumwater.json
|
|
22
|
-
* key the "config" source names (pi's legacy `agentBin`, or `
|
|
22
|
+
* key the "config" source names (pi's legacy `agentBin`, or `backends.<id>.bin`), so a claude
|
|
23
23
|
* failure names its own var and key rather than pi's. */
|
|
24
24
|
export function agentBinSourceLabel(source, binEnv = "TUMWATER_PI_BIN", configKey = "agentBin") {
|
|
25
25
|
switch (source) {
|
|
@@ -45,7 +45,7 @@ export function piMissingMessage(resolved) {
|
|
|
45
45
|
/** The missing-binary message for any agent's resolved binary, used by the startup gate's walk
|
|
46
46
|
* over every enabled seam's agent. pi delegates to piMissingMessage so its pinned wording is
|
|
47
47
|
* untouched; any other agent names itself, the resolved value, and where it came from, so a
|
|
48
|
-
* wrong `
|
|
48
|
+
* wrong `backends.<id>.bin` or `TUMWATER_<ID>_BIN` never reads as a missing pi. */
|
|
49
49
|
export function agentMissingMessage(resolved, agent) {
|
|
50
50
|
if (agent === "pi")
|
|
51
51
|
return piMissingMessage(resolved);
|
|
@@ -61,14 +61,14 @@ function agentBinEnv(agent) {
|
|
|
61
61
|
return `TUMWATER_${agent.toUpperCase()}_BIN`;
|
|
62
62
|
}
|
|
63
63
|
/** The tumwater.json key that names one agent's binary: pi's legacy `agentBin`, or the
|
|
64
|
-
* per-agent `
|
|
64
|
+
* per-agent `backends.<id>.bin`. Shared by the missing-binary and spawn-error wording so a
|
|
65
65
|
* non-pi failure never names pi's key. */
|
|
66
66
|
export function agentBinConfigKey(agent) {
|
|
67
|
-
return agent === "pi" ? "agentBin" : `
|
|
67
|
+
return agent === "pi" ? "agentBin" : `backends.${agent}.bin`;
|
|
68
68
|
}
|
|
69
69
|
/** The source label for one agent's resolved binary, deriving the env var and config key from
|
|
70
70
|
* the agent id when the caller has no backend defaults in hand. pi keeps its pinned legacy
|
|
71
|
-
* wording; any other agent names its own `TUMWATER_<ID>_BIN` and `
|
|
71
|
+
* wording; any other agent names its own `TUMWATER_<ID>_BIN` and `backends.<id>.bin`. One home
|
|
72
72
|
* for the convention agentMissingMessage and doctor's ok detail both read. */
|
|
73
73
|
export function agentBinSourceLabelFor(agent, source) {
|
|
74
74
|
return agent === "pi"
|
|
@@ -79,13 +79,19 @@ export function agentBinSourceLabelFor(agent, source) {
|
|
|
79
79
|
* the startup gate should refuse to boot and doctor should fail its check. This is the whole
|
|
80
80
|
* executability rule in one place so the two askers cannot drift: a path-shaped value (already
|
|
81
81
|
* normalized against the process cwd by resolveAgentBin) is tested directly with
|
|
82
|
-
* accessSync(X_OK)
|
|
83
|
-
*
|
|
82
|
+
* accessSync(X_OK) and required to be a regular file — a directory passes X_OK, but can never
|
|
83
|
+
* be spawned, so it is rejected here with exactly the file requirement findOnPath's bare-name
|
|
84
|
+
* branch applies (a bin pointed at a directory would otherwise pass the preflight and die
|
|
85
|
+
* mid-run at the spawn instead of failing the gate with the "not an executable" message). A
|
|
86
|
+
* bare name resolves through PATH. `pathEnv` is injectable so tests need no PATH mutation —
|
|
87
|
+
* the gate and doctor both default it to the process PATH. */
|
|
84
88
|
export function findAgentBinary(resolved, pathEnv = envPath()) {
|
|
85
89
|
if (resolved.bin.includes("/")) {
|
|
86
90
|
try {
|
|
87
|
-
fs.accessSync(resolved.bin, fs.constants.X_OK);
|
|
88
|
-
|
|
91
|
+
fs.accessSync(resolved.bin, fs.constants.X_OK); // Directories pass X_OK; require a file.
|
|
92
|
+
if (fs.statSync(resolved.bin).isFile())
|
|
93
|
+
return resolved.bin;
|
|
94
|
+
return null;
|
|
89
95
|
}
|
|
90
96
|
catch {
|
|
91
97
|
return null;
|
|
@@ -106,7 +112,7 @@ function absBin(value) {
|
|
|
106
112
|
/** The precedence every backend shares, parameterized by its own env override and default
|
|
107
113
|
* binary name: `<binEnv>` env → the configured entry's bin → `defaultBin`. An empty or
|
|
108
114
|
* whitespace value falls through to the next source, so `TUMWATER_PI_BIN= tumwater run` cannot
|
|
109
|
-
* wedge the
|
|
115
|
+
* wedge the team on a typo'd export. This is precedence only — resolvability is checked at the
|
|
110
116
|
* preflight sites (cmdRun and doctor), never here. Private: the backends and the gate resolve
|
|
111
117
|
* through resolveAgentBin, its one caller. */
|
|
112
118
|
function resolveBinFrom(binEnv, defaultBin, configured) {
|
|
@@ -119,7 +125,7 @@ function resolveBinFrom(binEnv, defaultBin, configured) {
|
|
|
119
125
|
return { bin: defaultBin, source: "default" };
|
|
120
126
|
}
|
|
121
127
|
/** Resolve which binary one agent spawns: `TUMWATER_<ID>_BIN` → the effective
|
|
122
|
-
* `
|
|
128
|
+
* `backends.<id>.bin` (config-views.ts resolvedAgentConfig, so pi's legacy `agentBin` still
|
|
123
129
|
* names pi) → the agent's default. `agent` defaults to pi, the historical backend; the
|
|
124
130
|
* backends pass their own id, binEnv and defaultBin, while the startup gate derives the
|
|
125
131
|
* reserved agents' conventional env name and default binary when it walks every enabled seam. */
|
|
@@ -51,7 +51,7 @@ export function parsePiEventLine(line, types) {
|
|
|
51
51
|
* empty-content update right after start, and only updates with real text prove the command is
|
|
52
52
|
* alive — so stalled-tool-call tracking (src/pi/pi-watchdogs.ts's warning,
|
|
53
53
|
* src/ui/progress-data.ts's flag) moves its clock on these alone: a content-free keepalive must
|
|
54
|
-
* not mask a hang
|
|
54
|
+
* not mask a hang — the same rule messageUpdateHasContent applies to streaming deltas. */
|
|
55
55
|
export function toolUpdateHasContent(partialResult) {
|
|
56
56
|
if (!isJsonObject(partialResult))
|
|
57
57
|
return false;
|
|
@@ -60,6 +60,22 @@ export function toolUpdateHasContent(partialResult) {
|
|
|
60
60
|
return false;
|
|
61
61
|
return content.some((block) => isJsonObject(block) && isNonBlankString(block.text));
|
|
62
62
|
}
|
|
63
|
+
/** True when a `message_update` carries new assistant content — a non-blank
|
|
64
|
+
* `assistantMessageEvent.delta`. pi's protocol strips the cumulative message snapshot from
|
|
65
|
+
* these lines, so the delta is all they carry: a content-free one (a zombie stream's
|
|
66
|
+
* keepalive, or the pre-strip shape with no delta event at all) proves nothing and must not
|
|
67
|
+
* reset the hang watchdog, exactly as a content-free tool_execution_update must not
|
|
68
|
+
* (toolUpdateHasContent). A content-bearing delta IS real generation, and the quiet watchdog
|
|
69
|
+
* counts it: a long thinking block on a slow model emits nothing else for its whole duration,
|
|
70
|
+
* so ignoring deltas killed live runs as hung (BUGS.md 2026-10-17). */
|
|
71
|
+
export function messageUpdateHasContent(event) {
|
|
72
|
+
if (!isJsonObject(event))
|
|
73
|
+
return false;
|
|
74
|
+
const update = event.assistantMessageEvent;
|
|
75
|
+
if (!isJsonObject(update))
|
|
76
|
+
return false;
|
|
77
|
+
return isNonBlankString(update.delta);
|
|
78
|
+
}
|
|
63
79
|
/** The call's full raw command text from a tool-execution event's args (bash-like tools name
|
|
64
80
|
* it `command`, some `cmd`; other tools yield ""). The two open-call trackers
|
|
65
81
|
* (src/pi/pi-watchdogs.ts's stall warning, src/ui/progress-data.ts's dashboard flag) store this
|