import type { GhClient } from './gh.js'; import type { GitOps } from './git.js'; import type { GateResult } from './gate.js'; import type { MergeQueueConfig } from './types.js'; export interface DaemonDeps { gh: GhClient; git: GitOps; runGate: (opts: { cwd: string; command: string; timeoutMs: number; logPath: string; env?: Record; pidFile?: string; }) => GateResult | Promise; config: MergeQueueConfig; mqDir: string; projectRoot: string; log: (msg: string) => void; now: () => Date; /** D15 seam: wait for a journal append or the poll timeout while idle. * Production default is waitForWake (fs.watch + debounce). */ wake?: (mqDir: string, timeoutMs: number) => Promise<'journal' | 'timeout'>; /** D15: fire ONE post-merge poller pass right after a landing (main just * moved). Best-effort; the scheduler remains the cross-machine safety net. */ triggerPoller?: () => void; /** Test seam for Beads feedback commands. Production defaults to `bd`. */ runBeadCommand?: (args: string[], cwd: string) => Promise; } export declare const PAUSED_FILE = "PAUSED"; export declare const GATE_PID_FILE = "gate.pid"; export declare function runBd(args: string[], cwd: string, timeoutMs?: number): Promise; export declare class MergeQueueDaemon { private deps; constructor(deps: DaemonDeps); /** Consecutive gh.viewPr failures per PR (spec: cap runaway retries). In-memory * only — resets on daemon restart, which is fine since reconcile() re-derives * ground truth from the journal/GitHub anyway. */ private viewFailures; private beadCommandsInFlight; private at; private state; /** Hand control back to the event loop so proper-lockfile's lock-renewal timer * can run between long synchronous git/gh calls (keeps the singleton lock from * going falsely stale during a busy cycle). */ private yieldToLoop; /** True when the PR's current journal state is terminal (LANDED/EJECTED/ * NEEDS_REBASE/CANCELLED) — i.e. it was withdrawn or finished elsewhere. */ private isTerminal; paused(): string | null; private pause; run(opts?: { once?: boolean; }): Promise; cycle(): Promise<'idle' | 'worked'>; private runBatch; private gateRun; private land; /** Requeue every not-yet-terminal member from `fromIndex` on, so a paused batch * never leaves its untried tail stranded in a mid-flight (PASSED/LANDING) state. */ private requeueUnlanded; private eject; /** A squash-merge that staged nothing means the diff is already on the base * (spec: don't wedge the batch on a stale-but-already-landed PR). No * queueFailures increment — this isn't a queue failure, it's a stale PR. */ private cancelAlreadyApplied; /** Bead feedback loop (spec §5.5): reopen the bead named in the PR body. */ private reopenBead; /** Fire-and-forget contract (spec §5.5): the DAEMON closes the bead on land — * the enqueueing agent moved on and never returns to verify the merge. */ private closeBead; /** knownBody lets callers that already fetched the PR skip a redundant gh.viewPr. */ private beadCmd; private nextBeadAttempt; private failedBeadResult; /** Retry failed closeouts and migrate legacy LANDED entries that predate * bead_sync receipts. Never-attempted and oldest-attempted work runs first; * the fixed batch size limits child-process bursts without starving history. */ private retryBeadCloseouts; /** Startup recovery (spec §5.4): journal vs refs vs GitHub. */ reconcile(): void; /** Recover a batch that crashed mid-LANDING as a UNIT (spec §5.4): query every * member, mark the merged ones LANDED + close their beads, and then — * - partial (some merged, some not): requeue the tail and PAUSE (a human must * reconcile what did/didn't land against a now-stale candidate); * - all merged: run the NRS tree assertion (pause on mismatch); * - none merged: requeue everyone for a clean rebuild. */ private recoverLandingBatch; /** Requeue (or, if it actually merged, LAND) a member left mid-flight by a crash. */ private recoverMember; /** Kill an orphaned gate process group left by a crashed prior daemon before the * gate worktree is reused — otherwise it mutates the worktree concurrently with * the new run and corrupts gate results (spec §5.4). Guards against PID reuse: * a stored PID that a reboot has recycled to an UNRELATED process must never be * SIGKILLed, so we only kill when the live process still runs our gate command. */ private reapOrphanGate; /** PID-reuse guard: the live process with this pid must still be running our * configured gate command before we send it SIGKILL. A recycled pid pointing * at some unrelated process fails this check and is left untouched. */ private looksLikeOurGate; } //# sourceMappingURL=daemon.d.ts.map