/** * Conductor finalization gate — repair/delivery readiness evaluation. * * This module decides whether a conductor run's worktree is ready to be * finalized (PR shipped / branch delivered). It is split into a pure * evaluator (`evaluateFinalization`) and an injectable shell-backed collector * (`collectFinalizationState`) so the decision logic is testable without * touching git or GitHub. * * The evaluator is intentionally strict but forgiving on transient artifacts: * untracked/modified paths under `.issue-delivery/` are treated as transient * porcelain and ignored when deciding * whether the worktree is "clean enough" to ship. A rename/copy is only ignored * when *both* sides of the rename live under a transient prefix — if a real * source file is moved into (or out of) one of those prefixes, that blocks * finalization. * * Evaluation rules (in order): * 1. Worktree clean modulo transient prefixes (and extra `ignorePathPrefixes`). * 2. `currentBranch` must match `expectedBranch` when the latter is provided. * 3. At least one commit beyond `baseRef` when `baseRef` is provided. * 4. Branch must be pushed to its upstream/remote. This is *required*, not * optional: an absent `pushedUpstream` is treated as unverified and * blocks `completed`. When SHAs are available, local and remote HEAD must * match. * 5. `prHeadSha` must equal `headSha` when provided. * 6. GitHub checks must be `success` (green) or `pending`/`neutral` (clearly * pending). `unknown` is NOT clearly pending — it surfaces as * `needs-human` with an actionable `gh`/auth/checks command so the gate * cannot silently pass without verifying checks. */ import type { ConductorRunStatus } from "./conductor-types.js"; /** Statuses a finalization check can yield. */ export type FinalizationStatus = "completed" | "needs-finalize" | "finalizing" | "needs-human" | "failed"; /** * Shaped result returned by the finalization gate. Mirrors * {@link ConductorRunStatus} but uses the finalization-specific status union * and always carries an actionable `nextAction`. */ export interface FinalizationCheckResult { status: FinalizationStatus; reason: string; nextAction: string; /** Optional human-readable extra context (e.g. failing check names). */ details?: string; /** Resolved to a {@link ConductorRunStatus}-compatible record. */ toRunStatus?: ConductorRunStatus; } /** * Inputs the pure evaluator consumes. * * `expectedBranch` / `baseRef` / `prHeadSha` are only enforced when provided. * `pushedUpstream` is *required* for `completed`: `undefined` is treated as * "unverified" and blocks completion (it does not silently pass). */ export interface FinalizationInput { /** Current working tree status (from `git status --porcelain`). */ porcelain: string; /** Current branch name (from `git rev-parse --abbrev-ref HEAD`). */ currentBranch?: string; /** Branch the run is expected to be on; enforced when provided. */ expectedBranch?: string; /** Base ref to compare commits against (e.g. `main` or `origin/main`). */ baseRef?: string; /** Number of commits on the branch beyond `baseRef`. */ commitsBeyondBase?: number; /** * Whether an upstream/remote tracking HEAD exists and matches local HEAD. * Required for `completed`: `undefined` blocks completion as unverified. */ pushedUpstream?: boolean; /** Remote head SHA, when known. Compared to local HEAD when provided. */ remoteHeadSha?: string; /** Local HEAD commit SHA. */ headSha?: string; /** Expected PR head SHA; enforced as an exact match when provided. */ prHeadSha?: string; /** Aggregate GitHub checks state. */ checksState?: "success" | "pending" | "failure" | "neutral" | "unknown"; /** Names of failing GitHub checks, when available. */ failingChecks?: string[]; /** Optional set of paths to additionally ignore when checking cleanliness. */ ignorePathPrefixes?: string[]; } /** * Pure evaluation of finalization readiness. See the module docstring for the * full rule list. Returns a {@link FinalizationCheckResult} with an actionable * `nextAction`. */ export declare function evaluateFinalization(input: FinalizationInput): FinalizationCheckResult; /** Injectable shell runner — same shape as {@link CodeReviewExecRunner}. */ export type FinalizationShellRunner = (file: string, args: string[], options: { cwd: string; maxBuffer: number; shell: false; }) => Promise<{ stdout: string; stderr: string; }>; /** * Options controlling what the collector gathers and what constraints it * enforces. Every field is optional; absent fields are not enforced (except * upstream push, which the evaluator always requires). */ export interface CollectFinalizationOptions { expectedBranch?: string; baseRef?: string; prHeadSha?: string; ignorePathPrefixes?: string[]; /** * When true, query GitHub checks via `gh pr checks --json`. Requires `gh` * to be authenticated. Defaults to true. */ queryChecks?: boolean; } /** * Shell-backed collector: gathers all git/gh state needed by the pure * evaluator and returns a {@link FinalizationInput}. * * The runner is injectable for testing. When omitted, the real * `child_process.execFile` is used. Git failures during collection surface * as `pushedUpstream: undefined` / `commitsBeyondBase: undefined` / * `checksState: "unknown"` rather than throwing, so the evaluator can decide * the right status; {@link checkFinalization} wraps this so a hard collector * failure becomes a `failed` result with an actionable command. */ export declare function collectFinalizationState(cwd: string, opts?: CollectFinalizationOptions, runner?: FinalizationShellRunner): Promise; /** * Convenience: collect state and evaluate it in one call. Collector failures * (e.g. `git` missing, not a git repo) are translated into a `failed` * {@link FinalizationCheckResult} with an actionable `nextAction` rather than * rejecting — per the contract, results always use * completed/needs-finalize/finalizing/needs-human/failed. */ export declare function checkFinalization(cwd: string, opts?: CollectFinalizationOptions, runner?: FinalizationShellRunner): Promise; /** * Inputs for the bounded finalization/nudge loop. */ export interface FinalizationLoopOptions { /** * Function that performs a finalization check. Defaults to calling * {@link checkFinalization} when `cwd`/`opts` are supplied via closure. */ check: () => Promise; /** * Optional nudge action invoked when a check returns `needs-finalize` or * `finalizing` — e.g. re-push, wait, or trigger CI. Should return a short * human-readable summary of what it did. */ nudge?: (result: FinalizationCheckResult) => Promise; /** Maximum number of nudge attempts. Defaults to 3. Must be >= 0. */ maxNudges?: number; } /** * Run the finalization/nudge loop, bounded by `maxNudges`. * * Each iteration: * 1. Runs `check()`. * 2. If the result is terminal (`completed`, `failed`, `needs-human`) it is * returned immediately. * 3. If the result is `needs-finalize` or `finalizing` and nudges remain, * `nudge()` is invoked (if provided) and the loop continues. * 4. Once nudges are exhausted, an unresolved `needs-finalize` or * `finalizing` is downgraded to `needs-human` with the accumulated * context. */ export declare function runFinalizationLoop(opts: FinalizationLoopOptions): Promise;