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
package/dist/src/loop/loop.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { loadLoopState, saveLoopState, zeroCounters } from "./loop-state.js";
|
|
2
2
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
3
3
|
import { baseRoleOf } from "../roles/loop-ids.js";
|
|
4
|
-
import { warnEventBestEffort } from "../events/events.js";
|
|
4
|
+
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
5
|
+
import { promptPreview } from "../inbox/inbox.js";
|
|
5
6
|
import { assembleTickPrompt } from "../tick/tick-prompt.js";
|
|
6
7
|
import { LoopPi } from "./loop-pi.js";
|
|
7
|
-
import { configForRole,
|
|
8
|
+
import { configForRole, seamHoldKey, withModelOverrides, } from "../config/config-views.js";
|
|
9
|
+
import { fallbackRoleConfig } from "../config/config-fallbacks.js";
|
|
8
10
|
import { PendingPrompt } from "../inbox/pending-prompt.js";
|
|
9
11
|
import { modelFallbackActive } from "./model-fallback.js";
|
|
10
12
|
import { clearBackoff } from "../scheduling/backoff.js";
|
|
@@ -26,20 +28,20 @@ export class LoopRunner {
|
|
|
26
28
|
* tick intervals, backoff, and role enablement for subsequent ticks. */
|
|
27
29
|
config;
|
|
28
30
|
/** The 429 observation from this loop's usage accounting (TickUsage.lastRateLimit,
|
|
29
|
-
* src/tick/tick-usage.ts): the orchestrator's
|
|
30
|
-
* runner (src/
|
|
31
|
+
* src/tick/tick-usage.ts): the orchestrator's team-wide hold wiring reads it through the
|
|
32
|
+
* runner (src/team/team-polls.ts), so the field keeps its place on the runner's surface. */
|
|
31
33
|
get lastRateLimit() {
|
|
32
34
|
return this.usage.lastRateLimit;
|
|
33
35
|
}
|
|
34
36
|
/** The backend-failure observation (TickUsage.lastBackendFailure, src/tick/tick-usage.ts): the
|
|
35
37
|
* non-429 sibling of lastRateLimit above — the connection, timeout, server, and model-load
|
|
36
|
-
* kinds the
|
|
38
|
+
* kinds the team-wide hold groups storms by (src/team/team-hold.ts). Same runner surface,
|
|
37
39
|
* same consumer. */
|
|
38
40
|
get lastBackendFailure() {
|
|
39
41
|
return this.usage.lastBackendFailure;
|
|
40
42
|
}
|
|
41
43
|
/** Per-tick and lifetime usage accounting (src/tick/tick-usage.ts): the turns/cost windows the
|
|
42
|
-
* commit trailer and
|
|
44
|
+
* commit trailer and run_end event read, the lifetime totals folded into state, and the
|
|
43
45
|
* observations above. Grown through foldUsage — the once-per-run choke point. */
|
|
44
46
|
usage = new TickUsage();
|
|
45
47
|
/** The dequeued user prompt a tick is executing and its requeue policy
|
|
@@ -49,7 +51,7 @@ export class LoopRunner {
|
|
|
49
51
|
pending;
|
|
50
52
|
/** Per-tick abort controller, recreated at every tick start: `abortTick()` kills the
|
|
51
53
|
* in-flight pi run without touching the harness shutdown signal (`this.signal`), which
|
|
52
|
-
* would stop the whole
|
|
54
|
+
* would stop the whole team. */
|
|
53
55
|
tickAbort = new AbortController();
|
|
54
56
|
/** Set by abortTick() when a user-initiated abort lands mid-tick: runTick's two abort
|
|
55
57
|
* branches (author run, review gate) then diverge from shutdown semantics — worktree reset
|
|
@@ -70,7 +72,14 @@ export class LoopRunner {
|
|
|
70
72
|
* resolve to, since different roles may run on different pairs.
|
|
71
73
|
* Transient — never persisted — and captured fresh at every tick start. */
|
|
72
74
|
tickPair;
|
|
73
|
-
/**
|
|
75
|
+
/** Whether the budget gate currently has the team on its free fallback pairs (src/gates/
|
|
76
|
+
* budget-gates.ts onFallback, set alongside `config` by the orchestrator's poll): an
|
|
77
|
+
* image reroute may then look only among the free fallback pairs, never a priced primary
|
|
78
|
+
* (plans/image-input.md "Reroute target" "Regime"). Pure scheduler input, never whether
|
|
79
|
+
* the LOOP itself is mid-fallback; defaults false, so standalone-loop tests stay on the
|
|
80
|
+
* primary regime unless they set it. */
|
|
81
|
+
budgetFallbackActive = false;
|
|
82
|
+
/** The merge failure this tick's leftover recovery is retrying, if any (a retriable merger
|
|
74
83
|
* outcome that kept the pin, which recovery re-queued): set in finishRecoveryTick and
|
|
75
84
|
* attached to the returned TickOutcome so applyTickOutcome can feed it into the error streak
|
|
76
85
|
* even though the tick itself ends `queued` (BUGS.md 2026-09-21). Reset at every tick start. */
|
|
@@ -102,9 +111,9 @@ export class LoopRunner {
|
|
|
102
111
|
signal: this.signal,
|
|
103
112
|
runSignal: () => this.runSignal(),
|
|
104
113
|
warn: (message) => this.warn(message),
|
|
105
|
-
foldUsage: (run) => this.foldUsage(run),
|
|
106
|
-
|
|
107
|
-
tickNumber: () => this.state.
|
|
114
|
+
foldUsage: (run, provider, pair) => this.foldUsage(run, provider, pair),
|
|
115
|
+
foldMergeUsage: (run, provider, pair) => this.foldMergeUsage(run, provider, pair),
|
|
116
|
+
tickNumber: () => this.state.runs,
|
|
108
117
|
sleep: this.sleep,
|
|
109
118
|
});
|
|
110
119
|
// A persisted running flag means the previous process died mid-tick WITHOUT the
|
|
@@ -114,6 +123,7 @@ export class LoopRunner {
|
|
|
114
123
|
this.state.resumePending = true;
|
|
115
124
|
this.state.running = false;
|
|
116
125
|
this.state.parkedSince = undefined;
|
|
126
|
+
this.state.parkedOn = undefined;
|
|
117
127
|
}
|
|
118
128
|
save() {
|
|
119
129
|
saveLoopState(this.root, this.state);
|
|
@@ -143,7 +153,7 @@ export class LoopRunner {
|
|
|
143
153
|
* when no tick is running — the orchestrator checks `state.running` before calling, and a
|
|
144
154
|
* direct call must not arm a stale flag. Otherwise set the userAborted flag and abort the
|
|
145
155
|
* per-tick controller: runPi terminates the in-flight pi child (SIGTERM → SIGKILL
|
|
146
|
-
* escalation) exactly like a harness shutdown, but only this loop's run dies — the
|
|
156
|
+
* escalation) exactly like a harness shutdown, but only this loop's run dies — the team
|
|
147
157
|
* keeps running. */
|
|
148
158
|
abortTick() {
|
|
149
159
|
if (!this.state.running)
|
|
@@ -173,7 +183,29 @@ export class LoopRunner {
|
|
|
173
183
|
* active (a due probe runs the primary), else the role's own resolved config. The hold and
|
|
174
184
|
* start passes read this so a role mid-episode is keyed by the provider it will actually use.
|
|
175
185
|
* Pure read of config + state + the caller's clock. */
|
|
186
|
+
/** Which pair set an image reroute may look among (plans/image-input.md "Reroute target"
|
|
187
|
+
* "Regime"): the free fallback pairs while the budget gate is on fallback OR the role is
|
|
188
|
+
* in a model-fallback episode; otherwise the primary tiers. One home for the rule, shared
|
|
189
|
+
* by the target-resolution sites (the verdict's reroute arm, the attachment preflight). */
|
|
190
|
+
rerouteRegime(now) {
|
|
191
|
+
if (this.budgetFallbackActive)
|
|
192
|
+
return "fallback";
|
|
193
|
+
if (modelFallbackActive(this.state.modelFallback, now))
|
|
194
|
+
return "fallback";
|
|
195
|
+
return "primary";
|
|
196
|
+
}
|
|
176
197
|
runConfig(now) {
|
|
198
|
+
// A pending image-input reroute (plans/image-input.md part 4/5) outranks the role's own
|
|
199
|
+
// tier and its model fallback: the next tick starts on the image-capable target the
|
|
200
|
+
// blind-read tick recorded, rebuilt from the stored seam over the live config.
|
|
201
|
+
if (this.state.imageReroute) {
|
|
202
|
+
const r = this.state.imageReroute;
|
|
203
|
+
return withModelOverrides(this.config, r.tier, {
|
|
204
|
+
provider: r.provider,
|
|
205
|
+
model: r.model,
|
|
206
|
+
thinking: r.thinking,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
177
209
|
const fallback = fallbackRoleConfig(this.config, this.role);
|
|
178
210
|
if (fallback !== null && modelFallbackActive(this.state.modelFallback, now))
|
|
179
211
|
return fallback;
|
|
@@ -196,10 +228,13 @@ export class LoopRunner {
|
|
|
196
228
|
* runner records it as pending — re-queued if the tick ends without fulfilling it. Null when
|
|
197
229
|
* the loop has nothing to run (an empty director inbox); a role loop's assembly never
|
|
198
230
|
* returns null. */
|
|
199
|
-
tickPrompt(agent) {
|
|
200
|
-
const assembled = assembleTickPrompt({ root: this.root, config: this.config, role: this.role, state: this.state, agent });
|
|
231
|
+
tickPrompt(agent, compact) {
|
|
232
|
+
const assembled = assembleTickPrompt({ root: this.root, config: this.config, role: this.role, state: this.state, agent, compact });
|
|
201
233
|
if (assembled === null)
|
|
202
234
|
return null;
|
|
235
|
+
if (assembled.userPrompt !== null) {
|
|
236
|
+
logEventBestEffort(this.root, { source: this.role, type: "prompt_taken", run: this.state.runs, preview: promptPreview(assembled.userPrompt) });
|
|
237
|
+
}
|
|
203
238
|
this.pending.record(assembled.userPrompt, assembled.userPromptFile);
|
|
204
239
|
return assembled.prompt;
|
|
205
240
|
}
|
|
@@ -212,45 +247,45 @@ export class LoopRunner {
|
|
|
212
247
|
return pinAndResetPhase(this.tickContext(), wt, sha);
|
|
213
248
|
}
|
|
214
249
|
/** Thin delegate to finishRecoveryTickPhase (src/loop/loop-recovery.ts). */
|
|
215
|
-
async finishRecoveryTick(recovered, userPrompt, wt,
|
|
216
|
-
return finishRecoveryTickPhase(this.tickContext(), recovered, userPrompt, wt,
|
|
250
|
+
async finishRecoveryTick(recovered, userPrompt, wt, priorMergeFailure) {
|
|
251
|
+
return finishRecoveryTickPhase(this.tickContext(), recovered, userPrompt, wt, priorMergeFailure);
|
|
217
252
|
}
|
|
218
253
|
/** Fold one pi run's usage into the tick's counters and the lifetime totals — the
|
|
219
254
|
* accounting itself lives in TickUsage.fold (src/tick/tick-usage.ts); this keeps the once-per-run
|
|
220
|
-
* choke point and the
|
|
221
|
-
* wiring (src/
|
|
222
|
-
foldUsage(run) {
|
|
223
|
-
this.usage.fold(this.state, run);
|
|
255
|
+
* choke point and the foldMergeUsage face on the runner, where LoopPi and the merge
|
|
256
|
+
* wiring (src/merge/merge-slot.ts) reach them. */
|
|
257
|
+
foldUsage(run, provider, pair) {
|
|
258
|
+
this.usage.fold(this.state, run, true, provider, pair);
|
|
224
259
|
}
|
|
225
|
-
/** Run pi for the orchestrator's
|
|
260
|
+
/** Run pi for the orchestrator's merge slot (merge queue 3/5): runRolePi's shared wiring
|
|
226
261
|
* with usage folded into this loop's counters, watching ONLY the harness shutdown signal
|
|
227
|
-
* (`this.signal`) — a
|
|
228
|
-
* reach it. The plumbing lives in src/loop/loop-pi.ts; this is the
|
|
262
|
+
* (`this.signal`) — a merge is outside any tick, so `tumwater abort --role` must never
|
|
263
|
+
* reach it. The plumbing lives in src/loop/loop-pi.ts; this is the merge slot's public
|
|
229
264
|
* face. */
|
|
230
|
-
async
|
|
231
|
-
return this.pi.
|
|
265
|
+
async runMergePi(wt, prompt, sessionName, config) {
|
|
266
|
+
return this.pi.runMergePi(wt, prompt, sessionName, config);
|
|
232
267
|
}
|
|
233
|
-
/** Run pi for the
|
|
268
|
+
/** Run pi for the merge gate's reviewer (the review run and its verdict follow-up)
|
|
234
269
|
* through the loop's shared transient-retry wiring — the same retry an authoring run gets,
|
|
235
270
|
* so a 429 in the gate is waited out and retried once instead of failing the review on
|
|
236
271
|
* first contact (BUGS.md 2026-10-01). A retried first attempt is folded here at retry time
|
|
237
|
-
* (the
|
|
272
|
+
* (the team-wide rate-limit hold's input); the FINAL run's usage is folded by the gate's
|
|
238
273
|
* caller, exactly as before — the plumbing lives in src/loop/loop-pi.ts; this is the
|
|
239
|
-
*
|
|
274
|
+
* merge wiring's public face. */
|
|
240
275
|
async runGatePi(opts) {
|
|
241
276
|
return this.pi.runGatePi(opts);
|
|
242
277
|
}
|
|
243
|
-
/** Public face of foldUsage for the orchestrator's
|
|
244
|
-
* (reviewer, conflict resolution) into the AUTHORING role's counters, so the
|
|
278
|
+
/** Public face of foldUsage for the orchestrator's merge wiring: folds a merge pi run
|
|
279
|
+
* (reviewer, conflict resolution) into the AUTHORING role's counters, so the team's daily
|
|
245
280
|
* cost and the per-role usage windows attribute that spend to the role that authored the
|
|
246
281
|
* work — no new counter semantics, just the same fold loop.ts applies to its tick runs. */
|
|
247
|
-
|
|
248
|
-
// authoring=false: a
|
|
282
|
+
foldMergeUsage(run, provider, pair) {
|
|
283
|
+
// authoring=false: a merge/review run's usage counts toward cost and prompt tokens, but
|
|
249
284
|
// never toward the tick's pre-edit counter (src/tick/tick-usage.ts fold).
|
|
250
|
-
this.usage.fold(this.state, run, false);
|
|
285
|
+
this.usage.fold(this.state, run, false, provider, pair);
|
|
251
286
|
}
|
|
252
287
|
/** Run one full tick of this role loop (the pipeline lives in src/loop/loop-tick.ts):
|
|
253
|
-
* `tickPhase` resolves the config and fallback episode, logs
|
|
288
|
+
* `tickPhase` resolves the config and fallback episode, logs run_start, runs the pipeline,
|
|
254
289
|
* and finalizes. Never throws: a failed tick is an "error" result, saved and logged. */
|
|
255
290
|
async tick() {
|
|
256
291
|
return tickPhase(this.tickContext());
|
|
@@ -284,7 +319,8 @@ export class LoopRunner {
|
|
|
284
319
|
save: () => this.save(),
|
|
285
320
|
warn: (message) => this.warn(message),
|
|
286
321
|
runSignal: () => this.runSignal(),
|
|
287
|
-
tickPrompt: (agent) => this.tickPrompt(agent),
|
|
322
|
+
tickPrompt: (agent, compact) => this.tickPrompt(agent, compact),
|
|
323
|
+
rerouteRegime: (now) => this.rerouteRegime(now),
|
|
288
324
|
get recoveryFailure() {
|
|
289
325
|
return self.recoveryFailure;
|
|
290
326
|
},
|
|
@@ -305,7 +341,7 @@ export class LoopRunner {
|
|
|
305
341
|
finishAbortedTick: (userPrompt, wt) => this.finishAbortedTick(userPrompt, wt),
|
|
306
342
|
pinAndReset: (wt, sha) => this.pinAndReset(wt, sha),
|
|
307
343
|
foldModelFallback: (fallbackCtx, pi) => this.foldModelFallback(fallbackCtx, pi),
|
|
308
|
-
finishRecoveryTick: (recovered, userPrompt, wt,
|
|
344
|
+
finishRecoveryTick: (recovered, userPrompt, wt, priorMergeFailure) => this.finishRecoveryTick(recovered, userPrompt, wt, priorMergeFailure),
|
|
309
345
|
};
|
|
310
346
|
}
|
|
311
347
|
}
|
|
@@ -15,11 +15,12 @@ export function modelFallbackProbe(state, now) {
|
|
|
15
15
|
return state !== undefined && state.since > 0 && now >= state.probeAt;
|
|
16
16
|
}
|
|
17
17
|
/** Classify one authoring run's verdict about the primary (src/tick/tick-usage.ts records the
|
|
18
|
-
* same flags for the
|
|
18
|
+
* same flags for the team-wide hold). A run that was aborted, quiet-killed, or timed out — or
|
|
19
|
+
* that read an image its model cannot see (the runner killed it, plans/image-input.md) — is
|
|
19
20
|
* inconclusive even when it also reports provider evidence: the harness killed it, so it is
|
|
20
21
|
* no verdict about the primary's health. */
|
|
21
22
|
export function modelFallbackVerdict(pi) {
|
|
22
|
-
if (pi.aborted || pi.quietKilled || pi.timedOut)
|
|
23
|
+
if (pi.aborted || pi.quietKilled || pi.timedOut || pi.imageBlind)
|
|
23
24
|
return "inconclusive";
|
|
24
25
|
if (!pi.ok && (pi.transientRateLimit || pi.transientBackend))
|
|
25
26
|
return "provider-failure";
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { gitTry } from "../git/git-run.js";
|
|
2
2
|
import { resetWorktreeToMain } from "../git/worktree.js";
|
|
3
|
-
import { conflictedFiles } from "../
|
|
4
|
-
/** The
|
|
5
|
-
* (plans/revise-rejected.md
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
3
|
+
import { conflictedFiles } from "../merge/merge-git.js";
|
|
4
|
+
/** The persistence count at which one repeated objection makes a rejection final
|
|
5
|
+
* (plans/revise-rejected.md; the repeat-finality policy in BUGS.md 2026-10-14): each numbered
|
|
6
|
+
* reason records how many consecutive revisions it has survived — a new (untagged) objection is
|
|
7
|
+
* 0, a `(prior N)` restatement is 1 plus the prior round's count for that objection — and when
|
|
8
|
+
* any reason reaches this count it was raised and then survived two revisions, so the change has
|
|
9
|
+
* stopped converging and the rejection is final. A converging chain keeps revising while each
|
|
10
|
+
* round's objections differ. */
|
|
11
|
+
export const REVISION_FINAL_ON_REPEAT = 2;
|
|
12
|
+
/** The hard backstop on one change's revision chain (plans/revise-rejected.md; BUGS.md
|
|
13
|
+
* 2026-10-14): at this many total rejections the rejection is final even when the objections
|
|
14
|
+
* never repeat, so a non-converging author cannot revise forever. Replaces the old fixed
|
|
15
|
+
* `REVISION_LIMIT = 2`, which ended a converging chain at its third rejection whenever each
|
|
16
|
+
* round raised a new objection. */
|
|
17
|
+
export const REVISION_BACKSTOP = 6;
|
|
9
18
|
/** Re-apply a rejected change `sha` onto current main as uncommitted edits in `wt`
|
|
10
19
|
* (plans/revise-rejected.md): the author's next tick starts from the rejected diff instead of
|
|
11
20
|
* re-deriving it. `git cherry-pick --no-commit <merge-base>..<sha>` leaves the edits in the
|
|
12
|
-
* worktree without committing. On any git failure — most often a conflict with what main
|
|
21
|
+
* worktree without committing. On any git failure — most often a conflict with what main merged
|
|
13
22
|
* since the rejection, but also a missing object — the cherry-pick is aborted and the worktree
|
|
14
23
|
* reset to main, and false is returned so the caller falls back to a plain rejection note. */
|
|
15
24
|
export async function applyRevision(wt, mainBranch, sha) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Deterministic first pass over a conflicted rebase for the backlog markdown files
|
|
2
|
+
* (plans/parallel-work-instances.md, "Insert-only backlog conflicts", part 3/7).
|
|
3
|
+
*
|
|
4
|
+
* Every finished entry lands as the first one under `## Done` or `## Fixed` — at the top of
|
|
5
|
+
* PLANS-DONE.md or BUGS-FIXED.md once tumwater archives it (plans/backlog-archive.md) — so two
|
|
6
|
+
* merges off the same base insert different entries at the same location. Git's diff3 conflict
|
|
7
|
+
* for that shape has an empty base section and both sides non-empty; keeping both inserted sides
|
|
8
|
+
* adds no authored bytes, so no model run is needed.
|
|
9
|
+
*
|
|
10
|
+
* One more shape settles the same way: inside a live `## Done`/`## Fixed` section, the change
|
|
11
|
+
* added whole `### ` entries right above lines that main has since removed or rewritten — a
|
|
12
|
+
* change committed before its repository's history moved to the archive, whose entries sat
|
|
13
|
+
* right below the insertion point. The change's side is then its new entries followed by
|
|
14
|
+
* exactly the base lines, and keeping those entries on top of main's side again adds no
|
|
15
|
+
* authored bytes. Anything else — the same entry edited on both sides, a code file, a deleted
|
|
16
|
+
* side — goes to the pi resolver untouched. */
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { BACKLOG_FILES, FINISHED_ARCHIVES } from "../backlog/backlog-md.js";
|
|
20
|
+
import { writeTextAtomic } from "../files/files.js";
|
|
21
|
+
import { git } from "../git/git-run.js";
|
|
22
|
+
/** The lines the change inserted in `hunk`, when it is one of the two shapes this pass settles,
|
|
23
|
+
* or null. Insert-only: an empty base section and both sides carrying lines — all of the
|
|
24
|
+
* change's side. Entries above a rewritten history (see the module comment): the hunk sits in a
|
|
25
|
+
* finished section, and the change's side is whole `### ` entries followed by exactly the base
|
|
26
|
+
* lines — the part before them. */
|
|
27
|
+
function insertedLines(hunk, lines) {
|
|
28
|
+
if (hunk.base.length === 0)
|
|
29
|
+
return hunk.ours.length > 0 && hunk.theirs.length > 0 ? hunk.theirs : null;
|
|
30
|
+
if (!inFinishedSection(lines, hunk.start) || hunk.theirs.length <= hunk.base.length)
|
|
31
|
+
return null;
|
|
32
|
+
const cut = hunk.theirs.length - hunk.base.length;
|
|
33
|
+
if (hunk.theirs.slice(cut).some((line, i) => line !== hunk.base[i]))
|
|
34
|
+
return null;
|
|
35
|
+
const inserted = hunk.theirs.slice(0, cut);
|
|
36
|
+
const first = inserted.find((line) => line.trim() !== "");
|
|
37
|
+
return first?.startsWith("### ") ? inserted : null;
|
|
38
|
+
}
|
|
39
|
+
/** True when the nearest `## ` heading above line `at` is a finished section (`## Done` or
|
|
40
|
+
* `## Fixed`), with no conflict marker in between — an earlier hunk's sides leave the section
|
|
41
|
+
* unknown, so the answer is then no. */
|
|
42
|
+
function inFinishedSection(lines, at) {
|
|
43
|
+
for (let i = at - 1; i >= 0; i--) {
|
|
44
|
+
const line = lines[i] ?? "";
|
|
45
|
+
if (/^(<{7}|\|{7}|={7}|>{7})/.test(line))
|
|
46
|
+
return false;
|
|
47
|
+
if (line.startsWith("## "))
|
|
48
|
+
return FINISHED_SECTIONS.has(line.slice(3).trim());
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/** The finished sections' titles, from FINISHED_ARCHIVES. */
|
|
53
|
+
const FINISHED_SECTIONS = new Set(FINISHED_ARCHIVES.map((a) => a.section));
|
|
54
|
+
/** Rewrite every settleable conflict hunk in `text` and return the result, or null when any
|
|
55
|
+
* hunk is neither shape insertedLines accepts (or the markers are malformed) — the caller then
|
|
56
|
+
* leaves the file for the model resolver. Each hunk is replaced by the lines the change (theirs)
|
|
57
|
+
* inserted, a blank separator when neither side already supplies one, then main's (ours)
|
|
58
|
+
* lines, so the later merge stays first. */
|
|
59
|
+
export function resolveInsertOnlyText(text) {
|
|
60
|
+
const lines = text.split("\n");
|
|
61
|
+
const hunks = parseHunks(lines);
|
|
62
|
+
if (hunks === null)
|
|
63
|
+
return null;
|
|
64
|
+
if (hunks.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
const inserted = hunks.map((hunk) => insertedLines(hunk, lines));
|
|
67
|
+
if (inserted.some((ins) => ins === null))
|
|
68
|
+
return null;
|
|
69
|
+
// Replace from the end so earlier indices stay valid.
|
|
70
|
+
for (let h = hunks.length - 1; h >= 0; h--) {
|
|
71
|
+
const hunk = hunks[h];
|
|
72
|
+
const theirs = inserted[h];
|
|
73
|
+
const separated = hunk.ours.length === 0 || theirs[theirs.length - 1] === "" || hunk.ours[0] === "";
|
|
74
|
+
const replacement = separated ? [...theirs, ...hunk.ours] : [...theirs, "", ...hunk.ours];
|
|
75
|
+
lines.splice(hunk.start, hunk.end - hunk.start + 1, ...replacement);
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
/** Split `lines` into conflict hunks. Returns null when a marker sequence is malformed (an
|
|
80
|
+
* unterminated hunk, or a missing `|||||||`/`=======`), so the caller can bail out. */
|
|
81
|
+
function parseHunks(lines) {
|
|
82
|
+
const hunks = [];
|
|
83
|
+
let i = 0;
|
|
84
|
+
while (i < lines.length) {
|
|
85
|
+
if (!lines[i].startsWith("<<<<<<<")) {
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const start = i;
|
|
90
|
+
const ours = [];
|
|
91
|
+
const base = [];
|
|
92
|
+
const theirs = [];
|
|
93
|
+
// Consume each side up to its marker (the `<<<<<<<` line itself is skipped by starting
|
|
94
|
+
// at i + 1); a missing marker means the diff3 hunk is malformed and the parse aborts.
|
|
95
|
+
const afterOurs = collectUntilMarker(lines, i + 1, "|||||||", ours);
|
|
96
|
+
if (afterOurs === null)
|
|
97
|
+
return null; // no base marker
|
|
98
|
+
const afterBase = collectUntilMarker(lines, afterOurs, "=======", base);
|
|
99
|
+
if (afterBase === null)
|
|
100
|
+
return null; // no separator
|
|
101
|
+
const afterTheirs = collectUntilMarker(lines, afterBase, ">>>>>>>", theirs);
|
|
102
|
+
if (afterTheirs === null)
|
|
103
|
+
return null; // no end marker
|
|
104
|
+
hunks.push({ start, end: afterTheirs - 1, ours, base, theirs });
|
|
105
|
+
i = afterTheirs;
|
|
106
|
+
}
|
|
107
|
+
return hunks;
|
|
108
|
+
}
|
|
109
|
+
/** Append lines from `start` into `into` until one starts with `marker` (the marker itself is
|
|
110
|
+
* consumed, not kept), and return the index just past it. Returns null when no line from
|
|
111
|
+
* `start` on carries the marker — the malformed-hunk signal parseHunks turns into a bail-out.
|
|
112
|
+
* The one home of the drain-until-marker step shared by parseHunks's three sides, so the
|
|
113
|
+
* marker-consumption rule cannot drift between `|||||||`, `=======`, and `>>>>>>>`. */
|
|
114
|
+
function collectUntilMarker(lines, start, marker, into) {
|
|
115
|
+
let i = start;
|
|
116
|
+
while (i < lines.length && !lines[i].startsWith(marker))
|
|
117
|
+
into.push(lines[i++]);
|
|
118
|
+
return i >= lines.length ? null : i + 1;
|
|
119
|
+
}
|
|
120
|
+
/** Settle the insert-only conflicts among `files` in `wt` (plans/parallel-work-instances.md,
|
|
121
|
+
* part 3/7): every conflicted repo-root backlog file (BACKLOG_FILES: PLANS.md, BUGS.md,
|
|
122
|
+
* QUESTIONS.md and the two archive files) whose hunks all settle is rewritten without markers and
|
|
123
|
+
* staged. Returns the files still conflicted —
|
|
124
|
+
* every file that was not fully settled, including non-backlog files and files with any other
|
|
125
|
+
* hunk — so the caller runs the model resolver only over those. */
|
|
126
|
+
export async function resolveBacklogInsertConflicts(wt, files) {
|
|
127
|
+
const remaining = [];
|
|
128
|
+
for (const file of files) {
|
|
129
|
+
if (path.dirname(file) !== "." || !BACKLOG_FILES.has(file)) {
|
|
130
|
+
remaining.push(file);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const p = path.join(wt, file);
|
|
134
|
+
let text;
|
|
135
|
+
try {
|
|
136
|
+
text = fs.readFileSync(p, "utf8");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
remaining.push(file); // deleted side or unreadable path — leave it to the resolver
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const resolved = resolveInsertOnlyText(text);
|
|
143
|
+
if (resolved === null) {
|
|
144
|
+
remaining.push(file);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// Atomic (tmp + rename): a merge process killed mid-write (SIGKILL, the timeout
|
|
148
|
+
// group-kill) must leave either the conflicted file or the fully resolved one, never a
|
|
149
|
+
// truncated backlog — the CLI's backlog writers (backlog-write.ts, question-answer.ts)
|
|
150
|
+
// already write these unbounded-history files through this helper.
|
|
151
|
+
writeTextAtomic(p, resolved);
|
|
152
|
+
await git(wt, "add", "--", file);
|
|
153
|
+
}
|
|
154
|
+
return remaining;
|
|
155
|
+
}
|