/** * Git worktree allocation and cleanup for `useWorktree: true` agents. * Spec: §4.1.2 — Worktree Allocation * * Per-agent lockfile pattern (review finding C2): * /.pi/active-.lock * * Worktree path convention: * /.pi/worktrees// * * Branch convention: * orca// * * All git operations are routed through an injectable `execGit` so tests can * exercise the orchestration without touching real repos. */ /** * Result of a single `git` invocation. `stderr` carries error text on failure; * `exitCode` is 0 on success. */ export interface GitExecResult { stdout: string; stderr: string; exitCode: number; } /** * Inject `git` invocation. Production code wires this to `execFile("git", ...)`; * tests stub it. `args` is the argv to git (no shell). `cwd` optionally pins * the working directory for the invocation. */ export type GitExec = (args: string[], opts?: { cwd?: string; }) => Promise; /** * Lockfile schema (YAML body). */ export interface WorktreeLockfile { agentId: string; parentSessionId: string; pid?: number; branch: string; worktreePath: string; acquiredAt: string; } /** * Per-agent worktree record retained for cleanup on terminal status. */ export interface WorktreeRecord { agentId: string; parentSessionId: string; parentSessionPath: string; repoRoot: string; branch: string; worktreePath: string; lockfilePath: string; } export declare function registerWorktree(record: WorktreeRecord): void; export declare function getWorktreeFor(agentId: string): WorktreeRecord | undefined; export declare function listWorktrees(): ReadonlyMap; /** * Clear all worktree registry entries. Called on session boundaries. * Spec: §4.5.2 reset block (W3+W11). Cleanup of on-disk state belongs to * the recovery sweep — this only drops in-memory references. */ export declare function clearWorktreeRegistry(): void; /** * Resolve the repository root from a given cwd via `git rev-parse --show-toplevel`. * Returns `null` when cwd is not inside a git repo. * * @param cwd - Working directory to probe * @param execGit - Optional git exec injector for tests */ export declare function detectRepoRoot(cwd: string, execGit?: GitExec): Promise; export declare function getWorktreePath(repoRoot: string, parentSessionId: string, agentId: string): string; export declare function getBranchName(parentSessionId: string, agentId: string): string; export declare function getLockfilePath(repoRoot: string, agentId: string): string; export declare function getLockfileGlobDir(repoRoot: string): string; export declare function readLockfile(path: string): Promise; export interface AllocateWorktreeParams { repoRoot: string; parentSessionId: string; parentSessionPath: string; agentId: string; /** Owner PID for the lockfile; defaults to process.pid. */ pid?: number; /** Override for tests. Defaults to isPidAlive from @pi-orca/core. */ isAliveOverride?: (pid: number) => boolean; execGit?: GitExec; } export interface AllocateWorktreeResult { worktreePath: string; branch: string; lockfilePath: string; } /** * Allocate a fresh worktree for an agent. * * - Creates `/.pi/worktrees//` via * `git worktree add -b orca//` (note: `-b`, NOT * `-B` — never silently overwrite another agent's branch). * - On "branch already exists" failure, attempts a targeted recovery: if no * live worktree references the branch AND no live-PID lockfile owns it, * deletes the stale branch and retries `-b` once. Any other failure * propagates as a spawn error. * - Writes the per-agent lockfile via `atomicWrite`. * * Registers the worktree in the module-scoped registry so terminal-status * cleanup can find it without threading the path through every spawner. */ export declare function allocateWorktree(params: AllocateWorktreeParams): Promise; export interface CleanupWorktreeParams { repoRoot: string; worktreePath: string; branch: string; lockfilePath: string; agentId: string; parentSessionPath: string; /** When true, bypass the uncommitted-work safeguard (config: agents.worktreeForceCleanupOnTerminal). */ force: boolean; notify?: (message: string, type?: "info" | "warning" | "error") => void; execGit?: GitExec; } export type CleanupResult = { kind: "removed"; } | { kind: "skipped"; reason: "uncommitted-changes"; details: string; } | { kind: "skipped"; reason: "not-a-worktree" | "no-such-file"; details: string; } | { kind: "failed"; error: string; }; /** * Remove a worktree, its lockfile, and its branch. * * Uncommitted-work safeguard (W9): unless `force === true`, refuses to remove * a worktree whose `git status --porcelain` is non-empty; instead writes * `cleanupBlockedReason` into the agent's status YAML and surfaces a notify. * * Idempotent: removing an already-removed worktree returns * `{ kind: "skipped", reason: "not-a-worktree" | "no-such-file" }`. */ export declare function cleanupWorktree(params: CleanupWorktreeParams): Promise; /** * Convenience: look up the registered worktree for an agent and clean it up. * No-op when no worktree is registered. Wired into the per-spawner terminal * handler so callers don't need to track worktree paths themselves. */ export declare function cleanupAgentWorktreeIfAllocated(params: { agentId: string; force: boolean; notify?: (message: string, type?: "info" | "warning" | "error") => void; execGit?: GitExec; }): Promise; export interface SweepStaleLockfilesParams { repoRoot: string; /** * Parent session path used when we need to write `cleanupBlockedReason` * into a recovered agent's status YAML. Optional — when omitted, the * safeguard skips the YAML write but still surfaces a notify. */ parentSessionPath?: string; /** When true, bypass the W9 safeguard (config: agents.worktreeForceCleanupOnTerminal). */ force: boolean; isAliveOverride?: (pid: number) => boolean; notify?: (message: string, type?: "info" | "warning" | "error") => void; execGit?: GitExec; } export interface SweepResult { scanned: number; reclaimed: string[]; blocked: { agentId: string; reason: string; }[]; skippedLive: string[]; failed: { agentId: string; error: string; }[]; } /** * Sweep stale per-agent lockfiles under `/.pi/active-*.lock`. * For each lockfile with a dead PID, reclaim the worktree via cleanupWorktree * (honoring the W9 safeguard). Runs `git worktree prune` once at the end to * reap any half-removed worktree metadata. * * Spec: §4.1.2 Cleanup contract + §5.3 Stale lockfile sweep. */ export declare function sweepStaleLockfiles(params: SweepStaleLockfilesParams): Promise; /** * Reset module-scoped state (worktree registry + cached git exec). * For tests only. */ export declare function _resetWorktreeModule(): void; //# sourceMappingURL=worktree.d.ts.map