From 5e6c623a69eafebfd8d6bbfa3f4d345a6bf521a2 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Sun, 21 Jun 2026 17:56:49 -0600 Subject: [PATCH 1/4] docs: prepare release notes for v3.15.0 --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++ README.md | 11 +++++++---- docs/roadmap/BACKLOG.md | 2 +- docs/roadmap/ROADMAP.md | 5 +++-- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfbdbd917..02f0e36ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,49 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [3.15.0](https://github.com/optave/ops-codegraph-tool/compare/v3.14.0...v3.15.0) (2026-06-21) + +**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 — `DynamicKind` taxonomy classifies every dynamic call at extraction time; sink edges flagged as `dynamic_kind` in a new DB column (migration v20); new `codegraph roles --dynamic` flag surfaces them; Rust native extractor fully mirrored ([#1629](https://github.com/optave/ops-codegraph-tool/pull/1629)) +* **dynamic-calls:** Reflect.apply/construct/get and TypeScript `@Foo` decorator detection (Phase 1) — JS/TS-specific reflection idioms emit `reflection`-kind calls; both walk and query extractor paths updated; Rust mirrored ([#1637](https://github.com/optave/ops-codegraph-tool/pull/1637)) +* **dynamic-calls:** JVM dynamic dispatch — Java `Method.invoke`/`Class.forName`/`getMethod`, Kotlin callable references (`::fn`), Scala/Groovy invoke patterns; Kotlin `::fn` refs resolve at 100% recall (Phase 2) ([#1646](https://github.com/optave/ops-codegraph-tool/pull/1646)) +* **dynamic-calls:** Python `getattr`/`eval`/`exec`/`functools.partial` detection; `getattr(obj, 'method')` resolves at 100% recall for top-level functions (Phase 3) ([#1653](https://github.com/optave/ops-codegraph-tool/pull/1653)) +* **dynamic-calls:** Ruby `send`/`public_send` and PHP `call_user_func`/`$fn()` detection; literal symbol calls resolve at 100% recall (Phase 4) ([#1654](https://github.com/optave/ops-codegraph-tool/pull/1654)) +* **dynamic-calls:** Go `reflect.MethodByName` and C/C++ function-pointer/`dlsym` detection; both resolve at 100% recall for literal names (Phase 5) ([#1655](https://github.com/optave/ops-codegraph-tool/pull/1655)) +* **dynamic-calls:** C# `GetMethod/Invoke`, Swift `NSSelectorFromString/performSelector`, Elixir `apply`, Lua `load`/`loadstring`/bracket-index calls, ObjC `performSelector`, Dart `Function.apply`; Swift resolves at 100% recall; Rust extractor mirrored for all four languages (Phase 6) ([#1657](https://github.com/optave/ops-codegraph-tool/pull/1657), [#1670](https://github.com/optave/ops-codegraph-tool/pull/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](https://github.com/optave/ops-codegraph-tool/pull/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 synthetic `dead` aggregate was summed alongside individual sub-role counts ([#1648](https://github.com/optave/ops-codegraph-tool/pull/1648)) +* **config:** add `ignoreAdditionalDirs` to `.codegraphrc.json` — array of directory names merged with the built-in `IGNORE_DIRS` at file-collection time; included in `BUILD_HASH_KEYS` to trigger a full rebuild when changed; `crates` removed from the global `IGNORE_DIRS` default (add it to `ignoreAdditionalDirs` in your own `.codegraphrc.json` if needed) ([#1666](https://github.com/optave/ops-codegraph-tool/pull/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](https://github.com/optave/ops-codegraph-tool/pull/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](https://github.com/optave/ops-codegraph-tool/pull/1658)) +* **dataflow:** purge dataflow rows keyed by `call_edge_id` before edge deletion — prevents FK constraint failures during incremental file purge that left stale nodes after file deletion ([#1662](https://github.com/optave/ops-codegraph-tool/pull/1662)) +* **dynamic-calls:** RES-3 type-aware method name lookup for JVM `getMethod` patterns — Rust resolver now uses receiver type to construct qualified lookup for Groovy, Java, and Scala `getMethod` calls +* **native:** Kotlin callable-ref prefers class method over top-level function; suppress spurious `invoke` sink edge that diverged from WASM ([#1686](https://github.com/optave/ops-codegraph-tool/pull/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](https://github.com/optave/ops-codegraph-tool/pull/1671), [#1678](https://github.com/optave/ops-codegraph-tool/pull/1678), [#1679](https://github.com/optave/ops-codegraph-tool/pull/1679)) +* **native:** always run JS role re-classification on full builds to fix `hasActiveFileSiblings` parity — incremental-only re-classification left stale role assignments on fresh builds +* **wasm:** preserve `dyn=1` when deduplicating edges with same source/target/kind/confidence — bare `@Log` decorator was silently dropped when `@Log()` call-expression had already been processed first ([#1688](https://github.com/optave/ops-codegraph-tool/pull/1688)) +* **native:** dispatch-table PTS resolution in JS extractor — `({a:fnA,b:fnB})[key]()` now resolves on the native path, matching WASM output ([#1690](https://github.com/optave/ops-codegraph-tool/pull/1690)) +* **stats:** exclude sink edges (dynamic call placeholders, confidence=0.0) from the call-confidence denominator; lift minimum confidence for resolved `ts-native` edges from 0.3 → 0.5 ([#1641](https://github.com/optave/ops-codegraph-tool/pull/1641)) +* **stats:** suppress false-positive high-fan-in warnings for Rust `::new()` constructors ([#1643](https://github.com/optave/ops-codegraph-tool/pull/1643)) +* **mcp:** break 37-file circular dependency by extracting `McpToolContext` to `mcp/types.ts` — two consecutive architectural audits had flagged this cycle ([#1638](https://github.com/optave/ops-codegraph-tool/pull/1638)) +* **config:** `ignoreAdditionalDirs` and `ignoreDirs` now respected in watch mode ([#1666](https://github.com/optave/ops-codegraph-tool/pull/1666)) +* **analysis:** exclude gitignored NAPI-RS artifacts from native gap detection — prevents spurious WARN and unnecessary WASM backfill on every fresh `--no-incremental` build ([#1647](https://github.com/optave/ops-codegraph-tool/pull/1647)) +* **analysis:** exclude NAPI-RS generated `index.js` from WASM engine analysis — eliminates false 359 cognitive-complexity reading for `requireNative` in `codegraph triage` ([#1636](https://github.com/optave/ops-codegraph-tool/pull/1636)) +* **types:** use `@types/better-sqlite3` types in `getDatabase()` and `McpToolContext` — removes last `any` usages in the DB layer ([#1639](https://github.com/optave/ops-codegraph-tool/pull/1639)) + +### Chores + +* **docs:** add ADRs for dynamic call resolution and interprocedural dataflow ([#1675](https://github.com/optave/ops-codegraph-tool/pull/1675)) +* **deps:** bump `better-sqlite3` from 12.10.0 to 12.11.1 ([#1634](https://github.com/optave/ops-codegraph-tool/pull/1634)) +* **deps:** bump `vitest` from 4.1.8 to 4.1.9 ([#1633](https://github.com/optave/ops-codegraph-tool/pull/1633)) +* **deps:** bump `actions/checkout` from 6 to 7 ([#1630](https://github.com/optave/ops-codegraph-tool/pull/1630)) + ## [3.14.0](https://github.com/optave/ops-codegraph-tool/compare/v3.13.0...v3.14.0) (2026-06-19) **Interprocedural dataflow analysis ships in full, plus a sweep of role-classifier accuracy fixes.** The headline feature is a complete variable-level dataflow model: `dataflow_vertices` tracks param, return, and local variable locations per function; `def_use` edges connect definitions to uses within a function; `arg_in` and `return_out` edges stitch caller and callee dataflow across call boundaries. All 34 supported languages have dataflow rules. Incremental re-stitch (P4) fires on both the WASM/JS and native engine paths so `arg_in` edges are rebuilt when only a callee file changes, without a full rebuild. `codegraph fn-impact --json` gains `direct` and `transitive` shorthand fields alongside the existing `levels` breakdown. The role classifier received five accuracy fixes that eliminate a family of false-positive dead-symbol reports on real TypeScript and Rust codebases: exported interfaces with no cross-file call edges, type-def kinds in files with active callables, Commander.js dispatch methods, methods with active file siblings, and self-sibling sole-callable false-negatives. Erlang WASM parity is restored after the malicious package removal in v3.13.0. diff --git a/README.md b/README.md index 947917272..2ec2edd76 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ codegraph where --file src/db.js # List symbols, imports, exports for a file codegraph stats # Graph health: nodes, edges, languages, quality score codegraph roles # Node role classification (entry, core, utility, adapter, dead, leaf) codegraph roles --role dead -T # Find dead code (unreferenced, non-exported symbols) +codegraph roles --dynamic # Show dynamic call sink edges (eval, computed-key, unresolved) codegraph roles --role core --file src/ # Core symbols in src/ codegraph exports src/queries.js # Per-symbol consumer analysis (who calls each export) codegraph children # List parameters, properties, constants of a symbol @@ -821,6 +822,7 @@ Create a `.codegraphrc.json` in your project root to customize behavior. The sni "include": ["src/**", "lib/**"], "exclude": ["**/*.test.js", "**/__mocks__/**"], "ignoreDirs": ["node_modules", ".git", "dist"], + "ignoreAdditionalDirs": ["crates", "vendor"], "extensions": [".js", ".ts", ".tsx", ".py"], "aliases": { "@/": "./src/", @@ -955,10 +957,11 @@ See **[ROADMAP.md](docs/roadmap/ROADMAP.md)** for the full development roadmap a 10. ~~**Analysis Depth**~~ — **Complete** (v3.12.0) — TypeScript-native resolution, inter-procedural type propagation, field-based points-to analysis, barrel re-export chain resolution, CHA+RTA dynamic dispatch 11. **Runtime & Extensibility** — event-driven pipeline, plugin system, query caching, pagination 12. **Quality, Security & Technical Debt** — supply-chain security (SBOM, SLSA), CI coverage gates, timer cleanup, tech debt kill list -13. **Intelligent Embeddings** — LLM-generated descriptions, enhanced embeddings, module summaries -14. **Natural Language Queries** — `codegraph ask` command, conversational sessions -15. **GitHub Integration & CI** — reusable GitHub Action, LLM-enhanced PR review, SARIF output -16. **Advanced Features** — dead code detection, monorepo support, agentic search +13. **Architectural Health** — split god files (`types.ts`, `dataflow.ts`), missing ADRs, language quality tiers, per-edge confidence transparency +14. **Intelligent Embeddings** — LLM-generated descriptions, enhanced embeddings, module summaries +15. **Natural Language Queries** — `codegraph ask` command, conversational sessions +16. **GitHub Integration & CI** — reusable GitHub Action, LLM-enhanced PR review, SARIF output +17. **Advanced Features** — dead code detection, monorepo support, agentic search ## 🤝 Contributing diff --git a/docs/roadmap/BACKLOG.md b/docs/roadmap/BACKLOG.md index 32f7a484d..e571dec9b 100644 --- a/docs/roadmap/BACKLOG.md +++ b/docs/roadmap/BACKLOG.md @@ -1,6 +1,6 @@ # Codegraph Feature Backlog -**Last updated:** 2026-06-19 +**Last updated:** 2026-06-21 **Source:** Features derived from [COMPETITIVE_ANALYSIS.md](../../generated/competitive/COMPETITIVE_ANALYSIS.md) and internal roadmap discussions. --- diff --git a/docs/roadmap/ROADMAP.md b/docs/roadmap/ROADMAP.md index cc90a616a..bffc6d102 100644 --- a/docs/roadmap/ROADMAP.md +++ b/docs/roadmap/ROADMAP.md @@ -1,6 +1,6 @@ # Codegraph Roadmap -> **Current version:** 3.13.0 | **Status:** Active development | **Updated:** 2026-06-16 +> **Current version:** 3.15.0 | **Status:** Active development | **Updated:** 2026-06-21 Codegraph is a strong local-first code graph CLI. This roadmap describes planned improvements across fourteen phases -- closing gaps with commercial code intelligence platforms while preserving codegraph's core strengths: fully local, open source, zero cloud dependency by default. @@ -2406,9 +2406,10 @@ Items to remove or rework, identified by architectural audit: 1. **ADR-002: TypeScript migration** — rationale for migrating from JS to TS, build pipeline changes (`tsup`, path aliases, `#shared/*` imports), `nodenext` module resolution choice 2. **ADR-003: Repository pattern in `db/`** — why `SqliteRepository` / `InMemoryRepository` over flat SQL functions, what the interface contract is, how `InMemoryRepository` enables unit testing 3. **ADR-004: MCP tool architecture** — barrel pattern in `tools/index.ts`, middleware layer, lazy-loading of `@modelcontextprotocol/sdk`, single-repo isolation default, `buildToolList(multiRepo)` dynamic schema -4. **ADR-005: Interprocedural dataflow model** — vertex schema design, `arg_in`/`return_out`/`def_use` edge semantics, stitching algorithm, phased P0–P6 delivery strategy, tradeoffs vs a purely intra-procedural model +4. ✅ **ADR-005: Interprocedural dataflow model** — vertex schema design, `arg_in`/`return_out`/`def_use` edge semantics, stitching algorithm, phased P0–P6 delivery strategy, tradeoffs vs a purely intra-procedural model (v3.15.0, [#1675](https://github.com/optave/ops-codegraph-tool/pull/1675)) 5. **ADR-006: TypeScript-native (ts-native) resolution technique** — what the technique does differently from the 6-level heuristic, why it was introduced alongside rather than replacing the existing resolver, known confidence tradeoffs, calibration plan 6. **ADR-007: User-level config and consent model** — XDG location, layered merge order (user > repo > defaults), per-repo consent mechanics, config-hash invalidation, security trust model +7. ✅ **ADR-008: Dynamic call resolution** — `DynamicKind` taxonomy, Phase 0–6 detection strategy, sink-edge semantics, RES-1/2/3 resolution passes, why flag-only vs resolve-and-flag distinction was chosen (v3.15.0, [#1675](https://github.com/optave/ops-codegraph-tool/pull/1675)) **Affected files:** `docs/architecture/decisions/` (6 new files) From 953046801c870c3033c59e8ecec16368a94d15e3 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Mon, 22 Jun 2026 00:06:53 -0600 Subject: [PATCH 2/4] fix(native): write dynamic_kind for sink edges in native orchestrator path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `insert_call_edge_rows` in pipeline.rs mapped ComputedEdge→EdgeRow but silently dropped dynamic_kind, so native sink edges landed in the DB with dynamic_kind IS NULL. The benchmark filter added in 39b9a000 (`OR e.dynamic_kind IS NULL`) then matched those NULL-kind rows and counted them as false positives, breaking precision for dynamic-javascript, dynamic-typescript, and dynamic-scala. Two-layer fix: 1. edges.rs: add dynamic_kind: Option to EdgeRow and update do_insert_edges to a 6-column INSERT (CHUNK 199→165 to stay under SQLite's 999 bind-parameter limit). None binds as SQL NULL for all non-sink edges; Some(dk) writes the kind string for sink edges. 2. pipeline.rs: pass e.dynamic_kind.clone() in insert_call_edge_rows so ComputedEdge.dynamic_kind reaches the DB insert. 3. resolution-benchmark.test.ts: add AND tgt.kind != 'file' to extractResolvedEdges — the correct semantic filter since sink edges are the only calls that ever target the file node, and no legitimate resolution does. This filter is correct regardless of dynamic_kind being set, removing the dependency on the back-fill path. --- .../codegraph-core/src/db/repository/edges.rs | 34 +++++++++++-------- .../src/domain/graph/builder/pipeline.rs | 1 + .../resolution/resolution-benchmark.test.ts | 1 + 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crates/codegraph-core/src/db/repository/edges.rs b/crates/codegraph-core/src/db/repository/edges.rs index 518d15786..853034311 100644 --- a/crates/codegraph-core/src/db/repository/edges.rs +++ b/crates/codegraph-core/src/db/repository/edges.rs @@ -7,7 +7,7 @@ use napi_derive::napi; use rusqlite::Connection; -/// A single edge row to insert: [source_id, target_id, kind, confidence, dynamic]. +/// A single edge row to insert: [source_id, target_id, kind, confidence, dynamic, dynamic_kind]. #[napi(object)] #[derive(Debug, Clone)] pub struct EdgeRow { @@ -18,15 +18,19 @@ pub struct EdgeRow { pub kind: String, pub confidence: f64, pub dynamic: u32, + /// Set only for sink edges (confidence=0, dynamic=1) emitted by FLAG_ONLY_KINDS. + /// NULL for all normal resolved call/receiver/hierarchy edges. + #[napi(js_name = "dynamicKind")] + pub dynamic_kind: Option, } // NOTE: The standalone `bulk_insert_edges` napi export was removed in Phase 6.17. // All callers now use `NativeDatabase::bulk_insert_edges()` which reuses the // persistent connection, eliminating the double-connection antipattern. -/// 199 rows × 5 params = 995 bind parameters per statement, safely under -/// the legacy `SQLITE_MAX_VARIABLE_NUMBER` default of 999. -const CHUNK: usize = 199; +/// 165 rows × 6 params = 990 bind parameters per statement, safely under +/// the legacy `SQLITE_LIMIT_VARIABLE_NUMBER` default of 999. +const CHUNK: usize = 165; pub(crate) fn do_insert_edges(conn: &Connection, edges: &[EdgeRow]) -> rusqlite::Result<()> { let tx = conn.unchecked_transaction()?; @@ -34,29 +38,31 @@ pub(crate) fn do_insert_edges(conn: &Connection, edges: &[EdgeRow]) -> rusqlite: for chunk in edges.chunks(CHUNK) { let placeholders: Vec = (0..chunk.len()) .map(|i| { - let base = i * 5; + let base = i * 6; format!( - "(?{},?{},?{},?{},?{})", + "(?{},?{},?{},?{},?{},?{})", base + 1, base + 2, base + 3, base + 4, - base + 5 + base + 5, + base + 6 ) }) .collect(); let sql = format!( - "INSERT OR IGNORE INTO edges (source_id, target_id, kind, confidence, dynamic) VALUES {}", + "INSERT OR IGNORE INTO edges (source_id, target_id, kind, confidence, dynamic, dynamic_kind) VALUES {}", placeholders.join(",") ); let mut stmt = tx.prepare_cached(&sql)?; for (i, edge) in chunk.iter().enumerate() { - let base = i * 5; - stmt.raw_bind_parameter(base +1, edge.source_id)?; - stmt.raw_bind_parameter(base +2, edge.target_id)?; - stmt.raw_bind_parameter(base +3, edge.kind.as_str())?; - stmt.raw_bind_parameter(base +4, edge.confidence)?; - stmt.raw_bind_parameter(base +5, edge.dynamic)?; + let base = i * 6; + stmt.raw_bind_parameter(base + 1, edge.source_id)?; + stmt.raw_bind_parameter(base + 2, edge.target_id)?; + stmt.raw_bind_parameter(base + 3, edge.kind.as_str())?; + stmt.raw_bind_parameter(base + 4, edge.confidence)?; + stmt.raw_bind_parameter(base + 5, edge.dynamic)?; + stmt.raw_bind_parameter(base + 6, edge.dynamic_kind.as_deref())?; } stmt.raw_execute()?; } diff --git a/crates/codegraph-core/src/domain/graph/builder/pipeline.rs b/crates/codegraph-core/src/domain/graph/builder/pipeline.rs index 434770f6d..53f05601d 100644 --- a/crates/codegraph-core/src/domain/graph/builder/pipeline.rs +++ b/crates/codegraph-core/src/domain/graph/builder/pipeline.rs @@ -1461,6 +1461,7 @@ fn insert_call_edge_rows(conn: &Connection, edges: &[crate::domain::graph::build kind: e.kind.clone(), confidence: e.confidence, dynamic: e.dynamic, + dynamic_kind: e.dynamic_kind.clone(), }) .collect(); let _ = crate::db::repository::edges::do_insert_edges(conn, &edge_rows); diff --git a/tests/benchmarks/resolution/resolution-benchmark.test.ts b/tests/benchmarks/resolution/resolution-benchmark.test.ts index aaf8f0f3a..bf83847f4 100644 --- a/tests/benchmarks/resolution/resolution-benchmark.test.ts +++ b/tests/benchmarks/resolution/resolution-benchmark.test.ts @@ -262,6 +262,7 @@ function extractResolvedEdges(fixtureDir: string) { JOIN nodes tgt ON e.target_id = tgt.id WHERE e.kind = 'calls' AND src.kind IN ('function', 'method') + AND tgt.kind != 'file' AND (e.confidence IS NULL OR e.confidence > 0 OR e.dynamic_kind IS NULL) `) .all(); From c5a5c3aa3dad372d452e5712a431527b092b40b8 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Mon, 22 Jun 2026 00:36:21 -0600 Subject: [PATCH 3/4] fix(benchmark): add comment explaining compound sink-edge filter in extractResolvedEdges (#1698) The tgt.kind != 'file' guard is the authoritative semantic exclusion for sink edges; the confidence/dynamic_kind clause is kept as belt-and-suspenders for databases built before the native fix where dynamic_kind was NULL. --- tests/benchmarks/resolution/resolution-benchmark.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/benchmarks/resolution/resolution-benchmark.test.ts b/tests/benchmarks/resolution/resolution-benchmark.test.ts index bf83847f4..0a07b3404 100644 --- a/tests/benchmarks/resolution/resolution-benchmark.test.ts +++ b/tests/benchmarks/resolution/resolution-benchmark.test.ts @@ -262,6 +262,10 @@ function extractResolvedEdges(fixtureDir: string) { JOIN nodes tgt ON e.target_id = tgt.id WHERE e.kind = 'calls' AND src.kind IN ('function', 'method') + -- Exclude sink edges: they target file nodes (the only 'calls' edges that do so). + -- The compound confidence/dynamic_kind clause below is belt-and-suspenders for + -- databases built before the Rust fix in #1698 where dynamic_kind was NULL for + -- sink edges, making tgt.kind != 'file' the authoritative semantic guard. AND tgt.kind != 'file' AND (e.confidence IS NULL OR e.confidence > 0 OR e.dynamic_kind IS NULL) `) From 09e915a1f932efb074ea761b99ffa911fac06314 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Mon, 22 Jun 2026 10:15:49 -0600 Subject: [PATCH 4/4] fix(benchmark): apply sink-edge exclusion filter to scripts/resolution-benchmark.ts The tgt.kind != 'file' and confidence/dynamic_kind guards were added to extractResolvedEdges in the vitest test file (95304680) but not to the matching SQL query in scripts/resolution-benchmark.ts. The CI gate reads precomputed JSON from that script (via RESOLUTION_RESULT_JSON), so sink edges (confidence=0, targeting file nodes) were counted as false positives, breaking precision for dynamic-java, dynamic-javascript, dynamic-scala, and dynamic-typescript. --- scripts/resolution-benchmark.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/resolution-benchmark.ts b/scripts/resolution-benchmark.ts index 8c4a105bb..b35aa65e4 100644 --- a/scripts/resolution-benchmark.ts +++ b/scripts/resolution-benchmark.ts @@ -299,6 +299,12 @@ try { JOIN nodes tgt ON e.target_id = tgt.id WHERE e.kind = 'calls' AND src.kind IN ('function', 'method') + -- Exclude sink edges (confidence=0, target=file node). + -- tgt.kind != 'file' is the authoritative guard; the + -- confidence/dynamic_kind clause is belt-and-suspenders + -- for DBs built before dynamic_kind was written for sinks. + AND tgt.kind != 'file' + AND (e.confidence IS NULL OR e.confidence > 0 OR e.dynamic_kind IS NULL) `) .all() as ResolvedEdge[]; } finally {