@optave/codegraph 3.1.0 → 3.1.1

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.
Files changed (47) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/rules/csharp.js +201 -0
  5. package/src/ast-analysis/rules/go.js +182 -0
  6. package/src/ast-analysis/rules/index.js +82 -0
  7. package/src/ast-analysis/rules/java.js +175 -0
  8. package/src/ast-analysis/rules/javascript.js +246 -0
  9. package/src/ast-analysis/rules/php.js +219 -0
  10. package/src/ast-analysis/rules/python.js +196 -0
  11. package/src/ast-analysis/rules/ruby.js +204 -0
  12. package/src/ast-analysis/rules/rust.js +173 -0
  13. package/src/ast-analysis/shared.js +223 -0
  14. package/src/ast.js +15 -28
  15. package/src/audit.js +4 -5
  16. package/src/boundaries.js +1 -1
  17. package/src/branch-compare.js +84 -79
  18. package/src/builder.js +0 -5
  19. package/src/cfg.js +106 -338
  20. package/src/check.js +3 -3
  21. package/src/cli.js +99 -179
  22. package/src/cochange.js +1 -1
  23. package/src/communities.js +13 -16
  24. package/src/complexity.js +196 -1239
  25. package/src/cycles.js +1 -1
  26. package/src/dataflow.js +269 -694
  27. package/src/db/connection.js +88 -0
  28. package/src/db/migrations.js +312 -0
  29. package/src/db/query-builder.js +280 -0
  30. package/src/db/repository.js +134 -0
  31. package/src/db.js +19 -399
  32. package/src/embedder.js +145 -141
  33. package/src/export.js +1 -1
  34. package/src/flow.js +161 -162
  35. package/src/index.js +34 -1
  36. package/src/kinds.js +49 -0
  37. package/src/manifesto.js +3 -8
  38. package/src/mcp.js +37 -20
  39. package/src/owners.js +132 -132
  40. package/src/queries-cli.js +866 -0
  41. package/src/queries.js +1323 -2267
  42. package/src/result-formatter.js +21 -0
  43. package/src/sequence.js +177 -182
  44. package/src/structure.js +200 -199
  45. package/src/test-filter.js +7 -0
  46. package/src/triage.js +120 -162
  47. package/src/viewer.js +1 -1
@@ -12,7 +12,9 @@ import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import Database from 'better-sqlite3';
14
14
  import { buildGraph } from './builder.js';
15
- import { isTestFile, kindIcon } from './queries.js';
15
+ import { kindIcon } from './queries.js';
16
+ import { outputResult } from './result-formatter.js';
17
+ import { isTestFile } from './test-filter.js';
16
18
 
17
19
  // ─── Git Helpers ────────────────────────────────────────────────────────
18
20
 
