import { type AgentRunner } from "./agent-runner.js"; import { fingerprint, type ClassAnalysis, type Finding, type IdentifiedFinding } from "./findings.js"; /** * `converged` — a round produced no new blocker or major findings. * `cap-reached` — the round cap ran out with findings still open. * `requires-replan`— a structural defect no spec edit can fix; the loop stops * immediately and refers the program back to planning. * `aborted` — the loop could not run (missing config, agent failure). */ export type LoopOutcome = "converged" | "cap-reached" | "requires-replan" | "aborted"; export interface Disagreement { finding: IdentifiedFinding; reason: string; /** Rounds in which the critic raised it and the writer declined it. */ rounds: number[]; } export interface RoundRecord { round: number; critic: string; writer?: string; scoped: boolean; scopedTo?: string[]; raised: number; fresh: number; applied: number; rejected: number; /** * What each agent said it did, verbatim. The findings list records *what* * the critic flagged; this is the only place its reasoning survives — the * parser keeps the structured block and discards everything around it. */ criticSummary?: string; writerSummary?: string; } /** Which agent held which role, so the choice is visible without reading source. */ export interface ResolvedAgents { author: string; validator: string; /** No `authorAgent` was configured, so the build agent was borrowed. */ borrowedBuildAgent: boolean; } export interface ValidateLoopResult { programId: string; outcome: LoopOutcome; /** The gate verdict, decided independently of why the loop stopped. */ result: "PASSED" | "FAILED"; reason?: string; agents?: ResolvedAgents; strict: boolean; rounds: RoundRecord[]; findings: IdentifiedFinding[]; openDisagreements: Disagreement[]; replanFindings: IdentifiedFinding[]; /** Written only after the complete semantic and mechanical gate passes. */ convergenceReceipt?: string; /** Full critic responses, including protocol-repair attempts. */ criticLogs: string[]; /** Durable handoff consumed by plan-program after requires-replan. */ replanReport?: string; /** Critic says the user's requirements need a human decision. */ requirementsChangeRequested?: boolean; /** Cross-run stalemates recorded in memory, awaiting `decide`. */ pendingDecisions?: string[]; } export interface ValidateLoopOptions { cwd: string; programId: string; rounds?: number; strict?: boolean; agentRunner?: AgentRunner; onProgress?: (line: string) => void; now?: () => Date; /** Explicitly accept unresolved non-blocking semantic findings after the round cap. */ allowSemanticRisks?: boolean; } export type CriticProtocolFailureKind = "missing-json" | "invalid-json" | "contract-mismatch"; export interface CriticProtocolFailure { kind: CriticProtocolFailureKind; message: string; } /** * Model replies wrap JSON in prose and fences. Scan fenced blocks from the * last backwards and return the first that parses **and** matches `accept`, * else the last balanced brace span. * * The predicate is not optional in practice, and leaving it out was a real * defect. "Last parseable fenced block" silently picks the wrong one the * moment a reply contains any other JSON after its answer — a critic quoting * a manifest fragment as evidence, say. The parse then finds no `findings` * key, reports zero findings, and the loop reads that as a clean round and * returns PASSED. A validation gate that fails open is worse than one that * fails, so callers state the shape they want and a reply that never * produces it is a protocol failure, not a clean bill of health. */ export declare function extractJson(output: string, accept?: (value: unknown) => boolean): unknown; /** True when `value` is an object whose `key` is an array. */ export declare function hasArrayKey(value: unknown, key: string): boolean; export interface CriticReply { /** * False when the reply contained no block matching the findings contract. * Callers must not treat that as "zero findings" — it means the critique * never arrived, which is the opposite of a clean review. */ found: boolean; findings: Finding[]; checkpointAssessments: CheckpointAssessment[]; missingAssessments: string[]; requirementsChangeRequested: boolean; requirementsChangeReason?: string; criteriaPatches: CriteriaPatch[]; classAnalyses: ClassAnalysis[]; missingClassAnalyses: string[]; protocolFailure?: CriticProtocolFailure; } export interface CriteriaPatch { criterionId: string; kind: "clarification" | "substantive"; intentPreserved: boolean; before: string; after: string; reason: string; } export interface CheckpointAssessment { workstreamId: string; status: "safe" | "unsafe"; reason: string; } /** Parse a critic reply, discarding anything that does not fit the contract. */ export declare function parseCriticReply(output: string, expectedWorkstreamIds?: readonly string[]): CriticReply; /** Findings only, for callers that treat an unparseable reply as empty. */ export declare function parseCriticFindings(output: string): Finding[]; export interface WriterVerdict { applied: string[]; rejected: Array<{ id: string; reason: string; }>; resolutionProofs: Array<{ id: string; changedPaths: string[]; checkedSubjects: string[]; completenessBasis: string; }>; /** False when the reply contained no block matching the verdict contract. */ found: boolean; } export declare function parseWriterVerdict(output: string): WriterVerdict; export declare function validateLoop(options: ValidateLoopOptions): Promise; export { fingerprint }; //# sourceMappingURL=validate-loop.d.ts.map