import { z } from "zod"; import { type AbandonedMemoryEntry } from "./analyzeAbandonedMemory.js"; import type { LeaksReport, NextCallSuggestion } from "../types.js"; /** * Cycle-semantic diff. Answers: "did the antipattern I targeted actually * resolve, and how many instances/bytes were freed?" * * Where `diffMemgraphs` returns a structural diff (new/gone/persisted * cycle signatures + class-count deltas), `verifyFix` returns a * **classifier-aware** diff: each known antipattern is checked for in * both snapshots, and a per-pattern PASS/FAIL verdict is emitted along * with the bytes freed and instances released. * * Designed for CI gating: a build script can check `overallVerdict` and * fail the merge if the pattern resolved by the PR has regressed. */ /** * v1.14 item M. Default list of classes that legitimately stay alive * across before/after snapshots in normal iOS app activity. Sourced from * DebugSwift's `Performance.LeakDetector.swift` `_ignoredViewControllerClassNames`, * `_ignoredViewClassNames`, and `_ignoredWindowClassNames` curated lists * (FLEXTool fork lineage via Janneman84/LeakedViewControllerDetector). * * When these appear in `regressionClasses[]` we surface them under * `expectedAlive[]` for transparency but don't let them flip the verdict * to FAIL. Users can extend via `expectedAliveClasses` input or disable * with `disableDefaultWhitelist: true` for strict matching. */ export declare const DEFAULT_EXPECTED_ALIVE_CLASSES: readonly string[]; export declare const verifyFixSchema: z.ZodObject<{ before: z.ZodString; after: z.ZodString; expectedPatternId: z.ZodOptional; expectedAliveClasses: z.ZodOptional>; }, "strip", z.ZodTypeAny, { pattern: string; mode: "exact" | "substring" | "regex"; }, { pattern: string; mode?: "exact" | "substring" | "regex" | undefined; }>]>, "many">>; disableDefaultWhitelist: z.ZodDefault; verbosity: z.ZodDefault>; }, "strip", z.ZodTypeAny, { verbosity: "compact" | "normal" | "full"; after: string; before: string; disableDefaultWhitelist: boolean; expectedPatternId?: string | undefined; expectedAliveClasses?: (string | { pattern: string; mode: "exact" | "substring" | "regex"; })[] | undefined; }, { after: string; before: string; verbosity?: "compact" | "normal" | "full" | undefined; expectedPatternId?: string | undefined; expectedAliveClasses?: (string | { pattern: string; mode?: "exact" | "substring" | "regex" | undefined; })[] | undefined; disableDefaultWhitelist?: boolean | undefined; }>; export type VerifyFixInput = z.infer; export interface PatternResolution { patternId: string; before: { count: number; rootAddresses: string[]; }; after: { count: number; rootAddresses: string[]; }; /** PASS = pattern entirely gone from `after`. PARTIAL = present but reduced. FAIL = same or more. */ verdict: "PASS" | "PARTIAL" | "FAIL"; /** Estimated bytes freed (sum of `instanceSize` across nodes in the disappeared cycles). */ bytesFreed: number; /** Class-level instance count changes for classes that appeared in this pattern's cycles. */ instancesFreed: Record; } export interface VerifyFixResult { ok: boolean; before: { path: string; leakCount: number; totalBytes: number; }; after: { path: string; leakCount: number; totalBytes: number; }; totals: { leakCountDelta: number; bytesDelta: number; }; patternResolution: PatternResolution[]; /** Top-line verdict. PASS = everything resolved or improved. FAIL = at least one pattern got worse. PARTIAL = mixed. */ overallVerdict: "PASS" | "PARTIAL" | "FAIL"; /** When the user supplied `expectedPatternId`, this is the verdict for that one specifically. */ expectedPatternVerdict?: PatternResolution["verdict"]; diagnosis: string; suggestedNextCalls?: NextCallSuggestion[]; /** * v1.12+. Where the overall verdict came from. `cycle-pattern` is the * v1.11 behavior (classified cycles). `abandoned-memory` is the new * fallback path: when zero cycle patterns fire on either side, * verifyFix internally chains into `analyzeAbandonedMemory` and bases * the verdict on `actionableShrinkage` / `actionableGrowth` instead. * Branch on this field if you need to know which signal the verdict * is based on. */ verdictSource?: "cycle-pattern" | "abandoned-memory"; /** * v1.12+. Populated when `verdictSource` is `abandoned-memory` and the * fix freed at least one actionable class. The top-N entries (by * absolute delta) of `analyzeAbandonedMemory.actionableShrinkage[]`. */ freedClasses?: AbandonedMemoryEntry[]; /** * v1.12+. Populated when `verdictSource` is `abandoned-memory` and * something grew between the snapshots (regression or unrelated). Top-N * entries of `analyzeAbandonedMemory.actionableGrowth[]`. */ regressionClasses?: AbandonedMemoryEntry[]; /** * v1.14+. Class names from the effective whitelist * (DEFAULT_EXPECTED_ALIVE_CLASSES + user-supplied) that DID appear in * the raw regression set before filtering. Surfaced for transparency: * the agent can see which "regressions" were intentionally ignored. * Empty array when nothing was filtered. Absent when no fallback ran. */ expectedAlive?: string[]; } /** Pure function: compute verifyFix result from two parsed reports. */ export declare function verifyFromReports(beforeReport: LeaksReport, afterReport: LeaksReport, beforePath: string, afterPath: string, input: VerifyFixInput): VerifyFixResult; export declare function verifyFix(input: VerifyFixInput): Promise;