diff --git a/cli/src/__tests__/worktree.test.ts b/cli/src/__tests__/worktree.test.ts index afb83f73..49c445b1 100644 --- a/cli/src/__tests__/worktree.test.ts +++ b/cli/src/__tests__/worktree.test.ts @@ -512,6 +512,45 @@ describe("worktree helpers", () => { } }); + it("preserves repo-managed worktree checkouts when --force re-runs from the source repo", async () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-force-preserve-")); + const repoRoot = path.join(tempRoot, "repo"); + const originalCwd = process.cwd(); + + try { + fs.mkdirSync(repoRoot, { recursive: true }); + const repoConfigDir = path.join(repoRoot, ".paperclip"); + fs.mkdirSync(repoConfigDir, { recursive: true }); + fs.writeFileSync(path.join(repoConfigDir, "config.json"), "stale", "utf8"); + fs.writeFileSync(path.join(repoConfigDir, ".env"), "STALE=1", "utf8"); + + // Simulate the repo-managed worktrees subfolder that holds every + // worktree checkout (the directory PAPA-358 reported as nuked). + const worktreesDir = path.join(repoConfigDir, "worktrees"); + const checkoutDir = path.join(worktreesDir, "PAP-100-feature"); + fs.mkdirSync(checkoutDir, { recursive: true }); + const sentinelPath = path.join(checkoutDir, "sentinel.txt"); + fs.writeFileSync(sentinelPath, "do-not-delete", "utf8"); + + process.chdir(repoRoot); + + await worktreeInitCommand({ + seed: false, + force: true, + fromConfig: path.join(tempRoot, "missing", "config.json"), + home: path.join(tempRoot, ".paperclip-worktrees"), + }); + + expect(fs.existsSync(sentinelPath)).toBe(true); + expect(fs.readFileSync(sentinelPath, "utf8")).toBe("do-not-delete"); + expect(fs.existsSync(path.join(repoConfigDir, "config.json"))).toBe(true); + expect(fs.readFileSync(path.join(repoConfigDir, "config.json"), "utf8")).not.toBe("stale"); + } finally { + process.chdir(originalCwd); + fs.rmSync(tempRoot, { recursive: true, force: true }); + } + }); + itEmbeddedPostgres( "seeds authenticated users into minimally cloned worktree instances", async () => { diff --git a/cli/src/commands/worktree.ts b/cli/src/commands/worktree.ts index faa8e490..fea6f2c3 100644 --- a/cli/src/commands/worktree.ts +++ b/cli/src/commands/worktree.ts @@ -1385,7 +1385,12 @@ async function runWorktreeInit(opts: WorktreeInitOptions): Promise { } if (opts.force) { - rmSync(paths.repoConfigDir, { recursive: true, force: true }); + // Only remove the specific files we're about to rewrite, not the whole + // repoConfigDir — that directory can contain sibling state such as + // /.paperclip/worktrees/ holding every repo-managed worktree + // checkout, and a recursive rmSync here would nuke them all. + rmSync(paths.configPath, { force: true }); + rmSync(paths.envPath, { force: true }); rmSync(paths.instanceRoot, { recursive: true, force: true }); }