Dynamic call detection ships across all 34 languages, plus richer codegraph stats output and a new ignoreAdditionalDirs config option. Seven phases of dynamic dispatch detection land in a single release: Reflect/decorator patterns in JS/TS, JVM dynamic dispatch with Kotlin callable-reference resolution, Python getattr/eval/functools.partial, Ruby send/public_send and PHP call_user_func, Go MethodByName and C/C++ dlsym/function-pointer detection, and C#/Swift/Elixir/Lua long-tail patterns. Every pattern is classified with a DynamicKind taxonomy (computed-literal, computed-key, reflection, eval, unresolved-dynamic) and visible via a new codegraph roles --dynamic flag; resolved calls remain in the normal call graph while unresolvable patterns emit zero-confidence sink edges that never pollute regular queries. A closed dispatch-table resolver (RES-2) handles ({a:fnA,b:fnB})[key]() via the existing PTS wildcard solver. codegraph stats now breaks down dead symbols by actionable sub-role (dead-leaf, dead-unresolved, dead-ffi, dead-entry, dead-callable), fixing a pre-existing double-counting bug in the dead total. A new ignoreAdditionalDirs config field lets projects append additional directories to the built-in IGNORE_DIRS without patching codegraph source.
Features
- dynamic-calls: detect and flag dynamic call sites in JS/TS —
DynamicKindtaxonomy classifies every dynamic call at extraction time; sink edges flagged asdynamic_kindin a new DB column (migration v20); newcodegraph roles --dynamicflag surfaces them; Rust native extractor fully mirrored (#1629) - dynamic-calls: Reflect.apply/construct/get and TypeScript
@Foodecorator detection (Phase 1) — JS/TS-specific reflection idioms emitreflection-kind calls; both walk and query extractor paths updated; Rust mirrored (#1637) - dynamic-calls: JVM dynamic dispatch — Java
Method.invoke/Class.forName/getMethod, Kotlin callable references (::fn), Scala/Groovy invoke patterns; Kotlin::fnrefs resolve at 100% recall (Phase 2) (#1646) - dynamic-calls: Python
getattr/eval/exec/functools.partialdetection;getattr(obj, 'method')resolves at 100% recall for top-level functions (Phase 3) (#1653) - dynamic-calls: Ruby
send/public_sendand PHPcall_user_func/$fn()detection; literal symbol calls resolve at 100% recall (Phase 4) (#1654) - dynamic-calls: Go
reflect.MethodByNameand C/C++ function-pointer/dlsymdetection; both resolve at 100% recall for literal names (Phase 5) (#1655) - dynamic-calls: C#
GetMethod/Invoke, SwiftNSSelectorFromString/performSelector, Elixirapply, Luaload/loadstring/bracket-index calls, ObjCperformSelector, DartFunction.apply; Swift resolves at 100% recall; Rust extractor mirrored for all four languages (Phase 6) (#1657, #1670) - dynamic-calls: RES-2 — closed dispatch-table resolution —
({a:fnA,b:fnB})[key]()resolves to each table entry via the PTS wildcard solver; Rust native mirror included (#1677) - stats: separate dead-code categories in
codegraph stats— per-sub-role breakdown with actionability labels (dead-leaf,dead-unresolved,dead-ffi,dead-entry,dead-callable); fixes pre-existing double-counting bug where the syntheticdeadaggregate was summed alongside individual sub-role counts (#1648) - config: add
ignoreAdditionalDirsto.codegraphrc.json— array of directory names merged with the built-inIGNORE_DIRSat file-collection time; included inBUILD_HASH_KEYSto trigger a full rebuild when changed;cratesremoved from the globalIGNORE_DIRSdefault (add it toignoreAdditionalDirsin your own.codegraphrc.jsonif needed) (#1666)
Bug Fixes
- dataflow: P4 incremental re-stitch + P6 vertex extraction on native engine path — vertex rows extracted during bulk-insert pass; re-stitch fires on callee-only changes without a full rebuild; P6 parity fix for incremental vs full-build paths (#1635)
- dataflow: make dataflow vertex write and inter-procedural stitch atomic — closes half-written state gap when the process is killed between vertex insert and stitch (#1658)
- dataflow: purge dataflow rows keyed by
call_edge_idbefore edge deletion — prevents FK constraint failures during incremental file purge that left stale nodes after file deletion (#1662) - dynamic-calls: RES-3 type-aware method name lookup for JVM
getMethodpatterns — Rust resolver now uses receiver type to construct qualified lookup for Groovy, Java, and ScalagetMethodcalls - native: Kotlin callable-ref prefers class method over top-level function; suppress spurious
invokesink edge that diverged from WASM (#1686) - native: CJS require bindings now produce receiver-edge parity with WASM —
require()-destructured class types emit receiver call edges on the native path (#1671, #1678, #1679) - native: always run JS role re-classification on full builds to fix
hasActiveFileSiblingsparity — incremental-only re-classification left stale role assignments on fresh builds - wasm: preserve
dyn=1when deduplicating edges with same source/target/kind/confidence — bare@Logdecorator was silently dropped when@Log()call-expression had already been processed first (#1688) - native: dispatch-table PTS resolution in JS extractor —
({a:fnA,b:fnB})[key]()now resolves on the native path, matching WASM output (#1690) - stats: exclude sink edges (dynamic call placeholders, confidence=0.0) from the call-confidence denominator; lift minimum confidence for resolved
ts-nativeedges from 0.3 → 0.5 (#1641) - stats: suppress false-positive high-fan-in warnings for Rust
::new()constructors (#1643) - mcp: break 37-file circular dependency by extracting
McpToolContexttomcp/types.ts— two consecutive architectural audits had flagged this cycle (#1638) - config:
ignoreAdditionalDirsandignoreDirsnow respected in watch mode (#1666) - analysis: exclude gitignored NAPI-RS artifacts from native gap detection — prevents spurious WARN and unnecessary WASM backfill on every fresh
--no-incrementalbuild (#1647) - analysis: exclude NAPI-RS generated
index.jsfrom WASM engine analysis — eliminates false 359 cognitive-complexity reading forrequireNativeincodegraph triage(#1636) - types: use
@types/better-sqlite3types ingetDatabase()andMcpToolContext— removes lastanyusages in the DB layer (#1639)