import { execFile } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { promisify } from "node:util"; const run = promisify(execFile); export interface Worktree { isolated: boolean; cwd: string; reason?: string; dir?: string; } /** * Create a throwaway git worktree for an isolated agent (opts.isolation: * 'worktree'). Falls back to non-isolated (runs in baseCwd) when the dir isn't a * git repo or git is unavailable — exactly like CC, where isolation is a * best-effort optimization, never a hard requirement. */ export async function createWorktree(baseCwd: string, name: string): Promise { const slug = name.replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 60); try { await run("git", ["rev-parse", "--is-inside-work-tree"], { cwd: baseCwd }); } catch { return { isolated: false, cwd: baseCwd, reason: "not a git repository" }; } const dir = path.join(os.tmpdir(), "cc-wf-worktrees", slug); try { fs.mkdirSync(path.dirname(dir), { recursive: true }); await run("git", ["worktree", "add", "--detach", "-f", dir, "HEAD"], { cwd: baseCwd }); return { isolated: true, cwd: dir, dir }; } catch (err) { return { isolated: false, cwd: baseCwd, reason: (err as Error).message }; } } /** Remove a worktree. Auto-prunes; best-effort (never throws). */ export async function removeWorktree(wt: Worktree, baseCwd: string): Promise { if (!wt.isolated || !wt.dir) return; try { await run("git", ["worktree", "remove", "--force", wt.dir], { cwd: baseCwd }); } catch { try { fs.rmSync(wt.dir, { recursive: true, force: true }); await run("git", ["worktree", "prune"], { cwd: baseCwd }).catch(() => {}); } catch { // best-effort } } }