@caupulican/pi-adaptative 0.81.38 → 0.81.39
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/CHANGELOG.md +79 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
- package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
- package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
- package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-paths.d.ts +49 -0
- package/dist/core/agent-paths.d.ts.map +1 -0
- package/dist/core/agent-paths.js +107 -0
- package/dist/core/agent-paths.js.map +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -3
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +234 -17
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +484 -62
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/contracts.d.ts +9 -0
- package/dist/core/autonomy/contracts.d.ts.map +1 -1
- package/dist/core/autonomy/contracts.js.map +1 -1
- package/dist/core/autonomy/lane-tracker.d.ts +10 -1
- package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tracker.js +5 -1
- package/dist/core/autonomy/lane-tracker.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +122 -6
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +446 -89
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-execution-controller.d.ts +4 -0
- package/dist/core/bash-execution-controller.d.ts.map +1 -1
- package/dist/core/bash-execution-controller.js +7 -1
- package/dist/core/bash-execution-controller.js.map +1 -1
- package/dist/core/compaction-support.d.ts +13 -3
- package/dist/core/compaction-support.d.ts.map +1 -1
- package/dist/core/compaction-support.js +43 -7
- package/dist/core/compaction-support.js.map +1 -1
- package/dist/core/context/context-audit.d.ts +33 -1
- package/dist/core/context/context-audit.d.ts.map +1 -1
- package/dist/core/context/context-audit.js +31 -5
- package/dist/core/context/context-audit.js.map +1 -1
- package/dist/core/context-gc.d.ts.map +1 -1
- package/dist/core/context-gc.js +16 -1
- package/dist/core/context-gc.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +26 -4
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +137 -14
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/cost-guard.d.ts +19 -3
- package/dist/core/cost-guard.d.ts.map +1 -1
- package/dist/core/cost-guard.js +18 -3
- package/dist/core/cost-guard.js.map +1 -1
- package/dist/core/delegation/session-worker-result.d.ts +34 -4
- package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
- package/dist/core/delegation/session-worker-result.js +64 -4
- package/dist/core/delegation/session-worker-result.js.map +1 -1
- package/dist/core/delegation/worker-result.d.ts +42 -1
- package/dist/core/delegation/worker-result.d.ts.map +1 -1
- package/dist/core/delegation/worker-result.js +68 -0
- package/dist/core/delegation/worker-result.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +20 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +51 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-loop-controller.d.ts +17 -1
- package/dist/core/goal-loop-controller.d.ts.map +1 -1
- package/dist/core/goal-loop-controller.js +79 -11
- package/dist/core/goal-loop-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
- package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-controller.js +77 -0
- package/dist/core/goals/goal-continuation-controller.js.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
- package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-defaults.js +42 -0
- package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
- package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
- package/dist/core/goals/goal-continuation-prompt.js +51 -10
- package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
- package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
- package/dist/core/goals/goal-runtime-snapshot.js +87 -4
- package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +89 -1
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +71 -5
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +56 -2
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +111 -5
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/goals/session-goal-state.d.ts +11 -2
- package/dist/core/goals/session-goal-state.d.ts.map +1 -1
- package/dist/core/goals/session-goal-state.js +32 -17
- package/dist/core/goals/session-goal-state.js.map +1 -1
- package/dist/core/keybindings.d.ts +10 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +10 -2
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/learning/observation-store.d.ts +12 -3
- package/dist/core/learning/observation-store.d.ts.map +1 -1
- package/dist/core/learning/observation-store.js +30 -15
- package/dist/core/learning/observation-store.js.map +1 -1
- package/dist/core/learning/skill-curator.d.ts +5 -1
- package/dist/core/learning/skill-curator.d.ts.map +1 -1
- package/dist/core/learning/skill-curator.js +21 -19
- package/dist/core/learning/skill-curator.js.map +1 -1
- package/dist/core/local-runtime-controller.d.ts +65 -3
- package/dist/core/local-runtime-controller.d.ts.map +1 -1
- package/dist/core/local-runtime-controller.js +186 -26
- package/dist/core/local-runtime-controller.js.map +1 -1
- package/dist/core/memory/providers/file-store.d.ts +1 -1
- package/dist/core/memory/providers/file-store.d.ts.map +1 -1
- package/dist/core/memory/providers/file-store.js +6 -6
- package/dist/core/memory/providers/file-store.js.map +1 -1
- package/dist/core/model-capability.d.ts +34 -0
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +42 -1
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-router/tool-escalation.d.ts +15 -0
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
- package/dist/core/model-router/tool-escalation.js +23 -1
- package/dist/core/model-router/tool-escalation.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +34 -7
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +95 -16
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/adaptation-store.d.ts +7 -0
- package/dist/core/models/adaptation-store.d.ts.map +1 -1
- package/dist/core/models/adaptation-store.js +47 -11
- package/dist/core/models/adaptation-store.js.map +1 -1
- package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
- package/dist/core/models/default-model-suggestions.js +17 -0
- package/dist/core/models/default-model-suggestions.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts +3 -0
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +11 -2
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/llamacpp-runtime.d.ts +180 -0
- package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
- package/dist/core/models/llamacpp-runtime.js +475 -0
- package/dist/core/models/llamacpp-runtime.js.map +1 -0
- package/dist/core/models/local-registration.d.ts +40 -0
- package/dist/core/models/local-registration.d.ts.map +1 -1
- package/dist/core/models/local-registration.js +94 -5
- package/dist/core/models/local-registration.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +6 -5
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/models/model-ref.d.ts +7 -0
- package/dist/core/models/model-ref.d.ts.map +1 -1
- package/dist/core/models/model-ref.js +26 -0
- package/dist/core/models/model-ref.js.map +1 -1
- package/dist/core/models/needle-runtime.d.ts +257 -0
- package/dist/core/models/needle-runtime.d.ts.map +1 -0
- package/dist/core/models/needle-runtime.js +519 -0
- package/dist/core/models/needle-runtime.js.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
- package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
- package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +11 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/process-matrix/codes.d.ts +72 -0
- package/dist/core/process-matrix/codes.d.ts.map +1 -0
- package/dist/core/process-matrix/codes.js +15 -0
- package/dist/core/process-matrix/codes.js.map +1 -0
- package/dist/core/process-matrix/runtime.d.ts +63 -0
- package/dist/core/process-matrix/runtime.d.ts.map +1 -0
- package/dist/core/process-matrix/runtime.js +310 -0
- package/dist/core/process-matrix/runtime.js.map +1 -0
- package/dist/core/process-matrix/store.d.ts +22 -0
- package/dist/core/process-matrix/store.d.ts.map +1 -0
- package/dist/core/process-matrix/store.js +80 -0
- package/dist/core/process-matrix/store.js.map +1 -0
- package/dist/core/process-matrix/supervisor.d.ts +72 -0
- package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
- package/dist/core/process-matrix/supervisor.js +130 -0
- package/dist/core/process-matrix/supervisor.js.map +1 -0
- package/dist/core/profile-registry.d.ts +7 -0
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +20 -0
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/python-runtime.d.ts.map +1 -1
- package/dist/core/python-runtime.js +3 -3
- package/dist/core/python-runtime.js.map +1 -1
- package/dist/core/reflection-controller.d.ts +29 -5
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +215 -126
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +36 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +44 -0
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +58 -5
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +209 -25
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/scout-controller.d.ts +6 -0
- package/dist/core/scout-controller.d.ts.map +1 -1
- package/dist/core/scout-controller.js +66 -52
- package/dist/core/scout-controller.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-role.d.ts +31 -0
- package/dist/core/session-role.d.ts.map +1 -0
- package/dist/core/session-role.js +52 -0
- package/dist/core/session-role.js.map +1 -0
- package/dist/core/settings-manager.d.ts +42 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +94 -60
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +12 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +6 -0
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/system-prompt.d.ts +14 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +20 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session-task-state.d.ts +11 -2
- package/dist/core/tasks/session-task-state.d.ts.map +1 -1
- package/dist/core/tasks/session-task-state.js +27 -11
- package/dist/core/tasks/session-task-state.js.map +1 -1
- package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
- package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
- package/dist/core/tasks/task-contract-monitor.js +56 -0
- package/dist/core/tasks/task-contract-monitor.js.map +1 -0
- package/dist/core/tasks/task-state.d.ts +8 -0
- package/dist/core/tasks/task-state.d.ts.map +1 -1
- package/dist/core/tasks/task-state.js +28 -1
- package/dist/core/tasks/task-state.js.map +1 -1
- package/dist/core/tool-gate-controller.d.ts.map +1 -1
- package/dist/core/tool-gate-controller.js +5 -0
- package/dist/core/tool-gate-controller.js.map +1 -1
- package/dist/core/tool-recovery-log-records.d.ts +7 -0
- package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
- package/dist/core/tool-recovery-log-records.js +14 -6
- package/dist/core/tool-recovery-log-records.js.map +1 -1
- package/dist/core/tool-selection/promotion.d.ts +54 -0
- package/dist/core/tool-selection/promotion.d.ts.map +1 -0
- package/dist/core/tool-selection/promotion.js +81 -0
- package/dist/core/tool-selection/promotion.js.map +1 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
- package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-performance-store.js +87 -3
- package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
- package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
- package/dist/core/tool-selection/tool-selection-controller.js +96 -0
- package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
- package/dist/core/tools/bash.d.ts +18 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +95 -16
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate-status.d.ts +14 -0
- package/dist/core/tools/delegate-status.d.ts.map +1 -1
- package/dist/core/tools/delegate-status.js +82 -7
- package/dist/core/tools/delegate-status.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +25 -8
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/goal.d.ts +94 -3
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +165 -15
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/model-fitness.d.ts +7 -0
- package/dist/core/tools/model-fitness.d.ts.map +1 -1
- package/dist/core/tools/model-fitness.js +2 -2
- package/dist/core/tools/model-fitness.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts +6 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +23 -2
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/tools/shell-session.d.ts +89 -0
- package/dist/core/tools/shell-session.d.ts.map +1 -0
- package/dist/core/tools/shell-session.js +432 -0
- package/dist/core/tools/shell-session.js.map +1 -0
- package/dist/core/tools/task-steps.d.ts +4 -0
- package/dist/core/tools/task-steps.d.ts.map +1 -1
- package/dist/core/tools/task-steps.js +63 -8
- package/dist/core/tools/task-steps.js.map +1 -1
- package/dist/core/tools/tmux-dispatch.d.ts +86 -0
- package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
- package/dist/core/tools/tmux-dispatch.js +91 -0
- package/dist/core/tools/tmux-dispatch.js.map +1 -0
- package/dist/core/tools/windows-shell-engine.d.ts +42 -0
- package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-engine.js +153 -0
- package/dist/core/tools/windows-shell-engine.js.map +1 -0
- package/dist/core/tools/windows-shell-state.d.ts +40 -0
- package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
- package/dist/core/tools/windows-shell-state.js +59 -0
- package/dist/core/tools/windows-shell-state.js.map +1 -0
- package/dist/core/tools/worktree-sync.d.ts +24 -0
- package/dist/core/tools/worktree-sync.d.ts.map +1 -0
- package/dist/core/tools/worktree-sync.js +338 -0
- package/dist/core/tools/worktree-sync.js.map +1 -0
- package/dist/core/trust-manager.d.ts +4 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +20 -2
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/util/atomic-file.d.ts +55 -0
- package/dist/core/util/atomic-file.d.ts.map +1 -0
- package/dist/core/util/atomic-file.js +255 -0
- package/dist/core/util/atomic-file.js.map +1 -0
- package/dist/core/util/minimatch-cache.d.ts +33 -0
- package/dist/core/util/minimatch-cache.d.ts.map +1 -0
- package/dist/core/util/minimatch-cache.js +0 -0
- package/dist/core/util/minimatch-cache.js.map +1 -0
- package/dist/core/worktree-sync/codes.d.ts +227 -0
- package/dist/core/worktree-sync/codes.d.ts.map +1 -0
- package/dist/core/worktree-sync/codes.js +14 -0
- package/dist/core/worktree-sync/codes.js.map +1 -0
- package/dist/core/worktree-sync/git-engine.d.ts +156 -0
- package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
- package/dist/core/worktree-sync/git-engine.js +1191 -0
- package/dist/core/worktree-sync/git-engine.js.map +1 -0
- package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
- package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
- package/dist/core/worktree-sync/lane-gate.js +360 -0
- package/dist/core/worktree-sync/lane-gate.js.map +1 -0
- package/dist/core/worktree-sync/runtime.d.ts +47 -0
- package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
- package/dist/core/worktree-sync/runtime.js +96 -0
- package/dist/core/worktree-sync/runtime.js.map +1 -0
- package/dist/core/worktree-sync/store.d.ts +69 -0
- package/dist/core/worktree-sync/store.d.ts.map +1 -0
- package/dist/core/worktree-sync/store.js +247 -0
- package/dist/core/worktree-sync/store.js.map +1 -0
- package/dist/core/worktree-sync/watcher.d.ts +29 -0
- package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
- package/dist/core/worktree-sync/watcher.js +93 -0
- package/dist/core/worktree-sync/watcher.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +94 -0
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +9 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +38 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +50 -8
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts +43 -0
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +290 -3
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +24 -4
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/utils/fs-watch.d.ts +11 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +20 -2
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/highlight-js-languages.d.ts +4 -0
- package/dist/utils/highlight-js-languages.d.ts.map +1 -0
- package/dist/utils/highlight-js-languages.js +573 -0
- package/dist/utils/highlight-js-languages.js.map +1 -0
- package/dist/utils/shell.d.ts +7 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +21 -6
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +53 -5
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +112 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/process-matrix.md +120 -0
- package/docs/settings.md +3 -0
- package/docs/tmux-agent-manager.md +85 -2
- package/docs/windows.md +52 -3
- package/docs/work-directory.md +29 -0
- package/docs/worktree-sync.md +250 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +10 -4
- package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
- package/docs/integration-sweep-resume-2026-07-09.md +0 -407
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codes.d.ts","sourceRoot":"","sources":["../../../src/core/worktree-sync/codes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;gGACgG;AAChG,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,WAAW,CAAC;AAE3C,MAAM,MAAM,gBAAgB,GACzB,IAAI,GACJ,UAAU,GACV,gBAAgB,GAChB,2BAA2B,GAC3B,aAAa,GACb,kBAAkB,GAClB,aAAa,GACb,gBAAgB,GAChB,mBAAmB,GACnB,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,uBAAuB,GACvB,0BAA0B,GAC1B,oBAAoB,GACpB,aAAa,GACb,0BAA0B,GAC1B,WAAW,GACX,WAAW,GACX,eAAe,GACf,iBAAiB,GACjB,uBAAuB,GACvB,oBAAoB,GACpB,kBAAkB,GAClB,UAAU,GACV,YAAY,GACZ,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,qBAAqB,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB;IACjF,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC9B;AAED;;yFAEyF;AACzF,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAE9F;gGACgG;AAChG,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;yFACyF;AACzF,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;CAC/B;AAED;mCACmC;AACnC,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC/C,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,KAAK,EAAE,OAAO,CAAC;IACf,wEAAwE;IACxE,KAAK,EAAE,OAAO,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,0BAA0B,GACnC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAE5E,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,iGAAiG;IACjG,IAAI,EAAE,eAAe,GAAG,YAAY,GAAG,eAAe,GAAG,iBAAiB,GAAG,SAAS,CAAC;IACvF,2GAA2G;IAC3G,gBAAgB,EAAE,OAAO,CAAC;CAC1B;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAChC,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,oBAAoB,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GACzB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACtC,mBAAmB,CACjB,gBAAgB,GAChB,2BAA2B,GAC3B,kBAAkB,GAClB,aAAa,GACb,mBAAmB,GACnB,WAAW,CACZ,CAAC;AAEL,MAAM,MAAM,iBAAiB,GAC1B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrC,mBAAmB,CACjB,gBAAgB,GAChB,2BAA2B,GAC3B,gBAAgB,GAChB,oBAAoB,GACpB,qBAAqB,GACrB,WAAW,CACZ,CAAC;AAEL,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IACvB,KAAK,EAAE,gBAAgB,GAAG,YAAY,GAAG,eAAe,GAAG,cAAc,CAAC;CAC1E;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,iBAAiB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,MAAM,eAAe,GACxB,gBAAgB,GAChB,mBAAmB,CAAC,gBAAgB,GAAG,2BAA2B,GAAG,WAAW,CAAC,CAAC;AAErF,MAAM,MAAM,cAAc,GACvB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACrF;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GACvE,mBAAmB,CACjB,gBAAgB,GAChB,2BAA2B,GAC3B,gBAAgB,GAChB,kBAAkB,GAClB,YAAY,GACZ,oBAAoB,GACpB,WAAW,CACZ,CAAC;AAEL,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GACvE,mBAAmB,CACjB,gBAAgB,GAChB,2BAA2B,GAC3B,gBAAgB,GAChB,kBAAkB,GAClB,uBAAuB,GACvB,0BAA0B,GAC1B,WAAW,CACZ,CAAC;AAEL,MAAM,MAAM,eAAe,GACxB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/B,mBAAmB,CACnB,gBAAgB,GAAG,2BAA2B,GAAG,gBAAgB,GAAG,uBAAuB,GAAG,WAAW,CACxG,CAAC;AAEL,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAA;CAAE,GACvF,mBAAmB,CACjB,gBAAgB,GAChB,2BAA2B,GAC3B,gBAAgB,GAChB,kBAAkB,GAClB,oBAAoB,GACpB,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,WAAW,GACX,oBAAoB,GACpB,aAAa,GACb,0BAA0B,GAC1B,WAAW,GACX,WAAW,GACX,iBAAiB,GACjB,qBAAqB,GACrB,WAAW,CACZ,CAAC;AAEL,kGAAkG;AAClG,MAAM,WAAW,eAAgB,SAAQ,SAAS;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,kFAAkF;IAClF,YAAY,EAAE,OAAO,CAAC;IACtB;0EACsE;IACtE,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED;qEACqE;AACrE,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IACvC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9E,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,gBAAgB,GACzB,UAAU,GACV,mBAAmB,CAAC,gBAAgB,GAAG,2BAA2B,GAAG,WAAW,CAAC,CAAC","sourcesContent":["/**\n * Worktree-sync SSOT for tagged result codes and shared record types.\n *\n * Every worktree-sync operation reports its outcome through one of these string-literal codes in\n * a typed result -- callers branch on `code`, NEVER on message/stderr substrings (the\n * `classifyDispatchError` substring compromise is deliberately not repeated here). Git stderr is\n * carried alongside as evidence for humans, not as a branch condition.\n */\n\n/** Cap on `WorktreeSyncEpoch.changedPaths`; beyond it the list is truncated and overlap checks\n * treat the land as conservatively overlapping EVERYTHING (deterministic, never optimistic). */\nexport const EPOCH_CHANGED_PATHS_CAP = 500;\n\n/** Branch namespace for lane branches: `pi/wt/<laneKey>`. */\nexport const LANE_BRANCH_PREFIX = \"pi/wt/\";\n\nexport type WorktreeSyncCode =\n\t| \"ok\"\n\t| \"disabled\"\n\t| \"not_a_git_repo\"\n\t| \"default_branch_unresolved\"\n\t| \"hub_missing\"\n\t| \"invalid_lane_key\"\n\t| \"lane_exists\"\n\t| \"lane_not_found\"\n\t| \"max_lanes_reached\"\n\t| \"lane_dirty\"\n\t| \"hub_dirty\"\n\t| \"stale_lane\"\n\t| \"sync_required\"\n\t| \"sync_clean\"\n\t| \"sync_conflicts\"\n\t| \"rebase_in_progress\"\n\t| \"no_rebase_in_progress\"\n\t| \"conflict_markers_present\"\n\t| \"gate_command_unset\"\n\t| \"gate_failed\"\n\t| \"lane_changed_during_gate\"\n\t| \"ff_failed\"\n\t| \"lock_busy\"\n\t| \"lock_takeover\"\n\t| \"nothing_to_land\"\n\t| \"main_mutation_refused\"\n\t| \"lane_unlanded_work\"\n\t| \"worktree_missing\"\n\t| \"released\"\n\t| \"reconciled\"\n\t| \"git_error\"\n\t| \"role_forbidden\"\n\t| \"path_outside_lane\"\n\t| \"lane_owner_conflict\";\n\n/**\n * A refusal/failure outcome. `message` is deterministic text assembled from facts (safe to show a\n * model verbatim); `gitStderr` is bounded raw evidence; `paths` names offending files where the\n * refusal is about specific files (dirty files, conflict markers); `holder` identifies the\n * integration-lock holder on `lock_busy`.\n */\nexport interface WorktreeSyncRefusal<C extends WorktreeSyncCode = WorktreeSyncCode> {\n\tcode: C;\n\tmessage: string;\n\tgitStderr?: string;\n\tpaths?: string[];\n\tholder?: IntegrationLockOwner;\n}\n\n/** Staleness-propagation policy (D9). `on_land_mandatory` is the default: every land marks every\n * other active lane sync_required. `overlap_mandatory` requires it only on changed-file overlap.\n * `land_time_only` keeps staleness advisory until the land gate (G3 always enforces). */\nexport type WorktreeSyncPolicy = \"on_land_mandatory\" | \"overlap_mandatory\" | \"land_time_only\";\n\n/** One lane registration record (`lanes/<laneKey>.json`). Identity/binding fields ONLY -- git\n * truths (freshness, dirtiness, ahead/behind) are always re-derived live, never stored here. */\nexport interface LaneRegistration {\n\tlaneKey: string;\n\tbranch: string;\n\tworktreePath: string;\n\tstatus: \"active\" | \"released\" | \"orphaned\";\n\tcreatedAt: string;\n\tupdatedAt: string;\n\tgoalId?: string;\n\trequirementId?: string;\n\t/** Host `LaneRecord.laneId` after dispatch correlation (tmux/in-process worker binding). */\n\tboundLaneId?: string;\n\townerPid?: number;\n\townerSessionId?: string;\n}\n\n/** `epoch.json` -- the integration epoch. `epoch` is monotonic messaging/audit sugar; correctness\n * always derives from git shas (`merge-base --is-ancestor`), never from this counter. */\nexport interface WorktreeSyncEpoch {\n\tepoch: number;\n\tmainSha: string;\n\tpreviousMainSha?: string;\n\tlandedLaneKey?: string;\n\tlandedAt?: string;\n\tchangedPaths: string[];\n\tchangedPathsTruncated: boolean;\n}\n\n/** Live, git-derived facts about one lane. Computed fresh per call -- there is no cached copy to\n * go stale, which is the point. */\nexport interface LaneFacts {\n\tlaneKey: string;\n\tbranch: string;\n\tworktreePath: string;\n\tregistrationStatus: LaneRegistration[\"status\"];\n\t/** Lane branch tip sha; undefined when the branch no longer exists. */\n\tbranchSha?: string;\n\t/** True iff current main is an ancestor of the lane tip (`merge-base --is-ancestor`). */\n\tfresh: boolean;\n\t/** True when `status --porcelain` in the lane worktree is non-empty. */\n\tdirty: boolean;\n\trebaseInProgress: boolean;\n\taheadOfMain: number;\n\tbehindMain: number;\n\tworktreePresent: boolean;\n\t/** Undefined when the lane has no recorded owner pid. */\n\townerAlive?: boolean;\n}\n\nexport interface IntegrationLockOwner {\n\tpid: number;\n\thostname: string;\n\t/** Random acquisition identity; PID/session are diagnostic only and never authorize release. */\n\ttoken: string;\n\tsessionId?: string;\n\tlaneKey?: string;\n\tacquiredAt: string;\n}\n\nexport type IntegrationLockAcquisition =\n\t| { acquired: true; takeover: boolean; token: string }\n\t| { acquired: false; holder?: IntegrationLockOwner; holderAlive: boolean };\n\n/** One conflicted file in a sync stopped on conflicts. */\nexport interface ConflictWorklistFile {\n\tpath: string;\n\t/** Both-modified is the common case; add/add and modify/delete surface as their index states. */\n\tkind: \"both_modified\" | \"both_added\" | \"deleted_by_us\" | \"deleted_by_them\" | \"unknown\";\n\t/** True when git rerere already replayed a recorded resolution for this file -- review, not re-resolve. */\n\tresolvedByRerere: boolean;\n}\n\n/** Structured conflict worklist returned when a sync/continue stops on conflicts. */\nexport interface ConflictWorklist {\n\t/** e.g. \"2/5\" -- rebase progress (stopped step / total steps). */\n\tstep: string;\n\tstoppedAtCommit?: { sha: string; subject: string };\n\tfiles: ConflictWorklistFile[];\n}\n\nexport type BindLaneWorkerResult =\n\t| { code: \"bound\"; laneKey: string; laneId: string }\n\t| { code: \"already_bound\"; laneKey: string; laneId: string }\n\t| { code: \"binding_conflict\"; laneKey: string; boundLaneId: string }\n\t| { code: \"lane_not_found\"; message: string }\n\t| { code: \"lane_not_active\"; message: string }\n\t| { code: \"store_error\"; message: string };\n\nexport type CreateLaneResult =\n\t| { code: \"ok\"; lane: LaneRegistration }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"invalid_lane_key\"\n\t\t\t| \"lane_exists\"\n\t\t\t| \"max_lanes_reached\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type ReleaseLaneResult =\n\t| { code: \"released\"; laneKey: string }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"lane_unlanded_work\"\n\t\t\t| \"lane_owner_conflict\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport interface LandingTransaction {\n\tlaneKey: string;\n\tpriorMainSha: string;\n\ttestedTipSha: string;\n\tchangedPaths: string[];\n\tchangedPathsTruncated: boolean;\n\tlockToken: string;\n\tgate: \"passed\" | \"off\";\n\tstage: \"ready_to_merge\" | \"main_moved\" | \"epoch_written\" | \"audit_logged\";\n}\n\nexport interface ReconcileSummary {\n\tcode: \"reconciled\";\n\torphanedLaneKeys: string[];\n\treRegisteredLaneKeys: string[];\n\townerClearedLaneKeys: string[];\n\tstaleLockReleased: boolean;\n}\n\nexport type ReconcileResult =\n\t| ReconcileSummary\n\t| WorktreeSyncRefusal<\"not_a_git_repo\" | \"default_branch_unresolved\" | \"git_error\">;\n\nexport type SyncLaneResult =\n\t| { code: \"sync_clean\"; laneKey: string; alreadyFresh: boolean; autoContinued: number }\n\t| { code: \"sync_conflicts\"; laneKey: string; worklist: ConflictWorklist }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"lane_dirty\"\n\t\t\t| \"rebase_in_progress\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type ContinueSyncResult =\n\t| { code: \"sync_clean\"; laneKey: string; autoContinued: number }\n\t| { code: \"sync_conflicts\"; laneKey: string; worklist: ConflictWorklist }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"no_rebase_in_progress\"\n\t\t\t| \"conflict_markers_present\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type AbortSyncResult =\n\t| { code: \"ok\"; laneKey: string }\n\t| WorktreeSyncRefusal<\n\t\t\t\"not_a_git_repo\" | \"default_branch_unresolved\" | \"lane_not_found\" | \"no_rebase_in_progress\" | \"git_error\"\n\t >;\n\nexport type LandResult =\n\t| { code: \"ok\"; laneKey: string; epoch: number; mainSha: string; gate: \"passed\" | \"off\" }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"rebase_in_progress\"\n\t\t\t| \"lane_dirty\"\n\t\t\t| \"stale_lane\"\n\t\t\t| \"hub_missing\"\n\t\t\t| \"hub_dirty\"\n\t\t\t| \"gate_command_unset\"\n\t\t\t| \"gate_failed\"\n\t\t\t| \"lane_changed_during_gate\"\n\t\t\t| \"ff_failed\"\n\t\t\t| \"lock_busy\"\n\t\t\t| \"nothing_to_land\"\n\t\t\t| \"lane_owner_conflict\"\n\t\t\t| \"git_error\"\n\t >;\n\n/** One lane's full status entry: live git facts plus registry identity plus policy derivation. */\nexport interface LaneStatusEntry extends LaneFacts {\n\tgoalId?: string;\n\trequirementId?: string;\n\tboundLaneId?: string;\n\t/** Not fresh: main moved past this lane's base. Derived, never stored. */\n\tstale: boolean;\n\t/** Stale AND the active policy makes syncing mandatory NOW (G8 gates on this). */\n\tsyncRequired: boolean;\n\t/** Lane-changed files intersecting the last land's changed files (conservatively the whole\n\t * lane-changed list when the epoch's changedPaths were truncated). */\n\toverlapWithLastLand: string[];\n}\n\n/** The deterministic full picture (`status` action): everything an agent would otherwise infer\n * probabilistically from porcelain output, computed mechanically. */\nexport interface SyncStatus {\n\tcode: \"ok\";\n\tmainBranch: string;\n\tmainSha: string;\n\tepoch: number;\n\thub?: { path: string; clean: boolean };\n\tlock: { held: boolean; holder?: IntegrationLockOwner; holderAlive?: boolean };\n\tlanes: LaneStatusEntry[];\n\t/** One deterministic advisory sentence assembled from the codes above; never model-generated. */\n\tadvice?: string;\n}\n\nexport type SyncStatusResult =\n\t| SyncStatus\n\t| WorktreeSyncRefusal<\"not_a_git_repo\" | \"default_branch_unresolved\" | \"git_error\">;\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree-sync SSOT for tagged result codes and shared record types.
|
|
3
|
+
*
|
|
4
|
+
* Every worktree-sync operation reports its outcome through one of these string-literal codes in
|
|
5
|
+
* a typed result -- callers branch on `code`, NEVER on message/stderr substrings (the
|
|
6
|
+
* `classifyDispatchError` substring compromise is deliberately not repeated here). Git stderr is
|
|
7
|
+
* carried alongside as evidence for humans, not as a branch condition.
|
|
8
|
+
*/
|
|
9
|
+
/** Cap on `WorktreeSyncEpoch.changedPaths`; beyond it the list is truncated and overlap checks
|
|
10
|
+
* treat the land as conservatively overlapping EVERYTHING (deterministic, never optimistic). */
|
|
11
|
+
export const EPOCH_CHANGED_PATHS_CAP = 500;
|
|
12
|
+
/** Branch namespace for lane branches: `pi/wt/<laneKey>`. */
|
|
13
|
+
export const LANE_BRANCH_PREFIX = "pi/wt/";
|
|
14
|
+
//# sourceMappingURL=codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codes.js","sourceRoot":"","sources":["../../../src/core/worktree-sync/codes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;gGACgG;AAChG,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAE3C,6DAA6D;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAQ,CAAC","sourcesContent":["/**\n * Worktree-sync SSOT for tagged result codes and shared record types.\n *\n * Every worktree-sync operation reports its outcome through one of these string-literal codes in\n * a typed result -- callers branch on `code`, NEVER on message/stderr substrings (the\n * `classifyDispatchError` substring compromise is deliberately not repeated here). Git stderr is\n * carried alongside as evidence for humans, not as a branch condition.\n */\n\n/** Cap on `WorktreeSyncEpoch.changedPaths`; beyond it the list is truncated and overlap checks\n * treat the land as conservatively overlapping EVERYTHING (deterministic, never optimistic). */\nexport const EPOCH_CHANGED_PATHS_CAP = 500;\n\n/** Branch namespace for lane branches: `pi/wt/<laneKey>`. */\nexport const LANE_BRANCH_PREFIX = \"pi/wt/\";\n\nexport type WorktreeSyncCode =\n\t| \"ok\"\n\t| \"disabled\"\n\t| \"not_a_git_repo\"\n\t| \"default_branch_unresolved\"\n\t| \"hub_missing\"\n\t| \"invalid_lane_key\"\n\t| \"lane_exists\"\n\t| \"lane_not_found\"\n\t| \"max_lanes_reached\"\n\t| \"lane_dirty\"\n\t| \"hub_dirty\"\n\t| \"stale_lane\"\n\t| \"sync_required\"\n\t| \"sync_clean\"\n\t| \"sync_conflicts\"\n\t| \"rebase_in_progress\"\n\t| \"no_rebase_in_progress\"\n\t| \"conflict_markers_present\"\n\t| \"gate_command_unset\"\n\t| \"gate_failed\"\n\t| \"lane_changed_during_gate\"\n\t| \"ff_failed\"\n\t| \"lock_busy\"\n\t| \"lock_takeover\"\n\t| \"nothing_to_land\"\n\t| \"main_mutation_refused\"\n\t| \"lane_unlanded_work\"\n\t| \"worktree_missing\"\n\t| \"released\"\n\t| \"reconciled\"\n\t| \"git_error\"\n\t| \"role_forbidden\"\n\t| \"path_outside_lane\"\n\t| \"lane_owner_conflict\";\n\n/**\n * A refusal/failure outcome. `message` is deterministic text assembled from facts (safe to show a\n * model verbatim); `gitStderr` is bounded raw evidence; `paths` names offending files where the\n * refusal is about specific files (dirty files, conflict markers); `holder` identifies the\n * integration-lock holder on `lock_busy`.\n */\nexport interface WorktreeSyncRefusal<C extends WorktreeSyncCode = WorktreeSyncCode> {\n\tcode: C;\n\tmessage: string;\n\tgitStderr?: string;\n\tpaths?: string[];\n\tholder?: IntegrationLockOwner;\n}\n\n/** Staleness-propagation policy (D9). `on_land_mandatory` is the default: every land marks every\n * other active lane sync_required. `overlap_mandatory` requires it only on changed-file overlap.\n * `land_time_only` keeps staleness advisory until the land gate (G3 always enforces). */\nexport type WorktreeSyncPolicy = \"on_land_mandatory\" | \"overlap_mandatory\" | \"land_time_only\";\n\n/** One lane registration record (`lanes/<laneKey>.json`). Identity/binding fields ONLY -- git\n * truths (freshness, dirtiness, ahead/behind) are always re-derived live, never stored here. */\nexport interface LaneRegistration {\n\tlaneKey: string;\n\tbranch: string;\n\tworktreePath: string;\n\tstatus: \"active\" | \"released\" | \"orphaned\";\n\tcreatedAt: string;\n\tupdatedAt: string;\n\tgoalId?: string;\n\trequirementId?: string;\n\t/** Host `LaneRecord.laneId` after dispatch correlation (tmux/in-process worker binding). */\n\tboundLaneId?: string;\n\townerPid?: number;\n\townerSessionId?: string;\n}\n\n/** `epoch.json` -- the integration epoch. `epoch` is monotonic messaging/audit sugar; correctness\n * always derives from git shas (`merge-base --is-ancestor`), never from this counter. */\nexport interface WorktreeSyncEpoch {\n\tepoch: number;\n\tmainSha: string;\n\tpreviousMainSha?: string;\n\tlandedLaneKey?: string;\n\tlandedAt?: string;\n\tchangedPaths: string[];\n\tchangedPathsTruncated: boolean;\n}\n\n/** Live, git-derived facts about one lane. Computed fresh per call -- there is no cached copy to\n * go stale, which is the point. */\nexport interface LaneFacts {\n\tlaneKey: string;\n\tbranch: string;\n\tworktreePath: string;\n\tregistrationStatus: LaneRegistration[\"status\"];\n\t/** Lane branch tip sha; undefined when the branch no longer exists. */\n\tbranchSha?: string;\n\t/** True iff current main is an ancestor of the lane tip (`merge-base --is-ancestor`). */\n\tfresh: boolean;\n\t/** True when `status --porcelain` in the lane worktree is non-empty. */\n\tdirty: boolean;\n\trebaseInProgress: boolean;\n\taheadOfMain: number;\n\tbehindMain: number;\n\tworktreePresent: boolean;\n\t/** Undefined when the lane has no recorded owner pid. */\n\townerAlive?: boolean;\n}\n\nexport interface IntegrationLockOwner {\n\tpid: number;\n\thostname: string;\n\t/** Random acquisition identity; PID/session are diagnostic only and never authorize release. */\n\ttoken: string;\n\tsessionId?: string;\n\tlaneKey?: string;\n\tacquiredAt: string;\n}\n\nexport type IntegrationLockAcquisition =\n\t| { acquired: true; takeover: boolean; token: string }\n\t| { acquired: false; holder?: IntegrationLockOwner; holderAlive: boolean };\n\n/** One conflicted file in a sync stopped on conflicts. */\nexport interface ConflictWorklistFile {\n\tpath: string;\n\t/** Both-modified is the common case; add/add and modify/delete surface as their index states. */\n\tkind: \"both_modified\" | \"both_added\" | \"deleted_by_us\" | \"deleted_by_them\" | \"unknown\";\n\t/** True when git rerere already replayed a recorded resolution for this file -- review, not re-resolve. */\n\tresolvedByRerere: boolean;\n}\n\n/** Structured conflict worklist returned when a sync/continue stops on conflicts. */\nexport interface ConflictWorklist {\n\t/** e.g. \"2/5\" -- rebase progress (stopped step / total steps). */\n\tstep: string;\n\tstoppedAtCommit?: { sha: string; subject: string };\n\tfiles: ConflictWorklistFile[];\n}\n\nexport type BindLaneWorkerResult =\n\t| { code: \"bound\"; laneKey: string; laneId: string }\n\t| { code: \"already_bound\"; laneKey: string; laneId: string }\n\t| { code: \"binding_conflict\"; laneKey: string; boundLaneId: string }\n\t| { code: \"lane_not_found\"; message: string }\n\t| { code: \"lane_not_active\"; message: string }\n\t| { code: \"store_error\"; message: string };\n\nexport type CreateLaneResult =\n\t| { code: \"ok\"; lane: LaneRegistration }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"invalid_lane_key\"\n\t\t\t| \"lane_exists\"\n\t\t\t| \"max_lanes_reached\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type ReleaseLaneResult =\n\t| { code: \"released\"; laneKey: string }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"lane_unlanded_work\"\n\t\t\t| \"lane_owner_conflict\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport interface LandingTransaction {\n\tlaneKey: string;\n\tpriorMainSha: string;\n\ttestedTipSha: string;\n\tchangedPaths: string[];\n\tchangedPathsTruncated: boolean;\n\tlockToken: string;\n\tgate: \"passed\" | \"off\";\n\tstage: \"ready_to_merge\" | \"main_moved\" | \"epoch_written\" | \"audit_logged\";\n}\n\nexport interface ReconcileSummary {\n\tcode: \"reconciled\";\n\torphanedLaneKeys: string[];\n\treRegisteredLaneKeys: string[];\n\townerClearedLaneKeys: string[];\n\tstaleLockReleased: boolean;\n}\n\nexport type ReconcileResult =\n\t| ReconcileSummary\n\t| WorktreeSyncRefusal<\"not_a_git_repo\" | \"default_branch_unresolved\" | \"git_error\">;\n\nexport type SyncLaneResult =\n\t| { code: \"sync_clean\"; laneKey: string; alreadyFresh: boolean; autoContinued: number }\n\t| { code: \"sync_conflicts\"; laneKey: string; worklist: ConflictWorklist }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"lane_dirty\"\n\t\t\t| \"rebase_in_progress\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type ContinueSyncResult =\n\t| { code: \"sync_clean\"; laneKey: string; autoContinued: number }\n\t| { code: \"sync_conflicts\"; laneKey: string; worklist: ConflictWorklist }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"no_rebase_in_progress\"\n\t\t\t| \"conflict_markers_present\"\n\t\t\t| \"git_error\"\n\t >;\n\nexport type AbortSyncResult =\n\t| { code: \"ok\"; laneKey: string }\n\t| WorktreeSyncRefusal<\n\t\t\t\"not_a_git_repo\" | \"default_branch_unresolved\" | \"lane_not_found\" | \"no_rebase_in_progress\" | \"git_error\"\n\t >;\n\nexport type LandResult =\n\t| { code: \"ok\"; laneKey: string; epoch: number; mainSha: string; gate: \"passed\" | \"off\" }\n\t| WorktreeSyncRefusal<\n\t\t\t| \"not_a_git_repo\"\n\t\t\t| \"default_branch_unresolved\"\n\t\t\t| \"lane_not_found\"\n\t\t\t| \"worktree_missing\"\n\t\t\t| \"rebase_in_progress\"\n\t\t\t| \"lane_dirty\"\n\t\t\t| \"stale_lane\"\n\t\t\t| \"hub_missing\"\n\t\t\t| \"hub_dirty\"\n\t\t\t| \"gate_command_unset\"\n\t\t\t| \"gate_failed\"\n\t\t\t| \"lane_changed_during_gate\"\n\t\t\t| \"ff_failed\"\n\t\t\t| \"lock_busy\"\n\t\t\t| \"nothing_to_land\"\n\t\t\t| \"lane_owner_conflict\"\n\t\t\t| \"git_error\"\n\t >;\n\n/** One lane's full status entry: live git facts plus registry identity plus policy derivation. */\nexport interface LaneStatusEntry extends LaneFacts {\n\tgoalId?: string;\n\trequirementId?: string;\n\tboundLaneId?: string;\n\t/** Not fresh: main moved past this lane's base. Derived, never stored. */\n\tstale: boolean;\n\t/** Stale AND the active policy makes syncing mandatory NOW (G8 gates on this). */\n\tsyncRequired: boolean;\n\t/** Lane-changed files intersecting the last land's changed files (conservatively the whole\n\t * lane-changed list when the epoch's changedPaths were truncated). */\n\toverlapWithLastLand: string[];\n}\n\n/** The deterministic full picture (`status` action): everything an agent would otherwise infer\n * probabilistically from porcelain output, computed mechanically. */\nexport interface SyncStatus {\n\tcode: \"ok\";\n\tmainBranch: string;\n\tmainSha: string;\n\tepoch: number;\n\thub?: { path: string; clean: boolean };\n\tlock: { held: boolean; holder?: IntegrationLockOwner; holderAlive?: boolean };\n\tlanes: LaneStatusEntry[];\n\t/** One deterministic advisory sentence assembled from the codes above; never model-generated. */\n\tadvice?: string;\n}\n\nexport type SyncStatusResult =\n\t| SyncStatus\n\t| WorktreeSyncRefusal<\"not_a_git_repo\" | \"default_branch_unresolved\" | \"git_error\">;\n"]}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree-sync git engine: every git-touching operation of the subsystem, built on one injected
|
|
3
|
+
* `exec` seam (the `improvement-loop.ts` pattern) so each function is unit-testable with a
|
|
4
|
+
* scripted exec fake and integration-testable against real git.
|
|
5
|
+
*
|
|
6
|
+
* Doctrine (see docs/worktree-sync.md):
|
|
7
|
+
* - Git is the single source of truth. Freshness ("is current main an ancestor of the lane
|
|
8
|
+
* tip?"), dirtiness, ahead/behind, and rebase-in-progress are RE-DERIVED per call -- the JSON
|
|
9
|
+
* store (`store.ts`) contributes identity/binding only and is rebuildable via {@link reconcile}.
|
|
10
|
+
* - Policy refusals are RETURNED as tagged codes (`codes.ts`), never thrown; git's own stderr
|
|
11
|
+
* rides along as evidence, never as a branch condition.
|
|
12
|
+
* - Nothing here deletes user work silently: releasing a lane with unlanded commits or dirty
|
|
13
|
+
* files requires the explicit discard confirmation, and reconcile only marks orphans.
|
|
14
|
+
*/
|
|
15
|
+
import { type ExecResult } from "../exec.ts";
|
|
16
|
+
import { type AbortSyncResult, type BindLaneWorkerResult, type ContinueSyncResult, type CreateLaneResult, type LandResult, type LaneFacts, type LaneRegistration, type ReconcileResult, type ReleaseLaneResult, type SyncLaneResult, type SyncStatusResult, type WorktreeSyncPolicy, type WorktreeSyncRefusal } from "./codes.ts";
|
|
17
|
+
import { type SyncStorePaths } from "./store.ts";
|
|
18
|
+
export type WorktreeSyncExec = (command: string, args: string[], options: {
|
|
19
|
+
cwd: string;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
maxBuffer?: number;
|
|
23
|
+
env?: NodeJS.ProcessEnv;
|
|
24
|
+
}) => Promise<ExecResult>;
|
|
25
|
+
export interface WorktreeSyncEngineOptions {
|
|
26
|
+
/** Overrides default-branch resolution; unset resolves local `main`, then `master` (D13). */
|
|
27
|
+
mainBranchOverride?: string;
|
|
28
|
+
/** Active-lane ceiling; `create_lane` refuses beyond it. */
|
|
29
|
+
maxLanes: number;
|
|
30
|
+
}
|
|
31
|
+
export interface WorktreeSyncEngineDeps {
|
|
32
|
+
exec: WorktreeSyncExec;
|
|
33
|
+
/** Session cwd -- any directory inside the repo (hub checkout or a lane worktree). */
|
|
34
|
+
cwd: string;
|
|
35
|
+
/** Base for lane checkouts (`agent-paths.worktreesDir(agentDir)`); engine appends `<repo-slug>/<laneKey>`. */
|
|
36
|
+
worktreesBaseDir: string;
|
|
37
|
+
options: WorktreeSyncEngineOptions;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
now?: () => string;
|
|
40
|
+
pid?: number;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
/** Injectable fs probe for deterministic tests. Default: `existsSync`. */
|
|
43
|
+
fileExists?: (path: string) => boolean;
|
|
44
|
+
/** Injectable file reader (conflict-marker scan, rebase progress). Default: `readFileSync`,
|
|
45
|
+
* returning undefined on any read error. */
|
|
46
|
+
readFile?: (path: string) => string | undefined;
|
|
47
|
+
/** Injectable same-host pid liveness for deterministic tests. */
|
|
48
|
+
isPidAlive?: (pid: number) => boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface RepoContext {
|
|
51
|
+
topLevel: string;
|
|
52
|
+
gitCommonDir: string;
|
|
53
|
+
mainBranch: string;
|
|
54
|
+
mainSha: string;
|
|
55
|
+
paths: SyncStorePaths;
|
|
56
|
+
slug: string;
|
|
57
|
+
/** Checkout where the main branch is checked out (the hub); undefined when main is not checked out. */
|
|
58
|
+
hubPath?: string;
|
|
59
|
+
}
|
|
60
|
+
/** Production exec: `exec.ts`'s bounded `execCommand` (rolling-tail output, timeout, abort). */
|
|
61
|
+
export declare function createDefaultWorktreeSyncExec(): WorktreeSyncExec;
|
|
62
|
+
export declare function laneBranch(laneKey: string): string;
|
|
63
|
+
export interface WorktreeListEntry {
|
|
64
|
+
path: string;
|
|
65
|
+
headSha?: string;
|
|
66
|
+
/** Full ref (`refs/heads/x`) when a branch is checked out; undefined for detached/bare. */
|
|
67
|
+
branchRef?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Parse `git worktree list --porcelain` blocks. */
|
|
70
|
+
export declare function parseWorktreeList(output: string): WorktreeListEntry[];
|
|
71
|
+
export declare function resolveRepoContext(deps: WorktreeSyncEngineDeps): Promise<RepoContext | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "git_error">>;
|
|
72
|
+
/**
|
|
73
|
+
* Live git facts for one lane. `fresh` is THE freshness definition of the whole subsystem
|
|
74
|
+
* (G3/G9): current main is an ancestor of the lane tip. Everything is re-derived; nothing is
|
|
75
|
+
* read back from a cache that could disagree with git.
|
|
76
|
+
*/
|
|
77
|
+
export declare function deriveLaneFacts(deps: WorktreeSyncEngineDeps, ctx: RepoContext, lane: LaneRegistration): Promise<LaneFacts>;
|
|
78
|
+
/** One-time repo git config (D7): shared rerere + zdiff3 conflict hunks -- the deterministic
|
|
79
|
+
* conflict-resolution substrate for every lane (the rr-cache lives in the common dir, so one
|
|
80
|
+
* resolution replays identically in all worktrees). Idempotent; only actual changes are audited. */
|
|
81
|
+
export declare function ensureRepoGitConfig(deps: WorktreeSyncEngineDeps, ctx: RepoContext): Promise<void>;
|
|
82
|
+
export interface CreateLaneArgs {
|
|
83
|
+
laneKey?: string;
|
|
84
|
+
goalId?: string;
|
|
85
|
+
requirementId?: string;
|
|
86
|
+
}
|
|
87
|
+
export declare function createLane(deps: WorktreeSyncEngineDeps, args?: CreateLaneArgs): Promise<CreateLaneResult>;
|
|
88
|
+
export interface BindLaneWorkerArgs {
|
|
89
|
+
laneKey: string;
|
|
90
|
+
laneId: string;
|
|
91
|
+
}
|
|
92
|
+
export declare function bindLaneWorker(deps: WorktreeSyncEngineDeps, args: BindLaneWorkerArgs): Promise<BindLaneWorkerResult>;
|
|
93
|
+
export interface ReleaseLaneArgs {
|
|
94
|
+
laneKey: string;
|
|
95
|
+
/** Required as exactly "yes-discard-lane" to release a lane with unlanded commits or dirty files (G11). */
|
|
96
|
+
confirm?: string;
|
|
97
|
+
}
|
|
98
|
+
export declare function releaseLane(deps: WorktreeSyncEngineDeps, args: ReleaseLaneArgs): Promise<ReleaseLaneResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Startup/repair pass (mirrors the tmux session reconcile): diff the registry against git
|
|
101
|
+
* reality, mark orphans (never delete -- G11), re-register lane worktrees git still knows that
|
|
102
|
+
* the registry lost (a deleted store is fully rebuildable), self-heal orphan registrations whose
|
|
103
|
+
* worktree+branch actually exist, clear dead owners, and release a provably-stale lock.
|
|
104
|
+
*/
|
|
105
|
+
export declare function reconcile(deps: WorktreeSyncEngineDeps): Promise<ReconcileResult>;
|
|
106
|
+
export interface SyncLaneArgs {
|
|
107
|
+
laneKey: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Rebase current main into the lane branch, locally (the awareness half of "perfect sync"; the
|
|
111
|
+
* land gate is the enforcement half). Pins the main sha it derived so a concurrent land cannot
|
|
112
|
+
* change this sync's meaning mid-flight (D-pinned-sha). Conflicts leave the rebase in progress
|
|
113
|
+
* and return a structured worklist; `continueSync` verifies and drives on.
|
|
114
|
+
*/
|
|
115
|
+
export declare function syncLane(deps: WorktreeSyncEngineDeps, args: SyncLaneArgs): Promise<SyncLaneResult>;
|
|
116
|
+
export interface ContinueSyncArgs {
|
|
117
|
+
laneKey: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* G9: verify the agent's conflict resolution mechanically (zero conflict markers by byte-scan --
|
|
121
|
+
* the agent never self-certifies), stage it, and drive the rebase on. More conflicts return the
|
|
122
|
+
* next worklist; completion returns sync_clean.
|
|
123
|
+
*/
|
|
124
|
+
export declare function continueSync(deps: WorktreeSyncEngineDeps, args: ContinueSyncArgs): Promise<ContinueSyncResult>;
|
|
125
|
+
export interface AbortSyncArgs {
|
|
126
|
+
laneKey: string;
|
|
127
|
+
}
|
|
128
|
+
/** Abort an in-progress sync rebase: the lane returns to its pre-sync tip -- still stale, and
|
|
129
|
+
* honestly reported as such by status. */
|
|
130
|
+
export declare function abortSync(deps: WorktreeSyncEngineDeps, args: AbortSyncArgs): Promise<AbortSyncResult>;
|
|
131
|
+
export interface LandLaneArgs {
|
|
132
|
+
laneKey: string;
|
|
133
|
+
/** "on" runs gateCommand (G4); "off" is the owner-level opt-out, recorded per land event. */
|
|
134
|
+
gate: "on" | "off";
|
|
135
|
+
gateCommand?: string;
|
|
136
|
+
gateTimeoutMs?: number;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The land gate -- the ONLY door to main (G1-G7). Serialized under the integration lock; every
|
|
140
|
+
* precondition is re-derived INSIDE the lock (the compare-and-swap that makes the whole system
|
|
141
|
+
* race-free: even if every notification failed, a stale lane cannot land). Main only ever moves
|
|
142
|
+
* by ff-only merge of an already-rebased lane branch; a successful land bumps the epoch and
|
|
143
|
+
* broadcasts in the same critical section.
|
|
144
|
+
*/
|
|
145
|
+
export declare function landLane(deps: WorktreeSyncEngineDeps, args: LandLaneArgs): Promise<LandResult>;
|
|
146
|
+
export interface SyncStatusArgs {
|
|
147
|
+
policy: WorktreeSyncPolicy;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The deterministic full picture (the tool's `status` action): epoch, hub, lock, and per-lane
|
|
151
|
+
* live facts with the policy derivation (stale / syncRequired / overlap). The `advice` line is
|
|
152
|
+
* assembled from codes -- never model-generated -- so every agent reads the same situation the
|
|
153
|
+
* same way.
|
|
154
|
+
*/
|
|
155
|
+
export declare function buildSyncStatus(deps: WorktreeSyncEngineDeps, args: SyncStatusArgs): Promise<SyncStatusResult>;
|
|
156
|
+
//# sourceMappingURL=git-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-engine.d.ts","sourceRoot":"","sources":["../../../src/core/worktree-sync/git-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,YAAY,CAAC;AAE1D,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAGzB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EAIrB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAYN,KAAK,cAAc,EAKnB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,gBAAgB,GAAG,CAC9B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,KACzG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB,MAAM,WAAW,yBAAyB;IACzC,6FAA6F;IAC7F,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,gBAAgB,CAAC;IACvB,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,8GAA8G;IAC9G,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC;gDAC4C;IAC5C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAChD,iEAAiE;IACjE,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,gGAAgG;AAChG,wBAAgB,6BAA6B,IAAI,gBAAgB,CAQhE;AAkBD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,oDAAoD;AACpD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAkBrE;AASD,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,sBAAsB,GAC1B,OAAO,CAAC,WAAW,GAAG,mBAAmB,CAAC,gBAAgB,GAAG,2BAA2B,GAAG,WAAW,CAAC,CAAC,CAwD1G;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,sBAAsB,EAC5B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,gBAAgB,GACpB,OAAO,CAAC,SAAS,CAAC,CAsDpB;AAED;;oGAEoG;AACpG,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,sBAAsB,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBvG;AAWD,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsEnH;AAmBD,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,cAAc,CACnC,IAAI,EAAE,sBAAsB,EAC5B,IAAI,EAAE,kBAAkB,GACtB,OAAO,CAAC,oBAAoB,CAAC,CA0B/B;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,2GAA2G;IAC3G,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0DjH;AA2ED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkHtF;AA8ND,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAqDxG;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiDpH;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;0CAC0C;AAC1C,wBAAsB,SAAS,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB3G;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAuOpG;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,kBAAkB,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6EnH","sourcesContent":["/**\n * Worktree-sync git engine: every git-touching operation of the subsystem, built on one injected\n * `exec` seam (the `improvement-loop.ts` pattern) so each function is unit-testable with a\n * scripted exec fake and integration-testable against real git.\n *\n * Doctrine (see docs/worktree-sync.md):\n * - Git is the single source of truth. Freshness (\"is current main an ancestor of the lane\n * tip?\"), dirtiness, ahead/behind, and rebase-in-progress are RE-DERIVED per call -- the JSON\n * store (`store.ts`) contributes identity/binding only and is rebuildable via {@link reconcile}.\n * - Policy refusals are RETURNED as tagged codes (`codes.ts`), never thrown; git's own stderr\n * rides along as evidence, never as a branch condition.\n * - Nothing here deletes user work silently: releasing a lane with unlanded commits or dirty\n * files requires the explicit discard confirmation, and reconcile only marks orphans.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { hostname as osHostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type ExecResult, execCommand } from \"../exec.ts\";\nimport { withFileLock } from \"../util/atomic-file.ts\";\nimport {\n\ttype AbortSyncResult,\n\ttype BindLaneWorkerResult,\n\ttype ConflictWorklist,\n\ttype ConflictWorklistFile,\n\ttype ContinueSyncResult,\n\ttype CreateLaneResult,\n\tEPOCH_CHANGED_PATHS_CAP,\n\tLANE_BRANCH_PREFIX,\n\ttype LandingTransaction,\n\ttype LandResult,\n\ttype LaneFacts,\n\ttype LaneRegistration,\n\ttype LaneStatusEntry,\n\ttype ReconcileResult,\n\ttype ReleaseLaneResult,\n\ttype SyncLaneResult,\n\ttype SyncStatusResult,\n\ttype WorktreeSyncPolicy,\n\ttype WorktreeSyncRefusal,\n} from \"./codes.ts\";\nimport {\n\tacquireIntegrationLock,\n\tappendAuditEvent,\n\tclearLandingTransaction,\n\tdefaultIsPidAlive,\n\tlistLanes,\n\treadEpoch,\n\treadLandingTransaction,\n\treadLane,\n\treadLockHolder,\n\treleaseIntegrationLock,\n\trepoSlug,\n\ttype SyncStorePaths,\n\tsyncStorePaths,\n\twriteEpoch,\n\twriteLandingTransaction,\n\twriteLane,\n} from \"./store.ts\";\n\nexport type WorktreeSyncExec = (\n\tcommand: string,\n\targs: string[],\n\toptions: { cwd: string; timeout?: number; signal?: AbortSignal; maxBuffer?: number; env?: NodeJS.ProcessEnv },\n) => Promise<ExecResult>;\n\nexport interface WorktreeSyncEngineOptions {\n\t/** Overrides default-branch resolution; unset resolves local `main`, then `master` (D13). */\n\tmainBranchOverride?: string;\n\t/** Active-lane ceiling; `create_lane` refuses beyond it. */\n\tmaxLanes: number;\n}\n\nexport interface WorktreeSyncEngineDeps {\n\texec: WorktreeSyncExec;\n\t/** Session cwd -- any directory inside the repo (hub checkout or a lane worktree). */\n\tcwd: string;\n\t/** Base for lane checkouts (`agent-paths.worktreesDir(agentDir)`); engine appends `<repo-slug>/<laneKey>`. */\n\tworktreesBaseDir: string;\n\toptions: WorktreeSyncEngineOptions;\n\tsignal?: AbortSignal;\n\tnow?: () => string;\n\tpid?: number;\n\tsessionId?: string;\n\t/** Injectable fs probe for deterministic tests. Default: `existsSync`. */\n\tfileExists?: (path: string) => boolean;\n\t/** Injectable file reader (conflict-marker scan, rebase progress). Default: `readFileSync`,\n\t * returning undefined on any read error. */\n\treadFile?: (path: string) => string | undefined;\n\t/** Injectable same-host pid liveness for deterministic tests. */\n\tisPidAlive?: (pid: number) => boolean;\n}\n\nexport interface RepoContext {\n\ttopLevel: string;\n\tgitCommonDir: string;\n\tmainBranch: string;\n\tmainSha: string;\n\tpaths: SyncStorePaths;\n\tslug: string;\n\t/** Checkout where the main branch is checked out (the hub); undefined when main is not checked out. */\n\thubPath?: string;\n}\n\nconst GIT_TIMEOUT_MS = 60_000;\nconst GIT_MAX_BUFFER = 1024 * 1024;\nconst LANE_KEY_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;\n\n/** Production exec: `exec.ts`'s bounded `execCommand` (rolling-tail output, timeout, abort). */\nexport function createDefaultWorktreeSyncExec(): WorktreeSyncExec {\n\treturn (command, args, options) =>\n\t\texecCommand(command, args, options.cwd, {\n\t\t\ttimeout: options.timeout,\n\t\t\tsignal: options.signal,\n\t\t\tmaxBuffer: options.maxBuffer,\n\t\t\tenv: options.env,\n\t\t});\n}\n\nfunction runGit(deps: WorktreeSyncEngineDeps, cwd: string, args: string[]): Promise<ExecResult> {\n\treturn deps.exec(\"git\", args, { cwd, timeout: GIT_TIMEOUT_MS, signal: deps.signal, maxBuffer: GIT_MAX_BUFFER });\n}\n\nfunction nowIso(deps: WorktreeSyncEngineDeps): string {\n\treturn (deps.now ?? (() => new Date().toISOString()))();\n}\n\nfunction fileExists(deps: WorktreeSyncEngineDeps, path: string): boolean {\n\treturn (deps.fileExists ?? existsSync)(path);\n}\n\nfunction gitError(message: string, result: ExecResult): WorktreeSyncRefusal<\"git_error\"> {\n\treturn { code: \"git_error\", message, gitStderr: (result.stderr || result.stdout).trim().slice(0, 4000) };\n}\n\nexport function laneBranch(laneKey: string): string {\n\treturn `${LANE_BRANCH_PREFIX}${laneKey}`;\n}\n\nexport interface WorktreeListEntry {\n\tpath: string;\n\theadSha?: string;\n\t/** Full ref (`refs/heads/x`) when a branch is checked out; undefined for detached/bare. */\n\tbranchRef?: string;\n}\n\n/** Parse `git worktree list --porcelain` blocks. */\nexport function parseWorktreeList(output: string): WorktreeListEntry[] {\n\tconst entries: WorktreeListEntry[] = [];\n\tlet current: WorktreeListEntry | undefined;\n\tfor (const line of output.split(/\\r?\\n/)) {\n\t\tif (line.startsWith(\"worktree \")) {\n\t\t\tif (current) entries.push(current);\n\t\t\tcurrent = { path: line.slice(\"worktree \".length) };\n\t\t} else if (line.startsWith(\"HEAD \") && current) {\n\t\t\tcurrent.headSha = line.slice(\"HEAD \".length);\n\t\t} else if (line.startsWith(\"branch \") && current) {\n\t\t\tcurrent.branchRef = line.slice(\"branch \".length);\n\t\t} else if (line.trim() === \"\" && current) {\n\t\t\tentries.push(current);\n\t\t\tcurrent = undefined;\n\t\t}\n\t}\n\tif (current) entries.push(current);\n\treturn entries;\n}\n\nasync function revParseBranch(deps: WorktreeSyncEngineDeps, cwd: string, branch: string): Promise<string | undefined> {\n\tconst result = await runGit(deps, cwd, [\"rev-parse\", \"--verify\", \"--quiet\", `refs/heads/${branch}`]);\n\tif (result.code !== 0) return undefined;\n\tconst sha = result.stdout.trim();\n\treturn sha || undefined;\n}\n\nexport async function resolveRepoContext(\n\tdeps: WorktreeSyncEngineDeps,\n): Promise<RepoContext | WorktreeSyncRefusal<\"not_a_git_repo\" | \"default_branch_unresolved\" | \"git_error\">> {\n\tconst roots = await runGit(deps, deps.cwd, [\n\t\t\"rev-parse\",\n\t\t\"--path-format=absolute\",\n\t\t\"--show-toplevel\",\n\t\t\"--git-common-dir\",\n\t]);\n\tif (roots.code !== 0) {\n\t\treturn {\n\t\t\tcode: \"not_a_git_repo\",\n\t\t\tmessage: `worktree-sync requires a git repository; ${deps.cwd} is not inside one`,\n\t\t\tgitStderr: roots.stderr.trim().slice(0, 1000),\n\t\t};\n\t}\n\tconst [topLevel, gitCommonDir] = roots.stdout\n\t\t.split(/\\r?\\n/)\n\t\t.map((line) => line.trim())\n\t\t.filter(Boolean);\n\tif (!topLevel || !gitCommonDir) {\n\t\treturn gitError(\"git rev-parse returned no toplevel/common-dir\", roots);\n\t}\n\n\t// D13: explicit resolution order -- override, `main`, `master`. Never guess further.\n\tconst candidates = deps.options.mainBranchOverride ? [deps.options.mainBranchOverride] : [\"main\", \"master\"];\n\tlet mainBranch: string | undefined;\n\tlet mainSha: string | undefined;\n\tfor (const candidate of candidates) {\n\t\tconst sha = await revParseBranch(deps, topLevel, candidate);\n\t\tif (sha) {\n\t\t\tmainBranch = candidate;\n\t\t\tmainSha = sha;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!mainBranch || !mainSha) {\n\t\treturn {\n\t\t\tcode: \"default_branch_unresolved\",\n\t\t\tmessage: deps.options.mainBranchOverride\n\t\t\t\t? `configured main branch '${deps.options.mainBranchOverride}' does not exist locally`\n\t\t\t\t: \"no local 'main' or 'master' branch; set the worktree-sync main branch explicitly\",\n\t\t};\n\t}\n\n\tconst worktrees = await runGit(deps, topLevel, [\"worktree\", \"list\", \"--porcelain\"]);\n\tif (worktrees.code !== 0) return gitError(\"git worktree list failed\", worktrees);\n\tconst hub = parseWorktreeList(worktrees.stdout).find((entry) => entry.branchRef === `refs/heads/${mainBranch}`);\n\n\treturn {\n\t\ttopLevel,\n\t\tgitCommonDir,\n\t\tmainBranch,\n\t\tmainSha,\n\t\tpaths: syncStorePaths(gitCommonDir),\n\t\tslug: repoSlug(topLevel, gitCommonDir),\n\t\thubPath: hub?.path,\n\t};\n}\n\n/**\n * Live git facts for one lane. `fresh` is THE freshness definition of the whole subsystem\n * (G3/G9): current main is an ancestor of the lane tip. Everything is re-derived; nothing is\n * read back from a cache that could disagree with git.\n */\nexport async function deriveLaneFacts(\n\tdeps: WorktreeSyncEngineDeps,\n\tctx: RepoContext,\n\tlane: LaneRegistration,\n): Promise<LaneFacts> {\n\tconst branchSha = await revParseBranch(deps, ctx.topLevel, lane.branch);\n\n\tlet fresh = false;\n\tlet aheadOfMain = 0;\n\tlet behindMain = 0;\n\tif (branchSha) {\n\t\tconst ancestry = await runGit(deps, ctx.topLevel, [\"merge-base\", \"--is-ancestor\", ctx.mainSha, branchSha]);\n\t\tfresh = ancestry.code === 0;\n\t\tconst counts = await runGit(deps, ctx.topLevel, [\n\t\t\t\"rev-list\",\n\t\t\t\"--left-right\",\n\t\t\t\"--count\",\n\t\t\t`${ctx.mainBranch}...${lane.branch}`,\n\t\t]);\n\t\tif (counts.code === 0) {\n\t\t\tconst [left, right] = counts.stdout.trim().split(/\\s+/);\n\t\t\tbehindMain = Number.parseInt(left ?? \"0\", 10) || 0;\n\t\t\taheadOfMain = Number.parseInt(right ?? \"0\", 10) || 0;\n\t\t}\n\t}\n\n\tlet worktreePresent = fileExists(deps, lane.worktreePath);\n\tlet dirty = false;\n\tlet rebaseInProgress = false;\n\tif (worktreePresent) {\n\t\tconst status = await runGit(deps, lane.worktreePath, [\"status\", \"--porcelain\"]);\n\t\tif (status.code === 0) {\n\t\t\tdirty = status.stdout.trim().length > 0;\n\t\t\trebaseInProgress = await isRebaseActive(deps, lane.worktreePath);\n\t\t} else {\n\t\t\t// The directory exists but is not a usable worktree (e.g. pruned metadata): not present.\n\t\t\tworktreePresent = false;\n\t\t}\n\t}\n\n\tconst isAlive =\n\t\tdeps.isPidAlive ??\n\t\t((pid: number) => defaultIsPidAlive({ pid, hostname: osHostname(), token: \"status\", acquiredAt: \"\" }));\n\n\treturn {\n\t\tlaneKey: lane.laneKey,\n\t\tbranch: lane.branch,\n\t\tworktreePath: lane.worktreePath,\n\t\tregistrationStatus: lane.status,\n\t\tbranchSha,\n\t\tfresh,\n\t\tdirty,\n\t\trebaseInProgress,\n\t\taheadOfMain,\n\t\tbehindMain,\n\t\tworktreePresent,\n\t\t...(lane.ownerPid !== undefined ? { ownerAlive: isAlive(lane.ownerPid) } : {}),\n\t};\n}\n\n/** One-time repo git config (D7): shared rerere + zdiff3 conflict hunks -- the deterministic\n * conflict-resolution substrate for every lane (the rr-cache lives in the common dir, so one\n * resolution replays identically in all worktrees). Idempotent; only actual changes are audited. */\nexport async function ensureRepoGitConfig(deps: WorktreeSyncEngineDeps, ctx: RepoContext): Promise<void> {\n\tconst wanted: Array<[string, string]> = [\n\t\t[\"rerere.enabled\", \"true\"],\n\t\t[\"rerere.autoUpdate\", \"true\"],\n\t\t[\"merge.conflictStyle\", \"zdiff3\"],\n\t];\n\tconst changed: string[] = [];\n\tfor (const [key, value] of wanted) {\n\t\tconst current = await runGit(deps, ctx.topLevel, [\"config\", \"--get\", key]);\n\t\tif (current.code === 0 && current.stdout.trim() === value) continue;\n\t\tconst set = await runGit(deps, ctx.topLevel, [\"config\", key, value]);\n\t\tif (set.code === 0) changed.push(`${key}=${value}`);\n\t}\n\tif (changed.length > 0) {\n\t\tawait appendAuditEvent(ctx.paths, { event: \"config_set\", changed }, nowIso(deps));\n\t}\n}\n\nfunction sanitizeScope(scope: string): string {\n\tconst cleaned = scope\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 32);\n\treturn cleaned || \"adhoc\";\n}\n\nexport interface CreateLaneArgs {\n\tlaneKey?: string;\n\tgoalId?: string;\n\trequirementId?: string;\n}\n\nexport async function createLane(deps: WorktreeSyncEngineDeps, args: CreateLaneArgs = {}): Promise<CreateLaneResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tif (args.laneKey !== undefined && !LANE_KEY_RE.test(args.laneKey)) {\n\t\t\treturn {\n\t\t\t\tcode: \"invalid_lane_key\",\n\t\t\t\tmessage: `laneKey '${args.laneKey}' is invalid: lowercase alphanumerics and inner dashes, max 63 chars`,\n\t\t\t};\n\t\t}\n\n\t\tconst lanes = await listLanes(ctx.paths);\n\t\tconst activeCount = lanes.filter((lane) => lane.status === \"active\").length;\n\t\tif (activeCount >= deps.options.maxLanes) {\n\t\t\treturn {\n\t\t\t\tcode: \"max_lanes_reached\",\n\t\t\t\tmessage: `active lane limit reached (${activeCount}/${deps.options.maxLanes}); release a lane first`,\n\t\t\t};\n\t\t}\n\n\t\tconst taken = new Set(lanes.filter((lane) => lane.status !== \"released\").map((lane) => lane.laneKey));\n\t\tconst isFree = async (candidate: string): Promise<boolean> => {\n\t\t\tif (taken.has(candidate)) return false;\n\t\t\tif (await revParseBranch(deps, ctx.topLevel, laneBranch(candidate))) return false;\n\t\t\tif (fileExists(deps, join(deps.worktreesBaseDir, ctx.slug, candidate))) return false;\n\t\t\treturn true;\n\t\t};\n\n\t\tlet laneKey: string;\n\t\tif (args.laneKey !== undefined) {\n\t\t\tif (!(await isFree(args.laneKey))) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"lane_exists\",\n\t\t\t\t\tmessage: `lane '${args.laneKey}' already exists (registration, branch, or checkout)`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tlaneKey = args.laneKey;\n\t\t} else {\n\t\t\t// Deterministic allocation: lowest free `<scope>-<n>` (scope = goalId or \"adhoc\").\n\t\t\tconst scope = sanitizeScope(args.goalId ?? \"adhoc\");\n\t\t\tlet n = 1;\n\t\t\twhile (!(await isFree(`${scope}-${n}`))) n++;\n\t\t\tlaneKey = `${scope}-${n}`;\n\t\t}\n\n\t\tawait ensureRepoGitConfig(deps, ctx);\n\n\t\tconst branch = laneBranch(laneKey);\n\t\tconst worktreePath = join(deps.worktreesBaseDir, ctx.slug, laneKey);\n\t\tconst add = await runGit(deps, ctx.topLevel, [\"worktree\", \"add\", \"-b\", branch, worktreePath, ctx.mainBranch]);\n\t\tif (add.code !== 0) return gitError(`git worktree add failed for lane '${laneKey}'`, add);\n\n\t\tconst at = nowIso(deps);\n\t\tconst lane: LaneRegistration = {\n\t\t\tlaneKey,\n\t\t\tbranch,\n\t\t\tworktreePath,\n\t\t\tstatus: \"active\",\n\t\t\tcreatedAt: at,\n\t\t\tupdatedAt: at,\n\t\t\t...(args.goalId !== undefined ? { goalId: args.goalId } : {}),\n\t\t\t...(args.requirementId !== undefined ? { requirementId: args.requirementId } : {}),\n\t\t\townerPid: deps.pid ?? process.pid,\n\t\t\t...(deps.sessionId !== undefined ? { ownerSessionId: deps.sessionId } : {}),\n\t\t};\n\t\tawait writeLane(ctx.paths, lane);\n\t\tawait appendAuditEvent(ctx.paths, { event: \"lane_created\", laneKey, branch, baseSha: ctx.mainSha }, at);\n\t\treturn { code: \"ok\", lane };\n\t});\n}\n\n/**\n * Does `lane` have a DIFFERENT, still-alive session owning it? Same same-host `isAlive` pattern\n * used elsewhere (`deps.isPidAlive`, else {@link defaultIsPidAlive} against this host). A lane\n * with no recorded owner, owned by THIS session, or whose owner is dead never conflicts --\n * `reconcile` already clears dead owners, but a race can still observe one here, and a dead owner\n * must never block land/release (D6, per R1: land/release ONLY -- sync/continue/abort_sync stay\n * ungated at the engine; worker cross-lane containment for those lives at the tool layer, D3).\n */\nfunction ownerConflicts(deps: WorktreeSyncEngineDeps, lane: LaneRegistration): boolean {\n\tif (lane.ownerSessionId == null || lane.ownerSessionId === deps.sessionId) return false;\n\tif (lane.ownerPid === undefined) return false;\n\tconst isAlive =\n\t\tdeps.isPidAlive ??\n\t\t((pid: number) => defaultIsPidAlive({ pid, hostname: osHostname(), token: \"status\", acquiredAt: \"\" }));\n\treturn isAlive(lane.ownerPid);\n}\n\nexport interface BindLaneWorkerArgs {\n\tlaneKey: string;\n\tlaneId: string;\n}\n\nexport async function bindLaneWorker(\n\tdeps: WorktreeSyncEngineDeps,\n\targs: BindLaneWorkerArgs,\n): Promise<BindLaneWorkerResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return { code: \"store_error\", message: ctx.message };\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane) return { code: \"lane_not_found\", message: `lane '${args.laneKey}' is not registered` };\n\t\tif (lane.status !== \"active\")\n\t\t\treturn { code: \"lane_not_active\", message: `lane '${args.laneKey}' is ${lane.status}` };\n\t\tif (lane.boundLaneId === args.laneId)\n\t\t\treturn { code: \"already_bound\", laneKey: args.laneKey, laneId: args.laneId };\n\t\tif (lane.boundLaneId) {\n\t\t\treturn { code: \"binding_conflict\", laneKey: args.laneKey, boundLaneId: lane.boundLaneId };\n\t\t}\n\t\tconst updated = { ...lane, boundLaneId: args.laneId, updatedAt: nowIso(deps) };\n\t\ttry {\n\t\t\tawait writeLane(ctx.paths, updated);\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{ event: \"lane_worker_bound\", laneKey: args.laneKey, laneId: args.laneId },\n\t\t\t\tnowIso(deps),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\treturn { code: \"store_error\", message: `lane binding persistence failed: ${String(error)}` };\n\t\t}\n\t\treturn { code: \"bound\", laneKey: args.laneKey, laneId: args.laneId };\n\t});\n}\n\nexport interface ReleaseLaneArgs {\n\tlaneKey: string;\n\t/** Required as exactly \"yes-discard-lane\" to release a lane with unlanded commits or dirty files (G11). */\n\tconfirm?: string;\n}\n\nexport async function releaseLane(deps: WorktreeSyncEngineDeps, args: ReleaseLaneArgs): Promise<ReleaseLaneResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane) return { code: \"lane_not_found\", message: `no registered lane '${args.laneKey}'` };\n\t\tif (lane.status === \"released\") return { code: \"released\", laneKey: lane.laneKey };\n\t\tif (ownerConflicts(deps, lane)) {\n\t\t\treturn {\n\t\t\t\tcode: \"lane_owner_conflict\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' is owned by another live session (pid ${lane.ownerPid}); only its owner may release it`,\n\t\t\t};\n\t\t}\n\n\t\tconst facts = await deriveLaneFacts(deps, ctx, lane);\n\t\tlet landed = true;\n\t\tif (facts.branchSha) {\n\t\t\tconst ancestry = await runGit(deps, ctx.topLevel, [\n\t\t\t\t\"merge-base\",\n\t\t\t\t\"--is-ancestor\",\n\t\t\t\tfacts.branchSha,\n\t\t\t\tctx.mainSha,\n\t\t\t]);\n\t\t\tlanded = ancestry.code === 0;\n\t\t}\n\t\tconst unlandedWork = (facts.branchSha !== undefined && !landed) || facts.dirty;\n\t\tconst discardConfirmed = args.confirm === \"yes-discard-lane\";\n\t\tif (unlandedWork && !discardConfirmed) {\n\t\t\treturn {\n\t\t\t\tcode: \"lane_unlanded_work\",\n\t\t\t\tmessage:\n\t\t\t\t\t`lane '${lane.laneKey}' has ${facts.dirty ? \"uncommitted changes\" : \"\"}` +\n\t\t\t\t\t`${facts.dirty && !landed ? \" and \" : \"\"}${!landed ? `${facts.aheadOfMain} unlanded commit(s)` : \"\"}; ` +\n\t\t\t\t\t`land it first, or pass confirm:\"yes-discard-lane\" to discard`,\n\t\t\t};\n\t\t}\n\n\t\tif (facts.worktreePresent) {\n\t\t\tconst removeArgs = [\"worktree\", \"remove\", ...(unlandedWork ? [\"--force\"] : []), lane.worktreePath];\n\t\t\tconst remove = await runGit(deps, ctx.topLevel, removeArgs);\n\t\t\tif (remove.code !== 0) return gitError(`git worktree remove failed for lane '${lane.laneKey}'`, remove);\n\t\t}\n\t\tif (facts.branchSha) {\n\t\t\tconst del = await runGit(deps, ctx.topLevel, [\"branch\", unlandedWork ? \"-D\" : \"-d\", lane.branch]);\n\t\t\tif (del.code !== 0) return gitError(`git branch delete failed for lane '${lane.laneKey}'`, del);\n\t\t}\n\t\tawait runGit(deps, ctx.topLevel, [\"worktree\", \"prune\"]);\n\n\t\tconst at = nowIso(deps);\n\t\tawait writeLane(ctx.paths, { ...lane, status: \"released\", updatedAt: at });\n\t\tawait appendAuditEvent(\n\t\t\tctx.paths,\n\t\t\t{ event: \"lane_released\", laneKey: lane.laneKey, ...(unlandedWork ? { discarded: true } : {}) },\n\t\t\tat,\n\t\t);\n\t\treturn { code: \"released\", laneKey: lane.laneKey };\n\t});\n}\n\n/** Lane worktrees git knows about that the registry does not (yet) track: a valid `pi/wt/<key>`\n * branch, key not already registered, checkout still present. One derivation shared by the\n * write-free no-op guard below and the re-registration pass, rather than two copies of the same\n * filter. */\nfunction findUnregisteredLaneWorktrees(\n\tdeps: WorktreeSyncEngineDeps,\n\tworktrees: WorktreeListEntry[],\n\tregistered: Set<string>,\n): Array<{ laneKey: string; path: string }> {\n\tconst found: Array<{ laneKey: string; path: string }> = [];\n\tfor (const entry of worktrees) {\n\t\tconst ref = entry.branchRef;\n\t\tif (!ref?.startsWith(`refs/heads/${LANE_BRANCH_PREFIX}`)) continue;\n\t\tconst laneKey = ref.slice(`refs/heads/${LANE_BRANCH_PREFIX}`.length);\n\t\tif (registered.has(laneKey) || !LANE_KEY_RE.test(laneKey)) continue;\n\t\tif (!fileExists(deps, entry.path)) continue;\n\t\tfound.push({ laneKey, path: entry.path });\n\t}\n\treturn found;\n}\n\nasync function reconcileLandingTransaction(\n\tdeps: WorktreeSyncEngineDeps,\n\tctx: RepoContext,\n\ttransaction: LandingTransaction,\n): Promise<WorktreeSyncRefusal<\"git_error\"> | undefined> {\n\tconst currentMainSha = await revParseBranch(deps, ctx.topLevel, ctx.mainBranch);\n\tif (currentMainSha === transaction.priorMainSha) {\n\t\tawait clearLandingTransaction(ctx.paths);\n\t\treturn undefined;\n\t}\n\tif (currentMainSha !== transaction.testedTipSha) {\n\t\treturn {\n\t\t\tcode: \"git_error\",\n\t\t\tmessage: `landing transaction is ambiguous: main is ${currentMainSha?.slice(0, 12) ?? \"missing\"}, expected prior ${transaction.priorMainSha.slice(0, 12)} or tested ${transaction.testedTipSha.slice(0, 12)}`,\n\t\t};\n\t}\n\n\tconst previous = await readEpoch(ctx.paths);\n\tlet epoch = previous?.epoch ?? 0;\n\tconst at = nowIso(deps);\n\tif (previous?.mainSha !== transaction.testedTipSha) {\n\t\tepoch++;\n\t\tawait writeEpoch(ctx.paths, {\n\t\t\tepoch,\n\t\t\tmainSha: transaction.testedTipSha,\n\t\t\tpreviousMainSha: transaction.priorMainSha,\n\t\t\tlandedLaneKey: transaction.laneKey,\n\t\t\tlandedAt: at,\n\t\t\tchangedPaths: transaction.changedPaths,\n\t\t\tchangedPathsTruncated: transaction.changedPathsTruncated,\n\t\t});\n\t\ttransaction = { ...transaction, stage: \"epoch_written\" };\n\t\tawait writeLandingTransaction(ctx.paths, transaction);\n\t}\n\tif (transaction.stage !== \"audit_logged\") {\n\t\tawait appendAuditEvent(\n\t\t\tctx.paths,\n\t\t\t{\n\t\t\t\tevent: \"epoch_reconciled\",\n\t\t\t\tepoch,\n\t\t\t\tlaneKey: transaction.laneKey,\n\t\t\t\tmainSha: transaction.testedTipSha,\n\t\t\t\tgate: transaction.gate,\n\t\t\t},\n\t\t\tat,\n\t\t);\n\t\tawait writeLandingTransaction(ctx.paths, { ...transaction, stage: \"audit_logged\" });\n\t}\n\tawait clearLandingTransaction(ctx.paths);\n\treturn undefined;\n}\n\n/**\n * Startup/repair pass (mirrors the tmux session reconcile): diff the registry against git\n * reality, mark orphans (never delete -- G11), re-register lane worktrees git still knows that\n * the registry lost (a deleted store is fully rebuildable), self-heal orphan registrations whose\n * worktree+branch actually exist, clear dead owners, and release a provably-stale lock.\n */\nexport async function reconcile(deps: WorktreeSyncEngineDeps): Promise<ReconcileResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\n\tconst landingTransaction = await readLandingTransaction(ctx.paths);\n\tif (landingTransaction) {\n\t\tconst recoveryLock = await acquireIntegrationLock(\n\t\t\tctx.paths,\n\t\t\t{ pid: deps.pid ?? process.pid, hostname: osHostname(), sessionId: deps.sessionId, laneKey: \"reconcile\" },\n\t\t\t{ now: deps.now },\n\t\t);\n\t\tif (!recoveryLock.acquired) {\n\t\t\treturn { code: \"git_error\", message: \"landing transaction recovery is blocked by an active integration lock\" };\n\t\t}\n\t\ttry {\n\t\t\tconst recovery = await reconcileLandingTransaction(deps, ctx, landingTransaction);\n\t\t\tif (recovery) return recovery;\n\t\t} finally {\n\t\t\tawait releaseIntegrationLock(ctx.paths, recoveryLock.token, { now: deps.now });\n\t\t}\n\t}\n\n\tconst worktreesResult = await runGit(deps, ctx.topLevel, [\"worktree\", \"list\", \"--porcelain\"]);\n\tif (worktreesResult.code !== 0) return gitError(\"git worktree list failed\", worktreesResult);\n\tconst worktrees = parseWorktreeList(worktreesResult.stdout);\n\n\tconst lanes = await listLanes(ctx.paths);\n\tconst registered = new Set(lanes.map((lane) => lane.laneKey));\n\tconst unregisteredWorktrees = findUnregisteredLaneWorktrees(deps, worktrees, registered);\n\n\t// Write-free fast path: worktree-sync now starts in every session, so a mere session open in a\n\t// git repo must never create `<git-common-dir>/pi-worktree-sync/`. When the store does not exist\n\t// yet and git shows nothing to recover, there is genuinely nothing to reconcile -- one early\n\t// determination, then skip every write below (writeLane/appendAuditEvent/releaseIntegrationLock)\n\t// rather than guarding each call individually. Once the store exists, or findings occur, behavior\n\t// is unchanged below (including the reconcile_summary audit append).\n\tif (!fileExists(deps, ctx.paths.root) && unregisteredWorktrees.length === 0) {\n\t\treturn {\n\t\t\tcode: \"reconciled\",\n\t\t\torphanedLaneKeys: [],\n\t\t\treRegisteredLaneKeys: [],\n\t\t\townerClearedLaneKeys: [],\n\t\t\tstaleLockReleased: false,\n\t\t};\n\t}\n\n\tconst isAlive =\n\t\tdeps.isPidAlive ??\n\t\t((pid: number) => defaultIsPidAlive({ pid, hostname: osHostname(), token: \"status\", acquiredAt: \"\" }));\n\tconst at = nowIso(deps);\n\n\tconst orphanedLaneKeys: string[] = [];\n\tconst reRegisteredLaneKeys: string[] = [];\n\tconst ownerClearedLaneKeys: string[] = [];\n\n\tfor (const lane of lanes) {\n\t\tif (lane.status === \"released\") continue;\n\t\tlet next = lane;\n\n\t\tconst branchSha = await revParseBranch(deps, ctx.topLevel, lane.branch);\n\t\tconst present = branchSha !== undefined && fileExists(deps, lane.worktreePath);\n\t\tif (lane.status === \"active\" && !present) {\n\t\t\tnext = { ...next, status: \"orphaned\", updatedAt: at };\n\t\t\torphanedLaneKeys.push(lane.laneKey);\n\t\t\tawait appendAuditEvent(ctx.paths, { event: \"lane_orphaned\", laneKey: lane.laneKey }, at);\n\t\t} else if (lane.status === \"orphaned\" && present) {\n\t\t\t// Self-heal: the checkout and branch are back (or never really left) -- active again.\n\t\t\tnext = { ...next, status: \"active\", updatedAt: at };\n\t\t\treRegisteredLaneKeys.push(lane.laneKey);\n\t\t\tawait appendAuditEvent(ctx.paths, { event: \"lane_reregistered\", laneKey: lane.laneKey }, at);\n\t\t}\n\n\t\tif (next.ownerPid !== undefined && !isAlive(next.ownerPid)) {\n\t\t\tconst { ownerPid: _pid, ownerSessionId: _session, ...rest } = next;\n\t\t\tnext = { ...rest, updatedAt: at };\n\t\t\townerClearedLaneKeys.push(lane.laneKey);\n\t\t}\n\n\t\tif (next !== lane) await writeLane(ctx.paths, next);\n\t}\n\n\t// Lane worktrees git knows but the registry does not: rebuild identity from git facts.\n\tfor (const { laneKey, path } of unregisteredWorktrees) {\n\t\tawait writeLane(ctx.paths, {\n\t\t\tlaneKey,\n\t\t\tbranch: `${LANE_BRANCH_PREFIX}${laneKey}`,\n\t\t\tworktreePath: path,\n\t\t\tstatus: \"active\",\n\t\t\tcreatedAt: at,\n\t\t\tupdatedAt: at,\n\t\t});\n\t\treRegisteredLaneKeys.push(laneKey);\n\t\tawait appendAuditEvent(ctx.paths, { event: \"lane_reregistered\", laneKey }, at);\n\t}\n\n\t// Provably-stale integration lock: dead same-host owner (never a foreign or live one).\n\tlet staleLockReleased = false;\n\tconst holder = await readLockHolder(ctx.paths);\n\tif (holder && holder.hostname === osHostname() && !isAlive(holder.pid) && holder.token) {\n\t\tstaleLockReleased = await releaseIntegrationLock(ctx.paths, holder.token, { now: deps.now });\n\t}\n\n\tawait appendAuditEvent(\n\t\tctx.paths,\n\t\t{\n\t\t\tevent: \"reconcile_summary\",\n\t\t\torphaned: orphanedLaneKeys,\n\t\t\treRegistered: reRegisteredLaneKeys,\n\t\t\townersCleared: ownerClearedLaneKeys,\n\t\t\tstaleLockReleased,\n\t\t},\n\t\tat,\n\t);\n\treturn { code: \"reconciled\", orphanedLaneKeys, reRegisteredLaneKeys, ownerClearedLaneKeys, staleLockReleased };\n}\n\n/** Rebase env for every rebase-driving call: never let git open an editor mid-automation. */\nfunction rebaseEnv(): NodeJS.ProcessEnv {\n\treturn { ...process.env, GIT_EDITOR: \"true\", GIT_SEQUENCE_EDITOR: \"true\" };\n}\n\nfunction runGitEnv(deps: WorktreeSyncEngineDeps, cwd: string, args: string[]): Promise<ExecResult> {\n\treturn deps.exec(\"git\", args, {\n\t\tcwd,\n\t\ttimeout: GIT_TIMEOUT_MS,\n\t\tsignal: deps.signal,\n\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\tenv: rebaseEnv(),\n\t});\n}\n\nfunction readFileMaybe(deps: WorktreeSyncEngineDeps, path: string): string | undefined {\n\tif (deps.readFile) return deps.readFile(path);\n\ttry {\n\t\treturn readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** A rebase is in progress iff the worktree's rebase-merge or rebase-apply state dir exists. */\nasync function isRebaseActive(deps: WorktreeSyncEngineDeps, worktreePath: string): Promise<boolean> {\n\tconst gitPaths = await runGit(deps, worktreePath, [\n\t\t\"rev-parse\",\n\t\t\"--path-format=absolute\",\n\t\t\"--git-path\",\n\t\t\"rebase-merge\",\n\t\t\"--git-path\",\n\t\t\"rebase-apply\",\n\t]);\n\tif (gitPaths.code !== 0) return false;\n\treturn gitPaths.stdout\n\t\t.split(/\\r?\\n/)\n\t\t.map((line) => line.trim())\n\t\t.filter(Boolean)\n\t\t.some((path) => fileExists(deps, path));\n}\n\n/** Paths with local modifications (`status --porcelain`), rename targets included. Capped. */\nasync function dirtyPaths(deps: WorktreeSyncEngineDeps, worktreePath: string): Promise<string[]> {\n\tconst status = await runGit(deps, worktreePath, [\"status\", \"--porcelain\"]);\n\tif (status.code !== 0) return [];\n\treturn status.stdout\n\t\t.split(/\\r?\\n/)\n\t\t.filter((line) => line.length > 3)\n\t\t.map((line) => {\n\t\t\tconst path = line.slice(3);\n\t\t\tconst renameArrow = path.indexOf(\" -> \");\n\t\t\treturn renameArrow >= 0 ? path.slice(renameArrow + 4) : path;\n\t\t})\n\t\t.slice(0, 100);\n}\n\nasync function unmergedFiles(deps: WorktreeSyncEngineDeps, worktreePath: string): Promise<string[]> {\n\tconst diff = await runGit(deps, worktreePath, [\"diff\", \"--name-only\", \"--diff-filter=U\"]);\n\tif (diff.code !== 0) return [];\n\treturn diff.stdout\n\t\t.split(/\\r?\\n/)\n\t\t.map((line) => line.trim())\n\t\t.filter(Boolean);\n}\n\n/** Conflict kind per path from `git ls-files -u` index stages (1=base, 2=ours, 3=theirs). */\nasync function unmergedKinds(\n\tdeps: WorktreeSyncEngineDeps,\n\tworktreePath: string,\n): Promise<Map<string, ConflictWorklistFile[\"kind\"]>> {\n\tconst lsFiles = await runGit(deps, worktreePath, [\"ls-files\", \"-u\"]);\n\tconst stagesByPath = new Map<string, Set<string>>();\n\tif (lsFiles.code === 0) {\n\t\tfor (const line of lsFiles.stdout.split(/\\r?\\n/)) {\n\t\t\tconst match = line.match(/^\\d+ [0-9a-f]+ ([123])\\t(.+)$/);\n\t\t\tif (!match?.[1] || !match[2]) continue;\n\t\t\tconst stages = stagesByPath.get(match[2]) ?? new Set<string>();\n\t\t\tstages.add(match[1]);\n\t\t\tstagesByPath.set(match[2], stages);\n\t\t}\n\t}\n\tconst kinds = new Map<string, ConflictWorklistFile[\"kind\"]>();\n\tfor (const [path, stages] of stagesByPath) {\n\t\tconst has = (stage: string) => stages.has(stage);\n\t\tif (has(\"1\") && has(\"2\") && has(\"3\")) kinds.set(path, \"both_modified\");\n\t\telse if (!has(\"1\") && has(\"2\") && has(\"3\")) kinds.set(path, \"both_added\");\n\t\telse if (has(\"1\") && !has(\"2\") && has(\"3\")) kinds.set(path, \"deleted_by_us\");\n\t\telse if (has(\"1\") && has(\"2\") && !has(\"3\")) kinds.set(path, \"deleted_by_them\");\n\t\telse kinds.set(path, \"unknown\");\n\t}\n\treturn kinds;\n}\n\n/** Rebase progress (\"<done>/<total>\") plus the commit the rebase stopped at, from the rebase\n * state files (merge backend: msgnum/end/stopped-sha; apply backend: next/last). */\nasync function rebaseProgress(\n\tdeps: WorktreeSyncEngineDeps,\n\tworktreePath: string,\n): Promise<{ step: string; stoppedAtCommit?: { sha: string; subject: string } }> {\n\tconst gitPaths = await runGit(deps, worktreePath, [\n\t\t\"rev-parse\",\n\t\t\"--path-format=absolute\",\n\t\t\"--git-path\",\n\t\t\"rebase-merge\",\n\t\t\"--git-path\",\n\t\t\"rebase-apply\",\n\t]);\n\tlet step = \"?\";\n\tlet stoppedSha: string | undefined;\n\tif (gitPaths.code === 0) {\n\t\tconst [mergeDir, applyDir] = gitPaths.stdout\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter(Boolean);\n\t\tconst readCounter = (dir: string | undefined, doneName: string, totalName: string): string | undefined => {\n\t\t\tif (!dir) return undefined;\n\t\t\tconst done = readFileMaybe(deps, join(dir, doneName))?.trim();\n\t\t\tconst total = readFileMaybe(deps, join(dir, totalName))?.trim();\n\t\t\treturn done && total ? `${done}/${total}` : undefined;\n\t\t};\n\t\tstep = readCounter(mergeDir, \"msgnum\", \"end\") ?? readCounter(applyDir, \"next\", \"last\") ?? \"?\";\n\t\tstoppedSha = mergeDir ? readFileMaybe(deps, join(mergeDir, \"stopped-sha\"))?.trim() : undefined;\n\t}\n\tif (!stoppedSha) return { step };\n\tconst subject = await runGit(deps, worktreePath, [\"log\", \"-1\", \"--format=%s\", stoppedSha]);\n\treturn {\n\t\tstep,\n\t\tstoppedAtCommit: { sha: stoppedSha, subject: subject.code === 0 ? subject.stdout.trim() : \"\" },\n\t};\n}\n\n/**\n * Build the structured conflict worklist (G9's input): the agent edits exactly these files, then\n * calls `continue` -- staging, marker verification, and rebase continuation are harness work.\n * Files rerere auto-resolved never appear here (they are staged, not unmerged) -- the mechanical\n * drive loop below continues past fully-replayed stops without involving the agent at all, so\n * `resolvedByRerere` marks nothing today and is reserved for partial-replay surfacing.\n */\nasync function buildWorklist(\n\tdeps: WorktreeSyncEngineDeps,\n\tworktreePath: string,\n\tunmerged: string[],\n): Promise<ConflictWorklist> {\n\tconst kinds = await unmergedKinds(deps, worktreePath);\n\tconst progress = await rebaseProgress(deps, worktreePath);\n\treturn {\n\t\tstep: progress.step,\n\t\t...(progress.stoppedAtCommit ? { stoppedAtCommit: progress.stoppedAtCommit } : {}),\n\t\tfiles: unmerged.map((path) => ({\n\t\t\tpath,\n\t\t\tkind: kinds.get(path) ?? \"unknown\",\n\t\t\tresolvedByRerere: false,\n\t\t})),\n\t};\n}\n\nconst CONFLICT_MARKER_RE = /^(?:<{7}|={7}|>{7}|\\|{7})/m;\n\n/**\n * Mechanically drive an in-progress rebase forward: finish it, stop at the next REAL conflict\n * (returning its worklist), auto-continue stops that rerere fully replayed, and skip steps that\n * provably reduced to nothing (their changes already landed). Bounded; never guesses -- an\n * unrecognized stop state surfaces as git_error with the evidence.\n */\nasync function driveRebase(\n\tdeps: WorktreeSyncEngineDeps,\n\tctx: RepoContext,\n\tlane: LaneRegistration,\n): Promise<\n\t| { code: \"sync_clean\"; laneKey: string; autoContinued: number }\n\t| { code: \"sync_conflicts\"; laneKey: string; worklist: ConflictWorklist }\n\t| WorktreeSyncRefusal<\"git_error\">\n> {\n\tconst worktreePath = lane.worktreePath;\n\tlet autoContinued = 0;\n\tfor (let iteration = 0; iteration <= 1000; iteration++) {\n\t\tif (!(await isRebaseActive(deps, worktreePath))) {\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{ event: \"sync_completed\", laneKey: lane.laneKey, autoContinued },\n\t\t\t\tnowIso(deps),\n\t\t\t);\n\t\t\treturn { code: \"sync_clean\", laneKey: lane.laneKey, autoContinued };\n\t\t}\n\t\tconst unmerged = await unmergedFiles(deps, worktreePath);\n\t\tif (unmerged.length > 0) {\n\t\t\tconst worklist = await buildWorklist(deps, worktreePath, unmerged);\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{ event: \"sync_conflicts\", laneKey: lane.laneKey, step: worklist.step, files: worklist.files.length },\n\t\t\t\tnowIso(deps),\n\t\t\t);\n\t\t\treturn { code: \"sync_conflicts\", laneKey: lane.laneKey, worklist };\n\t\t}\n\t\t// Stopped with nothing unmerged: rerere fully replayed the resolution (auto-staged via\n\t\t// rerere.autoUpdate) or the step reduced to nothing. Continue mechanically.\n\t\tconst cont = await runGitEnv(deps, worktreePath, [\"rebase\", \"--continue\"]);\n\t\tautoContinued++;\n\t\tif (cont.code === 0) continue;\n\t\tif ((await unmergedFiles(deps, worktreePath)).length > 0) continue;\n\t\tif (!(await isRebaseActive(deps, worktreePath))) continue;\n\t\tconst staged = await runGit(deps, worktreePath, [\"diff\", \"--cached\", \"--quiet\"]);\n\t\tconst unstaged = await runGit(deps, worktreePath, [\"diff\", \"--quiet\"]);\n\t\tif (staged.code === 0 && unstaged.code === 0) {\n\t\t\t// Provably-empty step: its changes are already on main. Skipping is the one correct move.\n\t\t\tconst skip = await runGitEnv(deps, worktreePath, [\"rebase\", \"--skip\"]);\n\t\t\tif (skip.code !== 0 && (await isRebaseActive(deps, worktreePath))) {\n\t\t\t\treturn gitError(\"git rebase --skip failed on a provably-empty step\", skip);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\treturn gitError(\"git rebase --continue failed in an unrecognized state\", cont);\n\t}\n\treturn {\n\t\tcode: \"git_error\",\n\t\tmessage: \"rebase drive exceeded 1000 mechanical steps; aborting the drive (rebase left in progress)\",\n\t};\n}\n\nexport interface SyncLaneArgs {\n\tlaneKey: string;\n}\n\n/**\n * Rebase current main into the lane branch, locally (the awareness half of \"perfect sync\"; the\n * land gate is the enforcement half). Pins the main sha it derived so a concurrent land cannot\n * change this sync's meaning mid-flight (D-pinned-sha). Conflicts leave the rebase in progress\n * and return a structured worklist; `continueSync` verifies and drives on.\n */\nexport async function syncLane(deps: WorktreeSyncEngineDeps, args: SyncLaneArgs): Promise<SyncLaneResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane || lane.status === \"released\") {\n\t\t\treturn { code: \"lane_not_found\", message: `no active lane '${args.laneKey}'` };\n\t\t}\n\t\tconst facts = await deriveLaneFacts(deps, ctx, lane);\n\t\tif (!facts.worktreePresent) {\n\t\t\treturn {\n\t\t\t\tcode: \"worktree_missing\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' checkout missing at ${lane.worktreePath}`,\n\t\t\t};\n\t\t}\n\t\tif (facts.rebaseInProgress) {\n\t\t\treturn {\n\t\t\t\tcode: \"rebase_in_progress\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' has a rebase in progress; resolve and call continue, or abort_sync`,\n\t\t\t};\n\t\t}\n\t\tif (facts.fresh) {\n\t\t\treturn { code: \"sync_clean\", laneKey: lane.laneKey, alreadyFresh: true, autoContinued: 0 };\n\t\t}\n\t\tif (facts.dirty) {\n\t\t\treturn {\n\t\t\t\tcode: \"lane_dirty\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' has uncommitted changes; commit them on the lane branch, then sync`,\n\t\t\t\tpaths: await dirtyPaths(deps, lane.worktreePath),\n\t\t\t};\n\t\t}\n\n\t\tawait appendAuditEvent(\n\t\t\tctx.paths,\n\t\t\t{ event: \"sync_started\", laneKey: lane.laneKey, ontoSha: ctx.mainSha },\n\t\t\tnowIso(deps),\n\t\t);\n\t\tconst rebase = await runGitEnv(deps, lane.worktreePath, [\"rebase\", ctx.mainSha]);\n\t\tif (rebase.code === 0) {\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{ event: \"sync_completed\", laneKey: lane.laneKey, autoContinued: 0 },\n\t\t\t\tnowIso(deps),\n\t\t\t);\n\t\t\treturn { code: \"sync_clean\", laneKey: lane.laneKey, alreadyFresh: false, autoContinued: 0 };\n\t\t}\n\t\tif (!(await isRebaseActive(deps, lane.worktreePath))) {\n\t\t\treturn gitError(`git rebase failed to start for lane '${lane.laneKey}'`, rebase);\n\t\t}\n\t\tconst driven = await driveRebase(deps, ctx, lane);\n\t\tif (driven.code === \"sync_clean\") return { ...driven, alreadyFresh: false };\n\t\treturn driven;\n\t});\n}\n\nexport interface ContinueSyncArgs {\n\tlaneKey: string;\n}\n\n/**\n * G9: verify the agent's conflict resolution mechanically (zero conflict markers by byte-scan --\n * the agent never self-certifies), stage it, and drive the rebase on. More conflicts return the\n * next worklist; completion returns sync_clean.\n */\nexport async function continueSync(deps: WorktreeSyncEngineDeps, args: ContinueSyncArgs): Promise<ContinueSyncResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane || lane.status === \"released\") {\n\t\t\treturn { code: \"lane_not_found\", message: `no active lane '${args.laneKey}'` };\n\t\t}\n\t\tif (!fileExists(deps, lane.worktreePath)) {\n\t\t\treturn {\n\t\t\t\tcode: \"worktree_missing\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' checkout missing at ${lane.worktreePath}`,\n\t\t\t};\n\t\t}\n\t\tif (!(await isRebaseActive(deps, lane.worktreePath))) {\n\t\t\treturn {\n\t\t\t\tcode: \"no_rebase_in_progress\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' has no rebase in progress; use sync`,\n\t\t\t};\n\t\t}\n\n\t\tconst unmerged = await unmergedFiles(deps, lane.worktreePath);\n\t\tconst withMarkers = unmerged.filter((path) => {\n\t\t\tconst content = readFileMaybe(deps, join(lane.worktreePath, path));\n\t\t\treturn content !== undefined && CONFLICT_MARKER_RE.test(content);\n\t\t});\n\t\tif (withMarkers.length > 0) {\n\t\t\treturn {\n\t\t\t\tcode: \"conflict_markers_present\",\n\t\t\t\tmessage: `${withMarkers.length} file(s) still contain conflict markers; resolve them fully, then continue`,\n\t\t\t\tpaths: withMarkers,\n\t\t\t};\n\t\t}\n\t\tif (unmerged.length > 0) {\n\t\t\tconst add = await runGit(deps, lane.worktreePath, [\"add\", \"-A\"]);\n\t\t\tif (add.code !== 0) return gitError(\"git add -A failed while staging conflict resolutions\", add);\n\t\t}\n\t\tconst cont = await runGitEnv(deps, lane.worktreePath, [\"rebase\", \"--continue\"]);\n\t\tif (cont.code === 0 && !(await isRebaseActive(deps, lane.worktreePath))) {\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{ event: \"sync_completed\", laneKey: lane.laneKey, autoContinued: 0 },\n\t\t\t\tnowIso(deps),\n\t\t\t);\n\t\t\treturn { code: \"sync_clean\", laneKey: lane.laneKey, autoContinued: 0 };\n\t\t}\n\t\tconst result = await driveRebase(deps, ctx, lane);\n\t\treturn result;\n\t});\n}\n\nexport interface AbortSyncArgs {\n\tlaneKey: string;\n}\n\n/** Abort an in-progress sync rebase: the lane returns to its pre-sync tip -- still stale, and\n * honestly reported as such by status. */\nexport async function abortSync(deps: WorktreeSyncEngineDeps, args: AbortSyncArgs): Promise<AbortSyncResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane || lane.status === \"released\") {\n\t\t\treturn { code: \"lane_not_found\", message: `no active lane '${args.laneKey}'` };\n\t\t}\n\t\tif (!(await isRebaseActive(deps, lane.worktreePath))) {\n\t\t\treturn { code: \"no_rebase_in_progress\", message: `lane '${lane.laneKey}' has no rebase in progress` };\n\t\t}\n\t\tconst abort = await runGitEnv(deps, lane.worktreePath, [\"rebase\", \"--abort\"]);\n\t\tif (abort.code !== 0) return gitError(\"git rebase --abort failed\", abort);\n\t\tawait appendAuditEvent(ctx.paths, { event: \"sync_aborted\", laneKey: lane.laneKey }, nowIso(deps));\n\t\treturn { code: \"ok\", laneKey: lane.laneKey };\n\t});\n}\n\nexport interface LandLaneArgs {\n\tlaneKey: string;\n\t/** \"on\" runs gateCommand (G4); \"off\" is the owner-level opt-out, recorded per land event. */\n\tgate: \"on\" | \"off\";\n\tgateCommand?: string;\n\tgateTimeoutMs?: number;\n}\n\nconst DEFAULT_GATE_TIMEOUT_MS = 900_000;\n\n/**\n * The land gate -- the ONLY door to main (G1-G7). Serialized under the integration lock; every\n * precondition is re-derived INSIDE the lock (the compare-and-swap that makes the whole system\n * race-free: even if every notification failed, a stale lane cannot land). Main only ever moves\n * by ff-only merge of an already-rebased lane branch; a successful land bumps the epoch and\n * broadcasts in the same critical section.\n */\nexport async function landLane(deps: WorktreeSyncEngineDeps, args: LandLaneArgs): Promise<LandResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\treturn withFileLock(ctx.paths.lifecycleLockFile, async () => {\n\t\tconst lane = await readLane(ctx.paths, args.laneKey);\n\t\tif (!lane || lane.status === \"released\") {\n\t\t\treturn { code: \"lane_not_found\", message: `no active lane '${args.laneKey}'` };\n\t\t}\n\t\tif (ownerConflicts(deps, lane)) {\n\t\t\treturn {\n\t\t\t\tcode: \"lane_owner_conflict\",\n\t\t\t\tmessage: `lane '${lane.laneKey}' is owned by another live session (pid ${lane.ownerPid}); only its owner may land it`,\n\t\t\t};\n\t\t}\n\t\tif (args.gate === \"on\" && !args.gateCommand?.trim()) {\n\t\t\treturn {\n\t\t\t\tcode: \"gate_command_unset\",\n\t\t\t\tmessage:\n\t\t\t\t\t'no gate command configured; set worktreeSync.gateCommand (or worktreeSync.gate: \"off\" as an owner-level opt-out)',\n\t\t\t};\n\t\t}\n\n\t\tconst acquisition = await acquireIntegrationLock(\n\t\t\tctx.paths,\n\t\t\t{\n\t\t\t\tpid: deps.pid ?? process.pid,\n\t\t\t\thostname: osHostname(),\n\t\t\t\t...(deps.sessionId !== undefined ? { sessionId: deps.sessionId } : {}),\n\t\t\t\tlaneKey: lane.laneKey,\n\t\t\t},\n\t\t\t{\n\t\t\t\tnow: deps.now,\n\t\t\t\t...(deps.isPidAlive\n\t\t\t\t\t? { isPidAlive: (owner) => (deps.isPidAlive as (pid: number) => boolean)(owner.pid) }\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t);\n\t\tif (!acquisition.acquired) {\n\t\t\treturn {\n\t\t\t\tcode: \"lock_busy\",\n\t\t\t\tmessage: acquisition.holder\n\t\t\t\t\t? `integration lock held by pid ${acquisition.holder.pid}${acquisition.holder.laneKey ? ` (lane '${acquisition.holder.laneKey}')` : \"\"}; retry after it lands`\n\t\t\t\t\t: \"integration lock held; retry shortly\",\n\t\t\t\t...(acquisition.holder ? { holder: acquisition.holder } : {}),\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\t// Everything below happens INSIDE the lock: re-derive, never trust pre-lock derivations.\n\t\t\tconst mainShaNow = await revParseBranch(deps, ctx.topLevel, ctx.mainBranch);\n\t\t\tif (!mainShaNow) {\n\t\t\t\treturn { code: \"default_branch_unresolved\", message: `main branch '${ctx.mainBranch}' vanished mid-land` };\n\t\t\t}\n\t\t\tif (!fileExists(deps, lane.worktreePath)) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"worktree_missing\",\n\t\t\t\t\tmessage: `lane '${lane.laneKey}' checkout missing at ${lane.worktreePath}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (await isRebaseActive(deps, lane.worktreePath)) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"rebase_in_progress\",\n\t\t\t\t\tmessage: `lane '${lane.laneKey}' has a rebase in progress; finish (continue) or abort_sync before landing`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst laneDirty = await dirtyPaths(deps, lane.worktreePath);\n\t\t\tif (laneDirty.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"lane_dirty\",\n\t\t\t\t\tmessage: `lane '${lane.laneKey}' has uncommitted changes; commit everything on the lane branch first (G2)`,\n\t\t\t\t\tpaths: laneDirty,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst tipSha = await revParseBranch(deps, ctx.topLevel, lane.branch);\n\t\t\tif (!tipSha) return { code: \"git_error\", message: `lane branch '${lane.branch}' has no commits/ref` };\n\t\t\tif (tipSha === mainShaNow) {\n\t\t\t\t// A no-op land would bump the epoch for nothing (notification churn for every lane).\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"nothing_to_land\",\n\t\t\t\t\tmessage: `lane '${lane.laneKey}' has no commits beyond main; nothing to land`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst ancestry = await runGit(deps, ctx.topLevel, [\"merge-base\", \"--is-ancestor\", mainShaNow, tipSha]);\n\t\t\tif (ancestry.code !== 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"stale_lane\",\n\t\t\t\t\tmessage: `lane '${lane.laneKey}' does not contain current main (${mainShaNow.slice(0, 12)}); sync first (G3)`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (!ctx.hubPath) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"hub_missing\",\n\t\t\t\t\tmessage: `main branch '${ctx.mainBranch}' is not checked out in any worktree; landing needs the hub checkout (G5)`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst changedResult = await runGit(deps, ctx.topLevel, [\"diff\", \"--name-only\", `${mainShaNow}..${tipSha}`]);\n\t\t\tif (changedResult.code !== 0)\n\t\t\t\treturn gitError(\"git diff --name-only failed while computing the land set\", changedResult);\n\t\t\tconst changed = changedResult.stdout\n\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t.map((line) => line.trim())\n\t\t\t\t.filter(Boolean);\n\n\t\t\t// G6, overlap-based: an ff merge only touches the land's changed files, and git itself\n\t\t\t// refuses to clobber local modifications -- so the deterministic pre-check refuses exactly\n\t\t\t// when hub-dirty paths intersect the incoming change set (not on unrelated hub dirt).\n\t\t\tconst hubDirty = await dirtyPaths(deps, ctx.hubPath);\n\t\t\tconst changedSet = new Set(changed);\n\t\t\tconst endangered = hubDirty.filter((path) => changedSet.has(path));\n\t\t\tif (endangered.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"hub_dirty\",\n\t\t\t\t\tmessage: `hub checkout has local modifications to ${endangered.length} file(s) this land would update; commit/stash them first (G6)`,\n\t\t\t\t\tpaths: endangered.slice(0, 50),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (args.gate === \"on\") {\n\t\t\t\tconst gateCommand = (args.gateCommand ?? \"\").trim();\n\t\t\t\tconst shell = process.platform === \"win32\" ? \"cmd\" : \"sh\";\n\t\t\t\tconst shellFlag = process.platform === \"win32\" ? \"/c\" : \"-c\";\n\t\t\t\tconst gateRun = await deps.exec(shell, [shellFlag, gateCommand], {\n\t\t\t\t\tcwd: lane.worktreePath,\n\t\t\t\t\ttimeout: args.gateTimeoutMs ?? DEFAULT_GATE_TIMEOUT_MS,\n\t\t\t\t\tsignal: deps.signal,\n\t\t\t\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\t\t\t});\n\t\t\t\tif (gateRun.code !== 0) {\n\t\t\t\t\tconst tail = `${gateRun.stdout}\\n${gateRun.stderr}`.trim().slice(-4000);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcode: \"gate_failed\",\n\t\t\t\t\t\tmessage: `gate command failed (exit ${gateRun.code}) at lane tip ${tipSha.slice(0, 12)} (G4)`,\n\t\t\t\t\t\tgitStderr: tail,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\t// The gate is an external process. Re-read the ref after it exits: a worker\n\t\t\t\t// may have committed while the gate was running, and the branch name is\n\t\t\t\t// mutable. Never merge a tip that was not the one actually tested.\n\t\t\t\tconst postGateTip = await revParseBranch(deps, ctx.topLevel, lane.branch);\n\t\t\t\tif (postGateTip !== tipSha) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcode: \"lane_changed_during_gate\",\n\t\t\t\t\t\tmessage: `lane '${lane.laneKey}' changed during the gate (tested ${tipSha.slice(0, 12)}, current ${postGateTip?.slice(0, 12) ?? \"missing\"}); rerun land`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst landingTransaction: LandingTransaction = {\n\t\t\t\tlaneKey: lane.laneKey,\n\t\t\t\tpriorMainSha: mainShaNow,\n\t\t\t\ttestedTipSha: tipSha,\n\t\t\t\tchangedPaths: changed.slice(0, EPOCH_CHANGED_PATHS_CAP),\n\t\t\t\tchangedPathsTruncated: changed.length > EPOCH_CHANGED_PATHS_CAP,\n\t\t\t\tlockToken: acquisition.token,\n\t\t\t\tgate: args.gate === \"on\" ? \"passed\" : \"off\",\n\t\t\t\tstage: \"ready_to_merge\",\n\t\t\t};\n\t\t\tawait writeLandingTransaction(ctx.paths, landingTransaction);\n\t\t\t// Move the named main ref with a compare-and-swap instead of `git merge` in whichever\n\t\t\t// branch happens to be checked out in the hub. A concurrent branch switch cannot redirect\n\t\t\t// this land to another branch, and a changed main ref makes update-ref fail without mutation.\n\t\t\tconst update = await runGit(deps, ctx.topLevel, [\n\t\t\t\t\"update-ref\",\n\t\t\t\t`refs/heads/${ctx.mainBranch}`,\n\t\t\t\ttipSha,\n\t\t\t\tmainShaNow,\n\t\t\t]);\n\t\t\tif (update.code !== 0) {\n\t\t\t\treturn gitError(`main ref changed or could not advance for lane '${lane.laneKey}' (G5)`, update);\n\t\t\t}\n\t\t\tconst landedSha = await revParseBranch(deps, ctx.topLevel, ctx.mainBranch);\n\t\t\tif (landedSha !== tipSha) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"git_error\",\n\t\t\t\t\tmessage: `main resolved to ${landedSha?.slice(0, 12) ?? \"missing\"} after the guarded ref update; expected gated tip ${tipSha.slice(0, 12)}`,\n\t\t\t\t};\n\t\t\t}\n\t\t\t// `update-ref` deliberately leaves a checked-out worktree untouched. Refresh only when\n\t\t\t// the hub is still on main, preserving unrelated local changes with reset --merge.\n\t\t\tconst checkedOut = await runGit(deps, ctx.hubPath, [\"symbolic-ref\", \"--short\", \"HEAD\"]);\n\t\t\tif (checkedOut.code !== 0 || checkedOut.stdout.trim() !== ctx.mainBranch) {\n\t\t\t\treturn {\n\t\t\t\t\tcode: \"git_error\",\n\t\t\t\t\tmessage: `main advanced safely, but hub checkout is no longer '${ctx.mainBranch}'; refusing to refresh another branch`,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst refresh = await runGit(deps, ctx.hubPath, [\"reset\", \"--merge\", tipSha]);\n\t\t\tif (refresh.code !== 0)\n\t\t\t\treturn gitError(`could not refresh hub checkout after landing '${lane.laneKey}'`, refresh);\n\n\t\t\tawait writeLandingTransaction(ctx.paths, { ...landingTransaction, stage: \"main_moved\" });\n\t\t\tconst previous = await readEpoch(ctx.paths);\n\t\t\tconst epochNumber = (previous?.epoch ?? 0) + 1;\n\t\t\tconst at = nowIso(deps);\n\t\t\tawait writeEpoch(ctx.paths, {\n\t\t\t\tepoch: epochNumber,\n\t\t\t\tmainSha: tipSha,\n\t\t\t\tpreviousMainSha: mainShaNow,\n\t\t\t\tlandedLaneKey: lane.laneKey,\n\t\t\t\tlandedAt: at,\n\t\t\t\tchangedPaths: changed.slice(0, EPOCH_CHANGED_PATHS_CAP),\n\t\t\t\tchangedPathsTruncated: changed.length > EPOCH_CHANGED_PATHS_CAP,\n\t\t\t});\n\t\t\tawait writeLandingTransaction(ctx.paths, { ...landingTransaction, stage: \"epoch_written\" });\n\t\t\tawait appendAuditEvent(\n\t\t\t\tctx.paths,\n\t\t\t\t{\n\t\t\t\t\tevent: \"epoch_advanced\",\n\t\t\t\t\tepoch: epochNumber,\n\t\t\t\t\tlaneKey: lane.laneKey,\n\t\t\t\t\tmainSha: tipSha,\n\t\t\t\t\tpreviousMainSha: mainShaNow,\n\t\t\t\t\tchangedFiles: changed.length,\n\t\t\t\t\tgate: args.gate === \"on\" ? \"passed\" : \"off\",\n\t\t\t\t},\n\t\t\t\tat,\n\t\t\t);\n\t\t\tawait writeLandingTransaction(ctx.paths, { ...landingTransaction, stage: \"audit_logged\" });\n\t\t\tawait clearLandingTransaction(ctx.paths);\n\t\t\treturn {\n\t\t\t\tcode: \"ok\",\n\t\t\t\tlaneKey: lane.laneKey,\n\t\t\t\tepoch: epochNumber,\n\t\t\t\tmainSha: tipSha,\n\t\t\t\tgate: args.gate === \"on\" ? \"passed\" : \"off\",\n\t\t\t};\n\t\t} finally {\n\t\t\tawait releaseIntegrationLock(ctx.paths, acquisition.token, { now: deps.now });\n\t\t}\n\t});\n}\n\nexport interface SyncStatusArgs {\n\tpolicy: WorktreeSyncPolicy;\n}\n\n/**\n * The deterministic full picture (the tool's `status` action): epoch, hub, lock, and per-lane\n * live facts with the policy derivation (stale / syncRequired / overlap). The `advice` line is\n * assembled from codes -- never model-generated -- so every agent reads the same situation the\n * same way.\n */\nexport async function buildSyncStatus(deps: WorktreeSyncEngineDeps, args: SyncStatusArgs): Promise<SyncStatusResult> {\n\tconst ctx = await resolveRepoContext(deps);\n\tif (\"code\" in ctx) return ctx;\n\n\tconst epochRecord = await readEpoch(ctx.paths);\n\tconst epochNumber = epochRecord?.epoch ?? 0;\n\tconst changedSet = new Set(epochRecord?.changedPaths ?? []);\n\tconst changedTruncated = epochRecord?.changedPathsTruncated === true;\n\n\tconst holder = await readLockHolder(ctx.paths);\n\tconst isAlive =\n\t\tdeps.isPidAlive ??\n\t\t((pid: number) => defaultIsPidAlive({ pid, hostname: osHostname(), token: \"status\", acquiredAt: \"\" }));\n\n\tlet hub: { path: string; clean: boolean } | undefined;\n\tif (ctx.hubPath) {\n\t\thub = { path: ctx.hubPath, clean: (await dirtyPaths(deps, ctx.hubPath)).length === 0 };\n\t}\n\n\tconst lanes: LaneStatusEntry[] = [];\n\tfor (const lane of await listLanes(ctx.paths)) {\n\t\tif (lane.status === \"released\") continue;\n\t\tconst facts = await deriveLaneFacts(deps, ctx, lane);\n\t\tlet laneChanged: string[] = [];\n\t\tif (facts.branchSha) {\n\t\t\tconst diff = await runGit(deps, ctx.topLevel, [\"diff\", \"--name-only\", `${ctx.mainBranch}...${lane.branch}`]);\n\t\t\tif (diff.code === 0) {\n\t\t\t\tlaneChanged = diff.stdout\n\t\t\t\t\t.split(/\\r?\\n/)\n\t\t\t\t\t.map((line) => line.trim())\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.slice(0, EPOCH_CHANGED_PATHS_CAP);\n\t\t\t}\n\t\t}\n\t\tconst overlap = changedTruncated ? laneChanged : laneChanged.filter((path) => changedSet.has(path));\n\t\tconst stale = !facts.fresh;\n\t\tconst syncRequired =\n\t\t\tstale &&\n\t\t\tfacts.registrationStatus === \"active\" &&\n\t\t\t(args.policy === \"on_land_mandatory\" || (args.policy === \"overlap_mandatory\" && overlap.length > 0));\n\t\tlanes.push({\n\t\t\t...facts,\n\t\t\t...(lane.goalId !== undefined ? { goalId: lane.goalId } : {}),\n\t\t\t...(lane.requirementId !== undefined ? { requirementId: lane.requirementId } : {}),\n\t\t\t...(lane.boundLaneId !== undefined ? { boundLaneId: lane.boundLaneId } : {}),\n\t\t\tstale,\n\t\t\tsyncRequired,\n\t\t\toverlapWithLastLand: overlap.slice(0, 50),\n\t\t});\n\t}\n\n\tconst inRebase = lanes.filter((lane) => lane.rebaseInProgress).map((lane) => lane.laneKey);\n\tconst mustSync = lanes.filter((lane) => lane.syncRequired && !lane.rebaseInProgress).map((lane) => lane.laneKey);\n\tlet advice: string | undefined;\n\tif (inRebase.length > 0) {\n\t\tadvice = `lane(s) ${inRebase.join(\", \")} have a rebase in progress: resolve conflicts and call continue (or abort_sync)`;\n\t} else if (mustSync.length > 0) {\n\t\tadvice = `lane(s) ${mustSync.join(\", \")} must rebase main (epoch ${epochNumber}${epochRecord?.landedLaneKey ? ` landed by ${epochRecord.landedLaneKey}` : \"\"}): call sync`;\n\t} else if (holder) {\n\t\tadvice = `integration lock held by pid ${holder.pid}${holder.laneKey ? ` (lane '${holder.laneKey}')` : \"\"}`;\n\t} else if (lanes.length > 0) {\n\t\tadvice = \"all lanes fresh\";\n\t}\n\n\treturn {\n\t\tcode: \"ok\",\n\t\tmainBranch: ctx.mainBranch,\n\t\tmainSha: ctx.mainSha,\n\t\tepoch: epochNumber,\n\t\t...(hub ? { hub } : {}),\n\t\tlock: {\n\t\t\theld: holder !== undefined,\n\t\t\t...(holder ? { holder, holderAlive: isAlive(holder.pid) } : {}),\n\t\t},\n\t\tlanes,\n\t\t...(advice ? { advice } : {}),\n\t};\n}\n"]}
|