fix(FN-6079): resolve task tools from merge worktrees

This commit is contained in:
gsxdsm
2026-06-09 08:19:41 -07:00
parent a2f4bb14c0
commit 85c3420f93
4 changed files with 127 additions and 3 deletions

View File

@@ -1,9 +1,14 @@
import { describe, expect, it } from "vitest";
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { execSync } from "node:child_process";
import { getProjectRootFromWorktree, resolvePiExtensionProjectRoot } from "../pi-extensions.js";
function git(cwd: string, args: string): string {
return execSync(`git ${args}`, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
}
describe("getProjectRootFromWorktree", () => {
it("detects POSIX worktree paths", () => {
expect(getProjectRootFromWorktree("/repo/.worktrees/fn-001")).toBe("/repo");
@@ -32,6 +37,35 @@ describe("getProjectRootFromWorktree", () => {
}),
).toBe("/tmp");
});
it("detects arbitrary Git linked worktree paths when the parent has Fusion metadata", () => {
const root = mkdtempSync(join(tmpdir(), "fn-6079-root-"));
const worktreeRoot = mkdtempSync(join(tmpdir(), "fusion-ai-merge-fn-6079-"));
try {
const expectedRoot = realpathSync(root);
git(root, "init -q -b main");
git(root, "config user.email test@example.com");
git(root, "config user.name Test");
mkdirSync(join(root, ".fusion"), { recursive: true });
writeFileSync(join(root, "base.txt"), "base\n");
git(root, "add -A");
git(root, "commit -q -m base");
git(root, `worktree add --detach ${JSON.stringify(worktreeRoot)} HEAD`);
mkdirSync(join(worktreeRoot, "subdir"), { recursive: true });
expect(getProjectRootFromWorktree(worktreeRoot)).toBe(expectedRoot);
expect(getProjectRootFromWorktree(join(worktreeRoot, "subdir"))).toBe(expectedRoot);
expect(resolvePiExtensionProjectRoot(worktreeRoot)).toBe(expectedRoot);
} finally {
try {
git(root, `worktree remove --force ${JSON.stringify(worktreeRoot)}`);
} catch {
// best effort cleanup
}
rmSync(worktreeRoot, { recursive: true, force: true });
rmSync(root, { recursive: true, force: true });
}
});
});
describe("@fusion/core export surface", () => {

View File

@@ -1,6 +1,7 @@
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { basename, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
import { spawnSync } from "node:child_process";
const FUSION_DISABLED_EXTENSIONS_KEY = "fusionDisabledExtensions";
@@ -66,9 +67,42 @@ export function getProjectRootFromWorktree(
}
}
const gitLinkedWorktreeRoot = getProjectRootFromGitLinkedWorktree(cwd);
if (gitLinkedWorktreeRoot) {
return gitLinkedWorktreeRoot;
}
return null;
}
function getProjectRootFromGitLinkedWorktree(cwd: string): string | null {
const resolvedCwd = resolve(cwd);
const commonDir = spawnSync("git", ["rev-parse", "--git-common-dir"], {
cwd: resolvedCwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
const gitDir = spawnSync("git", ["rev-parse", "--git-dir"], {
cwd: resolvedCwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
});
if (commonDir.status !== 0 || gitDir.status !== 0) {
return null;
}
const resolvedCommonDir = resolve(resolvedCwd, commonDir.stdout.trim());
const resolvedGitDir = resolve(resolvedCwd, gitDir.stdout.trim());
if (!resolvedCommonDir || !resolvedGitDir || resolvedCommonDir === resolvedGitDir) {
return null;
}
const parentRoot = resolvedCommonDir.endsWith(`${sep}.git`)
? dirname(resolvedCommonDir)
: resolvedCommonDir;
return existsSync(join(parentRoot, ".fusion")) ? parentRoot : null;
}
export function resolvePiExtensionProjectRoot(cwd: string): string {
const worktreeProjectRoot = getProjectRootFromWorktree(cwd);
if (worktreeProjectRoot && existsSync(join(worktreeProjectRoot, ".fusion"))) {