/** * Symbol identity continuity — deterministic rename/move detection between two * adjacent indexed states. (change: add-symbol-identity-continuity) * * The memory moat rests on anchoring: a memory/decision is pinned to a symbol by * `{ nodeId, stableId, symbolName, filePath, contentHash }` and recall refuses to * serve an orphaned anchor as authoritative. A pure RENAME (`computeTax` → * `calculateTax`) changes the symbol's name → changes its `stableId` → the anchor * orphans, even though the function still exists and the note is still true. This * module recovers that case: between the symbols that DISAPPEARED and the symbols * that APPEARED across a re-analysis, it matches `(old → new)` pairs on strong, * unambiguous evidence so a caller can carry the old symbol's anchors forward. * * It is intentionally pure: it operates on minimal node views, so the matching * logic is unit-tested without disk or the edge store. The disk-backed carry- * forward that applies these pairs to the persisted stores lives in * `../decisions/continuity-carry-forward.ts`. * * Discipline (mirrors the proposal's Decision): * - `exact-body` — the new span is byte-identical to the old one (a pure move; the * name did not change). * - `exact-signature` — the new span is identical to the old one EXCEPT the * symbol's own name changed (a rename). This is verified by substituting the * candidate's new name back to the old name and checking the span hashes to the * old baseline — NOT a mere parameter-shape match. This matters: a same-shape * newcomer with a *different* body (e.g. an unrelated `(req, res)` handler that * appeared the same run an anchored handler was deleted) does NOT pair, so a * genuinely deleted symbol is never re-anchored onto an unrelated newcomer. * - Carry forward only on a strict ONE-TO-ONE match, AND only when the normalized * (name-independent) body is unique among all new symbols, so two identical-body * clones never produce a confident single match. * - Anything ambiguous yields NO pair — the disappeared symbol is surfaced with * its candidate destinations, never silently re-attached to a guess. * - No similarity score, no threshold, no tuning constant, no clock, no model. * The result is a pure, byte-identical function of the two state views. */ import type { ContinuityReason, ContinuityBasis } from '../../types/index.js'; /** * Replace whole-identifier occurrences of `from` with `to` in source text. A name * embedded in a larger identifier (ASCII or Unicode) is left alone. Used to test * body-identity-modulo-name. Requires the `u` flag for the Unicode property escapes. */ export declare function renameIdentifier(text: string, from: string, to: string): string; /** * The name-independent body hash of a span: its own declared name replaced by a * fixed sentinel, then hashed. Two functions that differ ONLY in their name share * this hash. Used for the new-side uniqueness guard. */ export declare function normalizedBodyHash(spanText: string, name: string): string; /** * True when `appearedSpan` is the old symbol's body with ONLY the symbol's own name * changed: substitute the new name back to the old name and check the span hashes to * the old baseline. Requires a real rename (`appearedName !== oldName`); a same-name * span is the `exact-body` case, handled separately. */ export declare function bodyMatchesModuloName(appearedSpan: string, appearedName: string, oldName: string, oldContentHash: string): boolean; /** A symbol that was present in the old state and is no longer resolvable. */ export interface DisappearedSymbol { /** Old call-graph node id (the anchor's `nodeId`). */ nodeId: string; /** Old content-addressed stable id, when the symbol had one. */ stableId?: string; name: string; filePath: string; /** The anchor's recorded baseline span hash (the old body). Required to match. */ contentHash?: string; } /** A symbol present in the new state that did not exist in the old state. */ export interface AppearedSymbol { id: string; stableId?: string; name: string; filePath: string; /** Current span hash of the new symbol. */ contentHash: string; /** Current source span text of the new symbol (for the modulo-name check). */ spanText: string; /** Name-independent body hash of the new symbol (for the uniqueness guard). */ normBodyHash: string; } /** A confident `(old → new)` continuity match. */ export interface ContinuityPair { from: DisappearedSymbol; to: AppearedSymbol; reason: ContinuityReason; basis: ContinuityBasis; } /** A disappeared symbol with more than one equally-plausible destination. */ export interface AmbiguousContinuity { from: DisappearedSymbol; /** Candidate new locations, sorted; surfaced for human/agent reconciliation. */ candidates: Array<{ id: string; name: string; filePath: string; }>; } export interface ContinuityResult { /** Confident one-to-one matches, sorted by `from.nodeId`. */ pairs: ContinuityPair[]; /** Disappeared symbols left ambiguous (no carry-forward), sorted by `from.nodeId`. */ ambiguous: AmbiguousContinuity[]; } /** * Compute the continuity map between two adjacent indexed states. * * `disappeared` are old symbols whose anchors no longer resolve; `appeared` are new * symbols absent from the old state. `newNormBodyCount` is the count of each * normalized (name-independent) body across ALL new internal symbols (not just the * appeared subset), used to reject identical-body clones. A pair is admitted only * when the match is MUTUALLY one-to-one: the disappeared symbol has exactly one * candidate AND that candidate is the candidate of exactly one disappeared symbol. * Every other case (zero candidates → no continuation; multiple candidates on * either side → ambiguous) yields no pair. Deterministic and order-independent. */ export declare function computeContinuity(disappeared: readonly DisappearedSymbol[], appeared: readonly AppearedSymbol[], newNormBodyCount: ReadonlyMap): ContinuityResult; //# sourceMappingURL=continuity.d.ts.map