import { type AlignDropReason } from './range-align'; import type { DroppedReplacement } from './llm-map'; import { type CacheEntryMeta } from '../llm/cache'; import type { LLMProvider } from '../llm/types'; import type { Replacement, SourceRange } from '../seam/types'; /** * The mutator-name prefix every stage-2 LLM proposal carries. A distinct, * non-built-in tag so reporting can separate LLM mutants from Stryker's 17 * formulaic operators (development-plan §3.3 / §4.4). The model's per-candidate * tag is appended after a slash, e.g. `llm/negate-condition`. */ export declare const PROPOSE_MUTATOR_PREFIX = "llm"; /** * A target the propose stage operates on: the exact source text of an ENCLOSING * FUNCTION chosen by stage-1 risk targeting (Gate 3 batches by function, ONE * provider.generate() per function), plus the function's 0-based {@link SourceRange} * in `fileName`. `range`/`spanText` describe the WHOLE function (what the model * reads); each candidate's TRUE per-edit range is derived later by locating + * node-aligning the candidate's sub-expression `original` inside the function. * * To node-align, propose needs the FULL file content and the function's ABSOLUTE * char offsets ({@link fileContent}/{@link spanStartOffset}/{@link spanEndOffset}); * the targeting stage populates them (it already parses the file and knows each * function node's `start`/`end`). They are OPTIONAL for backward-compat — when * absent, propose falls back to `spanText` as the function source and offset 0, * which still aligns correctly for a single-function `spanText`. */ export interface ProposeTarget { /** Path of the file the span lives in. Flows onto `Replacement.fileName`. */ fileName: string; /** The enclosing function's 0-based Stryker range (used only as a fallback). */ range: SourceRange; /** The enclosing function's source text the model reads + mutates within. */ spanText: string; /** * Optional surrounding file context (e.g. the enclosing function or whole * file) to help the model reason about behavior. Sent to the model for * grounding only; never used to derive positions. */ context?: string; /** * The FULL file source — used to node-align each candidate's sub-expression. * Defaults to {@link spanText} (so a standalone single-function span aligns). */ fileContent?: string; /** * The enclosing function's absolute char START offset in {@link fileContent}. * Defaults to 0 (the start of `spanText` when `fileContent` is omitted). */ spanStartOffset?: number; /** * The enclosing function's absolute char END offset in {@link fileContent} * (exclusive). Defaults to the length of the resolved file source. */ spanEndOffset?: number; /** * The function's name when it has one (a declaration's `id`, a method's * `key`); absent for anonymous / arrow functions. Provenance only — it is * recorded on the cache entry's `meta`, never used for positions or keys. */ functionName?: string; } /** * Options for {@link propose}. All optional — the common call is * `propose(provider, target)`. */ export interface ProposeOptions { /** * Upper bound on how many candidates to request from the model for this * span. Maps to the stage-2 `maxCandidatesPerFile` budget cap at the call * site (development-plan §4.3). The returned array is truncated to this * length defensively even if the model over-produces. Default 8. */ maxCandidates?: number; /** Optional model override forwarded verbatim to the provider. */ model?: string; /** Optional content-addressed cache-key hint forwarded to the provider. */ cacheKey?: string; /** Optional cache-entry provenance forwarded to the provider alongside `cacheKey`. */ cacheMeta?: CacheEntryMeta; /** Optional cooperative cancellation signal forwarded to the provider. */ signal?: AbortSignal; } /** * The cache identity of one propose call: the content-addressed key the * budgeted provider files the response under, plus the provenance `meta` * written onto a NEW entry. This is the ONE place the key is computed — the * pre-pass (to issue the call) and the targeting stage's `isCached` probe (to * decide which functions are free) must agree byte-for-byte, so both call this. */ export interface ProposeCacheIdentity { /** The `ProviderRequest.cacheKey` for this target. */ cacheKey: string; /** The `CacheEntry.meta` provenance to record on a new entry. */ meta: CacheEntryMeta; } /** * Compute the {@link ProposeCacheIdentity} for a target. The key goes through * the same `computeCacheKey(model, prompt, system, schema)` digest as before, * but the `prompt` slot holds `fp:|max:` — the * STRUCTURAL fingerprint of the function (`./fingerprint`) rather than its * verbatim text — so comment / whitespace / literal-spelling edits keep hitting * the same entry, while the model, the system instructions, the schema and the * candidate cap still change it (they change what the model was asked to do). * * @param target The enclosing-function target (only `spanText`, `fileName`, `functionName` are read). * @param model The requested model id or alias (the same string the provider is given). * @param maxCandidates The per-call candidate cap (shapes both the prompt and the schema). * @param log Optional sink for the (rare) fingerprint text-hash fallback notice, * suffixed with the target's location; the pre-pass passes its logger, the * targeting probe (which keys the same targets again) passes none. */ export declare function proposeCacheIdentity(target: ProposeTarget, model: string, maxCandidates?: number, log?: (line: string) => void): ProposeCacheIdentity; /** The seam-ready replacements + the candidates dropped during node-alignment. */ export interface ProposeResult { /** Node-aligned, seam-ready replacements (each range = a real EXPRESSION node). */ replacements: Replacement[]; /** Candidates dropped because their `original` could not be node-aligned. */ dropped: DroppedReplacement[]; /** * True when the provider served this call from its cache (a free hit) rather * than a paid model call. The pre-pass counts only PAID calls toward its * call tally and diminishing-returns window. */ cached: boolean; /** * Per-TYPED-reason tally of the node-alignment drops in {@link dropped}. The * typed {@link AlignDropReason} is lost once each drop is flattened into the * human-readable `reason` string, so it is preserved here for the pre-pass's * per-function drop summary (which buckets by category WITHOUT string-parsing * the reason text). Only reasons that actually occurred carry a key. */ dropCounts: Partial>; /** * How many of {@link replacements} were re-found by AST SHAPE rather than * verbatim (range-align's structural fallback) — cached candidates replayed * against respelled source. Logged per call next to the drop buckets. */ recovered: number; } /** * Ask the injected provider for behavior-changing mutation candidates for one * ENCLOSING FUNCTION and return them as NODE-ALIGNED, seam-ready * {@link Replacement}s plus the candidates dropped during alignment. * * Each candidate's sub-expression `original` is located + node-aligned inside the * function (`./range-align`) so its `range` equals a REAL EXPRESSION node — the * invariant the map-builder + `LLMMutator` + Stryker's expression placer require. * Candidates that fail alignment (not-found / ambiguous / non-node-aligned / * not-an-expression / not-expression-placeable) are returned in `dropped` (NOT emitted). Beyond alignment * this performs NO filtering except truncating to `maxCandidates` — parse-check, * `replacement === original` rejection, dedup, and near-equivalence are the job of * `./filters` + `./near-equivalence`, which this stage's output feeds into. The * provider has already validated the response against the JSON schema. * * Rejects (propagates the provider's `Error`) on transport/auth failure, abort, * or a terminal schema-validation failure — the provider contract guarantees it * only resolves with schema-valid output (development-plan §4.1). * * @param provider DI'd LLM provider (mock in tests; never imported concretely). * @param target The enclosing function to mutate within, plus alignment inputs. * @param options Budget cap, model/cache/abort forwarding. * @returns The node-aligned replacements (≤ `maxCandidates`) and the align-drops. */ export declare function propose(provider: LLMProvider, target: ProposeTarget, options?: ProposeOptions): Promise;