feat(FN-5320): canonicalize worktrunk binary name and manifest

Implements canonical worktrunk binary naming and manifest handling (FN-5320), adding a worktrunk installer that canonicalizes the executable name and manifest data, with documentation updates for architecture and settings, plus test alignments across routes, audit, and worktree acquisition fixtures.

Fusion-Task-Id: FN-5320
This commit is contained in:
Fusion (runfusion.ai)
2026-05-20 07:44:09 -07:00
committed by gsxdsm
parent 232a9fea86
commit 9011c2107e
14 changed files with 321 additions and 75 deletions

View File

@@ -696,11 +696,13 @@ Guardrails: this routine does **not** retry merges, does **not** apply to mixed/
### Worktree and naming helpers
- `WorktreePool` (`worktree-pool.ts`) — idle worktree reuse
- `WorktreeBackend` (`worktree-backend.ts`) — abstraction for worktree operations used by `acquireTaskWorktree`. `native` (default) preserves existing `git worktree` behavior (including sibling-branch retry semantics), while `resolveWorktreeBackend(settings)` selects `worktrunk` when `settings.worktrunk?.enabled === true`.
- `WorktreeBackend` (`worktree-backend.ts`) — abstraction for worktree operations used by `acquireTaskWorktree`. `native` (default) preserves existing `git worktree` behavior (including sibling-branch retry semantics), while `resolveWorktreeBackend(settings)` selects [worktrunk](https://github.com/max-sixty/worktrunk) when `settings.worktrunk?.enabled === true`.
- Worktrunk path delegates five decisions with per-op timeouts: `create` (120s), `sync` (180s), `prune` (60s), `remove` (60s), and layout resolution (5s).
- Direct worktrunk CLI delegates: `create``wt switch --create ... --no-hooks --no-cd`, `remove``wt remove --foreground`.
- Fusion probes the canonical `wt` binary on `$PATH`; explicit `worktrunk.binaryPath` overrides still win when operators pin a different location.
- Worktrunk-aware fallback implementations where worktrunk lacks a dedicated primitive: `sync` uses git fetch+rebase semantics, and `prune` uses `git worktree list --porcelain` plus per-branch `remove` calls.
- Layout precedence: when `worktrunk.enabled=true`, `resolveTaskWorktreePathForBackend(...)` defers to backend `resolveWorktreePath(...)` (using `wt config show --format json` template data with default `{{ repo_path }}/.worktrees/{{ branch | sanitize }}` fallback); otherwise it remains byte-identical to FN-4606 `resolveTaskWorktreePath(...)` behavior.
- Auto-install remains fail-closed while the pinned release manifest is `upstream-pending-verification`: the pre-approved install path now rejects missing asset URLs/checksums instead of fabricating a local binary. This preserves the FN-4704/FN-4705 disabled-install contract until a human verifies a real upstream release manifest.
- `worktrunk.onFailure` controls fail-hard vs fallback-native create behavior and emits `worktree:worktrunk-*` run-audit events for create/fallback paths.
- `WorktreeNames` (`worktree-names.ts`) — deterministic worktree/branch naming
@@ -1534,7 +1536,7 @@ The GitHub tracking state listener now attaches to every registered project stor
#### WorktreeBackend abstraction
- Backend contract: `WorktreeBackend` (`packages/engine/src/worktree-backend.ts`, re-exported via `packages/engine/src/worktree-pool.ts`).
- Implementations: `NativeWorktreeBackend` (Fusion-managed `git worktree` flow) and `WorktrunkWorktreeBackend` (delegates to external `worktrunk` CLI).
- Implementations: `NativeWorktreeBackend` (Fusion-managed `git worktree` flow) and `WorktrunkWorktreeBackend` (delegates to the external `wt` CLI from [max-sixty/worktrunk](https://github.com/max-sixty/worktrunk)).
- Backend selection is driven by `worktrunk.enabled`; when enabled, worktrunk-managed layout overrides `worktreesDir` for delegated operations.
- Worktrunk layout is authoritative on create: after `wt switch --create`, Fusion resolves the actual registered worktree path via `git worktree list --porcelain` and uses that path (instead of assuming `resolveTaskWorktreePath` alignment).
- Delegated operation surface in the interface: `create`, `sync`, `prune`, `remove` (plus backend path resolution via `resolveWorktreePath`).
@@ -1542,6 +1544,7 @@ The GitHub tracking state listener now attaches to every registered project stor
- Worktree removal is backend-mediated across merger, self-healing, worktree-pool, executor, and step-session cleanup paths via `removeWorktree(...)` (`WorktreeBackend.remove()`).
- Self-healing is worktrunk-aware for failure recovery: tasks paused with `pausedReason: "worktrunk_operation_failed"` are explicitly skipped in reclaim sweeps (`self-healing.ts`) until operator intervention.
- Failure contract: delegated worktrunk errors preserve stderr context (`WorktrunkOperationError`) and are handled by `worktrunk.onFailure``"fail"` pauses the task, while `"fallback-native"` retries on the native backend and emits one-shot fallback telemetry.
- Install contract: Fusion only auto-installs from a source-of-truth manifest. The shipped placeholder manifest intentionally stays in `upstream-pending-verification` until a human verifies upstream asset URLs and checksums, so install attempts fail closed rather than guessing release metadata.
#### Stale `index.lock` recovery on worktree create
- Native worktree create paths now classify `git worktree add` failures containing `.../index.lock: File exists` before falling back to generic branch-conflict handling.

View File

@@ -273,8 +273,8 @@ Sandbox backend precedence is:
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `worktreesDir` | `string` | `undefined` | Optional container directory for task worktrees. Supports absolute paths, project-relative paths, `~` expansion, and `{repo}` token substitution (project root basename). Defaults to `<projectRoot>/.worktrees` when unset and applies to newly-created worktrees/pool scans. When `worktrunk.enabled` is `true`, worktrunk-managed layout takes precedence and this directory is ignored until worktrunk is disabled. |
| `worktrunk.enabled` | `boolean` | `false` | Enables the worktrunk backend (`WorktreeBackend`) for worktree operations. When enabled, worktrunk layout supersedes Fusions `.worktrees/<task-id>` and `worktreesDir` behavior. This key exists in global and project settings; project values override global values for matching fields. Setting this to `true` is rejected by the settings API and CLI until the pinned `worktrunk` binary resolves and probe-verifies. Install first via Settings → Worktrunk integration (or `GET /api/worktrunk/status` + `POST /api/worktrunk/install-request`). See [Architecture: WorktreeBackend abstraction](./architecture.md#worktreebackend-abstraction). |
| `worktrunk.binaryPath` | `string \| undefined` | `undefined` | Optional absolute override for the `worktrunk` binary. When unset, Fusion resolves from `$PATH`/cached install path and then falls through to the auto-install flow on first use (guarded by `network_api` action-gate approval for `worktrunk_install`; currently disabled by default). When enabling `worktrunk.enabled`, this resolved/overridden path is still probe-verified before the setting is accepted. |
| `worktrunk.enabled` | `boolean` | `false` | Enables the worktrunk backend (`WorktreeBackend`) for worktree operations. When enabled, worktrunk layout supersedes Fusions `.worktrees/<task-id>` and `worktreesDir` behavior. This key exists in global and project settings; project values override global values for matching fields. Setting this to `true` is rejected by the settings API and CLI until the pinned `wt` binary resolves and probe-verifies. Install first via Settings → Worktrunk integration (or `GET /api/worktrunk/status` + `POST /api/worktrunk/install-request`). Auto-install remains fail-closed until the upstream manifest is human-verified, so the default placeholder manifest will not fabricate a binary. See [Architecture: WorktreeBackend abstraction](./architecture.md#worktreebackend-abstraction). |
| `worktrunk.binaryPath` | `string \| undefined` | `undefined` | Optional absolute override for the `wt` binary. When unset, Fusion probes `wt` on `$PATH`, then checks the cached install path, and only then considers the gated auto-install flow. Auto-install is currently fail-closed until the upstream manifest is human-verified, so operators who enable `worktrunk.enabled` should set `worktrunk.binaryPath` or install `wt` themselves. When enabling `worktrunk.enabled`, this resolved/overridden path is still probe-verified before the setting is accepted. |
| `worktrunk.onFailure` | `"fail" \| "fallback-native"` | `"fail"` | Failure behavior for delegated worktrunk operations. `"fail"` (default) pauses the task with `pausedReason: "worktrunk_operation_failed"` and surfaces worktrunk stderr via `task.worktrunkFailure`. `"fallback-native"` switches to the native backend and emits a one-shot dashboard fallback alert per task (`task.worktrunkFallbackAlertedAt`). |
Default notes: