@optave/codegraph 3.7.0 → 3.8.0

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 (148) hide show
  1. package/README.md +25 -14
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +158 -1
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +0 -1
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +2 -75
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/cli/commands/ast.js +2 -2
  12. package/dist/cli/commands/ast.js.map +1 -1
  13. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  14. package/dist/domain/graph/builder/pipeline.js +128 -6
  15. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  16. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  17. package/dist/domain/graph/builder/stages/build-edges.js +101 -1
  18. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  19. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  20. package/dist/domain/graph/builder/stages/collect-files.js +17 -5
  21. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  22. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  23. package/dist/domain/graph/builder/stages/detect-changes.js +98 -50
  24. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  25. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  26. package/dist/domain/graph/builder/stages/finalize.js +32 -5
  27. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  28. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  29. package/dist/domain/graph/builder/stages/insert-nodes.js +20 -7
  30. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  31. package/dist/domain/parser.d.ts +1 -1
  32. package/dist/domain/parser.d.ts.map +1 -1
  33. package/dist/domain/parser.js +88 -4
  34. package/dist/domain/parser.js.map +1 -1
  35. package/dist/extractors/clojure.d.ts +12 -0
  36. package/dist/extractors/clojure.d.ts.map +1 -0
  37. package/dist/extractors/clojure.js +245 -0
  38. package/dist/extractors/clojure.js.map +1 -0
  39. package/dist/extractors/cuda.d.ts +11 -0
  40. package/dist/extractors/cuda.d.ts.map +1 -0
  41. package/dist/extractors/cuda.js +302 -0
  42. package/dist/extractors/cuda.js.map +1 -0
  43. package/dist/extractors/erlang.d.ts +14 -0
  44. package/dist/extractors/erlang.d.ts.map +1 -0
  45. package/dist/extractors/erlang.js +239 -0
  46. package/dist/extractors/erlang.js.map +1 -0
  47. package/dist/extractors/fsharp.d.ts +13 -0
  48. package/dist/extractors/fsharp.d.ts.map +1 -0
  49. package/dist/extractors/fsharp.js +218 -0
  50. package/dist/extractors/fsharp.js.map +1 -0
  51. package/dist/extractors/gleam.d.ts +14 -0
  52. package/dist/extractors/gleam.d.ts.map +1 -0
  53. package/dist/extractors/gleam.js +229 -0
  54. package/dist/extractors/gleam.js.map +1 -0
  55. package/dist/extractors/groovy.d.ts +10 -0
  56. package/dist/extractors/groovy.d.ts.map +1 -0
  57. package/dist/extractors/groovy.js +304 -0
  58. package/dist/extractors/groovy.js.map +1 -0
  59. package/dist/extractors/index.d.ts +11 -0
  60. package/dist/extractors/index.d.ts.map +1 -1
  61. package/dist/extractors/index.js +11 -0
  62. package/dist/extractors/index.js.map +1 -1
  63. package/dist/extractors/julia.d.ts +16 -0
  64. package/dist/extractors/julia.d.ts.map +1 -0
  65. package/dist/extractors/julia.js +287 -0
  66. package/dist/extractors/julia.js.map +1 -0
  67. package/dist/extractors/objc.d.ts +9 -0
  68. package/dist/extractors/objc.d.ts.map +1 -0
  69. package/dist/extractors/objc.js +406 -0
  70. package/dist/extractors/objc.js.map +1 -0
  71. package/dist/extractors/ocaml.js +74 -0
  72. package/dist/extractors/ocaml.js.map +1 -1
  73. package/dist/extractors/r.d.ts +13 -0
  74. package/dist/extractors/r.d.ts.map +1 -0
  75. package/dist/extractors/r.js +251 -0
  76. package/dist/extractors/r.js.map +1 -0
  77. package/dist/extractors/solidity.d.ts +9 -0
  78. package/dist/extractors/solidity.d.ts.map +1 -0
  79. package/dist/extractors/solidity.js +374 -0
  80. package/dist/extractors/solidity.js.map +1 -0
  81. package/dist/extractors/verilog.d.ts +9 -0
  82. package/dist/extractors/verilog.d.ts.map +1 -0
  83. package/dist/extractors/verilog.js +286 -0
  84. package/dist/extractors/verilog.js.map +1 -0
  85. package/dist/features/ast.d.ts.map +1 -1
  86. package/dist/features/ast.js +1 -2
  87. package/dist/features/ast.js.map +1 -1
  88. package/dist/graph/algorithms/bfs.d.ts +2 -0
  89. package/dist/graph/algorithms/bfs.d.ts.map +1 -1
  90. package/dist/graph/algorithms/bfs.js +27 -0
  91. package/dist/graph/algorithms/bfs.js.map +1 -1
  92. package/dist/graph/algorithms/centrality.d.ts +2 -0
  93. package/dist/graph/algorithms/centrality.d.ts.map +1 -1
  94. package/dist/graph/algorithms/centrality.js +28 -0
  95. package/dist/graph/algorithms/centrality.js.map +1 -1
  96. package/dist/graph/algorithms/louvain.d.ts +3 -4
  97. package/dist/graph/algorithms/louvain.d.ts.map +1 -1
  98. package/dist/graph/algorithms/louvain.js +29 -0
  99. package/dist/graph/algorithms/louvain.js.map +1 -1
  100. package/dist/graph/algorithms/shortest-path.d.ts +2 -0
  101. package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
  102. package/dist/graph/algorithms/shortest-path.js +18 -1
  103. package/dist/graph/algorithms/shortest-path.js.map +1 -1
  104. package/dist/types.d.ts +122 -2
  105. package/dist/types.d.ts.map +1 -1
  106. package/grammars/tree-sitter-clojure.wasm +0 -0
  107. package/grammars/tree-sitter-cuda.wasm +0 -0
  108. package/grammars/tree-sitter-erlang.wasm +0 -0
  109. package/grammars/tree-sitter-fsharp.wasm +0 -0
  110. package/grammars/tree-sitter-gleam.wasm +0 -0
  111. package/grammars/tree-sitter-groovy.wasm +0 -0
  112. package/grammars/tree-sitter-julia.wasm +0 -0
  113. package/grammars/tree-sitter-objc.wasm +0 -0
  114. package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
  115. package/grammars/tree-sitter-r.wasm +0 -0
  116. package/grammars/tree-sitter-solidity.wasm +0 -0
  117. package/grammars/tree-sitter-verilog.wasm +0 -0
  118. package/package.json +18 -7
  119. package/src/ast-analysis/engine.ts +183 -1
  120. package/src/ast-analysis/rules/javascript.ts +0 -1
  121. package/src/ast-analysis/visitors/ast-store-visitor.ts +2 -75
  122. package/src/cli/commands/ast.ts +2 -2
  123. package/src/domain/graph/builder/pipeline.ts +142 -6
  124. package/src/domain/graph/builder/stages/build-edges.ts +158 -1
  125. package/src/domain/graph/builder/stages/collect-files.ts +18 -7
  126. package/src/domain/graph/builder/stages/detect-changes.ts +109 -55
  127. package/src/domain/graph/builder/stages/finalize.ts +39 -9
  128. package/src/domain/graph/builder/stages/insert-nodes.ts +18 -7
  129. package/src/domain/parser.ts +108 -2
  130. package/src/extractors/clojure.ts +273 -0
  131. package/src/extractors/cuda.ts +316 -0
  132. package/src/extractors/erlang.ts +252 -0
  133. package/src/extractors/fsharp.ts +253 -0
  134. package/src/extractors/gleam.ts +246 -0
  135. package/src/extractors/groovy.ts +332 -0
  136. package/src/extractors/index.ts +11 -0
  137. package/src/extractors/julia.ts +318 -0
  138. package/src/extractors/objc.ts +431 -0
  139. package/src/extractors/ocaml.ts +78 -0
  140. package/src/extractors/r.ts +253 -0
  141. package/src/extractors/solidity.ts +398 -0
  142. package/src/extractors/verilog.ts +315 -0
  143. package/src/features/ast.ts +1 -2
  144. package/src/graph/algorithms/bfs.ts +34 -0
  145. package/src/graph/algorithms/centrality.ts +30 -0
  146. package/src/graph/algorithms/louvain.ts +31 -4
  147. package/src/graph/algorithms/shortest-path.ts +20 -1
  148. package/src/types.ts +117 -2
