import { type ValidationIssue, type JudgeReport, type WorkBlock, type WorkBlockSeam } from "audit-tools/shared"; import { readIntakeRiskSignal, type AdversarialDepth } from "../riskSignal.js"; import { type ContractPipelineCrossGateInputs, type GateOutcome } from "../validation/contractPipelineGates.js"; import type { Finding } from "audit-tools/shared"; import type { ContractPipelineArtifactName } from "../contractPipeline/artifactStore.js"; import type { RemediationStep } from "./types.js"; import { intakePaths } from "../intake.js"; /** * Runaway backstop for the judge↔repair loop — the LOUD exception path, NOT the * normal terminator. The loop normally terminates by *convergence*: it keeps * repairing only while each round surfaces a genuinely NEW accepted counterexample * (real progress), reaches a fixpoint when the judge approves, and escalates to the * user the moment a round re-accepts an already-addressed counterexample without * progress (a stall/oscillation). This ceiling exists only so a pathological run * that keeps minting brand-new accepted counterexamples forever cannot loop without * bound; hitting it is itself an escalation (loud), never a silent proceed. It is * deliberately generous — a genuinely deep but converging design (each round a new * real defect) must not be cut mid-convergence (the failure mode of the former N=2). */ export declare const MAX_CONTRACT_REPAIR_ITERATIONS = 8; /** Maximum implementation_dag regenerations after traceability rejections. */ export declare const MAX_DAG_REGENERATION_ATTEMPTS = 2; /** * Maximum LLM cycle-break resolution attempts before routing to user-decision * (and, if that also fails, to `blocked`). */ export declare const MAX_CYCLIC_SEAM_RESOLUTION_ATTEMPTS = 2; export interface CyclicSeamRepairState { schema_version: "remediate-code-contract-pipeline/cyclic-seam-repair-state/v1alpha1"; /** * Each attempt to resolve the detected cycles (keyed by obligation_ledger * hash). `recheck_reason` records WHY the re-check rejected an attempt, so the * next resolution prompt can state it instead of re-asking for the same claim * and burning the attempt cap on an unexplained retry. */ attempts: { ledger_hash: string; at: string; recheck_passed: boolean; recheck_reason?: string; }[]; /** Whether a user-decision step has been emitted. */ user_decision_emitted: boolean; } export declare function readCyclicSeamRepairState(artifactsDir: string): Promise; export declare function writeCyclicSeamRepairState(artifactsDir: string, state: CyclicSeamRepairState): Promise; /** Outcome of an archive attempt. */ export interface ArchiveOutcome { /** * Timestamped history path the original was moved to, or undefined when the * source did not exist (nothing to archive). */ archivedPath?: string; /** * True when the original path is now free for a fresh Write (the move * succeeded, or there was nothing to archive). False when the move failed and * the original was preserved in place — the caller must NOT assume the path is * re-authorable. */ originalFree: boolean; } /** * Archive an artifact into `/history/` instead of deleting it, so a * repair loop never silently destroys an LLM output. Two disjoint files exist * per artifact (D3): the host's plain INPUT (`.input.json` — the LLM * emission) and the tool's canonical envelope (`.json` — regenerable * bookkeeping). On a stale/invalid re-emit BOTH are moved to history: the input * to preserve the LLM output AND free its path for a fresh host Write, the * canonical so the completion gate (`contractArtifactExists`) re-fires and the * producing phase re-emits. The returned `archivedPath` references the input * archive when present (what the host re-authors), else the canonical archive. * A tool-derived artifact with no input file (e.g. a merged-shard artifact) * archives only its canonical envelope. If any move throws, the rest are left * in place (`originalFree: false`) rather than silently dropped. `renameFn` is a * DI seam so a failed history move is testable. */ export declare function archiveContractArtifact(artifactsDir: string, name: ContractPipelineArtifactName, label: "stale" | "invalid", renameFn?: (from: string, to: string) => Promise): Promise; /** * The explicit re-author signpost appended to every inline rejection re-emit: * the prior output was archived, so the worker must Write a fresh complete * artifact at the ORIGINAL path — never Edit the previous (now-archived) file. */ export declare function rejectionRewriteInstruction(archived: { archivedPath?: string; originalFree?: boolean; } | string | undefined): string; export interface ContractIngestionResult { /** Raw worker payloads that validated and were wrapped into envelopes. */ ingested: ContractPipelineArtifactName[]; /** Raw worker payloads that failed validation (archived; phase re-emitted). */ invalid: { name: ContractPipelineArtifactName; issues: ValidationIssue[]; }[]; } /** * Derive validated canonical envelopes from the host's plain INPUT files (D3). * The host writes the bare payload the role schema describes to * `.input.json`; the tool reads it here, validates it, and writes the * content-hash envelope to the canonical `.json` — the host's input file * is never mutated in place. CP_ARTIFACT_NAMES is dependency-ordered, so * dependencies are enveloped before their dependents and dependency hashes are * always available. */ export declare function ingestContractArtifacts(artifactsDir: string): Promise; export interface ContractPipelineCheckResult { /** True when the contract pipeline should handle the next step. */ shouldHandleContractPipeline: boolean; /** True when all pipeline phases (up to implementation_dag) are complete. */ pipelineComplete: boolean; } /** * Determine whether the contract pipeline should be entered for this run. * The pipeline is entered for ALL intake source types (structured_audit, * document, conversation) when an extracted-plan.json has not yet been * produced. Path A (structured_audit) seeds the pipeline via a path_a_seed.json * before the first phase step, so goal_normalization and context_collection * prompts can reference the auditor findings. */ export declare function shouldEnterContractPipeline(artifactsDir: string, _intakeSourceType: string | undefined): ContractPipelineCheckResult; /** Return the first pipeline phase whose output artifact does not exist. */ export declare function nextMissingContractPhase(artifactsDir: string): string | null; export interface ContractPipelineStepOptions { root: string; artifactsDir: string; runId: string; sourcePaths?: string[]; /** * The same DI seam {@link archiveContractArtifact} already exposes, lifted to * the entry point so a FAILED history move is reachable from an end-to-end * test. Undefined uses `node:fs/promises` rename, so production behavior is * unchanged. It exists because the archive-failure branch (COR-114e4941) is a * correctness gate whose whole point is what the pipeline does when the move * does not succeed — a branch no fixture can reach by arranging files. */ renameFn?: (from: string, to: string) => Promise; } export interface PathASeed { schema_version: "remediate-code-contract-pipeline/path-a-seed/v1alpha2"; /** Absolute path to the audit-findings.json source file. */ audit_findings_path: string; /** Number of findings in the report. */ finding_count: number; /** Short per-finding summaries (id + title + lens). */ findings_summary: Array<{ id: string; title: string; lens: string; }>; /** Repo-relative paths cited as affected_files across all findings. */ affected_files: string[]; /** Auditor-produced bounded work topology. */ work_blocks: WorkBlock[]; /** Explicit cross-block overlaps; required seams must be prepared before refactors. */ work_block_seams: WorkBlockSeam[]; /** * Seed source-digest binding. One sha256 per source path the seed was built * FROM, recorded at seed-build time: the audit-findings file itself plus every * `affected_files` path that existed on disk. `buildNextContractPipelineStep` * re-hashes each on entry and refuses when one no longer matches, instead of * spending a whole design pipeline on content that no longer holds the * findings the seed enumerates. * * OPTIONAL for READING, always written for WRITING: a seed persisted before * this field existed carries none, and an absent list binds nothing rather * than blocking a run mid-flight. Paths are stored exactly as the seed knows * them — the findings path absolute, `affected_files` repo-relative — and the * verifier resolves a relative entry against the repo root it is handed. */ source_digests?: Array<{ path: string; sha256: string; }>; created_at: string; } /** * Write a Path-A seed file from a parsed audit-findings report. * The seed is written once (idempotent: skipped when it already exists). * goal_normalization and context_collection prompts detect the seed and * include its contents so every pipeline node traces to an auditor finding. */ export declare function writePathASeedFromFindings(artifactsDir: string, auditFindingsPath: string, auditFindings: unknown): Promise; /** One seed-recorded source path whose content no longer matches its digest. */ export interface SeedSourceDigestMismatch { path: string; expected: string; /** The path's current sha256, or `null` when it is no longer readable. */ actual: string | null; } /** * Seed source-digest binding — re-hash every path the path_a seed recorded and * report the ones that moved. Pure over (root, seed): the caller decides what a * mismatch means, so this is directly red-green testable without a pipeline. * * A seed with no `source_digests` (written before the field existed) binds * nothing and yields no mismatches. */ export declare function detectSeedSourceDigestMismatches(root: string, seed: PathASeed | undefined): Promise; /** * When a judge report omits `repair_directive`, infer the repair target from * the failing classifications. Post-redesign the default is * `finalized_module_contracts` (not `design_spec`). */ type ExtendedRepairTarget = "finalized_module_contracts" | "obligation_ledger" | "contract_assessment_report"; /** * Infer the most appropriate repair target from judge classifications when no * explicit repair_directive is provided. Examines only accepted classifications * and keyword-matches their rationale text. * * Priority (first match wins): * obligation/ledger/invariant/constraint keywords → obligation_ledger * assessment/finding/gap keywords → contract_assessment_report * fallback → finalized_module_contracts */ export declare function inferRepairTarget(classifications: JudgeReport["classifications"] | undefined): ExtendedRepairTarget; type CritiqueGate = { kind: "proceed"; } | { kind: "escalate"; reason: "stall" | "runaway"; blocking: string[]; note: string; } | { kind: "repair"; critiqueHash: string; blockingIds: string[]; }; /** * Decide whether the pipeline may advance past the conceptual-design critique. * * The routing signal is MECHANICAL and derived only from the critique items: a * critique carrying ANY `severity: "blocking"` item means the design is not * approved and must be repaired — regardless of the author-stated `verdict` * string. This closes the contradictory-combo gap: a critique that marks items * `blocking` while declaring `approved` / `approved_with_concerns` (which the * pipeline previously waved through, since only a judge verdict ever gated * anything) no longer silently proceeds. Enforce-in-tooling: the verdict label * is advisory display; the blocking-item set is the contract. * * Convergence-terminated, mirroring {@link evaluateJudgeGate}: the first blocking * critique ⇒ repair the design (`finalized_module_contracts`); repairing it * re-stales and re-emits the critique (it depends on the finalized contracts), so * a clean re-critique ⇒ proceed (the fixpoint). A fresh critique whose blocking * ids were ALL already addressed by a prior repair, with none new ⇒ escalate * (stall — the design loop is not converging) rather than repair forever; the * runaway backstop also escalates (loud). */ export declare function evaluateCritiqueGate(artifactsDir: string): Promise; export interface DagTraceabilityResult { ok: boolean; violations: string[]; } /** * The traceability invariant: no implementation_dag node may exist without * tracing to an obligation from the ledger (satisfies_obligations or * verification_obligation_ids) or to a judge-accepted counterexample * (addresses_counterexamples). Untraceable nodes are unattributable work — the * exact thing the contract pipeline exists to prevent. */ export declare function validateImplementationDagTraceability(artifactsDir: string): Promise; export interface ContractObligationsGateResult { ok: boolean; violations: string[]; } /** * Run the fail-closed contract-obligation gates against the persisted contract * artifacts: paired obligations, evidence threading, source-scoped digest * coverage, and INV-CO-12 reconciliation derivation. * * Branch on `evaluated` before trusting emptiness. This no longer flattens four `ValidationIssue[]` * into one array, where a gate that never RAN and a gate that ran CLEAN both * contributed nothing and were indistinguishable. It consumes the shared * gate-outcome record and branches on `evaluated` first: at this boundary every * phase artifact exists, so a skipped gate is a violation, not a pass. See * {@link consumeGateOutcomes} for the per-boundary `required` policy and the one * declared exception (`digest_coverage`). */ export declare function evaluateContractObligationsPromotionGate(artifactsDir: string, root?: string, inputs?: ContractPipelineCrossGateInputs): Promise; /** * Pre-adversarial structural floor (S5). The subset of the contract-obligation * gates whose inputs all exist by the time the critic phase is reached * (paired-obligation coverage, source-scoped digest coverage, and seam * reconciliation derivation — none of which need the judge verdict or the * implementation_dag). Running them BEFORE the expensive critic/judge loop means * the adversarial phases only ever see structurally-sound obligations, tests, and * contracts, and a structural gap is re-emitted to the precise responsible phase * instead of being discovered at promotion (after the adversarial budget is spent) * and re-emitted to the wrong phase. The full {@link evaluateContractObligationsPromotionGate} * — including the evidence-threading check that needs the judge + DAG — still runs * at promotion as the fail-closed backstop; this gate never replaces it. * * Returns the first failing gate's responsible phase + rendered error lines, or * null when the structural floor is clean. Branches on each outcome's * `evaluated` before its empty issue list is allowed to mean clean * (the branch-on-evaluated rule); `contract_finalization`, `seam_reconciliation` * and `test_validator_plan` all precede `critic` in the phase order, so a * skipped gate here is a malformed payload rather than an absent one. */ export declare function evaluatePreCriticStructuralGate(artifactsDir: string, root?: string, inputs?: ContractPipelineCrossGateInputs): Promise<{ phase: "contract_finalization" | "test_validator_plan"; errorLines: string[]; } | null>; /** * Promotion-backstop citation grounding over the promoted extracted-plan * findings. Returns rendered violation lines, or null when every finding grounds. */ export declare function evaluatePromotedPlanCitationGrounding(artifactsDir: string, repoRoot: string): Promise<{ violations: string[]; } | null>; /** The phase(s) that fan out per module, and the artifact each produces. */ declare const PARALLEL_MODULE_PHASES: { readonly module_contract_drafting: "module_contracts"; }; type ParallelModulePhase = keyof typeof PARALLEL_MODULE_PHASES; export declare function isParallelModulePhase(phase: string): phase is ParallelModulePhase; /** * Everything a gate is handed. Assembled once per invocation, then passed to * every gate in the walk, so each gate is a module-level function that can be * called and tested on its own. * * Two fields are deliberately MUTABLE, each written by exactly one gate: * • `artifactsSettled` — set by the staleness gate once this invocation's own * ingestion + archive pass has run. {@link readCrossGatePayloads} REFUSES * before it is set, which is how the branch-on-evaluated invariant's freshness * half is enforced mechanically instead of by a caller remembering to * re-read: a payload cached from before the pass is unrepresentable. * • `nextPhase` — the phase frontier, resolved AFTER the archive pass because * archiving a stale artifact re-opens its producing phase. */ export interface ContractGateContext { readonly options: ContractPipelineStepOptions; readonly root: string; readonly artifactsDir: string; readonly runId: string; readonly sourcePaths?: string[]; readonly paths: ReturnType; readonly artifactPaths: Partial>; /** Present only for structured_audit (path-A) runs. */ readonly pathASeedPath?: string; readonly riskSignal: Awaited>; readonly adversarialDepth: AdversarialDepth | undefined; artifactsSettled: boolean; nextPhase: string | null; } /** The verdict a call site derives from a subset of the shared gate outcomes. */ export interface CrossGateVerdict { ok: boolean; violations: string[]; } /** * Consume a subset of the shared cross-gate outcomes, branching on `evaluated` * BEFORE an empty `issues` array is allowed to mean "clean". * * `required` is DECLARED PER CALL SITE, as data, because "did not run" means * different things at different boundaries. At a boundary whose upstream phase * order guarantees the gate's input exists, a skip is a refusal — its empty * issue list is proof of nothing. Earlier in the pipeline the same skip means * "not applicable yet", and the gate is simply not required there. * * THE UNCOVERED HALF, stated rather than implied: `digest_coverage` is the one * gate of the eight whose skip is a DOMAIN non-applicability (a source that is * not finding-enumerable) rather than a missing payload, so no boundary lists * it as required and a genuinely absent finding-enumeration file for an * enumerable source still skips silently. Closing that needs the gate module to * expose its enumerability predicate — an edit outside this work item's write * scope. */ export declare function consumeGateOutcomes(outcomes: readonly GateOutcome[], selected: readonly GateOutcome["gate"][], required: ReadonlySet): CrossGateVerdict; /** * The gate walk order, exported so a drift guard reads the real set instead of * reconstructing one by reflecting over a chain of `if` statements. * * This and the scaffold's `handledKeys` are BOTH `Object.keys` of the SAME * object literal, and the walk consumes `handledKeys` directly — so the two * agree by construction, not by a test that compares them. No such test exists, * and none is needed: there is no second list to drift from. */ export declare const CONTRACT_PIPELINE_GATE_ORDER: readonly string[]; /** * Build and write the next contract-pipeline step. * Returns null when the pipeline is complete and the extracted plan is ready. */ export declare function buildNextContractPipelineStep(options: ContractPipelineStepOptions): Promise; /** * The obligation-kind vocabulary, in priority order (higher index = higher * priority; `invariant` is highest). * * MNT-114e4941-3: this used to be a THIRD independent copy of the vocabulary — * a local `type ObligationKind` union beside derive.ts's `TESTABLE_KINDS` and * contractPipelineGates.ts's `TESTABLE_OBLIGATION_KINDS`, with nothing forcing * the three to agree, while the ledger's own `obligation.kind` is typed as a * bare `string`. The consequence was not theoretical: an unrecognized kind was * CAST to this union, scored -1 by `indexOf`, and then indexed the lens map to * `undefined` — so a ledger kind outside these four promoted a finding with * `lens: undefined`. * * It is now single-sourced two ways at once: * • MEMBERSHIP — {@link obligationKindVocabularyDivergence} reconciles this * list against the gate module's exported `TESTABLE_OBLIGATION_KINDS`, so a * kind added there and not here is a red contract test rather than a silent * misclassification; * • SEMANTICS — an unrecognized kind is not dropped or cast. It is routed * through the gate module's own `isTestablePhaseObligation` predicate, so * the two modules answer "is this kind testable?" with ONE implementation. */ export declare const OBLIGATION_KIND_PRIORITY: readonly ["test", "structural", "behavioral", "invariant"]; export type ObligationKind = (typeof OBLIGATION_KIND_PRIORITY)[number]; /** * Classify a raw ledger `kind` string (which the ledger types as a bare * `string`) into this module's vocabulary. A recognized kind maps to itself; an * unrecognized one is classified by the SHARED testability predicate rather * than guessed here — testable ⇒ `behavioral` (the testable default, so it * carries a real lens and a mid severity), otherwise ⇒ `structural`. */ export declare function classifyObligationKind(kind: string): ObligationKind; /** * Kinds the gate module declares TESTABLE that this module's vocabulary does * not carry — the drift MNT-114e4941-3 names, reported as data so a contract * test can go red on it instead of a reviewer having to notice. * Empty when the two agree. */ export declare function obligationKindVocabularyDivergence(): string[]; /** * Normalize one block's declared write scope into repo-relative, forward-slashed, * unique, sorted paths, refusing outright anything that leaves the repository. * * Case is PRESERVED (`repoRelativePath`, not the lowercasing * `normalizeRepoPath`): this string is the write scope a host enforces against a * landed diff on a case-sensitive filesystem, so lowercasing it would make a * legitimate edit look out of scope. Lowercasing is used only as a MATCH key, * never as the stored value. * * The tracked-tree half is NOT here: see * {@link evaluatePromotedPlanWriteScope}, which runs at this same promotion * boundary with a bounded re-emit instead of an unrecoverable throw. */ export interface BlockWriteScopeNormalization { /** The entries that normalized cleanly, repo-relative, unique and sorted. */ touched_files: string[]; /** One line per REFUSED entry. Non-empty ⇒ the caller must not promote. */ refusals: string[]; } export declare function normalizeBlockTouchedFiles(root: string, files: readonly string[], blockId: string): BlockWriteScopeNormalization; /** * The tracked-tree half of The normalized-write-scope invariant, run against * the PROMOTED plan so a violation takes the same bounded re-emit path the M-B3 * citation gate takes, rather than throwing out of the promotion. * * It exists because the citation gate is NOT a superset: a finding grounds if * ANY cited path OR SYMBOL is real, so a node whose prose names a real symbol * can ground while its declared write scope is still fabricated — and the write * scope is what a host binds a worker to. * * A path that is not tracked but whose parent directory IS stays legal: a * remediation block legitimately creates new files, and dropping a declared * write target is the failure mode that strands an implementer with an * obligation it has no scope to discharge. Fail-open on an unreadable tree, as * the citation gate does. */ export declare function evaluatePromotedPlanWriteScope(artifactsDir: string, root: string): Promise<{ violations: string[]; } | null>; /** * Refuse a targeted command that leaves the declared shape — a single * invocation with no shell chaining, substitution or redirection. Deliberately * ecosystem-neutral: this module never asserts WHICH runner is legitimate * (language-neutral by contract), only that the string handed to a shell cannot * do more than invoke one command. * * The RULE is not here. It is `commandLeavesDeclaredShape` * (`audit-tools/shared`), the one predicate the host-handoff consumer and the * triage re-verification spawn also ask. This function owns only the WORDING and * the refusals-as-data contract. It used to own a second, quote-BLIND regex * instead, and the two disagreed in both directions: `pytest -k 'not slow'` * cleared promotion and then dead-ended at the consumer as * `block_contract_invalid`, while `echo "a & b"` was refused here though the * consumer admits it. * * Refusals are RETURNED, never thrown: the promotion boundary turns them into * the same bounded re-emit every other promotion rejection takes, so a * malformed command re-emits implementation_planning instead of wedging every * subsequent next-step with an unclassified stack. */ export interface BlockCommandNormalization { targeted_commands: string[]; /** One line per REFUSED command. Non-empty ⇒ the caller must not promote. */ refusals: string[]; } export declare function normalizeBlockTargetedCommands(commands: readonly string[], blockId: string): BlockCommandNormalization; /** * Collect every write-scope and command refusal the promotion WOULD hit, before * a plan is written. Runs the same two normalizers over the same derived node * scope the promoter uses, so this pre-check and the promotion cannot disagree * about what is refusable — and the refusal reaches the host as the bounded * `implementation_planning` re-emit every other promotion rejection takes, * rather than as a thrown stack that wedges every subsequent next-step. */ export declare function collectDagWriteScopeRefusals(artifactsDir: string, root: string): Promise; /** * Path-A canonical-block membership validation, run BEFORE anything is promoted * (OBL-seam-prep-remediate-core-inv-2 / COR-114e4941). These are exactly the * checks the promoter itself performs while building its node→canonical-group * map — but there they THROW out of `promoteImplementationDagToExtractedPlan`, * an unclassified stack that wedged every subsequent next-step. Hoisted here so * an invalid `source_finding_ids` declaration takes the same bounded re-emit as * every other promotion rejection, with no gate having executed past it. * * Returns one line per violation; empty means the DAG is promotable on this * axis (or Path A is not in play at all). */ export declare function collectPathARefusals(artifactsDir: string): Promise; /** * Convert a completed ImplementationDAG into the extracted-plan.json format * that the existing handlePendingExtractedPlan/applyPlanPipeline path consumes. * * `root` defaults to the repository that owns `artifactsDir`, so the existing * one-argument callers keep working while the pipeline passes the run's real * root for write-scope normalization. */ export declare function promoteImplementationDagToExtractedPlan(artifactsDir: string, root?: string): Promise; /** * The PINNED shape of everything this module's two planning producers decide — * `promoteImplementationDagToExtractedPlan`'s canonical block membership and * normalized write scope, and `writePathASeedFromFindings`' sha256 baselines — * exported as ONE record so the regression guard that pins them imports a real * contract instead of re-deriving the plan's internals from JSON by hand. * * CDC-03: the guard consumes this token, so the phase deriver places it adjacent * to this module rather than four phases later, and a change to block membership * goes red in the phase immediately after this one. * * `estimated_tokens` is byte-derived and LOCAL (`estimateTokensFromBytes`) — it * describes content size only and is never a backend-fit claim; audit-tools does * not route. */ export interface ContractPipelinePlanningOutputs { /** Canonical membership + normalized write scope, in block-id order. */ block_membership: Array<{ block_id: string; items: string[]; touched_files: string[]; targeted_commands: string[]; }>; /** Every promoted finding id, and whether each is claimed by exactly one block. */ coverage: { finding_ids: string[]; exhaustive_once: boolean; }; /** Byte-derived per-block estimate over the findings the block claims. */ token_estimates: Array<{ block_id: string; estimated_tokens: number; }>; /** The path_a seed's recorded per-path sha256 baselines, path-sorted. */ seed_source_digests: Array<{ path: string; sha256: string; }>; } /** * Read the pinned planning outputs for a run, or null when no plan has been * promoted yet. */ export declare function readContractPipelinePlanningOutputs(artifactsDir: string): Promise; /** Source tag stamped on a lean-fast-path extracted plan (distinguishes it from `contract_pipeline`). */ export declare const LEAN_FAST_PATH_SOURCE = "lean_fast_path"; /** The minimal `extracted-plan.json` shape the lean path emits. */ export interface LeanExtractedPlan { plan_id: string; findings: Finding[]; project_type: string; source: typeof LEAN_FAST_PATH_SOURCE; candidate_closing_actions: string[]; } /** * Build the lean extracted plan from the approved findings. Blocks are * intentionally omitted: `normalizeExtractedPlan` synthesizes one block per * finding and `applyPlanPipeline` then merges blocks sharing a file + splits by * context budget — the same deterministic block derivation the contract pipeline * feeds into, single-sourced rather than reimplemented here. */ export declare function buildLeanExtractedPlan(findings: Finding[], planId: string): LeanExtractedPlan; export {}; //# sourceMappingURL=contractPipeline.d.ts.map