Add configurable worktree root

* Add configurable worktree root

Allow new Paseo worktrees to be created under a custom worktrees.root from config.json while preserving the existing PASEO_HOME/worktrees default. Thread the resolved root through create/list/archive/ownership flows, MCP/session paths, checkout metadata, schema, docs, and targeted tests.

* Address worktree root review feedback

Use the resolved Paseo worktree base when a custom PASEO_HOME is provided, and remove an unused worktreesRoot dependency from worktree list handling.

* Fix checkout worktree path test on Windows

Use platform-native paths in the custom PASEO_HOME regression test so Windows CI compares against the same resolved root shape as production code.

* Clarify optional worktrees config docs

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Matt Cowger
2026-06-01 16:19:29 +08:00
committed by GitHub
co-authored by Mohamed Boudra
parent 8c6abcb41f
commit d406c29e16
25 changed files with 324 additions and 42 deletions
@@ -49,6 +49,7 @@ interface LegacyCreateWorktreeTestOptions {
worktreeSlug: string;
runSetup?: boolean;
paseoHome?: string;
worktreesRoot?: string;
}
function createLegacyWorktreeForTest(
@@ -68,6 +69,7 @@ function createLegacyWorktreeForTest(
},
runSetup: options.runSetup ?? true,
paseoHome: options.paseoHome,
worktreesRoot: options.worktreesRoot,
});
}
@@ -118,6 +120,38 @@ describe.skipIf(isPlatform("win32"))("worktree POSIX-only", () => {
expect(metadata).toMatchObject({ version: 1, baseRefName: "main" });
});
it("creates and owns worktrees under a configured root", async () => {
const worktreesRoot = join(tempDir, "custom-worktrees");
const projectHash = await deriveWorktreeProjectHash(repoDir);
const result = await createLegacyWorktreeForTest({
branchName: "custom-root",
cwd: repoDir,
baseBranch: "main",
worktreeSlug: "custom-root",
paseoHome,
worktreesRoot,
});
expect(result.worktreePath).toBe(join(worktreesRoot, projectHash, "custom-root"));
await expect(
isPaseoOwnedWorktreeCwd(result.worktreePath, { paseoHome, worktreesRoot }),
).resolves.toMatchObject({ allowed: true, worktreeRoot: join(worktreesRoot, projectHash) });
await expect(
isPaseoOwnedWorktreeCwd(result.worktreePath, { paseoHome }),
).resolves.toMatchObject({ allowed: false });
const worktrees = await listPaseoWorktrees({ cwd: repoDir, paseoHome, worktreesRoot });
expect(worktrees.map((entry) => entry.path)).toContain(result.worktreePath);
await deletePaseoWorktree({
cwd: repoDir,
worktreePath: result.worktreePath,
paseoHome,
worktreesBaseRoot: worktreesRoot,
});
expect(existsSync(result.worktreePath)).toBe(false);
});
it.skip("detects paseo-owned worktrees across realpath differences (macOS /var vs /private/var)", async () => {
// Intentionally create repo using the non-realpath tmpdir() variant (often /var/... on macOS).
const varTempDir = mkdtempSync(join(tmpdir(), "worktree-realpath-test-"));