{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/worktree.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { type AuthorityPolicyConfig, resolveAuthorityDecision } from \"../../policy/authority.ts\";\n\nexport interface WorktreeSetup {\n\tcwd: string;\n\tworktrees: WorktreeInfo[];\n\tbaseCommit: string;\n\tcapturedDiffs?: WorktreeDiff[];\n}\n\nexport interface WorktreeInfo {\n\tpath: string;\n\tagentCwd: string;\n\tbranch: string;\n\tindex: number;\n\tnodeModulesLinked: boolean;\n\tsyntheticPaths: string[];\n}\n\nexport interface WorktreeDiff {\n\tindex: number;\n\tagent: string;\n\tbranch: string;\n\tdiffStat: string;\n\tfilesChanged: number;\n\tinsertions: number;\n\tdeletions: number;\n\tpatchPath: string;\n\terror?: string;\n}\n\nexport interface WorktreeCleanupTask {\n\tindex: number;\n\tpath: string;\n\tbranch: string;\n\tworktreeRemoved: boolean;\n\tbranchRemoved: boolean;\n\tpreserved?: boolean;\n\treason?: string;\n\terrors?: string[];\n}\n\nexport type WorktreeCleanupIntent =\n\t| { kind: \"preserve\"; capturedDiffs?: WorktreeDiff[]; handoffManifestPath?: string }\n\t| {\n\t\t\tkind: \"discard\";\n\t\t\tauthorization:\n\t\t\t\t| { kind: \"policy\"; policy?: AuthorityPolicyConfig }\n\t\t\t\t| { kind: \"confirmed\"; policy?: AuthorityPolicyConfig };\n\t  }\n\t| { kind: \"setup-rollback\" };\n\nexport interface WorktreeCleanupReport {\n\tstate: \"complete\" | \"partial\";\n\ttasks: WorktreeCleanupTask[];\n\tpruned: boolean;\n\terrors?: string[];\n}\n\ninterface WorktreeTaskCwdConflict {\n\tindex: number;\n\tagent: string;\n\tcwd: string;\n}\n\ninterface WorktreeSetupHookConfig {\n\thookPath: string;\n\ttimeoutMs?: number;\n}\n\ninterface CreateWorktreesOptions {\n\tagents?: string[];\n\tsetupHook?: WorktreeSetupHookConfig;\n\tbaseDir?: string;\n}\n\ninterface ResolvedWorktreeSetupHook {\n\thookPath: string;\n\ttimeoutMs: number;\n}\n\ninterface WorktreeSetupHookInput {\n\tversion: 1;\n\trepoRoot: string;\n\tworktreePath: string;\n\tagentCwd: string;\n\tbranch: string;\n\tindex: number;\n\trunId: string;\n\tbaseCommit: string;\n\tagent?: string;\n}\n\ninterface WorktreeSetupHookOutput {\n\tsyntheticPaths?: string[];\n}\n\ninterface GitResult {\n\tstdout: string;\n\tstderr: string;\n\tstatus: number | null;\n}\n\ninterface RepoState {\n\ttoplevel: string;\n\tcwdRelative: string;\n\tbaseCommit: string;\n}\n\nconst DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS = 30000;\n\nfunction runGit(cwd: string, args: string[]): GitResult {\n\tconst result = spawnSync(\"git\", [\"-C\", cwd, ...args], { encoding: \"utf-8\" });\n\treturn {\n\t\tstdout: result.stdout ?? \"\",\n\t\tstderr: result.stderr ?? \"\",\n\t\tstatus: result.status,\n\t};\n}\n\nfunction runGitChecked(cwd: string, args: string[]): string {\n\tconst result = runGit(cwd, args);\n\tif (result.status !== 0) {\n\t\tconst command = `git -C ${cwd} ${args.join(\" \")}`;\n\t\tconst message = result.stderr.trim() || result.stdout.trim() || `${command} failed`;\n\t\tthrow new Error(message);\n\t}\n\treturn result.stdout;\n}\n\nfunction resolveRepoState(cwd: string): RepoState {\n\tconst cwdRelative = resolveRepoCwdRelative(cwd);\n\tconst toplevel = runGitChecked(cwd, [\"rev-parse\", \"--show-toplevel\"]).trim();\n\n\t// pi-subagents writes durable runtime state under .pi-subagents/ by default;\n\t// that state must not make managed isolation unusable for later runs.\n\tconst status = runGitChecked(toplevel, [\"status\", \"--porcelain\", \"--\", \":!.pi-subagents\"]);\n\tif (status.trim().length > 0) {\n\t\tthrow new Error(\"worktree isolation requires a clean git working tree. Commit or stash changes first.\");\n\t}\n\n\tconst baseCommit = runGitChecked(toplevel, [\"rev-parse\", \"HEAD\"]).trim();\n\treturn { toplevel, cwdRelative, baseCommit };\n}\n\nfunction normalizeComparableCwd(cwd: string): string {\n\tconst resolved = path.resolve(cwd);\n\ttry {\n\t\treturn fs.realpathSync(resolved);\n\t} catch {\n\t\t// Use the unresolved absolute path when realpath resolution is unavailable.\n\t\treturn resolved;\n\t}\n}\n\nexport function findWorktreeTaskCwdConflict(\n\ttasks: ReadonlyArray<{ agent: string; cwd?: string }>,\n\tsharedCwd: string,\n): WorktreeTaskCwdConflict | undefined {\n\tconst normalizedSharedCwd = normalizeComparableCwd(sharedCwd);\n\tfor (let index = 0; index < tasks.length; index++) {\n\t\tconst task = tasks[index]!;\n\t\tif (!task.cwd) continue;\n\t\tconst taskCwd = path.isAbsolute(task.cwd) ? task.cwd : path.resolve(sharedCwd, task.cwd);\n\t\tif (normalizeComparableCwd(taskCwd) === normalizedSharedCwd) continue;\n\t\treturn { index, agent: task.agent, cwd: task.cwd };\n\t}\n\treturn undefined;\n}\n\nexport function formatWorktreeTaskCwdConflict(conflict: WorktreeTaskCwdConflict, sharedCwd: string): string {\n\treturn `worktree isolation uses the shared cwd (${sharedCwd}); task ${conflict.index + 1} (${conflict.agent}) sets cwd to ${conflict.cwd}. Remove task-level cwd overrides or disable worktree.`;\n}\n\nfunction safePatchAgentName(agent: string): string {\n\treturn agent.replace(/[^\\w.-]/g, \"_\");\n}\n\nfunction buildWorktreeBranch(runId: string, index: number): string {\n\treturn `pi-parallel-${runId}-${index}`;\n}\n\nfunction resolveWorktreeBaseDir(configuredBaseDir: string | undefined, repoRoot: string): string {\n\tconst rawBaseDir = configuredBaseDir ?? process.env.PI_SUBAGENTS_WORKTREE_DIR;\n\tif (rawBaseDir === undefined) return os.tmpdir();\n\n\tconst trimmed = rawBaseDir.trim();\n\tif (!trimmed) throw new Error(\"worktree base directory cannot be empty\");\n\n\tconst expanded = trimmed.startsWith(\"~/\") ? path.join(os.homedir(), trimmed.slice(2)) : trimmed;\n\tconst resolved = path.isAbsolute(expanded) ? expanded : path.resolve(repoRoot, expanded);\n\ttry {\n\t\tfs.mkdirSync(resolved, { recursive: true });\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`failed to create worktree base directory ${resolved}: ${message}`);\n\t}\n\treturn resolved;\n}\n\nfunction buildWorktreePath(baseDir: string, runId: string, index: number): string {\n\treturn path.join(baseDir, `pi-worktree-${runId}-${index}`);\n}\n\nfunction resolveRepoCwdRelative(cwd: string): string {\n\tconst repoCheck = runGit(cwd, [\"rev-parse\", \"--is-inside-work-tree\"]);\n\tif (repoCheck.status !== 0 || repoCheck.stdout.trim() !== \"true\") {\n\t\tthrow new Error(\"worktree isolation requires a git repository\");\n\t}\n\tconst rawPrefix = runGitChecked(cwd, [\"rev-parse\", \"--show-prefix\"]).trim();\n\tconst normalizedPrefix = rawPrefix ? path.normalize(rawPrefix.replace(/[\\\\/]+$/, \"\")) : \"\";\n\treturn normalizedPrefix === \".\" ? \"\" : normalizedPrefix;\n}\n\nexport function resolveExpectedWorktreeAgentCwd(cwd: string, runId: string, index: number, baseDir?: string): string {\n\tconst cwdRelative = resolveRepoCwdRelative(cwd);\n\tconst repoRoot = runGitChecked(cwd, [\"rev-parse\", \"--show-toplevel\"]).trim();\n\tconst worktreePath = buildWorktreePath(resolveWorktreeBaseDir(baseDir, repoRoot), runId, index);\n\treturn cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;\n}\n\nfunction linkNodeModulesIfPresent(toplevel: string, worktreePath: string): boolean {\n\tconst nodeModulesPath = path.join(toplevel, \"node_modules\");\n\tconst nodeModulesLinkPath = path.join(worktreePath, \"node_modules\");\n\tif (!fs.existsSync(nodeModulesPath) || fs.existsSync(nodeModulesLinkPath)) return false;\n\ttry {\n\t\tfs.symlinkSync(nodeModulesPath, nodeModulesLinkPath);\n\t\treturn true;\n\t} catch {\n\t\t// Symlink creation is optional (e.g., unsupported filesystems on CI runners).\n\t\treturn false;\n\t}\n}\n\nfunction parseHookTimeout(timeoutMs: number | undefined): number {\n\tif (timeoutMs === undefined) return DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS;\n\tif (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {\n\t\tthrow new Error(\"worktree setup hook timeout must be an integer greater than 0\");\n\t}\n\treturn timeoutMs;\n}\n\nfunction resolveWorktreeSetupHook(\n\trepoRoot: string,\n\tconfig: WorktreeSetupHookConfig | undefined,\n): ResolvedWorktreeSetupHook | undefined {\n\tif (!config) return undefined;\n\tconst hookPath = config.hookPath.trim();\n\tif (!hookPath) {\n\t\tthrow new Error(\"worktree setup hook path cannot be empty\");\n\t}\n\n\tconst expandedHookPath = hookPath.startsWith(\"~/\") ? path.join(os.homedir(), hookPath.slice(2)) : hookPath;\n\tlet resolvedPath: string;\n\tif (path.isAbsolute(expandedHookPath)) {\n\t\tresolvedPath = expandedHookPath;\n\t} else if (expandedHookPath.includes(\"/\") || expandedHookPath.includes(\"\\\\\")) {\n\t\tresolvedPath = path.resolve(repoRoot, expandedHookPath);\n\t} else {\n\t\tthrow new Error(\"worktree setup hook must be an absolute path or a repo-relative path\");\n\t}\n\n\tif (!fs.existsSync(resolvedPath)) {\n\t\tthrow new Error(`worktree setup hook not found: ${resolvedPath}`);\n\t}\n\tif (fs.statSync(resolvedPath).isDirectory()) {\n\t\tthrow new Error(`worktree setup hook must be a file, got directory: ${resolvedPath}`);\n\t}\n\n\treturn {\n\t\thookPath: resolvedPath,\n\t\ttimeoutMs: parseHookTimeout(config.timeoutMs),\n\t};\n}\n\nfunction normalizeSyntheticPath(worktreePath: string, rawPath: string): string {\n\tconst trimmed = rawPath.trim();\n\tif (!trimmed) throw new Error(\"synthetic path cannot be empty\");\n\tif (path.isAbsolute(trimmed)) throw new Error(`synthetic path must be relative: ${rawPath}`);\n\n\tconst resolved = path.resolve(worktreePath, trimmed);\n\tconst relative = path.relative(worktreePath, resolved);\n\tif (!relative || relative === \".\") {\n\t\tthrow new Error(`synthetic path cannot target the worktree root: ${rawPath}`);\n\t}\n\tif (relative === \"..\" || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {\n\t\tthrow new Error(`synthetic path escapes the worktree root: ${rawPath}`);\n\t}\n\treturn path.normalize(relative);\n}\n\nfunction hasTrackedEntries(worktreePath: string, relativePath: string): boolean {\n\tconst result = runGit(worktreePath, [\"ls-files\", \"--\", relativePath]);\n\treturn result.status === 0 && result.stdout.trim().length > 0;\n}\n\nfunction parseWorktreeSetupHookOutput(rawStdout: string): WorktreeSetupHookOutput {\n\tconst trimmed = rawStdout.trim();\n\tif (!trimmed) {\n\t\tthrow new Error(\"worktree setup hook returned empty stdout; expected JSON object\");\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(trimmed);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`worktree setup hook returned invalid JSON: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(\"worktree setup hook stdout must be a JSON object\");\n\t}\n\treturn parsed as WorktreeSetupHookOutput;\n}\n\nfunction runWorktreeSetupHook(hook: ResolvedWorktreeSetupHook, input: WorktreeSetupHookInput): string[] {\n\tconst result = spawnSync(hook.hookPath, [], {\n\t\tcwd: input.worktreePath,\n\t\tencoding: \"utf-8\",\n\t\tinput: JSON.stringify(input),\n\t\ttimeout: hook.timeoutMs,\n\t\tshell: false,\n\t});\n\n\tif (result.error) {\n\t\tconst code = \"code\" in result.error ? result.error.code : undefined;\n\t\tif (code === \"ETIMEDOUT\") {\n\t\t\tthrow new Error(`worktree setup hook timed out after ${hook.timeoutMs}ms`);\n\t\t}\n\t\tthrow new Error(`worktree setup hook failed: ${result.error.message}`);\n\t}\n\n\tif (result.status !== 0) {\n\t\tconst details = result.stderr.trim() || result.stdout.trim() || \"no output\";\n\t\tthrow new Error(`worktree setup hook failed with exit code ${result.status}: ${details}`);\n\t}\n\n\tconst output = parseWorktreeSetupHookOutput(result.stdout);\n\tif (output.syntheticPaths === undefined) return [];\n\tif (!Array.isArray(output.syntheticPaths)) {\n\t\tthrow new Error(\"worktree setup hook output field 'syntheticPaths' must be an array of relative paths\");\n\t}\n\n\tconst uniquePaths = new Set<string>();\n\tfor (const candidate of output.syntheticPaths) {\n\t\tif (typeof candidate !== \"string\") {\n\t\t\tthrow new Error(\"worktree setup hook output field 'syntheticPaths' must contain only strings\");\n\t\t}\n\t\tconst normalizedPath = normalizeSyntheticPath(input.worktreePath, candidate);\n\t\tif (hasTrackedEntries(input.worktreePath, normalizedPath)) {\n\t\t\tthrow new Error(`worktree setup hook cannot mark tracked paths as synthetic: ${normalizedPath}`);\n\t\t}\n\t\tuniquePaths.add(normalizedPath);\n\t}\n\treturn [...uniquePaths];\n}\n\nfunction createSingleWorktree(\n\ttoplevel: string,\n\tcwdRelative: string,\n\trunId: string,\n\tindex: number,\n\tbaseCommit: string,\n\tsetupHook: ResolvedWorktreeSetupHook | undefined,\n\tagent: string | undefined,\n\tbaseDir: string,\n): WorktreeInfo {\n\tconst branch = buildWorktreeBranch(runId, index);\n\tconst worktreePath = buildWorktreePath(baseDir, runId, index);\n\tconst add = runGit(toplevel, [\"worktree\", \"add\", worktreePath, \"-b\", branch, \"HEAD\"]);\n\tif (add.status !== 0) {\n\t\tconst message = add.stderr.trim() || add.stdout.trim() || `failed to create worktree ${worktreePath}`;\n\t\tthrow new Error(message);\n\t}\n\n\tconst agentCwd = cwdRelative ? path.join(worktreePath, cwdRelative) : worktreePath;\n\ttry {\n\t\tconst nodeModulesLinked = linkNodeModulesIfPresent(toplevel, worktreePath);\n\t\tconst syntheticPaths = nodeModulesLinked ? [\"node_modules\"] : [];\n\n\t\tif (setupHook) {\n\t\t\tconst hookSyntheticPaths = runWorktreeSetupHook(setupHook, {\n\t\t\t\tversion: 1,\n\t\t\t\trepoRoot: toplevel,\n\t\t\t\tworktreePath,\n\t\t\t\tagentCwd,\n\t\t\t\tbranch,\n\t\t\t\tindex,\n\t\t\t\trunId,\n\t\t\t\tbaseCommit,\n\t\t\t\tagent,\n\t\t\t});\n\t\t\tsyntheticPaths.push(...hookSyntheticPaths);\n\t\t}\n\n\t\treturn {\n\t\t\tpath: worktreePath,\n\t\t\tagentCwd,\n\t\t\tbranch,\n\t\t\tindex,\n\t\t\tnodeModulesLinked,\n\t\t\tsyntheticPaths,\n\t\t};\n\t} catch (error) {\n\t\ttry {\n\t\t\trunGitChecked(toplevel, [\"worktree\", \"remove\", \"--force\", worktreePath]);\n\t\t} catch {\n\t\t\t// Best-effort rollback; preserve the original setup failure.\n\t\t}\n\t\ttry {\n\t\t\trunGitChecked(toplevel, [\"branch\", \"-D\", branch]);\n\t\t} catch {\n\t\t\t// Best-effort rollback; preserve the original setup failure.\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nfunction removeSyntheticPath(worktree: WorktreeInfo, syntheticPath: string): void {\n\tconst resolved = path.resolve(worktree.path, syntheticPath);\n\tconst relative = path.relative(worktree.path, resolved);\n\tif (\n\t\t!relative ||\n\t\trelative === \".\" ||\n\t\trelative === \"..\" ||\n\t\trelative.startsWith(`..${path.sep}`) ||\n\t\tpath.isAbsolute(relative)\n\t) {\n\t\treturn;\n\t}\n\n\tlet stat: fs.Stats;\n\ttry {\n\t\tstat = fs.lstatSync(resolved);\n\t} catch (error) {\n\t\tconst code =\n\t\t\terror && typeof error === \"object\" && \"code\" in error ? (error as { code?: unknown }).code : undefined;\n\t\tif (code === \"ENOENT\") return;\n\t\tthrow error;\n\t}\n\n\tif (stat.isSymbolicLink()) {\n\t\tfs.unlinkSync(resolved);\n\t\treturn;\n\t}\n\tif (stat.isDirectory()) {\n\t\tfs.rmSync(resolved, { recursive: true, force: true });\n\t\treturn;\n\t}\n\tfs.rmSync(resolved, { force: true });\n}\n\nfunction removeSyntheticPathsBeforeDiff(worktree: WorktreeInfo): void {\n\tif (worktree.syntheticPaths.length === 0) return;\n\tconst seen = new Set<string>();\n\tfor (const syntheticPath of worktree.syntheticPaths) {\n\t\tif (seen.has(syntheticPath)) continue;\n\t\tseen.add(syntheticPath);\n\t\tremoveSyntheticPath(worktree, syntheticPath);\n\t}\n}\n\nfunction emptyDiff(index: number, agent: string, branch: string, patchPath: string, error?: string): WorktreeDiff {\n\treturn {\n\t\tindex,\n\t\tagent,\n\t\tbranch,\n\t\tdiffStat: \"\",\n\t\tfilesChanged: 0,\n\t\tinsertions: 0,\n\t\tdeletions: 0,\n\t\tpatchPath,\n\t\t...(error ? { error } : {}),\n\t};\n}\n\nfunction parseNumstat(numstat: string): { filesChanged: number; insertions: number; deletions: number } {\n\tconst lines = numstat\n\t\t.split(\"\\n\")\n\t\t.map((line) => line.trim())\n\t\t.filter(Boolean);\n\tlet filesChanged = 0;\n\tlet insertions = 0;\n\tlet deletions = 0;\n\n\tfor (const line of lines) {\n\t\tconst [rawInsertions, rawDeletions] = line.split(\"\\t\");\n\t\tif (rawInsertions === undefined || rawDeletions === undefined) continue;\n\t\tfilesChanged++;\n\t\tif (/^\\d+$/.test(rawInsertions)) insertions += parseInt(rawInsertions, 10);\n\t\tif (/^\\d+$/.test(rawDeletions)) deletions += parseInt(rawDeletions, 10);\n\t}\n\n\treturn { filesChanged, insertions, deletions };\n}\n\nfunction captureWorktreeDiff(\n\tsetup: WorktreeSetup,\n\tworktree: WorktreeInfo,\n\tagent: string,\n\tpatchPath: string,\n): WorktreeDiff {\n\tremoveSyntheticPathsBeforeDiff(worktree);\n\trunGitChecked(worktree.path, [\"add\", \"-A\"]);\n\tconst diffStat = runGitChecked(worktree.path, [\"diff\", \"--cached\", \"--stat\", setup.baseCommit]).trim();\n\tconst patch = runGitChecked(worktree.path, [\"diff\", \"--cached\", setup.baseCommit]);\n\tconst numstat = runGitChecked(worktree.path, [\"diff\", \"--cached\", \"--numstat\", setup.baseCommit]);\n\tfs.writeFileSync(patchPath, patch, \"utf-8\");\n\n\tif (!patch.trim()) {\n\t\treturn emptyDiff(worktree.index, agent, worktree.branch, patchPath);\n\t}\n\n\tconst parsed = parseNumstat(numstat);\n\treturn {\n\t\tindex: worktree.index,\n\t\tagent,\n\t\tbranch: worktree.branch,\n\t\tdiffStat,\n\t\tfilesChanged: parsed.filesChanged,\n\t\tinsertions: parsed.insertions,\n\t\tdeletions: parsed.deletions,\n\t\tpatchPath,\n\t};\n}\n\nfunction writeEmptyPatch(patchPath: string): void {\n\ttry {\n\t\tfs.writeFileSync(patchPath, \"\", \"utf-8\");\n\t} catch {\n\t\t// Diff artifact writing is best-effort in error paths.\n\t}\n}\n\nfunction handoffRecordsPatch(manifestPath: string | undefined, patchPath: string): boolean {\n\tif (!manifestPath || !fs.existsSync(manifestPath)) return false;\n\ttry {\n\t\tconst manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf-8\")) as {\n\t\t\tversion?: unknown;\n\t\t\tgroups?: Array<{ children?: Array<{ patch?: { path?: unknown; error?: unknown } }> }>;\n\t\t};\n\t\tif (manifest.version !== 1 || !Array.isArray(manifest.groups)) return false;\n\t\tconst resolvedPatchPath = path.resolve(patchPath);\n\t\treturn manifest.groups.some(\n\t\t\t(group) =>\n\t\t\t\tArray.isArray(group.children) &&\n\t\t\t\tgroup.children.some(\n\t\t\t\t\t(child) =>\n\t\t\t\t\t\tchild.patch?.error === undefined &&\n\t\t\t\t\t\ttypeof child.patch?.path === \"string\" &&\n\t\t\t\t\t\tpath.resolve(child.patch.path) === resolvedPatchPath,\n\t\t\t\t),\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction cleanupSingleWorktree(\n\tsetup: WorktreeSetup,\n\tworktree: WorktreeInfo,\n\tintent: WorktreeCleanupIntent,\n): WorktreeCleanupTask {\n\tconst errors: string[] = [];\n\tlet worktreeRemoved = false;\n\tlet branchRemoved = false;\n\tif (intent.kind !== \"setup-rollback\") {\n\t\ttry {\n\t\t\tremoveSyntheticPathsBeforeDiff(worktree);\n\t\t} catch (error) {\n\t\t\terrors.push(`synthetic path cleanup failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t\tconst status = runGit(worktree.path, [\"status\", \"--porcelain\"]);\n\t\tconst baseDiff = runGit(worktree.path, [\"diff\", \"--quiet\", setup.baseCommit, \"--\"]);\n\t\tif (status.status !== 0 || (baseDiff.status !== 0 && baseDiff.status !== 1)) {\n\t\t\tconst reason =\n\t\t\t\tstatus.status !== 0\n\t\t\t\t\t? status.stderr.trim() || status.stdout.trim() || \"git status failed\"\n\t\t\t\t\t: baseDiff.stderr.trim() || baseDiff.stdout.trim() || \"git diff check failed\";\n\t\t\treturn {\n\t\t\t\tindex: worktree.index,\n\t\t\t\tpath: worktree.path,\n\t\t\t\tbranch: worktree.branch,\n\t\t\t\tworktreeRemoved: false,\n\t\t\t\tbranchRemoved: false,\n\t\t\t\tpreserved: true,\n\t\t\t\treason: \"cleanup safety check failed\",\n\t\t\t\terrors: [...errors, `cleanup refused: ${reason}`],\n\t\t\t};\n\t\t}\n\t\tconst hasWork = status.stdout.trim().length > 0 || baseDiff.status === 1;\n\t\tif (hasWork && intent.kind === \"preserve\") {\n\t\t\tconst captured = (intent.capturedDiffs ?? setup.capturedDiffs)?.find((diff) => diff.index === worktree.index);\n\t\t\tconst patchCaptured =\n\t\t\t\tcaptured !== undefined &&\n\t\t\t\tcaptured.error === undefined &&\n\t\t\t\tfs.existsSync(captured.patchPath) &&\n\t\t\t\tfs.statSync(captured.patchPath).size > 0 &&\n\t\t\t\thandoffRecordsPatch(intent.handoffManifestPath, captured.patchPath);\n\t\t\tif (!patchCaptured) {\n\t\t\t\tconst reason = \"worktree contains changes that are not represented by a captured handoff patch\";\n\t\t\t\treturn {\n\t\t\t\t\tindex: worktree.index,\n\t\t\t\t\tpath: worktree.path,\n\t\t\t\t\tbranch: worktree.branch,\n\t\t\t\t\tworktreeRemoved: false,\n\t\t\t\t\tbranchRemoved: false,\n\t\t\t\t\tpreserved: true,\n\t\t\t\t\treason,\n\t\t\t\t\terrors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tif (hasWork && intent.kind === \"discard\") {\n\t\t\tconst decision = resolveAuthorityDecision({ action: \"discardWorktree\", policy: intent.authorization.policy });\n\t\t\tconst authorized =\n\t\t\t\tdecision === \"auto\" || (decision === \"confirm\" && intent.authorization.kind === \"confirmed\");\n\t\t\tif (!authorized) {\n\t\t\t\tconst reason =\n\t\t\t\t\tdecision === \"forbid\"\n\t\t\t\t\t\t? \"authority policy forbids worktree discard\"\n\t\t\t\t\t\t: \"worktree discard requires explicit user confirmation\";\n\t\t\t\treturn {\n\t\t\t\t\tindex: worktree.index,\n\t\t\t\t\tpath: worktree.path,\n\t\t\t\t\tbranch: worktree.branch,\n\t\t\t\t\tworktreeRemoved: false,\n\t\t\t\t\tbranchRemoved: false,\n\t\t\t\t\tpreserved: true,\n\t\t\t\t\treason,\n\t\t\t\t\terrors: [...errors, `cleanup refused: ${reason}; preserved ${worktree.path}`],\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t}\n\ttry {\n\t\trunGitChecked(setup.cwd, [\"worktree\", \"remove\", \"--force\", worktree.path]);\n\t\tworktreeRemoved = true;\n\t} catch (error) {\n\t\terrors.push(`worktree removal failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (worktreeRemoved) {\n\t\ttry {\n\t\t\trunGitChecked(setup.cwd, [\"branch\", \"-D\", worktree.branch]);\n\t\t\tbranchRemoved = true;\n\t\t} catch (error) {\n\t\t\terrors.push(`branch removal failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\treturn {\n\t\tindex: worktree.index,\n\t\tpath: worktree.path,\n\t\tbranch: worktree.branch,\n\t\tworktreeRemoved,\n\t\tbranchRemoved,\n\t\t...(errors.length ? { errors } : {}),\n\t};\n}\n\nfunction hasWorktreeChanges(diff: WorktreeDiff): boolean {\n\treturn diff.filesChanged > 0 || diff.insertions > 0 || diff.deletions > 0 || diff.diffStat.trim().length > 0;\n}\n\nexport function createWorktrees(\n\tcwd: string,\n\trunId: string,\n\tcount: number,\n\toptions?: CreateWorktreesOptions,\n): WorktreeSetup {\n\tconst repo = resolveRepoState(cwd);\n\tconst setupHook = resolveWorktreeSetupHook(repo.toplevel, options?.setupHook);\n\tconst baseDir = resolveWorktreeBaseDir(options?.baseDir, repo.toplevel);\n\tconst worktrees: WorktreeInfo[] = [];\n\n\ttry {\n\t\tfor (let index = 0; index < count; index++) {\n\t\t\tworktrees.push(\n\t\t\t\tcreateSingleWorktree(\n\t\t\t\t\trepo.toplevel,\n\t\t\t\t\trepo.cwdRelative,\n\t\t\t\t\trunId,\n\t\t\t\t\tindex,\n\t\t\t\t\trepo.baseCommit,\n\t\t\t\t\tsetupHook,\n\t\t\t\t\toptions?.agents?.[index],\n\t\t\t\t\tbaseDir,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tcleanupWorktrees(\n\t\t\t{\n\t\t\t\tcwd: repo.toplevel,\n\t\t\t\tworktrees,\n\t\t\t\tbaseCommit: repo.baseCommit,\n\t\t\t},\n\t\t\t{ kind: \"setup-rollback\" },\n\t\t);\n\t\tthrow error;\n\t}\n\n\treturn {\n\t\tcwd: repo.toplevel,\n\t\tworktrees,\n\t\tbaseCommit: repo.baseCommit,\n\t};\n}\n\nexport function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir: string): WorktreeDiff[] {\n\ttry {\n\t\tfs.mkdirSync(diffsDir, { recursive: true });\n\t} catch {\n\t\t// Returning no diffs is safer than failing the whole command on artifact-dir issues.\n\t\treturn [];\n\t}\n\n\tconst diffs: WorktreeDiff[] = [];\n\tfor (let index = 0; index < setup.worktrees.length; index++) {\n\t\tconst worktree = setup.worktrees[index]!;\n\t\tconst agent = agents[index] ?? `task-${index + 1}`;\n\t\tconst patchPath = path.join(diffsDir, `task-${index}-${safePatchAgentName(agent)}.patch`);\n\t\ttry {\n\t\t\tdiffs.push(captureWorktreeDiff(setup, worktree, agent, patchPath));\n\t\t} catch (error) {\n\t\t\t// Preserve execution flow while retaining the failed capture as handoff evidence.\n\t\t\twriteEmptyPatch(patchPath);\n\t\t\tdiffs.push(\n\t\t\t\temptyDiff(index, agent, worktree.branch, patchPath, error instanceof Error ? error.message : String(error)),\n\t\t\t);\n\t\t}\n\t}\n\n\tsetup.capturedDiffs = diffs;\n\treturn diffs;\n}\n\nexport function cleanupWorktrees(\n\tsetup: WorktreeSetup,\n\tintent: WorktreeCleanupIntent = {\n\t\tkind: \"preserve\",\n\t\t...(setup.capturedDiffs ? { capturedDiffs: setup.capturedDiffs } : {}),\n\t},\n): WorktreeCleanupReport {\n\tconst tasks: WorktreeCleanupTask[] = [];\n\tfor (let index = setup.worktrees.length - 1; index >= 0; index--) {\n\t\ttasks.push(cleanupSingleWorktree(setup, setup.worktrees[index]!, intent));\n\t}\n\ttasks.sort((left, right) => left.index - right.index);\n\tconst errors: string[] = [];\n\tlet pruned = false;\n\ttry {\n\t\trunGitChecked(setup.cwd, [\"worktree\", \"prune\"]);\n\t\tpruned = true;\n\t} catch (error) {\n\t\terrors.push(`worktree prune failed: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tconst state = tasks.every((task) => task.worktreeRemoved && task.branchRemoved) && pruned ? \"complete\" : \"partial\";\n\treturn {\n\t\tstate,\n\t\ttasks,\n\t\tpruned,\n\t\t...(errors.length ? { errors } : {}),\n\t};\n}\n\nexport function formatWorktreeDiffSummary(diffs: WorktreeDiff[]): string {\n\tconst changed = diffs.filter(hasWorktreeChanges);\n\tif (changed.length === 0) return \"\";\n\n\tconst lines: string[] = [\"=== Worktree Changes ===\", \"\"];\n\tfor (const diff of changed) {\n\t\tlines.push(\n\t\t\t`--- Task ${diff.index + 1} (${diff.agent}): ${diff.filesChanged} files changed, +${diff.insertions} -${diff.deletions} ---`,\n\t\t);\n\t\tif (diff.diffStat.trim().length > 0) {\n\t\t\tlines.push(diff.diffStat);\n\t\t}\n\t\tlines.push(\"\");\n\t}\n\n\tconst patchesDir = path.dirname(changed[0]!.patchPath);\n\tlines.push(`Full patches: ${patchesDir}`);\n\treturn lines.join(\"\\n\").trimEnd();\n}\n"]}