/**
 * A CI check result. Shaped after `gh pr checks <pr> --json name,state,link,workflow`,
 * where `state` is one of SUCCESS | FAILURE | PENDING | ERROR | CANCELLED | SKIPPING | ...
 *
 * Note on identity: `gh pr checks` exposes NO stable check-run id — `link` is the
 * per-run URL, which changes every time a workflow re-runs. So dedupe keys off the
 * check NAME (stable across re-runs), not the link (see `checkDedupeKey`). `link`
 * is retained only for the human-facing prompt and for scraping run logs.
 */
export interface PrCheck {
    name: string;
    state: string;
    link?: string;
    workflow?: string;
}
/** A PR review comment. Shaped after GitHub's `repos/{owner}/{repo}/pulls/{n}/comments`. */
export interface PrReviewComment {
    id: number;
    body: string;
    user?: string;
    path?: string;
    html_url?: string;
}
/** One PR under watch, with its current CI + comment snapshot. */
export interface PrSnapshot {
    /** The PR URL (https://github.com/{owner}/{repo}/pull/{n}). */
    prUrl: string;
    /**
     * Name of the teammate whose work opened this PR — the `--after` anchor the
     * spawned fix/bugfix teammate links to. Null when the PR can't be traced to a
     * named teammate (the reaction still fires, just without a dependency edge).
     */
    sourceTeammate: string | null;
    checks: PrCheck[];
    comments: PrReviewComment[];
}
/**
 * Default cap on how many fix/bugfix waves pr-watch spawns for a single PR before
 * it gives up and escalates to a human. A persistently-RED PR re-runs CI on every
 * follow-up commit; without a cap the loop would spawn teammates without bound
 * (issue #338). Configurable via `--max-waves`.
 */
export declare const DEFAULT_MAX_WAVES = 3;
/** A decision to spawn a follow-up teammate. Pure output of `decidePrActions`. */
export type PrWatchAction = {
    kind: 'ci-fix';
    prUrl: string;
    sourceTeammate: string | null;
    check: PrCheck;
    /** Idempotency key — record it in `handled` once acted on so it never re-fires. */
    dedupeKey: string;
    /** 1-based wave number for this PR (used to name the spawned teammate uniquely). */
    wave: number;
} | {
    kind: 'review-fix';
    prUrl: string;
    sourceTeammate: string | null;
    comment: PrReviewComment;
    dedupeKey: string;
    wave: number;
} | {
    /**
     * The per-PR wave budget is exhausted — stop spawning, escalate to a human.
     * Emitted once per PR (deduped via `needsHumanKey`) instead of an N+1th fix.
     */
    kind: 'needs-human';
    prUrl: string;
    sourceTeammate: string | null;
    /** What triggered the escalation (e.g. `CI check "test"`). */
    subject: string;
    /** Waves already spent on this PR when the cap was hit. */
    waves: number;
    dedupeKey: string;
};
/** The spawnable subset of actions — everything except the `needs-human` escalation. */
export type PrWatchSpawnAction = Extract<PrWatchAction, {
    kind: 'ci-fix' | 'review-fix';
}>;
/** True when a check is in a terminal failing state. */
export declare function isFailedCheck(check: PrCheck): boolean;
/**
 * Idempotency key for a failed check on a PR — dedupe by check NAME.
 *
 * The name is the ONLY identity that's stable across re-runs: when a fixer pushes
 * a follow-up commit, GitHub creates a fresh workflow run with a new `link` URL but
 * the SAME check name. Keying off `link` (as an earlier draft did) would treat every
 * re-run as a brand-new failure and spawn an unbounded chain of fixers (issue #338).
 * Keying off the name means a re-run of the same check is recognised as the same
 * logical failure; the per-PR wave counter then bounds how many times we retry it.
 */
export declare function checkDedupeKey(prUrl: string, check: PrCheck): string;
/** Idempotency key for a review comment on a PR — dedupe by comment id. */
export declare function commentDedupeKey(prUrl: string, comment: PrReviewComment): string;
/** Idempotency key for the one-shot "needs human" escalation on a PR. */
export declare function needsHumanKey(prUrl: string): string;
/**
 * The pure heart of pr-watch: given a PR snapshot, the set of dedupe keys already
 * acted on, and how many fix waves each PR has already spent, decide which
 * follow-up teammates to spawn. Emits:
 *   - one `ci-fix` action per NEW failed check (dedup by check NAME)
 *   - one `review-fix` action per NEW review comment (dedup by comment id)
 *   - a single `needs-human` action (per PR) once the wave budget is exhausted,
 *     INSTEAD of spawning — so a persistently-failing PR escalates rather than
 *     spawning teammates without bound (issue #338).
 *
 * `waves` maps a PR URL to the number of fix/bugfix teammates already spawned for
 * it; once that reaches `maxWaves`, no further spawns are emitted for that PR.
 * Both the CI-fix and comment-routing paths draw from the SAME per-PR budget, so
 * neither can loop unboundedly.
 *
 * No network, no side effects, no reference to state beyond the passed
 * `handled` / `waves` — so the same failure never spawns twice and the caller
 * stays in control of when a wave is "spent".
 */
