import { type RemoveResult, type WorktreeRow } from './worktrees.js';
/** One worktree this sweep removed. */
export interface RemovedWorktree {
    /** The agent id, which is also the worktree's directory name. */
    agentId: string;
}
/** A worktree the sweep tried to reclaim and could not, and why. */
export interface FailedRemoval {
    agentId: string;
    error: string;
}
/** What {@link removeMergedWorktrees} did. */
export interface MergedSweepResult {
    removed: RemovedWorktree[];
    /** Worktrees it tried and could not reclaim — most often a branch it could not push. */
    failed: FailedRemoval[];
}
/** Injectable seams so the sweep is unit-testable off disk. */
export interface MergedSweepDeps {
    /** The worktrees on disk (default {@link listProjectWorktrees}). */
    worktrees?: (cwd: string) => Promise<WorktreeRow[]>;
    /** Removes one worktree (default {@link removeProjectWorktree}). */
    remove?: (cwd: string, agentId: string) => Promise<RemoveResult>;
    /** Whether the repo has a remote at all (default {@link repoHasRemote}). */
    hasRemote?: (cwd: string) => Promise<boolean>;
    /** Agent ids whose checkouts the daemon is still responsible for; see {@link MergedSweepOptions.busy}. */
    busy?: ReadonlySet<string>;
}
/**
 * Reclaim every retained worktree in `cwd` whose work can reach the remote (E5).
 *
 * The decision is entirely {@link removeProjectWorktree}'s — commit what is pending, push the
 * branch, remove only once the remote has it — so the automatic path and the manual one (the
 * dashboard's Remove button) are one behaviour rather than two that can disagree. This adds the
 * loop, the one thing it must never touch (a live agent's checkout, where its agent is working), and
 * the agent lock.
 *
 * The lock is load-bearing: an agent's meta flips to `done` a beat before its teardown finishes
 * archiving, so a sweep landing in that window would remove the checkout out from under the
 * archive — which then recreates the directory it was reading from, and the removal silently
 * un-happens. Every other actor on a checkout already takes this lock.
 */
export declare function removeMergedWorktrees(cwd: string, deps?: MergedSweepDeps): Promise<MergedSweepResult>;
/** A running sweep, in the shape the daemon's other background services use. */
export interface MergedWorktreeSweep {
    /** Run one sweep now, awaiting it. Exposed for tests and for a caller that wants it on demand. */
    tick: () => Promise<void>;
    stop: () => void;
}
/** What {@link startMergedWorktreeSweep} needs from the daemon. */
export interface MergedSweepOptions {
    /** The registered projects to sweep. */
    projects: () => Promise<readonly {
        path: string;
    }[]>;
    log: (message: string) => void;
    /**
     * The agents the daemon is still responsible for — spawning, running, or mid-retirement — whose
     * checkouts this must not touch.
     *
     * "Not live" on disk is not "the daemon is finished with it": an agent's meta flips to `done` a
     * beat before its teardown archives the history and reclaims the checkout, and a sweep landing
     * in that window races the teardown for the same directory. Absent means nothing is busy, which
     * is right for a caller that spawns no agents.
     */
    busy?: () => ReadonlySet<string>;
    /** The per-project sweep (default {@link removeMergedWorktrees}). */
    sweep?: (cwd: string) => Promise<MergedSweepResult>;
}
/**
 * Sweep every registered project's reclaimable worktrees (#1036), one turn per call.
 *
 * Says what it removed rather than removing it silently: a checkout vanishing from under someone
 * with no line explaining why reads as a bug, even when the work behind it is safe.
 */
export declare function startMergedWorktreeSweep(opts: MergedSweepOptions): MergedWorktreeSweep;
//# sourceMappingURL=merged-worktrees.d.ts.map