import type { AxisName, Finding, PerRuleOpportunity, ProjectionMeta } from "../types.js"; import { type ScoreModel } from "../scorer.js"; /** The subset of scoring inputs `computeProjection` re-scores against. */ export interface ProjectionRun { opportunitiesByAxis: Record; perRuleOpportunities: PerRuleOpportunity[]; } /** Scorer options threaded through to the projection re-score (per model). */ export interface ProjectionScoreOpts { minSampleSize?: number; aiGovernanceGrace?: number; } /** * Default distinct-file threshold above which a fix group is flagged * `migrationScale` (large blast radius — sample before you sweep). * Overridable via `.lyse.yaml` `advisory.migrationScaleFileCount`. */ export declare const MIGRATION_SCALE_FILE_COUNT_DEFAULT = 40; /** One coherent fix: all findings sharing a `fixGroup.key` (or bare `ruleId` when ungrouped). */ export interface FindingGroup { /** `fixGroup.key` when present, else `ruleId`. */ key: string; ruleId: string; /** `fixGroup.from` of the representative finding. */ from?: string; /** `fixGroup.to` of the representative finding (single-candidate replacement). */ to?: string; findings: Finding[]; /** Distinct `location.file` count across the group's findings. */ fileCount: number; /** True when `fileCount >= migrationScaleFileCount`. */ migrationScale: boolean; } /** * Groups findings by their fix — `fixGroup.key` when present, else `ruleId`. * Sort is deterministic: group size (findings.length) descending, then * severity ascending (error < warning < info — a group's severity is its * `findings[0].severity`, uniform per group by construction), then key * ascending as the final tiebreaker. */ export declare function groupFindings(findings: Finding[], migrationScaleFileCount: number): FindingGroup[]; /** * Computes a deterministic score projection for the largest fix groups. * Candidates are the top `cap * 2` groups by count (as ranked by * `groupFindings`); each candidate's gain is the Health Score delta from * removing its findings, floored at 0. Groups with zero gain are dropped. * `totalGainTop3` is a single additional scorer run with every kept * top-entry's findings removed at once — gains do not add linearly. * * Removal is by object identity (`Finding` has no unique id), so `groups` * must be derived from `allFindings` itself — e.g. `groupFindings(findings)` * followed by `computeProjection(groups, findings, ...)`. Passing a * deep-equal but distinct `Finding[]` (e.g. round-tripped through JSON) * silently yields zero gain for every candidate. * * Returns undefined when `finalScore` is `"N/A"` or no group has gain > 0. * * The re-score runs under the SAME `model` (v2 or v3) that produced * `finalScore`, so projected gains are consistent with the headline score. */ export declare function computeProjection(groups: FindingGroup[], allFindings: Finding[], run: ProjectionRun, model: ScoreModel, scoreOpts: ProjectionScoreOpts, finalScore: number | "N/A", cap?: number): ProjectionMeta | undefined;