/** * Deterministic "rename sweep" signal for PR reviews. * * A mechanical rename (`semantic_search` → `search_code` across 40 files) is the * failure mode that anesthetizes an LLM reviewer: the diff is huge, uniform, and * "obviously fine", so the agent skims it and reports nothing. But a token-swap * inside a comment, docstring, or string literal carries a CLAIM — "reports as * disabled without embeddings", "meaning-based search" — that was renamed but * never re-verified. PR #658 did exactly this; Lien Review found nothing while * CodeRabbit caught two stale-prose claims that the rename silently invalidated. * * This module pre-computes the structural fact instead of hoping the agent * reviews every file hard, mirroring the `` / * `` precedents: infer the identifier substitution(s) the * diff repeats across many files, then hand the agent two short, explicit * worklists per mapping: * 1. PROSE-TOUCHED LINES — changed lines where the swap landed inside a * comment/docstring/string; each frames "verify the claim still holds". * 2. SURVIVORS — occurrences of the OLD name still present (the rename may be * incomplete). * * It injects FACTS (mapping + file:line + the post-image sentence), never a * verdict — the agent still judges each item. Everything here is computed from * the diff (and, when available, the indexed repo chunks); ZERO LLM calls. */ import type { SignalContext } from './signal-context.js'; /** An identifier substitution A → B the diff repeats across the PR. */ export interface RenameMapping { /** Old identifier (removed side). */ from: string; /** New identifier (added side). */ to: string; /** How many changed lines carry this exact swap. */ occurrenceCount: number; /** How many distinct files carry this swap. */ fileCount: number; } /** A changed line where the A → B swap landed inside prose. */ export interface ProseTouchedLine { file: string; /** New-file line number of the (post-image) changed line. */ line: number; /** Where the swap sits: 'doc' (prose file) | 'comment' | 'docstring' | 'string'. */ kind: ProseKind; /** The post-image line text (trimmed, capped). */ sentence: string; } /** A surviving occurrence of the OLD name after the sweep. */ export interface SurvivorSite { file: string; line: number; snippet: string; /** True when found repo-wide (an untouched file), false when in the diff post-image. */ repoWide: boolean; } /** The full signal for one detected rename mapping. */ export interface RenameSweepSignal { mapping: RenameMapping; proseTouched: ProseTouchedLine[]; /** Prose lines dropped by the cap (0 when none). */ proseOverflow: number; survivors: SurvivorSite[]; /** Survivor sites dropped by the cap (0 when none). */ survivorOverflow: number; } type ProseKind = 'doc' | 'comment' | 'docstring' | 'string'; /** * Infer the single identifier substitution turning `removed` into `added`, or * null when they differ by anything other than a consistent A → B token swap. * * Both lines are split into alternating glue/identifier segments. For a clean * rename: the arrays are the same length, every glue segment is identical, and * every differing identifier position is the SAME (from → to) pair. Anything * else (glue changed, a number changed, two different mappings on one line) * returns null — we only trust an unambiguous mechanical swap. */ export declare function inferSingleTokenSwap(removed: string, added: string): { from: string; to: string; } | null; /** * Detect rename sweeps from the diff alone. Exposed for testing — returns the * mappings that meet the occurrence/file threshold, most-repeated first. */ export declare function detectRenameSweeps(patches: Map): RenameMapping[]; /** * Classify whether the `token` swap on the post-image line `text` of `file` * landed inside prose (comment / docstring / string / prose-file), and which. * Returns null when the swap is in live code. * * Heuristics (documented, deliberately not a parser): * - a prose file extension (.md, .txt, …) → the whole line is prose ('doc'); * - a triple-quote (`"""` / `'''`) enclosing the token → 'docstring'; * - the token inside any quoted-string span → 'string'; * - a leading `//` `#` `*` `/*` `