tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -9,7 +9,7 @@ import { POST_REVIEW_FAILURE_LIMIT, addPostReview, readPostReviews, reviewMerged
|
|
|
9
9
|
import { shortSha } from "../text/format.js";
|
|
10
10
|
import { formatDate } from "../text/datetime.js";
|
|
11
11
|
import { collapseWhitespace, errorMessage } from "../text/text.js";
|
|
12
|
-
import { fileBugsOnMain } from "../backlog/backlog-
|
|
12
|
+
import { fileBugsOnMain } from "../backlog/backlog-file-bugs.js";
|
|
13
13
|
import { abortOnShutdown, acquireUnlessAborted, guardMergeTask } from "./merge-pipeline.js";
|
|
14
14
|
import { resolveWaitStage } from "./merge-slot.js";
|
|
15
15
|
import { mergeToMain } from "./merge-to-main.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { errCode } from "../errno.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { ensureDir } from "../files/files.js";
|
|
4
5
|
import { listQueueFiles, queueFileName, removeQueueFile } from "../files/file-queue.js";
|
|
5
6
|
import { isJsonObject, nonNegativeNumber } from "../files/json-object.js";
|
|
6
7
|
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
@@ -49,7 +50,7 @@ export function legacyMigrateLandQueueDir(root) {
|
|
|
49
50
|
const notes = { moved: [], kept: [], failed: [] };
|
|
50
51
|
const files = listQueueFiles(from, ".json");
|
|
51
52
|
if (files.length > 0)
|
|
52
|
-
|
|
53
|
+
ensureDir(to);
|
|
53
54
|
for (const file of files) {
|
|
54
55
|
const name = path.basename(file);
|
|
55
56
|
const dest = path.join(to, name);
|
|
@@ -4,7 +4,7 @@ import { changeBaseRev, commitMessage, headOf } from "../git/git.js";
|
|
|
4
4
|
import { aheadOfMainFiles, treeDiffFiles } from "../git/git-diff.js";
|
|
5
5
|
import { resolvedDiffDiverges } from "./merge-diff.js";
|
|
6
6
|
import { conflictedFileHunks, conflictedFiles, continueRebase, ffMainToOrWhy, overwriteBlockedReason, primaryCheckoutBlockers, hasConflictMarkers, mainCommitsTouching, rebaseOntoMain, rebaseOntoMainLeaveConflicts, } from "./merge-git.js";
|
|
7
|
-
import { resolveBacklogInsertConflicts } from "./backlog-conflicts.js";
|
|
7
|
+
import { resolveBacklogInsertConflicts } from "./merge-backlog-conflicts.js";
|
|
8
8
|
import { abortSync } from "../git/worktree.js";
|
|
9
9
|
import { runScopedBuildCheck } from "../build/build-check-scoped.js";
|
|
10
10
|
import { detectBuildCheck, gateCommandOf, mergeCommandOf } from "../build/build-check-detect.js";
|
|
@@ -24,8 +24,9 @@ export const VERIFY_TIER = -1;
|
|
|
24
24
|
* verification parked for a permit, because the merge is the one serial step every queued change
|
|
25
25
|
* waits on. (Nothing that holds a permit ever waits on the merge: no verification takes the merge
|
|
26
26
|
* lock, and the resolver runs outside it — merge-to-main.ts's mergeToMain — so this cannot
|
|
27
|
-
* deadlock.) Shared by merge-drain.ts's merge slot and merge-post-review.ts's fix
|
|
28
|
-
* reviewer's fix-merge resolver rides the same tier and the two cannot
|
|
27
|
+
* deadlock.) Shared by merge-drain.ts's merge slot and merge-post-review-drain.ts's fix
|
|
28
|
+
* landing, so the reviewer's fix-merge resolver rides the same tier and the two cannot
|
|
29
|
+
* drift. */
|
|
29
30
|
export const MERGE_TIER = VERIFY_TIER - 1;
|
|
30
31
|
/** How many verifications may be in flight (parked or running) at once: every `maxConcurrent`
|
|
31
32
|
* permit but one, so a deep merge queue never takes the last slot from the roles that could
|
|
@@ -18,7 +18,7 @@ import { normalizePauseReason, pauseTeam, pauseRole, pausedRoles, resumeTeam, re
|
|
|
18
18
|
import { readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
19
19
|
import { pidAlive } from "../process/process.js";
|
|
20
20
|
import { inspectReclaimCandidates } from "../team/reclaim.js";
|
|
21
|
-
import { markerApplyNote, NO_HARNESS_ERROR, PAUSE_FOR_MAX_MS, cancelScheduledStopMessage, cancelWakes, listWakes, renderWakeList, requestAbort, requestResetCounters, requestRestart, requestScheduledStop, requestWake, rolePauseMessage, roleResumeMessage, runManualReclaim, timedPauseBits, } from "./operator-intent.js";
|
|
21
|
+
import { markerApplyNote, NO_HARNESS_ERROR, PAUSE_FOR_MAX_MS, cancelScheduledStopMessage, cancelWakes, listWakes, renderWakeList, requestAbort, requestAbortAll, requestResetCounters, requestRestart, requestScheduledStop, requestWake, rolePauseMessage, roleResumeMessage, runManualReclaim, timedPauseBits, } from "./operator-intent.js";
|
|
22
22
|
/** The CLI layer of the operator commands: the `reset-counters`, `wake`, `reclaim`, `abort`,
|
|
23
23
|
* `pause`, `resume`, `stop`, and `retire` commands, split out of cli.ts so the command bodies
|
|
24
24
|
* live beside their shared `--role` resolution. The marker-writing cores and shared
|
|
@@ -182,17 +182,28 @@ export async function cmdReclaim(root, args) {
|
|
|
182
182
|
}
|
|
183
183
|
say(await runManualReclaim(root));
|
|
184
184
|
}
|
|
185
|
-
/** `tumwater abort --role <id>[,<id>...]`: kill the named roles' in-flight runs
|
|
186
|
-
* comma list writes one marker per role)
|
|
185
|
+
/** `tumwater abort [--role <id>[,<id>...]] [--all]`: kill the named roles' in-flight runs
|
|
186
|
+
* right now (a comma list writes one marker per role), or with `--all` every enabled role's in
|
|
187
|
+
* flight run in one request (one marker per enabled runner). The CLI cannot
|
|
187
188
|
* reach into the orchestrator process, so the request rides on disk like reset-counters':
|
|
188
189
|
* a per-role marker file a running team consumes within one poll cycle (the runner's
|
|
189
190
|
* abortRun kills the pi child and resets the worktree to main). Requires a live harness —
|
|
190
|
-
* with no team there is nothing to consume the marker. The
|
|
191
|
+
* with no team there is nothing to consume the marker. The roles stay enabled: they back
|
|
191
192
|
* off normally and later runs proceed as usual. */
|
|
192
193
|
export async function cmdAbort(root, args) {
|
|
194
|
+
if (args.includes("--all")) {
|
|
195
|
+
if (flagValue(args, "--role") !== null) {
|
|
196
|
+
fail("abort takes either --role <id>[,<id>...] or --all, not both");
|
|
197
|
+
}
|
|
198
|
+
const result = requestAbortAll(root);
|
|
199
|
+
if (!result.ok)
|
|
200
|
+
fail(result.error);
|
|
201
|
+
say(result.message);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
193
204
|
// As in cmdLogs: the config exists only to validate the id against built-ins plus
|
|
194
205
|
// user-defined roles; a missing --role fails before it is ever needed.
|
|
195
|
-
const roles = namedRoleList(root, args) ?? fail("abort requires --role <id> (e.g. `--role feature`)");
|
|
206
|
+
const roles = namedRoleList(root, args) ?? fail("abort requires --role <id>[,<id>...] or --all (e.g. `--role feature`)");
|
|
196
207
|
for (const role of roles) {
|
|
197
208
|
const result = requestAbort(root, role);
|
|
198
209
|
if (!result.ok)
|
|
@@ -436,37 +447,10 @@ export async function cmdRetire(root, args) {
|
|
|
436
447
|
say(`nothing to remove for role ${role}`);
|
|
437
448
|
}
|
|
438
449
|
const REVERT_USAGE = "usage: tumwater revert <sha>|<role> <n>|<role> --last [--dry-run] [--json]";
|
|
439
|
-
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
|
|
443
|
-
function revertRunSha(root, positionals, last) {
|
|
444
|
-
const role = canonicalRoleId(positionals[0]);
|
|
445
|
-
const ids = knownRoleIdsCached(root);
|
|
446
|
-
if (!ids.includes(role))
|
|
447
|
-
fail(`${unknownRoleMessage(role, ids)} (${REVERT_USAGE})`);
|
|
448
|
-
const run = last
|
|
449
|
-
? (readRunRows(root, 1, role)[0]?.run ?? null)
|
|
450
|
-
: parseCountFlag("revert <n>", positionals[1]);
|
|
451
|
-
if (run === null)
|
|
452
|
-
fail(`no completed run of ${role} in the event log`);
|
|
453
|
-
const detail = readRunDetail(root, role, run);
|
|
454
|
-
if (detail === null)
|
|
455
|
-
fail(runNotFoundMessage(role, run));
|
|
456
|
-
const found = runRevertSha(role, run, detail);
|
|
457
|
-
if (!found.ok)
|
|
458
|
-
fail(found.error);
|
|
459
|
-
const sha = found.sha;
|
|
460
|
-
return { sha, line: `run ${role} #${run} merged as ${shortSha(sha)}`, role, run };
|
|
461
|
-
}
|
|
462
|
-
/** `tumwater revert <sha>|<role> <n>|<role> --last`: undo one merged change on main,
|
|
463
|
-
* committing the inverse as `tumwater(operator): Revert <short sha>` under the primary-worktree
|
|
464
|
-
* lock. The lock, branch guard, and fail-closed ref/conflict handling live in
|
|
465
|
-
* src/operator/revert.ts; this renderer names the new main head and names the role
|
|
466
|
-
* that made the reverted commit, when one did, as the one that may redo the work.
|
|
467
|
-
* `--json` prints the preview or the revert as one JSON object instead (refusals still fail on
|
|
468
|
-
* stderr). */
|
|
469
|
-
export async function cmdRevert(root, args) {
|
|
450
|
+
/** Scans `args` for `--dry-run`, `--last` and `--json`, refuses a flag-shaped token as a
|
|
451
|
+
* dropped flag and checks the positional arity, returning the flags and the remaining
|
|
452
|
+
* positionals. */
|
|
453
|
+
function parseRevertArgs(args) {
|
|
470
454
|
// `--dry-run` and `--last` are the only flags; the positionals select a sha (one), a run
|
|
471
455
|
// (`<role> <n>`) or the newest run of a role (`<role>` with --last). A flag or any other
|
|
472
456
|
// shape fails before anything is looked up or written.
|
|
@@ -483,20 +467,24 @@ export async function cmdRevert(root, args) {
|
|
|
483
467
|
fail(`revert takes only --dry-run, --last and --json (got ${flagged}); pass the commit or run to undo as the argument`);
|
|
484
468
|
if (last ? rest.length !== 1 : rest.length < 1 || rest.length > 2)
|
|
485
469
|
fail(`revert needs a commit or a run (${REVERT_USAGE})`);
|
|
470
|
+
return { dryRun, last, json, rest };
|
|
471
|
+
}
|
|
472
|
+
/** Resolves the target sha, its run line and its run ref (`revertRunSha` for `<role> <n>` and
|
|
473
|
+
* `<role> --last`, else the sha positional directly). */
|
|
474
|
+
function revertTarget(root, rest, last) {
|
|
486
475
|
const run = last || rest.length === 2 ? revertRunSha(root, rest, last) : null;
|
|
487
476
|
const sha = run?.sha ?? rest[0];
|
|
488
477
|
const runLine = run?.line ?? null;
|
|
489
478
|
const runRef = run === null ? null : { role: run.role, run: run.run };
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
479
|
+
return { sha, runLine, runRef };
|
|
480
|
+
}
|
|
481
|
+
/** Renders the `--dry-run` preview (the run line, `would revert …`, the path list,
|
|
482
|
+
* `applies cleanly`/`would conflict`/the dirty-checkout line) and its exit code. */
|
|
483
|
+
function sayRevertPreview(p, runLine, runRef, json) {
|
|
484
|
+
if (json) {
|
|
485
|
+
sayJson({ dryRun: true, run: runRef, sha: p.sha, subject: p.subject, paths: p.paths, head: p.head, primary: p.primary, applies: p.applies, dirty: p.dirty });
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
500
488
|
if (runLine !== null)
|
|
501
489
|
say(runLine);
|
|
502
490
|
say(`would revert ${shortSha(p.sha)} ${p.subject}`);
|
|
@@ -509,13 +497,12 @@ export async function cmdRevert(root, args) {
|
|
|
509
497
|
say(`would conflict ${on}`);
|
|
510
498
|
if (p.dirty)
|
|
511
499
|
say("the checkout has staged or unstaged changes, so tumwater revert would refuse until they are committed or stashed; nothing was written");
|
|
512
|
-
if (!p.applies || p.dirty)
|
|
513
|
-
process.exitCode = 1;
|
|
514
|
-
return;
|
|
515
500
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
501
|
+
if (!p.applies || p.dirty)
|
|
502
|
+
process.exitCode = 1;
|
|
503
|
+
}
|
|
504
|
+
/** Renders the committed revert (`main is now …`, the role follow-up) or its `--json` payload. */
|
|
505
|
+
function sayRevertOutcome(result, runLine, runRef, json) {
|
|
519
506
|
if (json) {
|
|
520
507
|
sayJson({ dryRun: false, run: runRef, subject: result.subject, headSha: result.headSha, beforeSha: result.beforeSha, followUp: revertFollowUp(result.subject) });
|
|
521
508
|
return;
|
|
@@ -527,3 +514,48 @@ export async function cmdRevert(root, args) {
|
|
|
527
514
|
if (followUp !== null)
|
|
528
515
|
say(followUp);
|
|
529
516
|
}
|
|
517
|
+
/** The sha the run `<role> <n>` (or the newest completed run of `<role>` under `last`) merged
|
|
518
|
+
* under, resolved the way `tumwater history <role> <n>` and the dashboard's revert resolve it
|
|
519
|
+
* (`runMergedSha`). Fails, exit 1, when the run is unknown or did not merge: revert is an
|
|
520
|
+
* action, so a miss is an error rather than history's exit-0 "not found". */
|
|
521
|
+
function revertRunSha(root, positionals, last) {
|
|
522
|
+
const role = canonicalRoleId(positionals[0]);
|
|
523
|
+
const ids = knownRoleIdsCached(root);
|
|
524
|
+
if (!ids.includes(role))
|
|
525
|
+
fail(`${unknownRoleMessage(role, ids)} (${REVERT_USAGE})`);
|
|
526
|
+
const run = last
|
|
527
|
+
? (readRunRows(root, 1, role)[0]?.run ?? null)
|
|
528
|
+
: parseCountFlag("revert <n>", positionals[1]);
|
|
529
|
+
if (run === null)
|
|
530
|
+
fail(`no completed run of ${role} in the event log`);
|
|
531
|
+
const detail = readRunDetail(root, role, run);
|
|
532
|
+
if (detail === null)
|
|
533
|
+
fail(runNotFoundMessage(role, run));
|
|
534
|
+
const found = runRevertSha(role, run, detail);
|
|
535
|
+
if (!found.ok)
|
|
536
|
+
fail(found.error);
|
|
537
|
+
const sha = found.sha;
|
|
538
|
+
return { sha, line: `run ${role} #${run} merged as ${shortSha(sha)}`, role, run };
|
|
539
|
+
}
|
|
540
|
+
/** `tumwater revert <sha>|<role> <n>|<role> --last`: undo one merged change on main,
|
|
541
|
+
* committing the inverse as `tumwater(operator): Revert <short sha>` under the primary-worktree
|
|
542
|
+
* lock. The lock, branch guard, and fail-closed ref/conflict handling live in
|
|
543
|
+
* src/operator/revert.ts; this renderer names the new main head and names the role
|
|
544
|
+
* that made the reverted commit, when one did, as the one that may redo the work.
|
|
545
|
+
* `--json` prints the preview or the revert as one JSON object instead (refusals still fail on
|
|
546
|
+
* stderr). */
|
|
547
|
+
export async function cmdRevert(root, args) {
|
|
548
|
+
const { dryRun, last, json, rest } = parseRevertArgs(args);
|
|
549
|
+
const { sha, runLine, runRef } = revertTarget(root, rest, last);
|
|
550
|
+
if (dryRun) {
|
|
551
|
+
const p = await previewRevert(root, sha);
|
|
552
|
+
if (!p.ok)
|
|
553
|
+
fail(p.error);
|
|
554
|
+
sayRevertPreview(p, runLine, runRef, json);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const result = await revertMergedCommit(root, sha);
|
|
558
|
+
if (!result.ok)
|
|
559
|
+
fail(result.error);
|
|
560
|
+
sayRevertOutcome(result, runLine, runRef, json);
|
|
561
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { cancelScheduledStop, isTeamPaused, scheduleStop } from "../team/team-state.js";
|
|
5
4
|
import { orchestratorAlive, readOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
@@ -8,12 +7,13 @@ import { formatDate, formatTime, humanUntil, DAY_MS } from "../text/datetime.js"
|
|
|
8
7
|
import { submitRolePrompt } from "../inbox/inbox-submit.js";
|
|
9
8
|
import { questionAsker } from "../backlog/question-answer.js";
|
|
10
9
|
import { knownRoleIdsCached, loadConfigCached } from "../config/config.js";
|
|
11
|
-
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
10
|
+
import { baseRoleOf, runnerIds } from "../roles/runner-ids.js";
|
|
12
11
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
13
12
|
import { saveRunnerState, zeroCounters } from "../runner/runner-state.js";
|
|
14
13
|
import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
15
14
|
import { clearBackoff } from "../scheduling/backoff.js";
|
|
16
15
|
import { writeJsonAtomic } from "../files/json-files.js";
|
|
16
|
+
import { ensureDir } from "../files/files.js";
|
|
17
17
|
import { reclaimPass } from "../team/reclaim.js";
|
|
18
18
|
import { formatGB } from "../text/format.js";
|
|
19
19
|
import { plural } from "../text/phrases.js";
|
|
@@ -134,7 +134,7 @@ export function requestWake(root, roles, inMs) {
|
|
|
134
134
|
// runner immediately, dropping a scheduled wake's notBeforeMs on the floor. A scheduled wake
|
|
135
135
|
// gets its own file so a later wake request, immediate or scheduled, cannot overwrite it.
|
|
136
136
|
if (deferred) {
|
|
137
|
-
|
|
137
|
+
ensureDir(scheduledWakeDir(root));
|
|
138
138
|
writeJsonAtomic(path.join(scheduledWakeDir(root), `${now}-${process.pid}-${crypto.randomBytes(4).toString("hex")}.json`), { at: now, roles, notBeforeMs: now + inMs });
|
|
139
139
|
}
|
|
140
140
|
else
|
|
@@ -251,6 +251,29 @@ export function requestAbort(root, role) {
|
|
|
251
251
|
}
|
|
252
252
|
return { ok: true, message: confirmation };
|
|
253
253
|
}
|
|
254
|
+
/** The marker-writing core of `tumwater abort --all`, shared with the GUI's POST /api/abort-all:
|
|
255
|
+
* drop one per-role marker per enabled runner (the same ids `runnerIds` names, each enabled role's
|
|
256
|
+
* bare id plus its `-2`… instances) that a running team consumes within one poll cycle — the
|
|
257
|
+
* team-wide twin of `requestAbort`'s one marker for one role. Idle roles' markers are swept by
|
|
258
|
+
* `consumeAbortRequests`' per-marker pass without aborting anything (`removeQuiet` runs even when
|
|
259
|
+
* no runner matches), so passing every enabled id is the same safe no-op `abort --role` of an idle
|
|
260
|
+
* role is. Reports the liveness gate structurally ({ok, message|error}) like requestAbort so the
|
|
261
|
+
* CLI can fail() and the GUI can shape its 409/200. */
|
|
262
|
+
export function requestAbortAll(root) {
|
|
263
|
+
if (!orchestratorAlive(root)) {
|
|
264
|
+
return { ok: false, error: NO_HARNESS_ERROR };
|
|
265
|
+
}
|
|
266
|
+
const { config } = loadConfigCached(root);
|
|
267
|
+
const ids = config ? runnerIds(config) : [];
|
|
268
|
+
if (ids.length === 0)
|
|
269
|
+
return { ok: false, error: "no roles are enabled to abort" };
|
|
270
|
+
for (const id of ids)
|
|
271
|
+
writeJsonAtomic(abortRequestPath(root, id), { at: Date.now() });
|
|
272
|
+
return {
|
|
273
|
+
ok: true,
|
|
274
|
+
message: `abort requested for ${plural(ids.length, "role", "roles")} — the running team applies each within ~2s`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
254
277
|
/** The timed-pause clauses both pause confirmations share, in the plan's "… paused for
|
|
255
278
|
* 30m — resumes automatically at 14:05" shape. The duration phrase comes from the parsed
|
|
256
279
|
* value, not from until minus the message's own now — a few ms of clock skew between the two
|
|
@@ -5,6 +5,7 @@ import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
|
5
5
|
import { runnerEnabled } from "../roles/runner-ids.js";
|
|
6
6
|
import { branchExists, countCommitsInRange, currentBranch, deleteRef, isDirty, isMergedInto, refSha, targetBranch, } from "../git/git.js";
|
|
7
7
|
import { errorMessage } from "../text/text.js";
|
|
8
|
+
import { plural } from "../text/phrases.js";
|
|
8
9
|
import { loadRunnerState } from "../runner/runner-state-load.js";
|
|
9
10
|
import { branchName, mergeRefName, legacyLandingRefName, worktreePath } from "../paths.js";
|
|
10
11
|
import { isUsableWorktree, removeWorktree } from "../git/worktree.js";
|
|
@@ -77,7 +78,7 @@ function objections(status) {
|
|
|
77
78
|
if (status.enabledInConfig)
|
|
78
79
|
reasons.push(`role ${status.role} is still enabled in tumwater.json — disable it first`);
|
|
79
80
|
if (status.aheadOfMain > 0)
|
|
80
|
-
reasons.push(`branch ${branchName(status.role)} holds ${status.aheadOfMain
|
|
81
|
+
reasons.push(`branch ${branchName(status.role)} holds ${plural(status.aheadOfMain, "unmerged commit")} — merge or discard them first`);
|
|
81
82
|
if (status.worktreePresent && !status.worktreeUsable)
|
|
82
83
|
reasons.push("the worktree is unusable and cannot be checked for uncommitted changes — remove it manually or use --force");
|
|
83
84
|
if (status.dirty)
|
|
@@ -52,6 +52,20 @@ export async function resolveRevertTarget(root, ref) {
|
|
|
52
52
|
return { ok: false, error: `${shortSha(full)} is not on ${primary}` };
|
|
53
53
|
return { ok: true, full, primary };
|
|
54
54
|
}
|
|
55
|
+
/** The shared read-only prologue of a revert and its preview, so the two cannot drift: resolve
|
|
56
|
+
* `ref` to a merged single-parent commit (the resolveRevertTarget/parentRefusal refusals), then
|
|
57
|
+
* read its subject. The subject is what the reverted-role follow-up and the event log read, and
|
|
58
|
+
* both callers needed exactly these three steps before diverging into their own work. */
|
|
59
|
+
async function resolveRevertSubject(root, ref) {
|
|
60
|
+
const target = await resolveRevertTarget(root, ref);
|
|
61
|
+
if (!target.ok)
|
|
62
|
+
return target;
|
|
63
|
+
const refusal = await parentRefusal(root, target.full);
|
|
64
|
+
if (refusal !== null)
|
|
65
|
+
return { ok: false, error: refusal };
|
|
66
|
+
const subject = await runGit(root, ["log", "-1", "--format=%s", target.full]);
|
|
67
|
+
return { ok: true, full: target.full, primary: target.primary, subject };
|
|
68
|
+
}
|
|
55
69
|
/** The refusal for a commit without exactly one parent (a root or a merge), or null. Shared by
|
|
56
70
|
* the revert and its preview: `git revert` of a root deletes the whole tree, of a merge needs
|
|
57
71
|
* `-m`. */
|
|
@@ -78,14 +92,10 @@ export function runRevertSha(role, run, detail) {
|
|
|
78
92
|
* touches the index, the worktree or a ref. Never throws. */
|
|
79
93
|
export async function previewRevert(root, ref) {
|
|
80
94
|
try {
|
|
81
|
-
const target = await
|
|
95
|
+
const target = await resolveRevertSubject(root, ref);
|
|
82
96
|
if (!target.ok)
|
|
83
97
|
return target;
|
|
84
|
-
const { full, primary } = target;
|
|
85
|
-
const refusal = await parentRefusal(root, full);
|
|
86
|
-
if (refusal !== null)
|
|
87
|
-
return { ok: false, error: refusal };
|
|
88
|
-
const subject = await runGit(root, ["log", "-1", "--format=%s", full]);
|
|
98
|
+
const { full, primary, subject } = target;
|
|
89
99
|
const paths = (await runGit(root, ["diff", "--name-only", "--no-renames", full + "^", full]))
|
|
90
100
|
.split("\n")
|
|
91
101
|
.filter((p) => p.length > 0);
|
|
@@ -137,16 +147,12 @@ export async function revertMergedCommit(root, ref) {
|
|
|
137
147
|
// awaits would block its event loop until the lock times out.
|
|
138
148
|
await new Promise((resolve) => setImmediate(resolve));
|
|
139
149
|
return await withPrimaryWorktreeLockAsync(root, async () => {
|
|
140
|
-
const target = await
|
|
150
|
+
const target = await resolveRevertSubject(root, ref);
|
|
141
151
|
if (!target.ok)
|
|
142
152
|
return target;
|
|
143
|
-
const { full } = target;
|
|
144
|
-
const refusal = await parentRefusal(root, full);
|
|
145
|
-
if (refusal !== null)
|
|
146
|
-
return { ok: false, error: refusal };
|
|
153
|
+
const { full, subject } = target;
|
|
147
154
|
// `head` is captured before the revert so a future caller can compare before/after.
|
|
148
155
|
const head = await headOf(root, "HEAD");
|
|
149
|
-
const subject = await runGit(root, ["log", "-1", "--format=%s", full]);
|
|
150
156
|
// Fail-closed on any staged or unstaged change to a tracked path: we refuse to revert a
|
|
151
157
|
// checkout that has one (untracked files are tolerated — the lock dir we just created is
|
|
152
158
|
// one). This keeps the inverse commit free of unrelated content and, just as important,
|
|
@@ -13,7 +13,7 @@ import { TUMWATER_SOURCE } from "../events/events.js";
|
|
|
13
13
|
import { consumeAbortRequests, consumeReclaimRequest, consumeRestartRequest, consumeResetRequest, consumeWakeRequest, } from "../operator/operator-requests.js";
|
|
14
14
|
import { checkPermitSnapshot } from "../concurrency/check-permit.js";
|
|
15
15
|
import { slotPoolSnapshot } from "../git/worktree-pool.js";
|
|
16
|
-
import { redeployTarget } from "../baseline/main-baseline.js";
|
|
16
|
+
import { redeployTarget, watchVerifiesMain } from "../baseline/main-baseline.js";
|
|
17
17
|
import { p75RunDurationMs } from "../run/run-timing.js";
|
|
18
18
|
import { pollTeamGates } from "../gates/gate-polls.js";
|
|
19
19
|
import { heldProviders, windowHeldProviders } from "../team/team-hold.js";
|
|
@@ -175,7 +175,7 @@ export async function pollRedeploy(ctx, state, { liveConfig, mainHead, now }) {
|
|
|
175
175
|
ctx.mainWatch.poll(mainHead, state.holdForRestart || state.diskHeld, liveConfig);
|
|
176
176
|
if (!redeploy)
|
|
177
177
|
return false;
|
|
178
|
-
const action = await redeploy.poll(await redeployTarget(ctx.root, mainHead, redeploy.build.sha), {
|
|
178
|
+
const action = await redeploy.poll(await redeployTarget(ctx.root, mainHead, redeploy.build.sha, watchVerifiesMain(ctx.root, liveConfig)), {
|
|
179
179
|
roleInFlight: ctx.rolePermitHolders.size,
|
|
180
180
|
directorInFlight: ctx.directorInFlight.size,
|
|
181
181
|
roleRunP75Ms: p75RunDurationMs(ctx.roleRunDurationsMs),
|
|
@@ -5,7 +5,7 @@ import { FALLBACK_BREAKER_POLICY, } from "../budget/fallback-breaker.js";
|
|
|
5
5
|
import { newPollState, pollGatesAndHolds, pollMergeDrain, pollRedeploy, pollRequestsAndWatch, pollReserveAndExit, pollSchedule, } from "./orchestrator-poll.js";
|
|
6
6
|
import { RoleRunner } from "../runner/runner.js";
|
|
7
7
|
import { dropDanglingTumwaterPins, removeLegacyLandWorktrees } from "../git/worktree.js";
|
|
8
|
-
import { pinCrashedRuns, retireLegacyRoleWorktrees } from "../git/worktree-pool.js";
|
|
8
|
+
import { pinCrashedRuns, retireLegacyRoleWorktrees } from "../git/worktree-pool-startup.js";
|
|
9
9
|
import { mergeTasks, newMergePipeline } from "../merge/merge-pipeline.js";
|
|
10
10
|
import { bestEffortEventLogger, logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
11
11
|
import { removeQuiet } from "../files/files.js";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* and the orchestrator only hands it the (possibly live-reloaded) window. */
|
|
7
7
|
import { logEventBestEffort, warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
8
8
|
import { DAY_MS } from "../text/datetime.js";
|
|
9
|
+
import { plural } from "../text/phrases.js";
|
|
9
10
|
import { ensureParentDir, pruneOldFiles } from "../files/files.js";
|
|
10
11
|
import { pruneSessionLedgers, pruneOpencodeSessions, pruneCodexSessions } from "../agent/session-ledger.js";
|
|
11
12
|
import { loadConfigSafe } from "../config/config.js";
|
|
@@ -90,7 +91,7 @@ export class RetentionPruner {
|
|
|
90
91
|
spillDir: opencodeSpillDir(root),
|
|
91
92
|
});
|
|
92
93
|
if (deleted > 0)
|
|
93
|
-
warnEventBestEffort(root, TUMWATER_SOURCE, `deleted ${deleted
|
|
94
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `deleted ${plural(deleted, "expired opencode session")}`);
|
|
94
95
|
// Codex's sessions also live outside .tumwater — under the team's CODEX_HOME (part 8/9).
|
|
95
96
|
// Resolve its binary and home exactly as a run's invocation would and purge its expired
|
|
96
97
|
// sessions from the ledger BEFORE the file pass below ages it out. Like the OpenCode twin,
|
|
@@ -106,21 +107,21 @@ export class RetentionPruner {
|
|
|
106
107
|
env: { ...process.env, CODEX_HOME: codexHomeDir },
|
|
107
108
|
});
|
|
108
109
|
if (deletedCodex > 0)
|
|
109
|
-
warnEventBestEffort(root, TUMWATER_SOURCE, `deleted ${deletedCodex
|
|
110
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `deleted ${plural(deletedCodex, "expired codex session")}`);
|
|
110
111
|
// Age out session/tool-output files now: an entirely stale dir is gone before the line
|
|
111
112
|
// pass reads it. A running role's ledger mtime stays young, so pruneOldFiles never ages
|
|
112
113
|
// that file out; each warning must name the unit it pruned.
|
|
113
114
|
const pruned = pruneOldFiles(sessions, days) + pruneOldFiles(toolOutputDir(root), days);
|
|
114
115
|
if (pruned > 0)
|
|
115
|
-
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${pruned
|
|
116
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${plural(pruned, "old agent session/tool-output file")}`);
|
|
116
117
|
const prunedLines = pruneSessionLedgers(sessions, days);
|
|
117
118
|
if (prunedLines > 0)
|
|
118
|
-
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${prunedLines
|
|
119
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${plural(prunedLines, "stale session ledger line")}`);
|
|
119
120
|
// Salvaged slot leftovers (src/git/slot-salvage.ts) age out on the same window as the
|
|
120
121
|
// sessions that made them: a patch still waiting by then belongs to a role that has not run
|
|
121
122
|
// since, and the operator had the window's length to take a kept one.
|
|
122
123
|
const prunedPatches = pruneOldFiles(salvageDir(root), days);
|
|
123
124
|
if (prunedPatches > 0)
|
|
124
|
-
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${prunedPatches
|
|
125
|
+
warnEventBestEffort(root, TUMWATER_SOURCE, `pruned ${plural(prunedPatches, "old salvaged workspace patch", "old salvaged workspace patches")}`);
|
|
125
126
|
}
|
|
126
127
|
}
|
package/dist/src/paths.js
CHANGED
|
@@ -123,12 +123,6 @@ export function worktreeUseLockPath(root) {
|
|
|
123
123
|
export function orchestratorStatePath(root) {
|
|
124
124
|
return path.join(tumwaterDir(root), "state", "orchestrator.json");
|
|
125
125
|
}
|
|
126
|
-
/** The doctor's image-probe verdict cache (src/doctor/image-probe.ts): how each claimed
|
|
127
|
-
* image-input model actually answered a live probe, keyed agent/provider/model. Runtime state
|
|
128
|
-
* under .tumwater/state/, never committed. */
|
|
129
|
-
export function imageProbesPath(root) {
|
|
130
|
-
return path.join(tumwaterDir(root), "state", "image-probes.json");
|
|
131
|
-
}
|
|
132
126
|
/** The images column `tumwater doctor` last read from `pi --list-models`, keyed provider/model.
|
|
133
127
|
* Runtime state under .tumwater/state/, never committed. */
|
|
134
128
|
export function piListedModelsPath(root) {
|
|
@@ -313,7 +307,7 @@ export function sessionDir(root, role) {
|
|
|
313
307
|
return path.join(sessionsRootDir(root), role);
|
|
314
308
|
}
|
|
315
309
|
/** `sessionDir`'s real team root — the nearest ancestor holding a `.tumwater` directory — or
|
|
316
|
-
* undefined when none does. run-
|
|
310
|
+
* undefined when none does. run-spawn installs the notes shim from THIS (never from teamRootOf,
|
|
317
311
|
* whose no-ancestor fallback can be a shared directory): writing `.tumwater` at the fallback
|
|
318
312
|
* would mint a phantom harness root every other suite on the host then found — BUGS.md
|
|
319
313
|
* 2026-10-18's `$TMPDIR/.tumwater/bin/tumwater-notes`. */
|
|
@@ -347,15 +341,15 @@ export function owningTeamRoot(dir) {
|
|
|
347
341
|
* unreachable in a harness run and used only by OpenCode's harness-only read-back lookups
|
|
348
342
|
* (opencodeDbPath, loadConfigSafe), which always see a real root. Never use this to decide
|
|
349
343
|
* where to WRITE a `.tumwater`: the fallback can be a shared directory, and creating `.tumwater`
|
|
350
|
-
* there mints a phantom root (BUGS.md 2026-10-18); run-
|
|
344
|
+
* there mints a phantom root (BUGS.md 2026-10-18); run-spawn's shim install uses
|
|
351
345
|
* nearestTumwaterRoot instead. Moved here from the opencode adapter (plans/notes-command.md) so
|
|
352
|
-
* run-
|
|
346
|
+
* run-spawn's shim install — which every backend's run needs — shares the one lookup instead of
|
|
353
347
|
* the opencode adapter keeping it private. */
|
|
354
348
|
export function teamRootOf(sessionDir) {
|
|
355
349
|
return nearestTumwaterRoot(sessionDir) ?? path.dirname(path.dirname(sessionDir));
|
|
356
350
|
}
|
|
357
351
|
/** The directory the notes shim lands in, under a team root: `<root>/.tumwater/bin`.
|
|
358
|
-
* run-
|
|
352
|
+
* run-spawn installs the `tumwater-notes` command there (src/agent/notes-shim.ts) and
|
|
359
353
|
* run-marker's backendBaseEnv prepends it to every run's PATH, so a run on any backend finds
|
|
360
354
|
* the command (plans/notes-command.md "Reaching it from a run"). */
|
|
361
355
|
export function notesShimDir(root) {
|
|
@@ -450,6 +444,12 @@ export function witnessWorktreePath(root) {
|
|
|
450
444
|
export function slotWorktreePath(root, n) {
|
|
451
445
|
return path.join(worktreesDir(root), `_slot-${n}`);
|
|
452
446
|
}
|
|
447
|
+
/** A slot dir is canonical when it is exactly a `_slot-<n>` checkout under the worktrees dir.
|
|
448
|
+
* A migrated legacy `<role>` dir is not, which marks it for retirement once its resume finishes
|
|
449
|
+
* (retireLegacyRoleWorktrees in worktree-pool-startup.ts and releaseSlot in worktree-pool.ts). */
|
|
450
|
+
export function isCanonicalSlotDir(dir) {
|
|
451
|
+
return /^_slot-\d+$/.test(path.basename(dir));
|
|
452
|
+
}
|
|
453
453
|
/** The worktree pool's dedicated checkout for the merge drain's merge-side work
|
|
454
454
|
* (plans/worktree-pool.md, "Layout"): a single change, a stack and its bisect, the conflict
|
|
455
455
|
* resolver and the post-resolve re-review. Kept out of the pool so a permit-waiting lease can
|
package/dist/src/pi/pi-bin.js
CHANGED
|
@@ -5,7 +5,7 @@ import { envPath, findOnPath, isExecutableFile } from "../files/files.js";
|
|
|
5
5
|
* resolution `TUMWATER_<ID>_BIN` → `backends.<id>.bin` (pi's legacy `agentBin` alias) → the
|
|
6
6
|
* backends' defaults (plans/agent-backends.md "Config"; plans/portability.md §5/7), the
|
|
7
7
|
* executability rule the preflight askers share, and the missing-binary wording they quote.
|
|
8
|
-
* The spawn (src/agent/run-
|
|
8
|
+
* The spawn (src/agent/run-spawn.ts) resolves through each backend's resolveBin; the startup
|
|
9
9
|
* gate (gates/startup-gate.ts) walks every enabled seam's backend through the same resolution and
|
|
10
10
|
* executability rule, and doctor (doctor/doctor-checks.ts) asks them for every backend in use
|
|
11
11
|
* (backendsInUse, the startup gate's rule). Each surface quotes this module's wording, so the
|
|
@@ -15,7 +15,7 @@ import { envPath, findOnPath, isExecutableFile } from "../files/files.js";
|
|
|
15
15
|
* layer. */
|
|
16
16
|
const PI_MISSING_MESSAGE = "pi not found on PATH — install it (https://github.com/badlogic/pi-mono) or add its bin directory to your PATH";
|
|
17
17
|
/** The operator-facing name of each source, as it appears in failure text and doctor's
|
|
18
|
-
* report. Exported so src/agent/run-
|
|
18
|
+
* report. Exported so src/agent/run-settle.ts's spawn errors and doctor's checks quote the same
|
|
19
19
|
* string. `binEnv` is the environment variable a backend reads (pi's TUMWATER_PI_BIN by
|
|
20
20
|
* default; claude's adapter passes TUMWATER_CLAUDE_BIN), and `configKey` is the tumwater.json
|
|
21
21
|
* key the "config" source names (pi's legacy `agentBin`, or `backends.<id>.bin`), so a claude
|
package/dist/src/pi/pi-models.js
CHANGED
|
@@ -141,6 +141,15 @@ export function seamContextWindow(seam, projectRoot, modelsPath = piModelsPath()
|
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
/** The resolved primary seam's declared context window for a role — `configForRole` resolves
|
|
145
|
+
* the role's seam (the view the role page, status table/drawer and run history all print), then
|
|
146
|
+
* `seamContextWindow` names the window that seam declares, or null when it cannot be known. The
|
|
147
|
+
* one home of that resolution so the role page, the status table/drawer and a run's history
|
|
148
|
+
* header cannot disagree about the peak-ctx denominator. `modelsPath` defaults to pi's own
|
|
149
|
+
* catalog path, as `seamContextWindow` does, for read-only callers that have no seam handle. */
|
|
150
|
+
export function seamContextWindowForRole(cfg, role, projectRoot, modelsPath = piModelsPath()) {
|
|
151
|
+
return seamContextWindow(configForRole(cfg, role), projectRoot, modelsPath);
|
|
152
|
+
}
|
|
144
153
|
/** A provider's `baseUrl` from pi's models.json, or null when the file, the provider or the
|
|
145
154
|
* field is missing — what doctor probes to tell a local server that is down from one that is up. */
|
|
146
155
|
export function piProviderBaseUrl(modelsPath, provider) {
|