/** * Code-anchored memory — deterministic anchoring + freshness engine. * (change: add-code-anchored-memory-staleness) * * A persisted memory (an architectural decision or a `remember` note) is bound to * the code it describes by one or more {@link StructuralAnchor}s. At recall time * a freshness verdict is computed against the *current* call graph using only * boolean inputs — symbol existence and content-hash equality — with no tunable * threshold and no weighted score. This is what lets recall be bullet-proof: a * memory whose anchored code moved or died is labeled, never served silently. * * This module is intentionally pure: it operates on a minimal node view and a * {@link GraphFreshnessView} of lookups, so the resolution/verdict logic is unit * tested without touching disk or the edge store. The disk-backed adapter that * supplies the views lives in `anchor-adapter.ts`. */ import type { StructuralAnchor, AnchorVerdict, MemoryFreshness, PendingDecision } from '../../types/index.js'; /** Stable, reproducible hash of a source span (or whole file). Unnormalized. */ export declare function hashSpan(text: string): string; /** Minimal node view the anchor engine needs to resolve a symbol to an anchor. */ export interface AnchorNode { id: string; name: string; filePath: string; /** Current hash of the node's source span. */ contentHash: string; /** Content-addressed stable id, when the symbol has a derivable one. */ stableId?: string; } /** Current-state lookups used to compute freshness against the live graph. */ export interface GraphFreshnessView { /** Current content hash for a node id, or `undefined` if the node is gone. */ nodeHash(nodeId: string): string | undefined; fileExists(filePath: string): boolean; /** Current whole-file content hash, or `undefined` if the file is gone. */ fileHash(filePath: string): string | undefined; /** * Resolve a symbol by its content-addressed stable id when its `nodeId` no * longer matches (the symbol was moved/renamed-file but otherwise survives). * Returns the relocated node's id + current span hash, or `undefined` when no * unambiguous node carries that stable id. Optional — absent on legacy views. * (change: add-content-addressed-stable-symbol-ids) * * Resolution is unique-only: because `stableId` is name+parameter-shape (a * homonym — a genuinely different symbol with the same name and signature — * shares it), the resolver returns a node only when exactly one carries the id, * never guessing between candidates. A residual false-`fresh` is still possible * if a homonym is the sole survivor AND its span hash happens to equal the * recorded one; the content-hash equality check in `anchorFreshness` is the * guard that makes any *changed* survivor read `drifted` instead. During an * incremental cross-file move the old and new rows can briefly both carry the id * (ambiguous → `undefined` → falls through to `orphaned`); the state self-heals * once the batch finishes and is corrected by the next full analyze. */ resolveStableId?(stableId: string): { nodeId: string; contentHash: string; } | undefined; /** * Confident rename mapping: given an absent node id, the new location label * (e.g. `newFile.ts::newName`) when `structural_diff` mapped it, else undefined. * Optional — when absent, a missing symbol is always `orphaned`. */ renameOf?(nodeId: string): string | undefined; } /** * Resolve symbol names to symbol-level anchors deterministically. A name resolves * only when it matches exactly one internal node — optionally narrowed to the * given `preferFiles` first. Ambiguous or unknown names are skipped (no guessing); * callers fall back to file-level anchoring for those. */ export declare function resolveSymbolAnchors(symbolNames: readonly string[], nodes: readonly AnchorNode[], preferFiles?: readonly string[]): StructuralAnchor[]; /** Build a file-level anchor, capturing the file content hash when available. */ export declare function fileAnchor(filePath: string, contentHash?: string): StructuralAnchor; /** * Compute the freshness verdict for a single anchor against the current graph. * Inputs are booleans only — no threshold, no score. */ export declare function anchorFreshness(anchor: StructuralAnchor, view: GraphFreshnessView): AnchorVerdict; /** * A memory's overall verdict is the worst of its anchors' verdicts * (orphaned > drifted > fresh). An unanchored memory is `fresh` — there is * nothing to invalidate; callers report `anchored: false` separately. */ export declare function aggregateFreshness(verdicts: readonly AnchorVerdict[]): MemoryFreshness; /** Compute per-anchor verdicts and the aggregate for a whole memory. */ export declare function memoryFreshness(anchors: readonly StructuralAnchor[], view: GraphFreshnessView): { freshness: MemoryFreshness; verdicts: AnchorVerdict[]; anchored: boolean; }; /** A memory reduced to what deterministic contradiction detection needs. */ export interface AnchoredItem { id: string; anchors: readonly StructuralAnchor[]; /** Aggregate freshness — only `fresh` items participate. */ freshness: MemoryFreshness; /** When true (superseded/invalidated) the item is excluded. */ invalidated?: boolean; } /** Two or more authoritative memories that all anchor to the same symbol. */ export interface UnreconciledGroup { /** The shared resolved symbol key (`stableId` ?? `nodeId`). */ symbol: string; /** The symbol's name, when known (for display). */ symbolName?: string; /** The symbol's file, when known (for display). */ filePath?: string; /** Ids of the authoritative memories sharing this symbol (sorted, ≥ 2). */ memberIds: string[]; } /** * Detect `unreconciled` pairs: when two or more authoritative (`fresh`, * non-invalidated) memories resolve to the SAME symbol anchor, they may contradict * and the agent should reconcile or supersede one. This is a pure set intersection * over symbol-level anchors — no LLM decides which wins, and "same file" is too coarse * to count (file-level anchors are ignored). Deterministic and order-independent. */ export declare function findUnreconciled(items: readonly AnchoredItem[]): UnreconciledGroup[]; /** * The anchors to check freshness against for a decision: its explicit structural * anchors when present, otherwise existence-only file-level anchors derived from * `affectedFiles` (legacy decisions recorded before anchoring). Shared by recall, * orient, and the drift detector so they agree on what a decision is bound to. */ export declare function decisionAnchors(d: Pick): StructuralAnchor[]; //# sourceMappingURL=anchor.d.ts.map