// engine/housekeeping/find-stale-worktrees.ts — SPEC-751
// Detects git worktrees that are safe to remove.
import { execFile as execFileCb } from 'node:child_process';
import { promisify } from 'node:util';
const execFile = promisify(execFileCb);
/**
 * Parse `git worktree list --porcelain` output into structured records.
 */
function parseWorktreePorcelain(output) {
    const worktrees = [];
    const blocks = output.trim().split(/\n\n+/);
    for (const block of blocks) {
        const lines = block.trim().split('\n');
        let path = null;
        let branch = null;
        let isMain = false;
        for (const line of lines) {
            if (line.startsWith('worktree ')) {
                path = line.slice('worktree '.length).trim();
            }
            else if (line.startsWith('branch ')) {
                // e.g. "branch refs/heads/feat/spec-123-foo"
                const ref = line.slice('branch '.length).trim();
                branch = ref.replace(/^refs\/heads\//, '');
            }
            else if (line === 'bare') {
                isMain = false;
            }
        }
        if (path) {
            // The first worktree block is always the main worktree
            if (worktrees.length === 0) {
                isMain = true;
            }
            worktrees.push({ path, branch, isMain });
        }
    }
    return worktrees;
}
/**
 * Check if a worktree has uncommitted or untracked changes.
 * Returns true if the working tree is clean.
 */
async function isWorktreeClean(worktreePath) {
    try {
        const { stdout } = await execFile('git', ['status', '--porcelain'], { cwd: worktreePath });
        return stdout.trim() === '';
    }
    catch {
        // If we can't check status, treat as dirty (safety first)
        return false;
    }
}
/**
 * Check if a worktree's branch is fully in main using `git cherry`.
 * Returns true if all commits are in main (by patch-id).
 */
async function isBranchInMain(mainRepoPath, branch, mainBranch = 'main') {
    try {
        const { stdout } = await execFile('git', ['cherry', mainBranch, branch], { cwd: mainRepoPath });
        const lines = stdout.trim().split('\n').filter(Boolean);
        return lines.length === 0 || !lines.some((l) => l.startsWith('+'));
    }
    catch {
        return false;
    }
}
export async function findStaleWorktrees(input) {
    const { projectPath } = input;
    let porcelainOutput;
    try {
        const { stdout } = await execFile('git', ['worktree', 'list', '--porcelain'], {
            cwd: projectPath,
        });
        porcelainOutput = stdout;
    }
    catch {
        return [];
    }
    const worktrees = parseWorktreePorcelain(porcelainOutput);
    const stale = [];
    for (const wt of worktrees) {
        // Never remove the main worktree
        if (wt.isMain) {
            continue;
        }
        // Safety: skip worktrees with uncommitted changes
        const clean = await isWorktreeClean(wt.path);
        if (!clean) {
            continue;
        }
        // If no branch, the worktree is detached — skip it (can't determine merged status)
        if (!wt.branch) {
            continue;
        }
        // Check if the branch is fully in main
        const merged = await isBranchInMain(projectPath, wt.branch);
        if (!merged) {
            continue;
        }
        stale.push({
            path: wt.path,
            branch: wt.branch,
            reason: 'merged_branch',
        });
    }
    return stale;
}
//# sourceMappingURL=find-stale-worktrees.js.map