@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.
Files changed (132) hide show
  1. package/README.md +12 -7
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +121 -48
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +15 -18
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +50 -1
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  12. package/dist/cli/commands/branch-compare.js +4 -0
  13. package/dist/cli/commands/branch-compare.js.map +1 -1
  14. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  15. package/dist/cli/commands/diff-impact.js +2 -1
  16. package/dist/cli/commands/diff-impact.js.map +1 -1
  17. package/dist/cli/commands/info.d.ts.map +1 -1
  18. package/dist/cli/commands/info.js +3 -2
  19. package/dist/cli/commands/info.js.map +1 -1
  20. package/dist/db/connection.d.ts +1 -0
  21. package/dist/db/connection.d.ts.map +1 -1
  22. package/dist/db/connection.js +22 -4
  23. package/dist/db/connection.js.map +1 -1
  24. package/dist/db/repository/base.d.ts +41 -0
  25. package/dist/db/repository/base.d.ts.map +1 -1
  26. package/dist/db/repository/base.js +22 -0
  27. package/dist/db/repository/base.js.map +1 -1
  28. package/dist/db/repository/index.d.ts +1 -0
  29. package/dist/db/repository/index.d.ts.map +1 -1
  30. package/dist/db/repository/index.js.map +1 -1
  31. package/dist/db/repository/native-repository.d.ts +8 -1
  32. package/dist/db/repository/native-repository.d.ts.map +1 -1
  33. package/dist/db/repository/native-repository.js +69 -1
  34. package/dist/db/repository/native-repository.js.map +1 -1
  35. package/dist/db/repository/sqlite-repository.d.ts +1 -0
  36. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  37. package/dist/db/repository/sqlite-repository.js +25 -0
  38. package/dist/db/repository/sqlite-repository.js.map +1 -1
  39. package/dist/domain/analysis/dependencies.d.ts +1 -28
  40. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  41. package/dist/domain/analysis/dependencies.js +24 -8
  42. package/dist/domain/analysis/dependencies.js.map +1 -1
  43. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  44. package/dist/domain/graph/builder/incremental.js +18 -0
  45. package/dist/domain/graph/builder/incremental.js.map +1 -1
  46. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  47. package/dist/domain/graph/builder/pipeline.js +298 -206
  48. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  49. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  50. package/dist/domain/graph/builder/stages/build-edges.js +56 -3
  51. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  52. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  53. package/dist/domain/graph/builder/stages/resolve-imports.js +19 -23
  54. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  55. package/dist/domain/graph/watcher.d.ts.map +1 -1
  56. package/dist/domain/graph/watcher.js +99 -95
  57. package/dist/domain/graph/watcher.js.map +1 -1
  58. package/dist/domain/parser.d.ts +4 -0
  59. package/dist/domain/parser.d.ts.map +1 -1
  60. package/dist/domain/parser.js +130 -61
  61. package/dist/domain/parser.js.map +1 -1
  62. package/dist/domain/search/models.d.ts.map +1 -1
  63. package/dist/domain/search/models.js +7 -5
  64. package/dist/domain/search/models.js.map +1 -1
  65. package/dist/extractors/go.js +53 -35
  66. package/dist/extractors/go.js.map +1 -1
  67. package/dist/extractors/javascript.js +85 -36
  68. package/dist/extractors/javascript.js.map +1 -1
  69. package/dist/features/complexity.d.ts.map +1 -1
  70. package/dist/features/complexity.js +78 -58
  71. package/dist/features/complexity.js.map +1 -1
  72. package/dist/features/dataflow.d.ts.map +1 -1
  73. package/dist/features/dataflow.js +109 -118
  74. package/dist/features/dataflow.js.map +1 -1
  75. package/dist/features/structure.d.ts.map +1 -1
  76. package/dist/features/structure.js +147 -97
  77. package/dist/features/structure.js.map +1 -1
  78. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  79. package/dist/graph/algorithms/louvain.js +4 -2
  80. package/dist/graph/algorithms/louvain.js.map +1 -1
  81. package/dist/graph/classifiers/roles.d.ts +2 -0
  82. package/dist/graph/classifiers/roles.d.ts.map +1 -1
  83. package/dist/graph/classifiers/roles.js +13 -5
  84. package/dist/graph/classifiers/roles.js.map +1 -1
  85. package/dist/presentation/communities.d.ts.map +1 -1
  86. package/dist/presentation/communities.js +38 -34
  87. package/dist/presentation/communities.js.map +1 -1
  88. package/dist/presentation/manifesto.d.ts.map +1 -1
  89. package/dist/presentation/manifesto.js +31 -33
  90. package/dist/presentation/manifesto.js.map +1 -1
  91. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  92. package/dist/presentation/queries-cli/inspect.js +47 -46
  93. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  94. package/dist/shared/file-utils.d.ts.map +1 -1
  95. package/dist/shared/file-utils.js +94 -72
  96. package/dist/shared/file-utils.js.map +1 -1
  97. package/dist/types.d.ts +83 -2
  98. package/dist/types.d.ts.map +1 -1
  99. package/grammars/tree-sitter-erlang.wasm +0 -0
  100. package/grammars/tree-sitter-gleam.wasm +0 -0
  101. package/package.json +9 -9
  102. package/src/ast-analysis/engine.ts +150 -55
  103. package/src/ast-analysis/visitors/ast-store-visitor.ts +19 -21
  104. package/src/ast-analysis/visitors/complexity-visitor.ts +55 -1
  105. package/src/cli/commands/branch-compare.ts +4 -0
  106. package/src/cli/commands/diff-impact.ts +2 -1
  107. package/src/cli/commands/info.ts +3 -2
  108. package/src/db/connection.ts +24 -5
  109. package/src/db/repository/base.ts +57 -0
  110. package/src/db/repository/index.ts +1 -0
  111. package/src/db/repository/native-repository.ts +92 -1
  112. package/src/db/repository/sqlite-repository.ts +26 -0
  113. package/src/domain/analysis/dependencies.ts +24 -6
  114. package/src/domain/graph/builder/incremental.ts +21 -0
  115. package/src/domain/graph/builder/pipeline.ts +396 -245
  116. package/src/domain/graph/builder/stages/build-edges.ts +53 -2
  117. package/src/domain/graph/builder/stages/resolve-imports.ts +20 -20
  118. package/src/domain/graph/watcher.ts +118 -98
  119. package/src/domain/parser.ts +131 -63
  120. package/src/domain/search/models.ts +11 -5
  121. package/src/extractors/go.ts +57 -32
  122. package/src/extractors/javascript.ts +88 -35
  123. package/src/features/complexity.ts +94 -58
  124. package/src/features/dataflow.ts +153 -132
  125. package/src/features/structure.ts +167 -95
  126. package/src/graph/algorithms/louvain.ts +5 -2
  127. package/src/graph/classifiers/roles.ts +14 -5
  128. package/src/presentation/communities.ts +44 -39
  129. package/src/presentation/manifesto.ts +35 -38
  130. package/src/presentation/queries-cli/inspect.ts +48 -46
  131. package/src/shared/file-utils.ts +116 -77
  132. 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 { Repository } from './base.js';
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
- for (const f of frontier) {
80
- if (visited.has(f.id)) continue;
81
- visited.add(f.id);
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);