Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> # List parameters, properties, constants of a symbol
Expand Down Expand Up @@ -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/",
Expand Down Expand Up @@ -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

Expand Down
34 changes: 20 additions & 14 deletions crates/codegraph-core/src/db/repository/edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -18,45 +18,51 @@ 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<String>,
}

// 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()?;

for chunk in edges.chunks(CHUNK) {
let placeholders: Vec<String> = (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()?;
}
Expand Down
1 change: 1 addition & 0 deletions crates/codegraph-core/src/domain/graph/builder/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap/BACKLOG.md
Original file line number Diff line number Diff line change
@@ -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.

---
Expand Down
5 changes: 3 additions & 2 deletions docs/roadmap/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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)

Expand Down
6 changes: 6 additions & 0 deletions scripts/resolution-benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions tests/benchmarks/resolution/resolution-benchmark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ 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.
Comment on lines +267 to +268

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Comment references wrong PR number for the Rust fix

The comment attributes the native dynamic_kind write fix to #1698, but the EdgeRow struct change that actually makes the native engine write dynamic_kind for sink edges is part of this PR (#1699). Anyone reading this comment in the future will look up #1698 and not find the corresponding edges.rs change. If #1698 was intended as a separate squash/predecessor, the match is still off-by-one here.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

AND tgt.kind != 'file'
AND (e.confidence IS NULL OR e.confidence > 0 OR e.dynamic_kind IS NULL)
`)
.all();
Expand Down
Loading