@optave/codegraph 3.1.0 → 3.1.2
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 +5 -5
- package/grammars/tree-sitter-go.wasm +0 -0
- package/package.json +8 -9
- package/src/ast-analysis/engine.js +365 -0
- package/src/ast-analysis/metrics.js +118 -0
- package/src/ast-analysis/rules/csharp.js +201 -0
- package/src/ast-analysis/rules/go.js +182 -0
- package/src/ast-analysis/rules/index.js +82 -0
- package/src/ast-analysis/rules/java.js +175 -0
- package/src/ast-analysis/rules/javascript.js +246 -0
- package/src/ast-analysis/rules/php.js +219 -0
- package/src/ast-analysis/rules/python.js +196 -0
- package/src/ast-analysis/rules/ruby.js +204 -0
- package/src/ast-analysis/rules/rust.js +173 -0
- package/src/ast-analysis/shared.js +223 -0
- package/src/ast-analysis/visitor-utils.js +176 -0
- package/src/ast-analysis/visitor.js +162 -0
- package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
- package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
- package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
- package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
- package/src/ast.js +26 -166
- package/src/audit.js +2 -88
- package/src/batch.js +0 -25
- package/src/boundaries.js +1 -1
- package/src/branch-compare.js +82 -172
- package/src/builder.js +48 -184
- package/src/cfg.js +148 -1174
- package/src/check.js +1 -84
- package/src/cli.js +118 -197
- package/src/cochange.js +1 -39
- package/src/commands/audit.js +88 -0
- package/src/commands/batch.js +26 -0
- package/src/commands/branch-compare.js +97 -0
- package/src/commands/cfg.js +55 -0
- package/src/commands/check.js +82 -0
- package/src/commands/cochange.js +37 -0
- package/src/commands/communities.js +69 -0
- package/src/commands/complexity.js +77 -0
- package/src/commands/dataflow.js +110 -0
- package/src/commands/flow.js +70 -0
- package/src/commands/manifesto.js +77 -0
- package/src/commands/owners.js +52 -0
- package/src/commands/query.js +21 -0
- package/src/commands/sequence.js +33 -0
- package/src/commands/structure.js +64 -0
- package/src/commands/triage.js +49 -0
- package/src/communities.js +22 -96
- package/src/complexity.js +234 -1591
- package/src/cycles.js +1 -1
- package/src/dataflow.js +274 -1352
- package/src/db/connection.js +88 -0
- package/src/db/migrations.js +312 -0
- package/src/db/query-builder.js +280 -0
- package/src/db/repository/build-stmts.js +104 -0
- package/src/db/repository/cfg.js +83 -0
- package/src/db/repository/cochange.js +41 -0
- package/src/db/repository/complexity.js +15 -0
- package/src/db/repository/dataflow.js +12 -0
- package/src/db/repository/edges.js +259 -0
- package/src/db/repository/embeddings.js +40 -0
- package/src/db/repository/graph-read.js +39 -0
- package/src/db/repository/index.js +42 -0
- package/src/db/repository/nodes.js +236 -0
- package/src/db.js +58 -399
- package/src/embedder.js +158 -174
- package/src/export.js +1 -1
- package/src/extractors/javascript.js +130 -5
- package/src/flow.js +153 -222
- package/src/index.js +53 -16
- package/src/infrastructure/result-formatter.js +21 -0
- package/src/infrastructure/test-filter.js +7 -0
- package/src/kinds.js +50 -0
- package/src/manifesto.js +1 -82
- package/src/mcp.js +37 -20
- package/src/owners.js +127 -182
- package/src/queries-cli.js +866 -0
- package/src/queries.js +1271 -2416
- package/src/sequence.js +179 -223
- package/src/structure.js +211 -269
- package/src/triage.js +117 -212
- package/src/viewer.js +1 -1
- package/src/watcher.js +7 -4
package/src/builder.js
CHANGED
|
@@ -4,7 +4,17 @@ import path from 'node:path';
|
|
|
4
4
|
import { performance } from 'node:perf_hooks';
|
|
5
5
|
import { loadConfig } from './config.js';
|
|
6
6
|
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
bulkNodeIdsByFile,
|
|
9
|
+
closeDb,
|
|
10
|
+
getBuildMeta,
|
|
11
|
+
getNodeId,
|
|
12
|
+
initSchema,
|
|
13
|
+
MIGRATIONS,
|
|
14
|
+
openDb,
|
|
15
|
+
purgeFilesData,
|
|
16
|
+
setBuildMeta,
|
|
17
|
+
} from './db.js';
|
|
8
18
|
import { readJournal, writeJournalHeader } from './journal.js';
|
|
9
19
|
import { debug, info, warn } from './logger.js';
|
|
10
20
|
import { loadNative } from './native.js';
|
|
@@ -350,88 +360,7 @@ function getChangedFiles(db, allFiles, rootDir) {
|
|
|
350
360
|
* @param {boolean} [options.purgeHashes=true] - Also delete file_hashes entries
|
|
351
361
|
*/
|
|
352
362
|
export function purgeFilesFromGraph(db, files, options = {}) {
|
|
353
|
-
|
|
354
|
-
if (!files || files.length === 0) return;
|
|
355
|
-
|
|
356
|
-
// Check if embeddings table exists
|
|
357
|
-
let hasEmbeddings = false;
|
|
358
|
-
try {
|
|
359
|
-
db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
|
|
360
|
-
hasEmbeddings = true;
|
|
361
|
-
} catch {
|
|
362
|
-
/* table doesn't exist */
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const deleteEmbeddingsForFile = hasEmbeddings
|
|
366
|
-
? db.prepare('DELETE FROM embeddings WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)')
|
|
367
|
-
: null;
|
|
368
|
-
const deleteNodesForFile = db.prepare('DELETE FROM nodes WHERE file = ?');
|
|
369
|
-
const deleteEdgesForFile = db.prepare(`
|
|
370
|
-
DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = @f)
|
|
371
|
-
OR target_id IN (SELECT id FROM nodes WHERE file = @f)
|
|
372
|
-
`);
|
|
373
|
-
const deleteMetricsForFile = db.prepare(
|
|
374
|
-
'DELETE FROM node_metrics WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
375
|
-
);
|
|
376
|
-
let deleteComplexityForFile;
|
|
377
|
-
try {
|
|
378
|
-
deleteComplexityForFile = db.prepare(
|
|
379
|
-
'DELETE FROM function_complexity WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
380
|
-
);
|
|
381
|
-
} catch {
|
|
382
|
-
deleteComplexityForFile = null;
|
|
383
|
-
}
|
|
384
|
-
let deleteDataflowForFile;
|
|
385
|
-
try {
|
|
386
|
-
deleteDataflowForFile = db.prepare(
|
|
387
|
-
'DELETE FROM dataflow WHERE source_id IN (SELECT id FROM nodes WHERE file = ?) OR target_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
388
|
-
);
|
|
389
|
-
} catch {
|
|
390
|
-
deleteDataflowForFile = null;
|
|
391
|
-
}
|
|
392
|
-
let deleteHashForFile;
|
|
393
|
-
if (purgeHashes) {
|
|
394
|
-
try {
|
|
395
|
-
deleteHashForFile = db.prepare('DELETE FROM file_hashes WHERE file = ?');
|
|
396
|
-
} catch {
|
|
397
|
-
deleteHashForFile = null;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
let deleteAstNodesForFile;
|
|
401
|
-
try {
|
|
402
|
-
deleteAstNodesForFile = db.prepare('DELETE FROM ast_nodes WHERE file = ?');
|
|
403
|
-
} catch {
|
|
404
|
-
deleteAstNodesForFile = null;
|
|
405
|
-
}
|
|
406
|
-
let deleteCfgForFile;
|
|
407
|
-
try {
|
|
408
|
-
deleteCfgForFile = db.prepare(
|
|
409
|
-
'DELETE FROM cfg_edges WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
410
|
-
);
|
|
411
|
-
} catch {
|
|
412
|
-
deleteCfgForFile = null;
|
|
413
|
-
}
|
|
414
|
-
let deleteCfgBlocksForFile;
|
|
415
|
-
try {
|
|
416
|
-
deleteCfgBlocksForFile = db.prepare(
|
|
417
|
-
'DELETE FROM cfg_blocks WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
418
|
-
);
|
|
419
|
-
} catch {
|
|
420
|
-
deleteCfgBlocksForFile = null;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
for (const relPath of files) {
|
|
424
|
-
deleteEmbeddingsForFile?.run(relPath);
|
|
425
|
-
deleteEdgesForFile.run({ f: relPath });
|
|
426
|
-
deleteMetricsForFile.run(relPath);
|
|
427
|
-
deleteComplexityForFile?.run(relPath);
|
|
428
|
-
deleteDataflowForFile?.run(relPath, relPath);
|
|
429
|
-
deleteAstNodesForFile?.run(relPath);
|
|
430
|
-
deleteCfgForFile?.run(relPath);
|
|
431
|
-
deleteCfgBlocksForFile?.run(relPath);
|
|
432
|
-
deleteNodesForFile.run(relPath);
|
|
433
|
-
if (purgeHashes) deleteHashForFile?.run(relPath);
|
|
434
|
-
}
|
|
363
|
+
purgeFilesData(db, files, options);
|
|
435
364
|
}
|
|
436
365
|
|
|
437
366
|
export async function buildGraph(rootDir, opts = {}) {
|
|
@@ -677,9 +606,12 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
677
606
|
}
|
|
678
607
|
}
|
|
679
608
|
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
609
|
+
const getNodeIdStmt = {
|
|
610
|
+
get: (name, kind, file, line) => {
|
|
611
|
+
const id = getNodeId(db, name, kind, file, line);
|
|
612
|
+
return id != null ? { id } : undefined;
|
|
613
|
+
},
|
|
614
|
+
};
|
|
683
615
|
|
|
684
616
|
// Batch INSERT helpers — multi-value INSERTs reduce SQLite round-trips
|
|
685
617
|
const BATCH_CHUNK = 200;
|
|
@@ -752,7 +684,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
752
684
|
}
|
|
753
685
|
|
|
754
686
|
// Bulk-fetch all node IDs for a file in one query (replaces per-node getNodeId calls)
|
|
755
|
-
const bulkGetNodeIds =
|
|
687
|
+
const bulkGetNodeIds = { all: (file) => bulkNodeIdsByFile(db, file) };
|
|
756
688
|
|
|
757
689
|
const insertAll = db.transaction(() => {
|
|
758
690
|
// Phase 1: Batch insert all file nodes + definitions + exports
|
|
@@ -1032,16 +964,22 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1032
964
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1033
965
|
// Skip barrel-only files — loaded for resolution, edges already in DB
|
|
1034
966
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1035
|
-
const fileNodeRow =
|
|
967
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1036
968
|
if (!fileNodeRow) continue;
|
|
1037
969
|
const fileNodeId = fileNodeRow.id;
|
|
1038
970
|
|
|
1039
971
|
// Import edges
|
|
1040
972
|
for (const imp of symbols.imports) {
|
|
1041
973
|
const resolvedPath = getResolved(path.join(rootDir, relPath), imp.source);
|
|
1042
|
-
const targetRow =
|
|
974
|
+
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
1043
975
|
if (targetRow) {
|
|
1044
|
-
const edgeKind = imp.reexport
|
|
976
|
+
const edgeKind = imp.reexport
|
|
977
|
+
? 'reexports'
|
|
978
|
+
: imp.typeOnly
|
|
979
|
+
? 'imports-type'
|
|
980
|
+
: imp.dynamicImport
|
|
981
|
+
? 'dynamic-imports'
|
|
982
|
+
: 'imports';
|
|
1045
983
|
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
1046
984
|
|
|
1047
985
|
if (!imp.reexport && isBarrelFile(resolvedPath)) {
|
|
@@ -1055,12 +993,16 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1055
993
|
!resolvedSources.has(actualSource)
|
|
1056
994
|
) {
|
|
1057
995
|
resolvedSources.add(actualSource);
|
|
1058
|
-
const actualRow =
|
|
996
|
+
const actualRow = getNodeIdStmt.get(actualSource, 'file', actualSource, 0);
|
|
1059
997
|
if (actualRow) {
|
|
1060
998
|
allEdgeRows.push([
|
|
1061
999
|
fileNodeId,
|
|
1062
1000
|
actualRow.id,
|
|
1063
|
-
edgeKind === 'imports-type'
|
|
1001
|
+
edgeKind === 'imports-type'
|
|
1002
|
+
? 'imports-type'
|
|
1003
|
+
: edgeKind === 'dynamic-imports'
|
|
1004
|
+
? 'dynamic-imports'
|
|
1005
|
+
: 'imports',
|
|
1064
1006
|
0.9,
|
|
1065
1007
|
0,
|
|
1066
1008
|
]);
|
|
@@ -1078,7 +1020,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1078
1020
|
const nativeFiles = [];
|
|
1079
1021
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1080
1022
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1081
|
-
const fileNodeRow =
|
|
1023
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1082
1024
|
if (!fileNodeRow) continue;
|
|
1083
1025
|
|
|
1084
1026
|
// Pre-resolve imported names (including barrel resolution)
|
|
@@ -1120,7 +1062,7 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1120
1062
|
// JS fallback — call/receiver/extends/implements edges
|
|
1121
1063
|
for (const [relPath, symbols] of fileSymbols) {
|
|
1122
1064
|
if (barrelOnlyFiles.has(relPath)) continue;
|
|
1123
|
-
const fileNodeRow =
|
|
1065
|
+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
1124
1066
|
if (!fileNodeRow) continue;
|
|
1125
1067
|
|
|
1126
1068
|
// Build import name -> target file mapping
|
|
@@ -1145,14 +1087,14 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1145
1087
|
if (call.line <= end) {
|
|
1146
1088
|
const span = end - def.line;
|
|
1147
1089
|
if (span < callerSpan) {
|
|
1148
|
-
const row =
|
|
1090
|
+
const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
|
|
1149
1091
|
if (row) {
|
|
1150
1092
|
caller = row;
|
|
1151
1093
|
callerSpan = span;
|
|
1152
1094
|
}
|
|
1153
1095
|
}
|
|
1154
1096
|
} else if (!caller) {
|
|
1155
|
-
const row =
|
|
1097
|
+
const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
|
|
1156
1098
|
if (row) caller = row;
|
|
1157
1099
|
}
|
|
1158
1100
|
}
|
|
@@ -1393,96 +1335,19 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1393
1335
|
}
|
|
1394
1336
|
}
|
|
1395
1337
|
|
|
1396
|
-
//
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
const { buildAstNodes } = await import('./ast.js');
|
|
1401
|
-
await buildAstNodes(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1402
|
-
} catch (err) {
|
|
1403
|
-
debug(`AST node extraction failed: ${err.message}`);
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
_t.astMs = performance.now() - _t.ast0;
|
|
1407
|
-
|
|
1408
|
-
// Compute per-function complexity metrics (cognitive, cyclomatic, nesting)
|
|
1409
|
-
_t.complexity0 = performance.now();
|
|
1410
|
-
if (opts.complexity !== false) {
|
|
1411
|
-
try {
|
|
1412
|
-
const { buildComplexityMetrics } = await import('./complexity.js');
|
|
1413
|
-
await buildComplexityMetrics(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1414
|
-
} catch (err) {
|
|
1415
|
-
debug(`Complexity analysis failed: ${err.message}`);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
_t.complexityMs = performance.now() - _t.complexity0;
|
|
1419
|
-
|
|
1420
|
-
// Pre-parse files missing WASM trees (native builds) so CFG + dataflow
|
|
1421
|
-
// share a single parse pass instead of each creating parsers independently.
|
|
1422
|
-
// Skip entirely when native engine already provides CFG + dataflow data.
|
|
1423
|
-
if (opts.cfg !== false || opts.dataflow !== false) {
|
|
1424
|
-
const needsCfg = opts.cfg !== false;
|
|
1425
|
-
const needsDataflow = opts.dataflow !== false;
|
|
1426
|
-
|
|
1427
|
-
let needsWasmTrees = false;
|
|
1428
|
-
for (const [, symbols] of astComplexitySymbols) {
|
|
1429
|
-
if (symbols._tree) continue; // already has a tree
|
|
1430
|
-
// CFG: need tree if any function/method def lacks native CFG
|
|
1431
|
-
if (needsCfg) {
|
|
1432
|
-
const fnDefs = (symbols.definitions || []).filter(
|
|
1433
|
-
(d) => (d.kind === 'function' || d.kind === 'method') && d.line,
|
|
1434
|
-
);
|
|
1435
|
-
if (
|
|
1436
|
-
fnDefs.length > 0 &&
|
|
1437
|
-
!fnDefs.every((d) => d.cfg === null || Array.isArray(d.cfg?.blocks))
|
|
1438
|
-
) {
|
|
1439
|
-
needsWasmTrees = true;
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
// Dataflow: need tree if file lacks native dataflow
|
|
1444
|
-
if (needsDataflow && !symbols.dataflow) {
|
|
1445
|
-
needsWasmTrees = true;
|
|
1446
|
-
break;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
if (needsWasmTrees) {
|
|
1451
|
-
_t.wasmPre0 = performance.now();
|
|
1452
|
-
try {
|
|
1453
|
-
const { ensureWasmTrees } = await import('./parser.js');
|
|
1454
|
-
await ensureWasmTrees(astComplexitySymbols, rootDir);
|
|
1455
|
-
} catch (err) {
|
|
1456
|
-
debug(`WASM pre-parse failed: ${err.message}`);
|
|
1457
|
-
}
|
|
1458
|
-
_t.wasmPreMs = performance.now() - _t.wasmPre0;
|
|
1459
|
-
} else {
|
|
1460
|
-
_t.wasmPreMs = 0;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
// CFG analysis (skip with --no-cfg)
|
|
1465
|
-
if (opts.cfg !== false) {
|
|
1466
|
-
_t.cfg0 = performance.now();
|
|
1467
|
-
try {
|
|
1468
|
-
const { buildCFGData } = await import('./cfg.js');
|
|
1469
|
-
await buildCFGData(db, astComplexitySymbols, rootDir, engineOpts);
|
|
1470
|
-
} catch (err) {
|
|
1471
|
-
debug(`CFG analysis failed: ${err.message}`);
|
|
1472
|
-
}
|
|
1473
|
-
_t.cfgMs = performance.now() - _t.cfg0;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// Dataflow analysis (skip with --no-dataflow)
|
|
1477
|
-
if (opts.dataflow !== false) {
|
|
1478
|
-
_t.dataflow0 = performance.now();
|
|
1338
|
+
// ── Unified AST analysis engine ──────────────────────────────────────
|
|
1339
|
+
// Replaces 4 sequential buildXxx calls with one coordinated pass.
|
|
1340
|
+
{
|
|
1341
|
+
const { runAnalyses } = await import('./ast-analysis/engine.js');
|
|
1479
1342
|
try {
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1343
|
+
const analysisTiming = await runAnalyses(db, astComplexitySymbols, rootDir, opts, engineOpts);
|
|
1344
|
+
_t.astMs = analysisTiming.astMs;
|
|
1345
|
+
_t.complexityMs = analysisTiming.complexityMs;
|
|
1346
|
+
_t.cfgMs = analysisTiming.cfgMs;
|
|
1347
|
+
_t.dataflowMs = analysisTiming.dataflowMs;
|
|
1482
1348
|
} catch (err) {
|
|
1483
|
-
debug(`
|
|
1349
|
+
debug(`Unified analysis engine failed: ${err.message}`);
|
|
1484
1350
|
}
|
|
1485
|
-
_t.dataflowMs = performance.now() - _t.dataflow0;
|
|
1486
1351
|
}
|
|
1487
1352
|
|
|
1488
1353
|
// Release any remaining cached WASM trees for GC
|
|
@@ -1601,7 +1466,6 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
1601
1466
|
rolesMs: +_t.rolesMs.toFixed(1),
|
|
1602
1467
|
astMs: +_t.astMs.toFixed(1),
|
|
1603
1468
|
complexityMs: +_t.complexityMs.toFixed(1),
|
|
1604
|
-
...(_t.wasmPreMs != null && { wasmPreMs: +_t.wasmPreMs.toFixed(1) }),
|
|
1605
1469
|
...(_t.cfgMs != null && { cfgMs: +_t.cfgMs.toFixed(1) }),
|
|
1606
1470
|
...(_t.dataflowMs != null && { dataflowMs: +_t.dataflowMs.toFixed(1) }),
|
|
1607
1471
|
},
|