export declare function decidePrActions(snapshot: PrSnapshot, handled: ReadonlySet<string>, waves?: ReadonlyMap<string, number>, maxWaves?: number): PrWatchAction[];
/**
 * Build the task prompt for a CI-fix teammate. Pure so it's unit-testable: the
 * failing check + the fetched logs are injected as data.
 */
export declare function buildCiFixPrompt(action: Extract<PrWatchAction, {
    kind: 'ci-fix';
}>, logs: string): string;
/**
 * Build the task prompt for a review-comment bugfix teammate. Pure so it's
 * unit-testable: the review comment is injected as data.
 */
export declare function buildReviewFixPrompt(action: Extract<PrWatchAction, {
    kind: 'review-fix';
}>): string;
/** Parse an owner/repo/number triple out of a GitHub PR URL. Returns null when it doesn't match. */
export declare function parsePrUrl(prUrl: string): {
    owner: string;
    repo: string;
    number: number;
} | null;
/** Fetch the current CI checks for a PR via `gh pr checks`. Returns [] on any gh error. */
export declare function fetchPrChecks(prUrl: string): Promise<PrCheck[]>;
/** Fetch review comments for a PR via `gh api`. Returns [] on any gh error. */
export declare function fetchPrReviewComments(prUrl: string): Promise<PrReviewComment[]>;
/** Fetch the failing-run logs for a check via `gh run view --log-failed`. Best-effort, truncated. */
export declare function fetchCiFailureLogs(check: PrCheck, maxChars?: number): Promise<string>;
/** Collect a live PR snapshot from `gh`. The poll-mode seam for `runPrWatch`. */
export declare function pollPrSnapshot(prUrl: string, sourceTeammate: string | null): Promise<PrSnapshot>;
/** A PR to watch, paired with the teammate that opened it. */
export interface WatchTarget {
    prUrl: string;
    sourceTeammate: string | null;
}
export interface PrWatchDeps {
    /** Discover which PRs to watch this pass (teammates may open PRs mid-run). */
    resolveTargets: () => Promise<WatchTarget[]>;
    /** Snapshot one PR's CI + review comments. Defaults to `pollPrSnapshot`. */
    pollSnapshot?: (prUrl: string, sourceTeammate: string | null) => Promise<PrSnapshot>;
    /** Fetch CI failure logs for a ci-fix action. Defaults to `fetchCiFailureLogs`. */
    fetchLogs?: (check: PrCheck) => Promise<string>;
    /**
     * React to one decided spawn action: spawn the fix/bugfix teammate. The prompt
     * is pre-built with logs/comment injected. Return the spawned teammate label for
     * logging, or null if the spawn was skipped. `needs-human` actions never reach
     * here — they surface as a `needs-human` event instead.
     */
    react: (action: PrWatchSpawnAction, prompt: string) => Promise<string | null>;
    /**
     * True when a previously-spawned reaction has finished. Once its fixer settles,
     * the dedupe guard for that check clears so a check that is STILL red can spawn
     * its next (budget-bounded) wave; without this, a check would only ever get one
     * fix attempt per pr-watch run. Optional — when omitted, a check gets a single
     * wave until pr-watch restarts.
     */
    reactionSettled?: (label: string) => Promise<boolean>;
    /** Progress sink — one call per notable event (poll, spawn, escalate, error). */
    onEvent?: (event: PrWatchEvent) => void;
}
export type PrWatchEvent = {
    type: 'poll';
    targets: number;
    timestamp: string;
} | {
    type: 'spawned';
    action: PrWatchSpawnAction;
    label: string | null;
    timestamp: string;
} | {
    type: 'needs-human';
    prUrl: string;
    subject: string;
    waves: number;
    timestamp: string;
} | {
    type: 'error';
    prUrl: string;
    message: string;
    timestamp: string;
};
export interface PrWatchOptions {
    intervalMs?: number;
    /** Stop after this many polls (0 / undefined = run until signalled). */
    maxPolls?: number;
    /** Seed of already-handled dedupe keys (e.g. restored from disk). */
    handled?: Set<string>;
    /** Per-PR fix-wave counts, carried across restarts so the budget survives a restart. */
    waves?: Map<string, number>;
    /** Cap on fix waves per PR before escalating to a human. Defaults to DEFAULT_MAX_WAVES. */
    maxWaves?: number;
    /** Abort signal — resolves the loop at the next interval boundary. */
    shouldStop?: () => boolean;
}
export interface PrWatchResult {
    polls: number;
    spawned: number;
    /** How many PRs escalated to a human (wave budget exhausted). */
    neededHuman: number;
    handled: Set<string>;
    waves: Map<string, number>;
    stoppedBy: 'max-polls' | 'signal';
}
/**
 * Run the pr-watch poll loop. Each pass: retire the dedupe guards of any fixers
 * that have finished (so a still-red check can spawn its next wave), resolve watch
 * targets, snapshot each PR, decide actions against the running `handled` set and
 * per-PR `waves` budget, then react — spawning fresh fixers, or emitting a
 * one-shot `needs-human` event once a PR's wave budget is spent. Every acted-on
 * dedupe key is recorded so a failure/comment never spawns twice within a wave,
 * and `waves` hard-caps the total spawns per PR (issue #338).
 */
export declare function runPrWatch(deps: PrWatchDeps, opts?: PrWatchOptions): Promise<PrWatchResult>;
