/** * In-memory dependency graph built from CodeChunk[] metadata. * * Resolves caller/callee relationships using imports, exports, and callSites * without any vector DB. Used by the bug-finding plugin to find all callers * of changed functions across the full repo. * * Phase 5 of the duplication refactor (issue #994): per-edge resolution now * routes through @liendev/parser's guarded, multi-language path-matching * primitives (`importMatchesTarget` and friends) instead of this module's own * `resolveImportPath` (deleted — it only ever handled relative JS/TS imports * with a hardcoded 6-extension list). The BFS traversal itself * (`bfsTransitiveCallers`, via `walkBounded`) already came from parser and is * untouched: what changed is how a single edge gets resolved, not how hops are * counted. Review's graph stays symbol/call-site-level throughout (a * genuinely different abstraction from parser's file-level `findDependents`) * — see the module doc on `resolveCallSiteEdges` for why the two were not * unified into one call. * * Post-#1011 regression fix: a pure re-export barrel (a file with no chunk * of its own carrying a real symbol name — see `NO_REPRESENTATIVE_SYMBOL`) * used to dead-end the BFS. `buildRepresentativeEdge` keyed the barrel's * *display* identity ('(module-level)') as the frontier node too, so * `getCallers(barrel, '(module-level)')` was queried next — a key nothing * is ever indexed under, since no call site literally names that sentinel. * The real dependents reachable only through the barrel (e.g. every * language extractor importing `calculateComplexity` from * `ast/complexity/index.ts` rather than `cyclomatic.ts` directly) silently * vanished at hop 2. Fixed by carrying the *traced* symbol forward as * `CallerEdge.frontierSymbol`, used only to decide the next frontier node — * the barrel's caller identity shown to a consumer stays '(module-level)' * (it is never credited as if it called anything; that false-attribution * shape is exactly what #1011 removed and must not come back), but the walk * now continues via the symbol actually being traced, not the placeholder. */ import type { CodeChunk } from '../types.js'; /** * How an edge was resolved, ranked precise-to-weakest. Lets a consumer (the * agent reading ``, or a caller of `get_dependents`) weight a * verified import edge above a name-matched guess instead of treating every * dependent as equally solid — see issue #994 Phase 5. * * - `same-file`: caller and callee are the same file; no import needed. * - `import-verified`: the caller's own import statement resolves to the * callee's file via `importMatchesTarget` (all of parser's #884/#887/#929 * guards applied) AND a call site names the called symbol directly. * - `import-only`: same verified import as above, but no call site in the * importing file names the symbol — e.g. a PHP `new Order()` construction, * a type hint, or a static/property access that never surfaces as a * `callSite`. Without this tier the dependent would silently vanish (see * the module doc on `buildImportOnlyEdges`); the caller identity attached * is the file's best-effort representative chunk, not a verified call site. * - `require-only`: the caller's import statement verifiably resolves to the * callee's FILE (same `importMatchesTarget` guards as `import-only`), but * the statement itself names only the FILE, never a symbol at all — Ruby's * `require`/`require_relative`/`load`/`autoload` (#1013), which never * mention a class/module name the way `use Ns\Foo;` or `import { Foo }` * do. Weaker than `import-only`: that tier at least verifies which * SPECIFIC symbol was imported (even if never called); this tier has no * symbol-level signal at all, only "this file is a real dependency of * that one" — see `buildRawImportsByFile`/`resolveRequireOnlyFallback`. * - `symbol-name-match`: the caller imports a same-named symbol from some * non-relative (package) path, but the specific source file was never * confirmed — a real edge only if the name isn't coincidentally reused * elsewhere in the corpus. * - `oop-method-import`: the caller imports the class (verified) and calls a * method whose name matches one declared on it; the class import is solid, * the specific method attribution is inferred. * - `namespace-inferred`: no import at all — resolved via a same-namespace/ * same-directory convention (PHP/Python/Rust), for C#, the #930/#971 * type-reference-matching fallback (`findCSharpTypeReferenceDependents`), * OR, for Java/Kotlin (#1005 Phase 2), the type-scoped same-package * resolver (`resolveJvmSamePackageDependentsForType`) — a same-package * reference needs NO import statement at all (JLS §6.5.5.1), so this tier * is pinned here and never `import-only`: tagging it `import-only` would * fabricate the exact claim `import-only`/`isImportOnlyEvidenceTier` exist * to make honestly (see that predicate's doc comment). The weakest tier: a * real structural signal, never an import edge. */ export type EdgeProvenance = 'same-file' | 'import-verified' | 'import-only' | 'require-only' | 'symbol-name-match' | 'oop-method-import' | 'namespace-inferred'; /** * True for the tiers where the SPECIFIC seed symbol is verifiably * imported/declared in the dependent, per `SYMBOL_VERIFIED_BY_PROVENANCE` * above (which carries the per-tier rationale). */ export declare function isPreciseProvenance(provenance: EdgeProvenance): boolean; /** * Provenance tiers safe to surface as "this file verifiably imports the * symbol" evidence when a type-symbol query's call-site attribution comes * back empty (`@liendev/lien`'s `get_dependents` `importedBy` field, #1015 * fix direction 2) -- a DELIBERATELY narrower set than `isPreciseProvenance` * alone: * * - `same-file` is precise but excluded: `findDependents` never counts an * intra-file caller as a dependent (a file doesn't "depend on" itself) -- * surfacing it here would contradict that policy. * - `require-only` and `symbol-name-match` are excluded even though * "precise" alone might look like the right bar. Both are already * `false` under `isPreciseProvenance` (so the gate below already drops * them), but they're named here explicitly, on purpose: an adversarial * review of an earlier version of this fix measured `require-only` * fabricating 68 edges for a TypeScript interface actually used in * exactly one file (it is NOT language-gated to Ruby despite being * Ruby-motivated -- see `EdgeProvenance`'s own doc comment), and * `symbol-name-match` resolving to the WRONG file 4 of 5 times when * multiple languages declare a same-named class. A future change to * `isPreciseProvenance` alone must not silently let either back in here. * * Exported from the parser (not left as a CLI-local/test-local copy) so * `get-dependents.ts` and `dependency-graph.test.ts` share ONE definition -- * a review finding on this same PR: a test-local mirror of this predicate * can't detect the predicate it's supposed to be checking drifting out from * under it. */ export declare function isImportOnlyEvidenceTier(provenance: EdgeProvenance): boolean; export interface SymbolNode { filepath: string; symbolName: string; chunk: CodeChunk; } export interface CallerEdge { caller: SymbolNode; callSiteLine: number; /** How this edge was resolved — see `EdgeProvenance`'s doc comment. */ provenance: EdgeProvenance; /** * The symbol to use in place of `caller.symbolName` when this edge's * `caller` becomes the next BFS frontier node. Only set by * `buildRepresentativeEdge` when the target file has no chunk of its own * carrying a real symbol name (`caller.symbolName === NO_REPRESENTATIVE_SYMBOL` * — a pure re-export barrel or similar pass-through file). `caller.symbolName` * stays the honest *display* identity ('(module-level)': this file doesn't * call anything); `frontierSymbol` is purely a traversal hint so * `bfsTransitiveCallers` keeps expanding via the symbol actually being * traced through the barrel instead of a placeholder nothing is indexed * under. See the module doc's "Post-#1011 regression fix" note. */ frontierSymbol?: string; } export interface TransitiveCallerEdge extends CallerEdge { /** Distance from the seed symbol. Direct callers are 1, callers-of-callers are 2. */ hops: number; /** The symbol on the call chain this caller resolved through. Equals the seed for hops=1. */ viaSymbol: string; } export interface TransitiveResult { callers: TransitiveCallerEdge[]; /** True if BFS stopped because it hit maxNodes before exploring the full graph. */ truncated: boolean; /** Count of distinct symbols whose callers were expanded (for diagnostics). */ visitedSymbols: number; } export interface TransitiveOptions { /** Max hop distance from the seed. Default 2. */ depth?: number; /** Max edges to emit. Default 30. */ maxNodes?: number; } export interface DependencyGraph { /** Find all chunks that call a given exported symbol. */ getCallers(filepath: string, symbolName: string): CallerEdge[]; /** * BFS-walk callers up to `depth` hops. Each caller is emitted exactly once, * at its shortest hop distance from the seed. Stops when `maxNodes` edges * have been emitted (sets `truncated=true`). */ getCallersTransitive(filepath: string, symbolName: string, opts?: TransitiveOptions): TransitiveResult; } /** * Build an in-memory dependency graph from CodeChunk[]. * * `workspaceRoot` only feeds `normalizePath`'s extension-stripping (see * `createNormalizer`) — pass the same value blast-radius.ts already threads * through as `ComputeBlastRadiusOptions.workspaceRoot` (`context.repoRootDir` * in production); omitting it is safe when chunk paths are already relative. * * Five-pass algorithm: * 1. Build export index: which files export/declare which symbols. * 2. Resolve imports: for each chunk, verify which of its import specifiers * resolve to a real exporting file (via parser's guarded matching). * 3. Build caller edges: for each call site, link it to the exported * symbol's definition, trying precise-to-weakest strategies in order. * 4. Build the import-only fallback index (#994 Phase 5): files that * verifiably import a symbol but never literally "call" it (e.g. a PHP * `new Order()`), so a class/type-shaped seed doesn't silently resolve to * zero dependents just because nothing invokes it by name. * 5. Build the raw-imports-by-file index (#1013): the require-only fallback * for languages (Ruby) whose import statement names a FILE, never a * symbol, so passes 2-4 (all keyed on `importedSymbols`) have nothing to * match on at all — see `buildRawImportsByFile`/`resolveRequireOnlyFallback`. * * `getCallers` itself resolves in two stages (#1005 Phase 2): `resolveBaseTier` * runs the ENTIRE chain above (unchanged), then `resolveJvmSamePackageTier` * (Java/Kotlin only) is UNIONED on top rather than tried as another * early-return branch — see `unionJvmSamePackageTier`'s doc comment for why a * single union point, not one at each intermediate step, is load-bearing. */ export declare function buildDependencyGraph(chunks: CodeChunk[], workspaceRoot?: string): DependencyGraph; //# sourceMappingURL=dependency-graph.d.ts.map