/** * run-step is the core use-case: it runs ONE agent step in its worktree and, * if the step is meant to produce work, commits the result. The commit SHA is * the durable handoff token the next step receives. * * Whether a step commits is a declarative property (`commitMessage`), not * runtime guesswork: a worker/planner step that produces artifacts sets a * commit message; a verifier step that only reads and returns a verdict leaves * it undefined and never commits. * * Pure orchestration: depends only on the AgentPort and GitPort interfaces, so * it is unit-tested against fakes. */ import type { AgentPort } from "../ports/agent.ts"; import { type GitPort } from "../ports/git.ts"; import type { GateMode, GatePort } from "../ports/gate.ts"; import type { PiResult } from "../domain/pi-result.ts"; import type { Tier } from "../domain/run-spec.ts"; import { type VerdictCategory } from "../domain/verdict.ts"; import { type GateOutcome } from "../domain/measured-verdict.ts"; import type { ProgressPort } from "../ports/progress.ts"; /** * Thrown when a verifier step does not return a passing verdict — either it * reported VERDICT: FAIL, or it returned no verdict line at all (silence is * treated as failure, never as success). Halts the pipeline fail-fast so a * stage with a type error or failing test cannot be claimed as complete. */ export declare class VerificationFailedError extends Error { readonly issue: string; readonly stage: string; readonly verdictText: string; /** * The routing category for this failure (run-stage reads it): "implementation" * → the worker can retry with feedback (bounded); "plan" → halt to a human. * A measured gate failure is always "implementation"; an LLM FAIL preserves * the verifier's declared category. */ readonly category: VerdictCategory; constructor(issue: string, stage: string, verdictText: string, /** * The routing category for this failure (run-stage reads it): "implementation" * → the worker can retry with feedback (bounded); "plan" → halt to a human. * A measured gate failure is always "implementation"; an LLM FAIL preserves * the verifier's declared category. */ category?: VerdictCategory); } /** * Thrown when an agent step exceeds its per-step deadline. run-step aborts the * agent (killing the underlying process via the abort signal) and throws this, * so an unattended step cannot hang forever. The caller halts the run cleanly * to a human rather than waiting indefinitely. */ export declare class StepTimeoutError extends Error { readonly issue: string; readonly stage: string; constructor(issue: string, stage: string); } export interface Step { tier: Tier; issue: string; stage: string; skills: string[]; inputs: string[]; instruction: string; worktree: string; /** * When set, the step commits its worktree changes with this message after a * successful agent run, and the resulting SHA is returned. When undefined, * the step produces no commit (e.g. a verifier that only reads state). */ commitMessage?: string; /** * The task's declared Target Files for this step (worker steps only). After a * commit, run-step compares the actually-committed files against these and * emits a `scope-warning` for any stray (non-target, non-test) file. Warn, * never block. Empty/omitted disables the check (nothing to scope against). */ targetFiles?: string[]; /** * The human checkpoint for this step, consulted AFTER the agent runs and * commits. "approval" asks the GatePort to confirm; "none" (or omitted) runs * fully AFK. Declining halts the pipeline with GateDeclinedError. */ gate?: GateMode; /** * When true, the step's output MUST end with a passing VERDICT line or the * pipeline halts (silence = failure). Defaults to true for verifier-tier * steps; set explicitly so a non-verifier step (e.g. a planner-tier FINAL * verification) can also enforce a verdict without being on the verifier tier. */ verifies?: boolean; } export interface RunStepDeps { agent: AgentPort; git: GitPort; /** Required only when a step requests an "approval" gate. */ gate?: GatePort; /** Optional progress sink; when present, the run loop emits structured events to it. */ progress?: ProgressPort; /** * Optional liveness-ticker factory. When present, runStep builds a heartbeat * around the (long, otherwise-silent) agent call: it is started before the * run and stopped after — even on failure. Each tick forwards the elapsed * time as a `heartbeat` progress event for this step's stage, so a surface * can show the run is alive without the agent emitting anything itself. The * factory receives the per-tick callback and returns a start/stop handle, so * the timer is injected and the wiring is unit-tested without a real clock. */ heartbeat?: (onTick: (elapsedMs: number) => void) => HeartbeatHandle; /** * Optional per-step deadline factory. When present, runStep arms a deadline * around the agent call: the factory receives an `onExpire` callback and * returns a start/stop handle. On expiry, runStep aborts the agent (killing * the underlying process via an AbortSignal) and throws StepTimeoutError, so * an unattended step cannot hang forever. Injected so the timeout is * unit-tested by firing expiry by hand, with no real timer. */ deadline?: (onExpire: () => void) => DeadlineHandle; /** * Optional global run-budget gate. When present, runStep calls `check()` * BEFORE running the agent; a breach throws (RunBudgetExceededError) and the * agent never runs, so a runaway run is stopped at the next step boundary. */ budget?: BudgetGate; /** * Optional engine-owned verification gate (ADR 0001). When present, a * VERIFYING step runs the configured gate commands in the worktree and the * MEASURED exit codes are combined with the LLM verdict — a non-zero exit * fails the stage regardless of the verdict. Absent, the step falls back to * the LLM verdict alone (and the caller should warn the run is verdict-only). */ verifyGate?: VerifyGate; } /** Runs the project's deterministic gate commands in a worktree (ADR 0001). */ export interface VerifyGate { run(worktree: string): Promise; } /** A started/stoppable per-step deadline handle (see RunStepDeps.deadline). */ export interface DeadlineHandle { start(): void; stop(): void; } /** The run-budget seam: check() throws when a ceiling is breached. */ export interface BudgetGate { check(): void; } /** A started/stoppable liveness ticker handle (see RunStepDeps.heartbeat). */ export interface HeartbeatHandle { start(): void; stop(): void; } export interface StepResult extends PiResult { /** The commit SHA produced by this step, if it committed. */ commit?: string; } export declare function runStep(deps: RunStepDeps, step: Step): Promise;