@optave/codegraph 3.0.1 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -195,6 +195,7 @@ Full agent setup: [AI Agent Guide](docs/guides/ai-agent-guide.md) · [CLAU
195
195
  | 👀 | **Watch mode** | Incrementally update the graph as files change |
196
196
  | 🤖 | **MCP server** | 30-tool MCP server for AI assistants; single-repo by default, opt-in multi-repo |
197
197
  | ⚡ | **Always fresh** | Three-tier incremental detection — sub-second rebuilds even on large codebases |
198
+ | 🔬 | **Data flow analysis** | Intraprocedural parameter tracking, return consumers, argument flows, and mutation detection — all 11 languages |
198
199
  | 🧮 | **Complexity metrics** | Cognitive, cyclomatic, nesting depth, Halstead, and Maintainability Index per function |
199
200
  | 🏘️ | **Community detection** | Louvain clustering to discover natural module boundaries and architectural drift |
200
201
  | 📜 | **Manifesto rule engine** | Configurable pass/fail rules with warn/fail thresholds for CI gates via `check` (exit code 1 on fail) |
@@ -208,7 +209,7 @@ Full agent setup: [AI Agent Guide](docs/guides/ai-agent-guide.md) · [CLAU
208
209
  | 📋 | **Composite audit** | Single `audit` command combining explain + impact + health metrics per function — one call instead of 3-4 |
209
210
  | 🚦 | **Triage queue** | `triage` merges connectivity, hotspots, roles, and complexity into a ranked audit priority queue |
210
211
  | 📦 | **Batch querying** | Accept a list of targets and return all results in one JSON payload — enables multi-agent parallel dispatch |
211
- | 🔬 | **Dataflow analysis** | Track how data moves through functions with `flows_to`, `returns`, and `mutates` edges — included by default (JS/TS), skip with `--no-dataflow` |
212
+ | 🔬 | **Dataflow analysis** | Track how data moves through functions with `flows_to`, `returns`, and `mutates` edges — all 11 languages, included by default, skip with `--no-dataflow` |
212
213
  | 🧩 | **Control flow graph** | Intraprocedural CFG construction for all 11 languages — `cfg` command with text/DOT/Mermaid output, included by default, skip with `--no-cfg` |
213
214
  | 🔎 | **AST node querying** | Stored queryable AST nodes (calls, `new`, string, regex, throw, await) — `ast` command with SQL GLOB pattern matching |
214
215
  | 🧬 | **Expanded node/edge types** | `parameter`, `property`, `constant` node kinds with `parent_id` for sub-declaration queries; `contains`, `parameter_of`, `receiver` edge kinds |
@@ -225,6 +226,7 @@ See [docs/examples](docs/examples) for real-world CLI and MCP usage examples.
225
226
  ```bash
226
227
  codegraph build [dir] # Parse and build the dependency graph
227
228
  codegraph build --no-incremental # Force full rebuild
229
+ codegraph build --dataflow # Extract data flow edges (flows_to, returns, mutates)
228
230
  codegraph build --engine wasm # Force WASM engine (skip native)
229
231
  codegraph watch [dir] # Watch for changes, update graph incrementally
230
232
  ```
@@ -327,7 +329,8 @@ codegraph ast -k call # Filter by kind: call, new, string, regex
327
329
  codegraph ast -k throw --file src/ # Combine kind and file filters
328
330
  ```
329
331
 
330
- > **Note:** Dataflow (JS/TS only) and CFG are included by default. Use `--no-dataflow` / `--no-cfg` for faster builds.
332
+ > **Note:** Dataflow and CFG are included by default for all 11 languages. Use `--no-dataflow` / `--no-cfg` for faster builds.
333
+
331
334
 
332
335
  ### Audit, Triage & Batch
333
336
 
@@ -477,15 +480,15 @@ codegraph registry remove <name> # Unregister
477
480
 
478
481
  | Language | Extensions | Coverage |
479
482
  |---|---|---|
480
- | ![JavaScript](https://img.shields.io/badge/-JavaScript-F7DF1E?style=flat-square&logo=javascript&logoColor=black) | `.js`, `.jsx`, `.mjs`, `.cjs` | Full — functions, classes, imports, call sites |
481
- | ![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white) | `.ts`, `.tsx` | Full — interfaces, type aliases, `.d.ts` |
482
- | ![Python](https://img.shields.io/badge/-Python-3776AB?style=flat-square&logo=python&logoColor=white) | `.py` | Functions, classes, methods, imports, decorators |
483
- | ![Go](https://img.shields.io/badge/-Go-00ADD8?style=flat-square&logo=go&logoColor=white) | `.go` | Functions, methods, structs, interfaces, imports, call sites |
484
- | ![Rust](https://img.shields.io/badge/-Rust-000000?style=flat-square&logo=rust&logoColor=white) | `.rs` | Functions, methods, structs, traits, `use` imports, call sites |
485
- | ![Java](https://img.shields.io/badge/-Java-ED8B00?style=flat-square&logo=openjdk&logoColor=white) | `.java` | Classes, methods, constructors, interfaces, imports, call sites |
486
- | ![C#](https://img.shields.io/badge/-C%23-512BD4?style=flat-square&logo=dotnet&logoColor=white) | `.cs` | Classes, structs, records, interfaces, enums, methods, constructors, using directives, invocations |
487
- | ![PHP](https://img.shields.io/badge/-PHP-777BB4?style=flat-square&logo=php&logoColor=white) | `.php` | Functions, classes, interfaces, traits, enums, methods, namespace use, calls |
488
- | ![Ruby](https://img.shields.io/badge/-Ruby-CC342D?style=flat-square&logo=ruby&logoColor=white) | `.rb` | Classes, modules, methods, singleton methods, require/require_relative, include/extend |
483
+ | ![JavaScript](https://img.shields.io/badge/-JavaScript-F7DF1E?style=flat-square&logo=javascript&logoColor=black) | `.js`, `.jsx`, `.mjs`, `.cjs` | Full — functions, classes, imports, call sites, dataflow |
484
+ | ![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white) | `.ts`, `.tsx` | Full — interfaces, type aliases, `.d.ts`, dataflow |
485
+ | ![Python](https://img.shields.io/badge/-Python-3776AB?style=flat-square&logo=python&logoColor=white) | `.py` | Functions, classes, methods, imports, decorators, dataflow |
486
+ | ![Go](https://img.shields.io/badge/-Go-00ADD8?style=flat-square&logo=go&logoColor=white) | `.go` | Functions, methods, structs, interfaces, imports, call sites, dataflow |
487
+ | ![Rust](https://img.shields.io/badge/-Rust-000000?style=flat-square&logo=rust&logoColor=white) | `.rs` | Functions, methods, structs, traits, `use` imports, call sites, dataflow |
488
+ | ![Java](https://img.shields.io/badge/-Java-ED8B00?style=flat-square&logo=openjdk&logoColor=white) | `.java` | Classes, methods, constructors, interfaces, imports, call sites, dataflow |
489
+ | ![C#](https://img.shields.io/badge/-C%23-512BD4?style=flat-square&logo=dotnet&logoColor=white) | `.cs` | Classes, structs, records, interfaces, enums, methods, constructors, using directives, invocations, dataflow |
490
+ | ![PHP](https://img.shields.io/badge/-PHP-777BB4?style=flat-square&logo=php&logoColor=white) | `.php` | Functions, classes, interfaces, traits, enums, methods, namespace use, calls, dataflow |
491
+ | ![Ruby](https://img.shields.io/badge/-Ruby-CC342D?style=flat-square&logo=ruby&logoColor=white) | `.rb` | Classes, modules, methods, singleton methods, require/require_relative, include/extend, dataflow |
489
492
  | ![Terraform](https://img.shields.io/badge/-Terraform-844FBA?style=flat-square&logo=terraform&logoColor=white) | `.tf`, `.hcl` | Resource, data, variable, module, output blocks |
490
493
 
491
494
  ## ⚙️ How It Works
@@ -552,14 +555,14 @@ Self-measured on every release via CI ([build benchmarks](generated/benchmarks/B
552
555
 
553
556
  | Metric | Latest |
554
557
  |---|---|
555
- | Build speed (native) | **4.4 ms/file** |
556
- | Build speed (WASM) | **13.7 ms/file** |
558
+ | Build speed (native) | **11.5 ms/file** |
559
+ | Build speed (WASM) | **17.8 ms/file** |
557
560
  | Query time | **3ms** |
558
- | No-op rebuild (native) | **4ms** |
559
- | 1-file rebuild (native) | **325ms** |
561
+ | No-op rebuild (native) | **5ms** |
562
+ | 1-file rebuild (native) | **384ms** |
560
563
  | Query: fn-deps | **0.8ms** |
561
564
  | Query: path | **0.8ms** |
562
- | ~50,000 files (est.) | **~220.0s build** |
565
+ | ~50,000 files (est.) | **~575.0s build** |
563
566
 
564
567
  Metrics are normalized per file for cross-version comparability. Times above are for a full initial build — incremental rebuilds only re-parse changed files.
565
568
 
@@ -804,7 +807,7 @@ const { results: fused } = await multiSearchData(
804
807
  - **No full type inference** — parses `.d.ts` interfaces but doesn't use TypeScript's type checker for overload resolution
805
808
  - **Dynamic calls are best-effort** — complex computed property access and `eval` patterns are not resolved
806
809
  - **Python imports** — resolves relative imports but doesn't follow `sys.path` or virtual environment packages
807
- - **Dataflow analysis** — currently JS/TS only; intraprocedural (single-function scope), not interprocedural
810
+ - **Dataflow analysis** — intraprocedural (single-function scope), not interprocedural
808
811
 
809
812
  ## 🗺️ Roadmap
810
813
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optave/codegraph",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -71,10 +71,10 @@
71
71
  },
72
72
  "optionalDependencies": {
73
73
  "@modelcontextprotocol/sdk": "^1.0.0",
74
- "@optave/codegraph-darwin-arm64": "3.0.1",
75
- "@optave/codegraph-darwin-x64": "3.0.1",
76
- "@optave/codegraph-linux-x64-gnu": "3.0.1",
77
- "@optave/codegraph-win32-x64-msvc": "3.0.1"
74
+ "@optave/codegraph-darwin-arm64": "3.0.3",
75
+ "@optave/codegraph-darwin-x64": "3.0.3",
76
+ "@optave/codegraph-linux-x64-gnu": "3.0.3",
77
+ "@optave/codegraph-win32-x64-msvc": "3.0.3"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@biomejs/biome": "^2.4.4",
package/src/ast.js CHANGED
@@ -165,13 +165,12 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
165
165
  }
166
166
  });
167
167
 
168
- let totalInserted = 0;
168
+ const allRows = [];
169
169
 
170
170
  for (const [relPath, symbols] of fileSymbols) {
171
- const rows = [];
172
171
  const defs = symbols.definitions || [];
173
172
 
174
- // Pre-load all node IDs for this file into a map
173
+ // Pre-load all node IDs for this file into a map (read-only, fast)
175
174
  const nodeIdMap = new Map();
176
175
  for (const row of bulkGetNodeIds.all(relPath)) {
177
176
  nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
@@ -186,7 +185,7 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
186
185
  parentNodeId =
187
186
  nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
188
187
  }
189
- rows.push({
188
+ allRows.push({
190
189
  file: relPath,
191
190
  line: call.line,
192
191
  kind: 'call',
@@ -205,7 +204,7 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
205
204
  // WASM path: walk the tree-sitter AST
206
205
  const astRows = [];
207
206
  walkAst(symbols._tree.rootNode, defs, relPath, astRows, nodeIdMap);
208
- rows.push(...astRows);
207
+ allRows.push(...astRows);
209
208
  } else if (symbols.astNodes?.length) {
210
209
  // Native path: use pre-extracted AST nodes from Rust
211
210
  for (const n of symbols.astNodes) {
@@ -215,7 +214,7 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
215
214
  parentNodeId =
216
215
  nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
217
216
  }
218
- rows.push({
217
+ allRows.push({
219
218
  file: relPath,
220
219
  line: n.line,
221
220
  kind: n.kind,
@@ -227,14 +226,13 @@ export async function buildAstNodes(db, fileSymbols, _rootDir, _engineOpts) {
227
226
  }
228
227
  }
229
228
  }
229
+ }
230
230
 
231
- if (rows.length > 0) {
232
- tx(rows);
233
- totalInserted += rows.length;
234
- }
231
+ if (allRows.length > 0) {
232
+ tx(allRows);
235
233
  }
236
234
 
237
- debug(`AST extraction: ${totalInserted} nodes stored`);
235
+ debug(`AST extraction: ${allRows.length} nodes stored`);
238
236
  }
239
237
 
240
238
  /**
package/src/builder.js CHANGED
@@ -1272,7 +1272,7 @@ export async function buildGraph(rootDir, opts = {}) {
1272
1272
  }
1273
1273
  _t.rolesMs = performance.now() - _t.roles0;
1274
1274
 
1275
- // For incremental builds, filter out reverse-dep-only files from AST/complexity
1275
+ // For incremental builds, filter out reverse-dep-only files from AST/complexity/CFG/dataflow
1276
1276
  // — their content didn't change, so existing ast_nodes/function_complexity rows are valid.
1277
1277
  let astComplexitySymbols = allSymbols;
1278
1278
  if (!isFullBuild) {
@@ -1287,13 +1287,12 @@ export async function buildGraph(rootDir, opts = {}) {
1287
1287
  }
1288
1288
  }
1289
1289
  debug(
1290
- `AST/complexity: processing ${astComplexitySymbols.size} changed files (skipping ${reverseDepFiles.size} reverse-deps)`,
1290
+ `AST/complexity/CFG/dataflow: processing ${astComplexitySymbols.size} changed files (skipping ${reverseDepFiles.size} reverse-deps)`,
1291
1291
  );
1292
1292
  }
1293
1293
  }
1294
1294
 
1295
1295
  // AST node extraction (calls, new, string, regex, throw, await)
1296
- // Must run before complexity which releases _tree references
1297
1296
  _t.ast0 = performance.now();
1298
1297
  if (opts.ast !== false) {
1299
1298
  try {
@@ -1317,12 +1316,25 @@ export async function buildGraph(rootDir, opts = {}) {
1317
1316
  }
1318
1317
  _t.complexityMs = performance.now() - _t.complexity0;
1319
1318
 
1319
+ // Pre-parse files missing WASM trees (native builds) so CFG + dataflow
1320
+ // share a single parse pass instead of each creating parsers independently
1321
+ if (opts.cfg !== false || opts.dataflow !== false) {
1322
+ _t.wasmPre0 = performance.now();
1323
+ try {
1324
+ const { ensureWasmTrees } = await import('./parser.js');
1325
+ await ensureWasmTrees(astComplexitySymbols, rootDir);
1326
+ } catch (err) {
1327
+ debug(`WASM pre-parse failed: ${err.message}`);
1328
+ }
1329
+ _t.wasmPreMs = performance.now() - _t.wasmPre0;
1330
+ }
1331
+
1320
1332
  // CFG analysis (skip with --no-cfg)
1321
1333
  if (opts.cfg !== false) {
1322
1334
  _t.cfg0 = performance.now();
1323
1335
  try {
1324
1336
  const { buildCFGData } = await import('./cfg.js');
1325
- await buildCFGData(db, allSymbols, rootDir, engineOpts);
1337
+ await buildCFGData(db, astComplexitySymbols, rootDir, engineOpts);
1326
1338
  } catch (err) {
1327
1339
  debug(`CFG analysis failed: ${err.message}`);
1328
1340
  }
@@ -1334,7 +1346,7 @@ export async function buildGraph(rootDir, opts = {}) {
1334
1346
  _t.dataflow0 = performance.now();
1335
1347
  try {
1336
1348
  const { buildDataflowEdges } = await import('./dataflow.js');
1337
- await buildDataflowEdges(db, allSymbols, rootDir, engineOpts);
1349
+ await buildDataflowEdges(db, astComplexitySymbols, rootDir, engineOpts);
1338
1350
  } catch (err) {
1339
1351
  debug(`Dataflow analysis failed: ${err.message}`);
1340
1352
  }
@@ -1434,6 +1446,7 @@ export async function buildGraph(rootDir, opts = {}) {
1434
1446
  rolesMs: +_t.rolesMs.toFixed(1),
1435
1447
  astMs: +_t.astMs.toFixed(1),
1436
1448
  complexityMs: +_t.complexityMs.toFixed(1),
1449
+ ...(_t.wasmPreMs != null && { wasmPreMs: +_t.wasmPreMs.toFixed(1) }),
1437
1450
  ...(_t.cfgMs != null && { cfgMs: +_t.cfgMs.toFixed(1) }),
1438
1451
  ...(_t.dataflowMs != null && { dataflowMs: +_t.dataflowMs.toFixed(1) }),
1439
1452
  },
package/src/complexity.js CHANGED
@@ -1769,9 +1769,6 @@ export async function buildComplexityMetrics(db, fileSymbols, rootDir, _engineOp
1769
1769
  );
1770
1770
  analyzed++;
1771
1771
  }
1772
-
1773
- // Release cached tree for GC
1774
- symbols._tree = null;
1775
1772
  }
1776
1773
  });
1777
1774