@@ -81,55 +83,57 @@ function makeSymbolKey(kind, file, name) {
81
83
 
82
84
  function loadSymbolsFromDb(dbPath, changedFiles, noTests) {
83
85
  const db = new Database(dbPath, { readonly: true });
84
- const symbols = new Map();
86
+ try {
87
+ const symbols = new Map();
85
88
 
86
- if (changedFiles.length === 0) {
87
- db.close();
88
- return symbols;
89
- }
89
+ if (changedFiles.length === 0) {
90
+ return symbols;
91
+ }
90
92
 
91
- // Query nodes in changed files
92
- const placeholders = changedFiles.map(() => '?').join(', ');
93
- const rows = db
94
- .prepare(
95
- `SELECT n.id, n.name, n.kind, n.file, n.line, n.end_line
96
- FROM nodes n
97
- WHERE n.file IN (${placeholders})
98
- AND n.kind NOT IN ('file', 'directory')
99
- ORDER BY n.file, n.line`,
100
- )
101
- .all(...changedFiles);
102
-
103
- // Compute fan_in and fan_out for each node
104
- const fanInStmt = db.prepare(
105
- `SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
106
- );
107
- const fanOutStmt = db.prepare(
108
- `SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
109
- );
93
+ // Query nodes in changed files
94
+ const placeholders = changedFiles.map(() => '?').join(', ');
95
+ const rows = db
96
+ .prepare(
97
+ `SELECT n.id, n.name, n.kind, n.file, n.line, n.end_line
98
+ FROM nodes n
99
+ WHERE n.file IN (${placeholders})
100
+ AND n.kind NOT IN ('file', 'directory')
101
+ ORDER BY n.file, n.line`,
102
+ )
103
+ .all(...changedFiles);
104
+
105
+ // Compute fan_in and fan_out for each node
106
+ const fanInStmt = db.prepare(
107
+ `SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
108
+ );
109
+ const fanOutStmt = db.prepare(
110
+ `SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
111
+ );
110
112
 
111
- for (const row of rows) {
112
- if (noTests && isTestFile(row.file)) continue;
113
-
114
- const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
115
- const fanIn = fanInStmt.get(row.id).cnt;
116
- const fanOut = fanOutStmt.get(row.id).cnt;
117
- const key = makeSymbolKey(row.kind, row.file, row.name);
118
-
119
- symbols.set(key, {
120
- id: row.id,
121
- name: row.name,
122
- kind: row.kind,
123
- file: row.file,
124
- line: row.line,
125
- lineCount,
126
- fanIn,
127
- fanOut,
128
- });
129
- }
113
+ for (const row of rows) {
114
+ if (noTests && isTestFile(row.file)) continue;
115
+
116
+ const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
117
+ const fanIn = fanInStmt.get(row.id).cnt;
118
+ const fanOut = fanOutStmt.get(row.id).cnt;
119
+ const key = makeSymbolKey(row.kind, row.file, row.name);
120
+
121
+ symbols.set(key, {
122
+ id: row.id,
123
+ name: row.name,
124
+ kind: row.kind,
125
+ file: row.file,
126
+ line: row.line,
127
+ lineCount,
128
+ fanIn,
129
+ fanOut,
130
+ });
131
+ }
130
132
 
131
- db.close();
132
- return symbols;
133
+ return symbols;
134
+ } finally {
135
+ db.close();
136
+ }
133
137
  }
134
138
 
135
139
  // ─── Caller BFS ─────────────────────────────────────────────────────────
@@ -138,40 +142,43 @@ function loadCallersFromDb(dbPath, nodeIds, maxDepth, noTests) {
138
142
  if (nodeIds.length === 0) return [];
139
143
 
140
144
  const db = new Database(dbPath, { readonly: true });
141
- const allCallers = new Set();
142
-
143
- for (const startId of nodeIds) {
144
- const visited = new Set([startId]);
145
- let frontier = [startId];
146
-
147
- for (let d = 1; d <= maxDepth; d++) {
148
- const nextFrontier = [];
149
- for (const fid of frontier) {
150
- const callers = db
151
- .prepare(
152
- `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
153
- FROM edges e JOIN nodes n ON e.source_id = n.id
154
- WHERE e.target_id = ? AND e.kind = 'calls'`,
155
- )
156
- .all(fid);
157
-
158
- for (const c of callers) {
159
- if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
160
- visited.add(c.id);
161
- nextFrontier.push(c.id);
162
- allCallers.add(
163
- JSON.stringify({ name: c.name, kind: c.kind, file: c.file, line: c.line }),
164
- );
145
+ try {
146
+ const allCallers = new Set();
147
+
148
+ for (const startId of nodeIds) {
149
+ const visited = new Set([startId]);
150
+ let frontier = [startId];
151
+
152
+ for (let d = 1; d <= maxDepth; d++) {
153
+ const nextFrontier = [];
154
+ for (const fid of frontier) {
155
+ const callers = db
156
+ .prepare(
157
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
158
+ FROM edges e JOIN nodes n ON e.source_id = n.id
159
+ WHERE e.target_id = ? AND e.kind = 'calls'`,
160
+ )
161
+ .all(fid);
162
+
163
+ for (const c of callers) {
164
+ if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
165
+ visited.add(c.id);
166
+ nextFrontier.push(c.id);
167
+ allCallers.add(
168
+ JSON.stringify({ name: c.name, kind: c.kind, file: c.file, line: c.line }),
169
+ );
170
+ }
165
171
  }
166
172
  }
173
+ frontier = nextFrontier;
174
+ if (frontier.length === 0) break;
167
175
  }
168
- frontier = nextFrontier;
169
- if (frontier.length === 0) break;
170
176
  }
171
- }
172
177
 
173
- db.close();
174
- return [...allCallers].map((s) => JSON.parse(s));
178
+ return [...allCallers].map((s) => JSON.parse(s));
179
+ } finally {
180
+ db.close();
181
+ }
175
182
  }
176
183
 
177
184
  // ─── Symbol Comparison ──────────────────────────────────────────────────
@@ -554,10 +561,8 @@ function formatText(data) {
554
561
  export async function branchCompare(baseRef, targetRef, opts = {}) {
555
562
  const data = await branchCompareData(baseRef, targetRef, opts);
556
563
 
557
- if (opts.json || opts.format === 'json') {
558
- console.log(JSON.stringify(data, null, 2));
559
- return;
560
- }
564
+ if (opts.format === 'json') opts = { ...opts, json: true };
565
+ if (outputResult(data, null, opts)) return;
561
566
 
562
567
  if (opts.format === 'mermaid') {
563
568
  console.log(branchCompareMermaid(data));
package/src/builder.js CHANGED
@@ -1448,16 +1448,12 @@ export async function buildGraph(rootDir, opts = {}) {
1448
1448
  }
1449
1449
 
1450
1450
  if (needsWasmTrees) {
1451
- _t.wasmPre0 = performance.now();
1452
1451
  try {
1453
1452
  const { ensureWasmTrees } = await import('./parser.js');
1454
1453
  await ensureWasmTrees(astComplexitySymbols, rootDir);
1455
1454
  } catch (err) {
1456
1455
  debug(`WASM pre-parse failed: ${err.message}`);
1457
1456
  }
1458
- _t.wasmPreMs = performance.now() - _t.wasmPre0;
1459
- } else {
1460
- _t.wasmPreMs = 0;
1461
1457
  }
1462
1458
  }
1463
1459
 
@@ -1601,7 +1597,6 @@ export async function buildGraph(rootDir, opts = {}) {
1601
1597
  rolesMs: +_t.rolesMs.toFixed(1),
1602
1598
  astMs: +_t.astMs.toFixed(1),
1603
1599
  complexityMs: +_t.complexityMs.toFixed(1),
1604
- ...(_t.wasmPreMs != null && { wasmPreMs: +_t.wasmPreMs.toFixed(1) }),
1605
1600
  ...(_t.cfgMs != null && { cfgMs: +_t.cfgMs.toFixed(1) }),
1606
1601
  ...(_t.dataflowMs != null && { dataflowMs: +_t.dataflowMs.toFixed(1) }),
1607
1602
  },