/** * The audit VERDICT engine — a header sentence + per-recommendation `pointsIfFixed`, * both derived by RE-SCORING, never by a hardcoded template with a fake number. * * The audit scorer ({@link auditScore}) is pure and deterministic: `overall` is * `100 − Σ(graded penalties)` (clamped to [0,100]) and the letter grade comes from * fixed thresholds (A ≥90 … F <60). Because it's pure, we can answer two "what if" * questions by RUNNING it again with a single recommendation's finding(s) removed * and diffing the `overall`: * * 1. `pointsIfFixed` per recommendation — the exact number of overall points the * grade gains if THAT one fix is applied (so a fix card can show `+N pts` and * sort by it). Computed as `overall(report − thisFinding) − overall(report)`. * 2. A verdict `sentence` for the report header — e.g. "Two fixes away * from an A." — where the COUNT is the minimal number of fixes whose COMBINED * removal actually crosses the next grade threshold (a real cumulative * re-score), and `pointsToNextGrade` is the real threshold gap. * * Every recommendation maps 1:1 to exactly one graded finding (see `optimize` / * `explainScore`): each is a single deduction of a known weight, so removing it * lowers the penalty by that weight (modulo the [0,100] clamp). We remove the * finding by its detector + content (matched against the given recommendation), * NOT by reproducing `explainScore`'s ordering — so this stays aligned with the * `recommendations` array the caller passes, by index. * * Pure over its inputs (the same pieces `buildAuditReport` already has): no fs, no * clock, no model, no mutation of the caller's report. * * LIMITATION (documented, not fabricated): some graded penalties have NO * corresponding recommendation (a hard lethal-trifecta contract, an MCP server * that can't start, a `disallowedTools` typo, an invalid model/color, unresolved * skill resources, an invisible skill, a misplaced plugin dir, an ineffective / * never-firing hook). Those cannot be "fixed" through a recommendation here, so * they never contribute to `pointsIfFixed` and can make `fixesToNextGrade` null * (the gap can't be closed by the deterministic fix list alone) — in which case * the verdict leads with the dominant blocking finding instead. The * fixes-to-next-grade count uses a greedy largest-delta-first ordering; that is * provably minimal when penalties are additive (the common case, away from the * score-0 clamp) and a close upper bound otherwise. */ import { type AuditScore } from "./audit-score.js"; import type { Recommendation } from "./optimize.js"; import { type PluginScore } from "./score-core.js"; import type { ScanReport } from "./scan.js"; import type { GradedView } from "./core/apply-rules.js"; /** * The inputs the verdict needs — exactly the pieces {@link buildAuditReport} * already holds. `score` is the authoritative base (`overall` + `grade`) the * report displays; `report` is re-scored with a finding removed to diff against * it; `recommendations` is the array whose indices `perRecommendation` aligns to. */ export interface VerdictInput { /** The graded view: an ungraded group's findings never reach the verdict. */ readonly report: GradedView; readonly score: AuditScore; readonly recommendations: readonly Recommendation[]; } /** One recommendation's overall-points gain if its single fix is applied. */ export interface RecommendationPoints { /** Index into the input `recommendations` array. */ readonly index: number; /** * `overall(report − thisFinding) − overall(report)` — always ≥ 0 (removing a * penalty can only raise or hold the score). Can be 0 when the score is clamped * at 0 (removing one weight still leaves the penalty ≥ 100). */ readonly pointsIfFixed: number; } export interface Verdict { /** The header sentence — real numbers from the re-score + grade thresholds. */ readonly sentence: string; /** The current letter grade (echoed from the base score). */ readonly grade: PluginScore["grade"]; /** Points to the next-higher grade band, or null when already an A. */ readonly pointsToNextGrade: number | null; /** * The minimal number of deterministic fixes whose COMBINED removal crosses the * next grade threshold (real cumulative re-score, greedy largest-delta-first), * or null when the deterministic fix list can't close the gap (non-recommendation * penalties dominate) or the grade is already an A. */ readonly fixesToNextGrade: number | null; readonly perRecommendation: readonly RecommendationPoints[]; } /** * Compute the audit verdict + per-recommendation `pointsIfFixed` by re-scoring. * Pure and deterministic over its inputs. `perRecommendation` is index-aligned to * the input `recommendations`. */ export declare function computeVerdict(input: VerdictInput): Verdict; //# sourceMappingURL=audit-verdict.d.ts.map