reconcilePhantomCommittedReservations emitted a task:reconcile-phantom-committed-reservation audit row every maintenance tick for the same orphaned committed reservations even when zero child rows were pruned (~19k no-op writes/day). Gate the audit emission on actual pruned work; the FN-7069 contract is unchanged (committed reservation stays committed so the ID is never reused).
clearPhantomExecutorBinding unconditionally unregistered held worktree paths from activeSessionRegistry, defeating the moveTask(preserveWorktree:true) the self-healing phantom reclaim pairs it with: re-dispatch then acquired a brand-new worktree instead of reattaching (FN-7249 lost its first worktree this way). Add a preserveWorktrees option that clears only the stale in-memory executor/lock bookkeeping and leaves the session-registry path entries intact; the self-healing caller opts in. Non-self-healing callers keep the default full-clear behavior.
- base-commit-capture.ts: shell-quote integration branch with a real
single-quoted POSIX literal instead of JSON.stringify (not shell-safe).
- TaskCard.tsx: memo compares full workspaceWorktrees values, not just key
sets, so a same-key worktreePath/branch change re-renders.
- TaskDetailModal.tsx: gate/render workspace summary off hydrated workingTask.
- worktree-acquisition.ts: null the singular worktree/branch columns in the
workspaceWorktrees write so isWorkspaceTask stays true; wrap non-fatal
post-acquire observability so logEntry/audit can't re-escalate to fatal.
- agent-tools.ts: register sub-repo worktree via onAcquired unconditionally
(idempotent) so a resumed/already-acquired path is tracked after restart.
- executor.ts: DB liveness fallback also checks task.workspaceWorktrees paths.
- executor-workspace.test.ts: root non-git assertion runs in fx.rootDir (".").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In workspace mode (loadWorkspaceConfig present), the executor now skips the
root acquireTaskWorktree({rootDir}) and every intervening rootDir git preflight
(base-commit capture, contamination, identity-guard, verifyWorktreeInvariants),
runs the agent session rooted at the non-git workspace root (cwd=rootDir,
browse-only; task.worktree never set), and tracks activeWorktrees as a per-task
Set<path>. scopePromptToWorktree is a no-op in workspace mode. The non-workspace
path is unchanged (every change branches on this.workspaceConfig; a single-repo
task holds a one-element Set).
Converted every activeWorktrees consumer to membership semantics (feasibility-
verified list): findActiveWorktreeOwner, hasActiveWorktreeBinding, the FN-6736
phantom-binding reclaim, listWorktreeHolders (flat-maps a Set into N holder rows
— verified the FN-6782 reaper keys off taskId only, so slot accounting is
unaffected), the conflict-set iteration, the three deleteActive* unregister
resolvers (loop every path), cleanup, getWorktreePath (undefined for a
multi-worktree workspace task), and the verifyWorktreeInvariants singular
resolution (gated off in workspace mode — per-repo verify returns in Phase B).
Rewrote executor-workspace.test.ts from vi.mock-the-subject to a real two-repo
git fixture harness (_workspace-fixture.ts, shared with later units), 13 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Allow registering a non-git parent directory that contains multiple git
repositories as a single Fusion project. The agent acquires per-repo
worktrees on demand via a new `fn_acquire_repo_worktree` tool as it
discovers it needs to work in each sub-repo.
This commit lays the foundation:
- detectWorkspaceRepos / loadWorkspaceConfig / saveWorkspaceConfig in
@fusion/core (config persisted to .fusion/workspace.json)
- Task.workspaceWorktrees data model + store plumbing (per-repo
worktree/branch map, distinct from the singular task.worktree)
- acquireWorkspaceRepoWorktree wraps acquireTaskWorktree per sub-repo,
clearing the singular worktree/branch fields so each sub-repo gets a
fresh worktree instead of resuming a sibling repo's worktree
- fn_acquire_repo_worktree agent tool + workspace prompt injection
- executor git-repository validation bypassed when a workspace config
is present
- CLI `fn init` detects a non-git dir containing sub-repos and writes a
workspace config
Known gap (intentionally left for design discussion, see PR): the
executor's main worktree-acquisition path still assumes a single git
root and is not yet workspace-aware. End-to-end execution (skipping the
root acquisition, per-repo merge, per-repo session scoping) is a
follow-on once the execution model is agreed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>