@optave/codegraph 3.8.1 → 3.9.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.
- package/README.md +12 -7
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +121 -48
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +15 -18
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +50 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/db/connection.d.ts +1 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +22 -4
- package/dist/db/connection.js.map +1 -1
- package/dist/db/repository/base.d.ts +41 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +22 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -0
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +8 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +69 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +1 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +25 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts +1 -28
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +24 -8
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +18 -0
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +298 -206
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +56 -3
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +19 -23
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +99 -95
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +4 -0
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +130 -61
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +7 -5
- package/dist/domain/search/models.js.map +1 -1
- package/dist/extractors/go.js +53 -35
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/javascript.js +85 -36
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +78 -58
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +109 -118
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +147 -97
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +4 -2
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts +2 -0
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +13 -5
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/presentation/communities.d.ts.map +1 -1
- package/dist/presentation/communities.js +38 -34
- package/dist/presentation/communities.js.map +1 -1
- package/dist/presentation/manifesto.d.ts.map +1 -1
- package/dist/presentation/manifesto.js +31 -33
- package/dist/presentation/manifesto.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +47 -46
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/shared/file-utils.d.ts.map +1 -1
- package/dist/shared/file-utils.js +94 -72
- package/dist/shared/file-utils.js.map +1 -1
- package/dist/types.d.ts +83 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +150 -55
- package/src/ast-analysis/visitors/ast-store-visitor.ts +19 -21
- package/src/ast-analysis/visitors/complexity-visitor.ts +55 -1
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/db/connection.ts +24 -5
- package/src/db/repository/base.ts +57 -0
- package/src/db/repository/index.ts +1 -0
- package/src/db/repository/native-repository.ts +92 -1
- package/src/db/repository/sqlite-repository.ts +26 -0
- package/src/domain/analysis/dependencies.ts +24 -6
- package/src/domain/graph/builder/incremental.ts +21 -0
- package/src/domain/graph/builder/pipeline.ts +396 -245
- package/src/domain/graph/builder/stages/build-edges.ts +53 -2
- package/src/domain/graph/builder/stages/resolve-imports.ts +20 -20
- package/src/domain/graph/watcher.ts +118 -98
- package/src/domain/parser.ts +131 -63
- package/src/domain/search/models.ts +11 -5
- package/src/extractors/go.ts +57 -32
- package/src/extractors/javascript.ts +88 -35
- package/src/features/complexity.ts +94 -58
- package/src/features/dataflow.ts +153 -132
- package/src/features/structure.ts +167 -95
- package/src/graph/algorithms/louvain.ts +5 -2
- package/src/graph/classifiers/roles.ts +14 -5
- package/src/presentation/communities.ts +44 -39
- package/src/presentation/manifesto.ts +35 -38
- package/src/presentation/queries-cli/inspect.ts +48 -46
- package/src/shared/file-utils.ts +116 -77
- package/src/types.ts +87 -1
|
@@ -45,7 +45,13 @@ import type {
|
|
|
45
45
|
TriageNodeRow,
|
|
46
46
|
TriageQueryOpts,
|
|
47
47
|
} from '../../types.js';
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
type FnDepsCallerNode,
|
|
50
|
+
type FnDepsEntry,
|
|
51
|
+
type FnDepsNode,
|
|
52
|
+
type FnDepsResult,
|
|
53
|
+
Repository,
|
|
54
|
+
} from './base.js';
|
|
49
55
|
|
|
50
56
|
// ── Row converters (napi camelCase → Repository snake_case) ─────────────
|
|
51
57
|
|
|
@@ -291,6 +297,31 @@ export class NativeRepository extends Repository {
|
|
|
291
297
|
return this.#ndb.findCallers(nodeId).map(toRelatedNodeRow);
|
|
292
298
|
}
|
|
293
299
|
|
|
300
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
|
|
301
|
+
if (nodeIds.length === 0) return new Map();
|
|
302
|
+
const placeholders = nodeIds.map(() => '?').join(',');
|
|
303
|
+
const rows = this.#ndb.queryAll(
|
|
304
|
+
`SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
|
|
305
|
+
FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
306
|
+
WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`,
|
|
307
|
+
nodeIds,
|
|
308
|
+
) as Array<Record<string, unknown>>;
|
|
309
|
+
const result = new Map<number, RelatedNodeRow[]>();
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
const qid = row.queried_id as number;
|
|
312
|
+
if (!result.has(qid)) result.set(qid, []);
|
|
313
|
+
result.get(qid)!.push({
|
|
314
|
+
id: row.id as number,
|
|
315
|
+
name: row.name as string,
|
|
316
|
+
kind: row.kind as string,
|
|
317
|
+
file: row.file as string,
|
|
318
|
+
line: row.line as number,
|
|
319
|
+
end_line: (row.end_line as number | null) ?? null,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
294
325
|
findDistinctCallers(nodeId: number): RelatedNodeRow[] {
|
|
295
326
|
return this.#ndb.findDistinctCallers(nodeId).map(toRelatedNodeRow);
|
|
296
327
|
}
|
|
@@ -436,4 +467,64 @@ export class NativeRepository extends Repository {
|
|
|
436
467
|
}
|
|
437
468
|
return false;
|
|
438
469
|
}
|
|
470
|
+
|
|
471
|
+
// ── Composite queries ──────────────────────────────────────────────
|
|
472
|
+
fnDeps(
|
|
473
|
+
name: string,
|
|
474
|
+
opts?: { depth?: number; noTests?: boolean; file?: string; kind?: string },
|
|
475
|
+
): FnDepsResult | null {
|
|
476
|
+
if (typeof this.#ndb.fnDeps !== 'function') return null;
|
|
477
|
+
const raw = this.#ndb.fnDeps(
|
|
478
|
+
name,
|
|
479
|
+
opts?.depth ?? undefined,
|
|
480
|
+
opts?.noTests ?? undefined,
|
|
481
|
+
opts?.file ?? undefined,
|
|
482
|
+
opts?.kind ?? undefined,
|
|
483
|
+
);
|
|
484
|
+
// Convert from native format (transitive_callers as array of groups)
|
|
485
|
+
// to JS format (transitiveCallers as Record<number, Array>)
|
|
486
|
+
return {
|
|
487
|
+
name: raw.name,
|
|
488
|
+
results: raw.results.map((entry: any): FnDepsEntry => {
|
|
489
|
+
const transitiveCallers: Record<number, FnDepsNode[]> = {};
|
|
490
|
+
for (const group of entry.transitiveCallers ?? []) {
|
|
491
|
+
transitiveCallers[group.depth] = (group.callers ?? []).map(
|
|
492
|
+
(c: any): FnDepsNode => ({
|
|
493
|
+
name: c.name,
|
|
494
|
+
kind: c.kind,
|
|
495
|
+
file: c.file,
|
|
496
|
+
line: c.line ?? null,
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
name: entry.name,
|
|
502
|
+
kind: entry.kind,
|
|
503
|
+
file: entry.file,
|
|
504
|
+
line: entry.line ?? null,
|
|
505
|
+
endLine: entry.endLine ?? entry.end_line ?? null,
|
|
506
|
+
role: entry.role ?? null,
|
|
507
|
+
fileHash: entry.fileHash ?? entry.file_hash ?? null,
|
|
508
|
+
callees: (entry.callees ?? []).map(
|
|
509
|
+
(c: any): FnDepsNode => ({
|
|
510
|
+
name: c.name,
|
|
511
|
+
kind: c.kind,
|
|
512
|
+
file: c.file,
|
|
513
|
+
line: c.line ?? null,
|
|
514
|
+
}),
|
|
515
|
+
),
|
|
516
|
+
callers: (entry.callers ?? []).map(
|
|
517
|
+
(c: any): FnDepsCallerNode => ({
|
|
518
|
+
name: c.name,
|
|
519
|
+
kind: c.kind,
|
|
520
|
+
file: c.file,
|
|
521
|
+
line: c.line ?? null,
|
|
522
|
+
viaHierarchy: c.viaHierarchy ?? c.via_hierarchy ?? undefined,
|
|
523
|
+
}),
|
|
524
|
+
),
|
|
525
|
+
transitiveCallers,
|
|
526
|
+
};
|
|
527
|
+
}),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
439
530
|
}
|
|
@@ -154,6 +154,32 @@ export class SqliteRepository extends Repository {
|
|
|
154
154
|
return findCallers(this.#db, nodeId);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
|
|
158
|
+
if (nodeIds.length === 0) return new Map();
|
|
159
|
+
const placeholders = nodeIds.map(() => '?').join(',');
|
|
160
|
+
const rows = this.#db
|
|
161
|
+
.prepare(
|
|
162
|
+
`SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
|
|
163
|
+
FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
164
|
+
WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`,
|
|
165
|
+
)
|
|
166
|
+
.all(...nodeIds) as Array<RelatedNodeRow & { queried_id: number }>;
|
|
167
|
+
const result = new Map<number, RelatedNodeRow[]>();
|
|
168
|
+
for (const row of rows) {
|
|
169
|
+
const qid = row.queried_id;
|
|
170
|
+
if (!result.has(qid)) result.set(qid, []);
|
|
171
|
+
result.get(qid)!.push({
|
|
172
|
+
id: row.id,
|
|
173
|
+
name: row.name,
|
|
174
|
+
kind: row.kind,
|
|
175
|
+
file: row.file,
|
|
176
|
+
line: row.line,
|
|
177
|
+
end_line: row.end_line,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
157
183
|
findDistinctCallers(nodeId: number): RelatedNodeRow[] {
|
|
158
184
|
return findDistinctCallers(this.#db, nodeId);
|
|
159
185
|
}
|
|
@@ -75,14 +75,20 @@ function buildTransitiveCallers(
|
|
|
75
75
|
let frontier = callers;
|
|
76
76
|
|
|
77
77
|
for (let d = 2; d <= depth; d++) {
|
|
78
|
+
// Collect unvisited frontier IDs for a single batched query per depth
|
|
79
|
+
const unvisited = frontier.filter((f) => !visited.has(f.id));
|
|
80
|
+
for (const f of unvisited) visited.add(f.id);
|
|
81
|
+
if (unvisited.length === 0) break;
|
|
82
|
+
|
|
83
|
+
const batchCallers = repo.findCallersBatch(unvisited.map((f) => f.id));
|
|
78
84
|
const nextFrontier: typeof frontier = [];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const upstream = repo.findCallers(f.id) as RelatedNodeRow[];
|
|
85
|
+
const nextFrontierIds = new Set<number>();
|
|
86
|
+
for (const f of unvisited) {
|
|
87
|
+
const upstream = batchCallers.get(f.id) || [];
|
|
83
88
|
for (const u of upstream) {
|
|
84
89
|
if (noTests && isTestFile(u.file)) continue;
|
|
85
|
-
if (!visited.has(u.id)) {
|
|
90
|
+
if (!visited.has(u.id) && !nextFrontierIds.has(u.id)) {
|
|
91
|
+
nextFrontierIds.add(u.id);
|
|
86
92
|
nextFrontier.push(u);
|
|
87
93
|
}
|
|
88
94
|
}
|
|
@@ -96,7 +102,6 @@ function buildTransitiveCallers(
|
|
|
96
102
|
}));
|
|
97
103
|
}
|
|
98
104
|
frontier = nextFrontier;
|
|
99
|
-
if (frontier.length === 0) break;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
return transitiveCallers;
|
|
@@ -168,6 +173,19 @@ export function fnDepsData(
|
|
|
168
173
|
} = {},
|
|
169
174
|
) {
|
|
170
175
|
return withRepo(customDbPath, (repo) => {
|
|
176
|
+
// Try native composite path — single NAPI call for the entire query.
|
|
177
|
+
const nativeResult = repo.fnDeps(name, {
|
|
178
|
+
depth: opts.depth,
|
|
179
|
+
noTests: opts.noTests,
|
|
180
|
+
file: opts.file,
|
|
181
|
+
kind: opts.kind,
|
|
182
|
+
});
|
|
183
|
+
if (nativeResult) {
|
|
184
|
+
const base = { name: nativeResult.name, results: nativeResult.results };
|
|
185
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Fallback: JS-orchestrated path (used when native engine is unavailable)
|
|
171
189
|
const depth = opts.depth || 3;
|
|
172
190
|
const noTests = opts.noTests || false;
|
|
173
191
|
const hc = new Map();
|
|
@@ -366,6 +366,27 @@ function buildImportEdges(
|
|
|
366
366
|
stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
|
|
367
367
|
edgesAdded++;
|
|
368
368
|
|
|
369
|
+
// Type-only imports: create symbol-level edges so the target symbols
|
|
370
|
+
// get fan-in credit and aren't falsely classified as dead code.
|
|
371
|
+
if (imp.typeOnly) {
|
|
372
|
+
for (const name of imp.names) {
|
|
373
|
+
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
374
|
+
let targetFile = resolvedPath;
|
|
375
|
+
if (db && isBarrelFile(db, resolvedPath)) {
|
|
376
|
+
const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
|
|
377
|
+
if (actual) targetFile = actual;
|
|
378
|
+
}
|
|
379
|
+
const candidates = stmts.findNodeInFile.all(cleanName, targetFile) as Array<{
|
|
380
|
+
id: number;
|
|
381
|
+
file: string;
|
|
382
|
+
}>;
|
|
383
|
+
if (candidates.length > 0) {
|
|
384
|
+
stmts.insertEdge.run(fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0);
|
|
385
|
+
edgesAdded++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
369
390
|
// Barrel resolution: create edges through re-export chains
|
|
370
391
|
if (!imp.reexport && db) {
|
|
371
392
|
edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeId, resolvedPath, imp);
|