@@ -0,0 +1,315 @@
1
+ import type { ExtractorOutput, SubDeclaration, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import { findChild, nodeEndLine } from './helpers.js';
3
+
4
+ /**
5
+ * Extract symbols from Verilog/SystemVerilog files.
6
+ *
7
+ * The tree-sitter-verilog grammar covers modules, interfaces, packages,
8
+ * tasks, functions, classes, always blocks, and instantiations.
9
+ */
10
+ export function extractVerilogSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
11
+ const ctx: ExtractorOutput = {
12
+ definitions: [],
13
+ calls: [],
14
+ imports: [],
15
+ classes: [],
16
+ exports: [],
17
+ typeMap: new Map(),
18
+ };
19
+
20
+ walkVerilogNode(tree.rootNode, ctx);
21
+ return ctx;
22
+ }
23
+
24
+ function walkVerilogNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
25
+ switch (node.type) {
26
+ case 'module_declaration':
27
+ handleModuleDecl(node, ctx);
28
+ break;
29
+ case 'interface_declaration':
30
+ handleInterfaceDecl(node, ctx);
31
+ break;
32
+ case 'package_declaration':
33
+ handlePackageDecl(node, ctx);
34
+ break;
35
+ case 'class_declaration':
36
+ handleClassDecl(node, ctx);
37
+ break;
38
+ case 'function_declaration':
39
+ handleFunctionDecl(node, ctx);
40
+ break;
41
+ case 'task_declaration':
42
+ handleTaskDecl(node, ctx);
43
+ break;
44
+ case 'module_instantiation':
45
+ handleModuleInstantiation(node, ctx);
46
+ break;
47
+ case 'package_import_declaration':
48
+ handlePackageImport(node, ctx);
49
+ break;
50
+ case 'include_compiler_directive':
51
+ handleIncludeDirective(node, ctx);
52
+ break;
53
+ }
54
+
55
+ for (let i = 0; i < node.childCount; i++) {
56
+ const child = node.child(i);
57
+ if (child) walkVerilogNode(child, ctx);
58
+ }
59
+ }
60
+
61
+ // ── Handlers ───────────────────────────────────────────────────────────────
62
+
63
+ function handleModuleDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
64
+ const nameNode = findModuleName(node);
65
+ if (!nameNode) return;
66
+
67
+ const ports = extractPorts(node);
68
+ ctx.definitions.push({
69
+ name: nameNode.text,
70
+ kind: 'module',
71
+ line: node.startPosition.row + 1,
72
+ endLine: nodeEndLine(node),
73
+ children: ports.length > 0 ? ports : undefined,
74
+ });
75
+ }
76
+
77
+ function handleInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
78
+ const nameNode = findDeclName(node);
79
+ if (!nameNode) return;
80
+
81
+ ctx.definitions.push({
82
+ name: nameNode.text,
83
+ kind: 'interface',
84
+ line: node.startPosition.row + 1,
85
+ endLine: nodeEndLine(node),
86
+ });
87
+ }
88
+
89
+ function handlePackageDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
90
+ const nameNode = findDeclName(node);
91
+ if (!nameNode) return;
92
+
93
+ ctx.definitions.push({
94
+ name: nameNode.text,
95
+ kind: 'module',
96
+ line: node.startPosition.row + 1,
97
+ endLine: nodeEndLine(node),
98
+ });
99
+ }
100
+
101
+ function handleClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
102
+ const nameNode = node.childForFieldName('name');
103
+ if (!nameNode) return;
104
+
105
+ ctx.definitions.push({
106
+ name: nameNode.text,
107
+ kind: 'class',
108
+ line: node.startPosition.row + 1,
109
+ endLine: nodeEndLine(node),
110
+ });
111
+
112
+ // Superclass via extends
113
+ const superclass = node.childForFieldName('superclass');
114
+ if (superclass) {
115
+ ctx.classes.push({
116
+ name: nameNode.text,
117
+ extends: superclass.text,
118
+ line: node.startPosition.row + 1,
119
+ });
120
+ }
121
+ }
122
+
123
+ function handleFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
124
+ const nameNode = findFunctionOrTaskName(node, 'function_identifier');
125
+ if (!nameNode) return;
126
+
127
+ const parentModule = findVerilogParent(node);
128
+ const fullName = parentModule ? `${parentModule}.${nameNode.text}` : nameNode.text;
129
+
130
+ ctx.definitions.push({
131
+ name: fullName,
132
+ kind: 'function',
133
+ line: node.startPosition.row + 1,
134
+ endLine: nodeEndLine(node),
135
+ });
136
+ }
137
+
138
+ function handleTaskDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
139
+ const nameNode = findFunctionOrTaskName(node, 'task_identifier');
140
+ if (!nameNode) return;
141
+
142
+ const parentModule = findVerilogParent(node);
143
+ const fullName = parentModule ? `${parentModule}.${nameNode.text}` : nameNode.text;
144
+
145
+ ctx.definitions.push({
146
+ name: fullName,
147
+ kind: 'function',
148
+ line: node.startPosition.row + 1,
149
+ endLine: nodeEndLine(node),
150
+ });
151
+ }
152
+
153
+ function handleModuleInstantiation(node: TreeSitterNode, ctx: ExtractorOutput): void {
154
+ // Module instantiations are like function calls: `ModuleName instance_name(...);`
155
+ const moduleType = node.childForFieldName('type') || node.child(0);
156
+ if (!moduleType) return;
157
+
158
+ ctx.calls.push({
159
+ name: moduleType.text,
160
+ line: node.startPosition.row + 1,
161
+ });
162
+ }
163
+
164
+ function handlePackageImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
165
+ // import pkg::item; or import pkg::*;
166
+ for (let i = 0; i < node.childCount; i++) {
167
+ const child = node.child(i);
168
+ if (!child) continue;
169
+ if (child.type === 'package_import_item') {
170
+ const text = child.text;
171
+ const parts = text.split('::');
172
+ const pkg = parts[0] ?? text;
173
+ const item = parts[1] ?? '*';
174
+ ctx.imports.push({
175
+ source: pkg,
176
+ names: [item],
177
+ line: node.startPosition.row + 1,
178
+ });
179
+ }
180
+ }
181
+ }
182
+
183
+ function handleIncludeDirective(node: TreeSitterNode, ctx: ExtractorOutput): void {
184
+ // `include "file.vh"
185
+ for (let i = 0; i < node.childCount; i++) {
186
+ const child = node.child(i);
187
+ if (child && (child.type === 'string_literal' || child.type === 'quoted_string')) {
188
+ const source = child.text.replace(/^["']|["']$/g, '');
189
+ ctx.imports.push({
190
+ source,
191
+ names: [source.split('/').pop() ?? source],
192
+ line: node.startPosition.row + 1,
193
+ cInclude: true,
194
+ });
195
+ return;
196
+ }
197
+ }
198
+ }
199
+
200
+ // ── Helpers ────────────────────────────────────────────────────────────────
201
+
202
+ function findModuleName(node: TreeSitterNode): TreeSitterNode | null {
203
+ // Try field name first, then look for module_header > identifier
204
+ const nameNode = node.childForFieldName('name');
205
+ if (nameNode) return nameNode;
206
+
207
+ const header = findChild(node, 'module_header');
208
+ if (header) {
209
+ const id = findChild(header, 'simple_identifier') || findChild(header, 'identifier');
210
+ if (id) return id;
211
+ }
212
+
213
+ // Direct child identifier after `module` keyword
214
+ for (let i = 0; i < node.childCount; i++) {
215
+ const child = node.child(i);
216
+ if (child && (child.type === 'simple_identifier' || child.type === 'identifier')) return child;
217
+ }
218
+ return null;
219
+ }
220
+
221
+ function findDeclName(node: TreeSitterNode): TreeSitterNode | null {
222
+ const nameNode = node.childForFieldName('name');
223
+ if (nameNode) return nameNode;
224
+
225
+ for (let i = 0; i < node.childCount; i++) {
226
+ const child = node.child(i);
227
+ if (child && (child.type === 'simple_identifier' || child.type === 'identifier')) return child;
228
+ }
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Find a function or task name by searching for the dedicated identifier node
234
+ * type (e.g. `function_identifier`, `task_identifier`) recursively through
235
+ * body declarations. Falls back to `findDeclName` for grammars that use
236
+ * plain identifiers.
237
+ */
238
+ function findFunctionOrTaskName(
239
+ node: TreeSitterNode,
240
+ identifierType: string,
241
+ ): TreeSitterNode | null {
242
+ // Try the standard approach first
243
+ const simple = findDeclName(node);
244
+ if (simple) return simple;
245
+
246
+ // Search children (including body declarations) for the dedicated identifier node
247
+ for (let i = 0; i < node.childCount; i++) {
248
+ const child = node.child(i);
249
+ if (!child) continue;
250
+ if (child.type === identifierType) return child;
251
+ // Look one level deeper into body declarations
252
+ for (let j = 0; j < child.childCount; j++) {
253
+ const grandchild = child.child(j);
254
+ if (grandchild && grandchild.type === identifierType) return grandchild;
255
+ }
256
+ }
257
+ return null;
258
+ }
259
+
260
+ function findVerilogParent(node: TreeSitterNode): string | null {
261
+ let current = node.parent;
262
+ while (current) {
263
+ if (
264
+ current.type === 'module_declaration' ||
265
+ current.type === 'interface_declaration' ||
266
+ current.type === 'package_declaration' ||
267
+ current.type === 'class_declaration'
268
+ ) {
269
+ const name = findDeclName(current) || findModuleName(current);
270
+ return name ? name.text : null;
271
+ }
272
+ current = current.parent;
273
+ }
274
+ return null;
275
+ }
276
+
277
+ function extractPorts(moduleNode: TreeSitterNode): SubDeclaration[] {
278
+ const ports: SubDeclaration[] = [];
279
+
280
+ // Look for port declarations in the module header or body
281
+ const collectFromNode = (node: TreeSitterNode): void => {
282
+ for (let i = 0; i < node.childCount; i++) {
283
+ const child = node.child(i);
284
+ if (!child) continue;
285
+
286
+ if (
287
+ child.type === 'ansi_port_declaration' ||
288
+ child.type === 'port_declaration' ||
289
+ child.type === 'input_declaration' ||
290
+ child.type === 'output_declaration' ||
291
+ child.type === 'inout_declaration'
292
+ ) {
293
+ const nameNode =
294
+ child.childForFieldName('name') ||
295
+ findChild(child, 'simple_identifier') ||
296
+ findChild(child, 'identifier');
297
+ if (nameNode) {
298
+ ports.push({ name: nameNode.text, kind: 'property', line: child.startPosition.row + 1 });
299
+ }
300
+ }
301
+
302
+ // Recurse into port list containers
303
+ if (
304
+ child.type === 'list_of_port_declarations' ||
305
+ child.type === 'module_header' ||
306
+ child.type === 'port_declaration_list'
307
+ ) {
308
+ collectFromNode(child);
309
+ }
310
+ }
311
+ };
312
+
313
+ collectFromNode(moduleNode);
314
+ return ports;
315
+ }
@@ -12,10 +12,9 @@ import type { ASTNodeKind, BetterSqlite3Database, Definition, TreeSitterNode } f
12
12
 
13
13
  // ─── Constants ────────────────────────────────────────────────────────
14
14
 
15
- export const AST_NODE_KINDS: ASTNodeKind[] = ['call', 'new', 'string', 'regex', 'throw', 'await'];
15
+ export const AST_NODE_KINDS: ASTNodeKind[] = ['new', 'string', 'regex', 'throw', 'await'];
16
16
 
17
17
  const KIND_ICONS: Record<string, string> = {
18
- call: '\u0192', // ƒ
19
18
  new: '\u2295', // ⊕
20
19
  string: '"',
21
20
  regex: '/',
@@ -1,3 +1,4 @@
1
+ import { loadNative } from '../../infrastructure/native.js';
1
2
  import type { CodeGraph } from '../model.js';
2
3
 
3
4
  export interface BfsOpts {
@@ -8,6 +9,8 @@ export interface BfsOpts {
8
9
  /**
9
10
  * Breadth-first traversal on a CodeGraph.
10
11
  *
12
+ * Tries the native Rust implementation first, falls back to JS.
13
+ *
11
14
  * @returns nodeId → depth from nearest start node
12
15
  */
13
16
  export function bfs(
@@ -19,6 +22,37 @@ export function bfs(
19
22
  const direction = opts.direction ?? 'forward';
20
23
  const starts = Array.isArray(startIds) ? startIds : [startIds];
21
24
 
25
+ const native = loadNative();
26
+ if (native?.bfsTraversal) {
27
+ const edges = graph.toEdgeArray();
28
+ const nativeMaxDepth = maxDepth === Infinity ? null : maxDepth;
29
+ // Undirected graphs deduplicate edges to one canonical direction in toEdgeArray(),
30
+ // so the Rust side must traverse both directions to preserve symmetry.
31
+ const nativeDirection = !graph.directed ? 'both' : direction;
32
+ const result = native.bfsTraversal(edges, starts, nativeMaxDepth, nativeDirection);
33
+ const depths = new Map<string, number>();
34
+ for (const entry of result) {
35
+ depths.set(entry.node, entry.depth);
36
+ }
37
+ // The Rust side only knows nodes referenced by edges; restore any isolated start nodes.
38
+ for (const startId of starts) {
39
+ if (graph.hasNode(startId) && !depths.has(startId)) {
40
+ depths.set(startId, 0);
41
+ }
42
+ }
43
+ return depths;
44
+ }
45
+
46
+ return bfsJS(graph, starts, maxDepth, direction);
47
+ }
48
+
49
+ /** Pure JS fallback for BFS (used when native addon is unavailable). */
50
+ function bfsJS(
51
+ graph: CodeGraph,
52
+ starts: string[],
53
+ maxDepth: number,
54
+ direction: string,
55
+ ): Map<string, number> {
22
56
  const depths = new Map<string, number>();
23
57
  const queue: string[] = [];
24
58
 
@@ -1,3 +1,4 @@
1
+ import { loadNative } from '../../infrastructure/native.js';
1
2
  import type { CodeGraph } from '../model.js';
2
3
 
3
4
  export interface FanInOut {
@@ -7,8 +8,37 @@ export interface FanInOut {
7
8
 
8
9
  /**
9
10
  * Fan-in / fan-out centrality for all nodes in a CodeGraph.
11
+ *
12
+ * Tries the native Rust implementation first, falls back to JS.
10
13
  */
11
14
  export function fanInOut(graph: CodeGraph): Map<string, FanInOut> {
15
+ const native = loadNative();
16
+ if (native?.fanInOut) {
17
+ let edges = graph.toEdgeArray();
18
+ if (!graph.directed) {
19
+ // Undirected: toEdgeArray() deduplicates to one canonical direction;
20
+ // mirror each edge so the Rust side counts symmetric in/out degrees.
21
+ edges = [...edges, ...edges.map((e) => ({ source: e.target, target: e.source }))];
22
+ }
23
+ const nativeResult = native.fanInOut(edges);
24
+ const result = new Map<string, FanInOut>();
25
+ for (const entry of nativeResult) {
26
+ result.set(entry.node, { fanIn: entry.fanIn, fanOut: entry.fanOut });
27
+ }
28
+ // Ensure isolated nodes (no edges) are included
29
+ for (const id of graph.nodeIds()) {
30
+ if (!result.has(id)) {
31
+ result.set(id, { fanIn: 0, fanOut: 0 });
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+
37
+ return fanInOutJS(graph);
38
+ }
39
+
40
+ /** Pure JS fallback for fan-in/out. */
41
+ function fanInOutJS(graph: CodeGraph): Map<string, FanInOut> {
12
42
  const result = new Map<string, FanInOut>();
13
43
  for (const id of graph.nodeIds()) {
14
44
  result.set(id, {
@@ -1,11 +1,13 @@
1
1
  /**
2
- * Community detection via vendored Leiden algorithm.
2
+ * Community detection via native Rust Louvain or vendored Leiden algorithm.
3
3
  * Maintains backward-compatible API: { assignments: Map<string, number>, modularity: number }
4
4
  *
5
- * Note: Always runs in undirected mode (`directed: false`) regardless of
6
- * the input graph's directedness. For direction-aware community detection,
7
- * use `detectClusters` from `./leiden/index.js` directly.
5
+ * Native path: classic Louvain (Rust, undirected modularity optimization).
6
+ * JS fallback: Leiden algorithm via `detectClusters` (always undirected, `directed: false`).
8
7
  */
8
+
9
+ import { warn } from '../../infrastructure/logger.js';
10
+ import { loadNative } from '../../infrastructure/native.js';
9
11
  import type { CodeGraph } from '../model.js';
10
12
  import type { DetectClustersResult } from './leiden/index.js';
11
13
  import { detectClusters } from './leiden/index.js';
@@ -28,6 +30,31 @@ export function louvainCommunities(graph: CodeGraph, opts: LouvainOptions = {}):
28
30
  }
29
31
 
30
32
  const resolution: number = opts.resolution ?? 1.0;
33
+
34
+ const native = loadNative();
35
+ if (native?.louvainCommunities) {
36
+ // maxLevels, maxLocalPasses, and refinementTheta are Leiden-specific tuning knobs
37
+ // not supported by the Rust Louvain implementation. Warn callers who set them.
38
+ if (opts.maxLevels != null || opts.maxLocalPasses != null || opts.refinementTheta != null) {
39
+ warn(
40
+ 'louvainCommunities: maxLevels/maxLocalPasses/refinementTheta are ignored by the native Rust path',
41
+ );
42
+ }
43
+ const edges = graph.toEdgeArray();
44
+ const nodeIds = graph.nodeIds();
45
+ const result = native.louvainCommunities(edges, nodeIds, resolution, 42);
46
+ const assignments = new Map<string, number>();
47
+ for (const entry of result.assignments) {
48
+ assignments.set(entry.node, entry.community);
49
+ }
50
+ return { assignments, modularity: result.modularity };
51
+ }
52
+
53
+ return louvainJS(graph, opts, resolution);
54
+ }
55
+
56
+ /** JS fallback using the vendored Leiden algorithm. */
57
+ function louvainJS(graph: CodeGraph, opts: LouvainOptions, resolution: number): LouvainResult {
31
58
  const result: DetectClustersResult = detectClusters(graph, {
32
59
  resolution,
33
60
  randomSeed: 42,
@@ -1,8 +1,11 @@
1
+ import { loadNative } from '../../infrastructure/native.js';
1
2
  import type { CodeGraph } from '../model.js';
2
3
 
3
4
  /**
4
5
  * BFS-based shortest path on a CodeGraph.
5
6
  *
7
+ * Tries the native Rust implementation first, falls back to JS.
8
+ *
6
9
  * @returns Path from fromId to toId (inclusive), or null if unreachable
7
10
  */
8
11
  export function shortestPath(graph: CodeGraph, fromId: string, toId: string): string[] | null {
@@ -12,6 +15,23 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
12
15
  if (!graph.hasNode(from) || !graph.hasNode(to)) return null;
13
16
  if (from === to) return [from];
14
17
 
18
+ const native = loadNative();
19
+ if (native?.shortestPath) {
20
+ let edges = graph.toEdgeArray();
21
+ if (!graph.directed) {
22
+ // Undirected: toEdgeArray() deduplicates to one canonical direction;
23
+ // mirror each edge so the Rust BFS can traverse in both directions.
24
+ edges = [...edges, ...edges.map((e) => ({ source: e.target, target: e.source }))];
25
+ }
26
+ const result = native.shortestPath(edges, from, to);
27
+ return result.length > 0 ? result : null;
28
+ }
29
+
30
+ return shortestPathJS(graph, from, to);
31
+ }
32
+
33
+ /** Pure JS fallback for shortest path. */
34
+ function shortestPathJS(graph: CodeGraph, from: string, to: string): string[] | null {
15
35
  const parent = new Map<string, string | null>();
16
36
  parent.set(from, null);
17
37
  const queue = [from];
@@ -23,7 +43,6 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
23
43
  if (parent.has(neighbor)) continue;
24
44
  parent.set(neighbor, current);
25
45
  if (neighbor === to) {
26
- // Reconstruct path
27
46
  const path: string[] = [];
28
47
  let node: string | null = to;
29
48
  while (node !== null) {
package/src/types.ts CHANGED
@@ -61,7 +61,7 @@ export type EdgeKind = CoreEdgeKind | StructuralEdgeKind;
61
61
  export type AnyEdgeKind = EdgeKind | DataflowEdgeKind;
62
62
 
63
63
  /** AST node kinds extracted during analysis. */
64
- export type ASTNodeKind = 'call' | 'new' | 'string' | 'regex' | 'throw' | 'await';
64
+ export type ASTNodeKind = 'new' | 'string' | 'regex' | 'throw' | 'await';
65
65
 
66
66
  /** Coarse role classifications for symbols based on connectivity. */
67
67
  export type CoreRole = 'entry' | 'core' | 'utility' | 'adapter' | 'dead' | 'test-only' | 'leaf';
@@ -96,7 +96,19 @@ export type LanguageId =
96
96
  | 'dart'
97
97
  | 'zig'
98
98
  | 'haskell'
99
- | 'ocaml';
99
+ | 'ocaml'
100
+ | 'ocaml-interface'
101
+ | 'fsharp'
102
+ | 'gleam'
103
+ | 'clojure'
104
+ | 'julia'
105
+ | 'r'
106
+ | 'erlang'
107
+ | 'solidity'
108
+ | 'objc'
109
+ | 'cuda'
110
+ | 'groovy'
111
+ | 'verilog';
100
112
 
101
113
  /** Engine mode selector. */
102
114
  export type EngineMode = 'native' | 'wasm' | 'auto';
@@ -383,6 +395,7 @@ export interface SubDeclaration {
383
395
  line: number;
384
396
  endLine?: number;
385
397
  visibility?: 'public' | 'private' | 'protected';
398
+ decorators?: string[];
386
399
  }
387
400
 
388
401
  /** Complexity metrics attached to a definition post-analysis. */
@@ -1828,8 +1841,42 @@ export interface NativeAddon {
1828
1841
  ): Array<{ fromFile: string; importSource: string; resolvedPath: string }>;
1829
1842
  computeConfidence(callerFile: string, targetFile: string, importedFrom: string | null): number;
1830
1843
  detectCycles(edges: Array<{ source: string; target: string }>): string[][];
1844
+ bfsTraversal(
1845
+ edges: Array<{ source: string; target: string }>,
1846
+ startIds: string[],
1847
+ maxDepth?: number | null,
1848
+ direction?: string | null,
1849
+ ): Array<{ node: string; depth: number }>;
1850
+ shortestPath(
1851
+ edges: Array<{ source: string; target: string }>,
1852
+ fromId: string,
1853
+ toId: string,
1854
+ ): string[];
1855
+ fanInOut(
1856
+ edges: Array<{ source: string; target: string }>,
1857
+ ): Array<{ node: string; fanIn: number; fanOut: number }>;
1858
+ louvainCommunities(
1859
+ edges: Array<{ source: string; target: string }>,
1860
+ nodeIds: string[],
1861
+ resolution?: number | null,
1862
+ randomSeed?: number | null,
1863
+ ): {
1864
+ assignments: Array<{ node: string; community: number }>;
1865
+ modularity: number;
1866
+ };
1831
1867
  buildCallEdges(files: unknown[], nodes: unknown[], builtinReceivers: string[]): unknown[];
1868
+ buildImportEdges?(
1869
+ files: unknown[],
1870
+ resolvedImports: unknown[],
1871
+ fileReexports: unknown[],
1872
+ fileNodeIds: unknown[],
1873
+ barrelFiles: string[],
1874
+ rootDir: string,
1875
+ ): unknown[];
1832
1876
  engineVersion(): string;
1877
+ analyzeComplexity(source: string, filePath: string): NativeFunctionComplexityResult[];
1878
+ buildCfgAnalysis(source: string, filePath: string): NativeFunctionCfgResult[];
1879
+ extractDataflowAnalysis(source: string, filePath: string): DataflowResult | null;
1833
1880
  ParseTreeCache: new () => NativeParseTreeCache;
1834
1881
  NativeDatabase: {
1835
1882
  openReadWrite(dbPath: string): NativeDatabase;
@@ -1837,6 +1884,40 @@ export interface NativeAddon {
1837
1884
  };
1838
1885
  }
1839
1886
 
1887
+ /** Per-function complexity result from native standalone analysis (napi output shape). */
1888
+ export interface NativeFunctionComplexityResult {
1889
+ name: string;
1890
+ line: number;
1891
+ endLine: number | null;
1892
+ complexity: {
1893
+ cognitive: number;
1894
+ cyclomatic: number;
1895
+ maxNesting: number;
1896
+ halstead?: {
1897
+ n1: number;
1898
+ n2: number;
1899
+ bigN1: number;
1900
+ bigN2: number;
1901
+ vocabulary: number;
1902
+ length: number;
1903
+ volume: number;
1904
+ difficulty: number;
1905
+ effort: number;
1906
+ bugs: number;
1907
+ };
1908
+ loc?: { loc: number; sloc: number; commentLines: number };
1909
+ maintainabilityIndex?: number | null;
1910
+ };
1911
+ }
1912
+
1913
+ /** Per-function CFG result from native standalone analysis. */
1914
+ export interface NativeFunctionCfgResult {
1915
+ name: string;
1916
+ line: number;
1917
+ endLine: number | null;
1918
+ cfg: { blocks: CfgBlock[]; edges: CfgEdge[] };
1919
+ }
1920
+
1840
1921
  /** Native parse-tree cache instance. */
1841
1922
  export interface NativeParseTreeCache {
1842
1923
  get(filePath: string): unknown;
@@ -2266,6 +2347,32 @@ export interface NativeDatabase {
2266
2347
  fanOut: number;
2267
2348
  }>;
2268
2349
 
2350
+ // ── Batched build-glue queries (6.18) ────────────────────────────────
2351
+ /** All file_hashes rows + table existence + max mtime in one call. */
2352
+ getFileHashData?(): {
2353
+ exists: boolean;
2354
+ rows: Array<{ file: string; hash: string; mtime: number; size: number }>;
2355
+ maxMtime: number;
2356
+ };
2357
+ /** CFG and dataflow table counts (-1 = table missing). */
2358
+ checkPendingAnalysis?(): { cfgCount: number; dataflowCount: number };
2359
+ /** Batch upsert file_hashes for metadata healing. */
2360
+ healFileMetadata?(
2361
+ entries: Array<{ file: string; hash: string; mtime: number; size: number }>,
2362
+ ): number;
2363
+ /** Find files with edges pointing to changed files. */
2364
+ findReverseDependencies?(changedFiles: string[]): string[];
2365
+ /** Node + edge counts in one call. */
2366
+ getFinalizeCounts?(): { nodeCount: number; edgeCount: number };
2367
+ /** Orphaned embeddings, stale embeddings, unused exports in one call. */
2368
+ runAdvisoryChecks?(hasEmbeddings: boolean): {
2369
+ orphanedEmbeddings: number;
2370
+ embedBuiltAt: string | null;
2371
+ unusedExports: number;
2372
+ };
2373
+ /** File_hashes count + all file paths in one call. */
2374
+ getCollectFilesData?(): { count: number; files: string[] };
2375
+
2269
2376
  // ── Generic query execution & version validation (6.16) ─────────────
2270
2377
  /** Execute a parameterized SELECT and return all rows as objects. */
2271
2378
  queryAll(sql: string, params: Array<string | number | null>): Record<string, unknown>[];
@@ -2273,6 +2380,14 @@ export interface NativeDatabase {
2273
2380
  queryGet(sql: string, params: Array<string | number | null>): Record<string, unknown> | null;
2274
2381
  /** Validate DB codegraph_version matches expected. Warns on mismatch. */
2275
2382
  validateSchemaVersion(expectedVersion: string): boolean;
2383
+
2384
+ // ── Full Rust build orchestration (#695) ─────────────────────────────
2385
+ /**
2386
+ * Run the entire build pipeline in Rust with zero napi boundary crossings.
2387
+ * Returns a JSON string with timing and build result data.
2388
+ * When unavailable, the JS pipeline (runPipelineStages) is used as fallback.
2389
+ */
2390
+ buildGraph?(rootDir: string, configJson: string, aliasesJson: string, optsJson: string): string;
2276
2391
  }
2277
2392
 
2278
2393
  // ════════════════════════════════════════════════════════════════════════