ai-mind-map 1.1.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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +554 -0
  3. package/dist/change-tracker/change-log.d.ts +160 -0
  4. package/dist/change-tracker/change-log.d.ts.map +1 -0
  5. package/dist/change-tracker/change-log.js +507 -0
  6. package/dist/change-tracker/change-log.js.map +1 -0
  7. package/dist/change-tracker/diff-engine.d.ts +149 -0
  8. package/dist/change-tracker/diff-engine.d.ts.map +1 -0
  9. package/dist/change-tracker/diff-engine.js +530 -0
  10. package/dist/change-tracker/diff-engine.js.map +1 -0
  11. package/dist/change-tracker/watcher.d.ts +137 -0
  12. package/dist/change-tracker/watcher.d.ts.map +1 -0
  13. package/dist/change-tracker/watcher.js +300 -0
  14. package/dist/change-tracker/watcher.js.map +1 -0
  15. package/dist/cli.d.ts +20 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +937 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/config.d.ts +38 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +222 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/context/compressor.d.ts +49 -0
  24. package/dist/context/compressor.d.ts.map +1 -0
  25. package/dist/context/compressor.js +769 -0
  26. package/dist/context/compressor.js.map +1 -0
  27. package/dist/context/progressive-disclosure.d.ts +71 -0
  28. package/dist/context/progressive-disclosure.d.ts.map +1 -0
  29. package/dist/context/progressive-disclosure.js +470 -0
  30. package/dist/context/progressive-disclosure.js.map +1 -0
  31. package/dist/context/token-budget.d.ts +121 -0
  32. package/dist/context/token-budget.d.ts.map +1 -0
  33. package/dist/context/token-budget.js +282 -0
  34. package/dist/context/token-budget.js.map +1 -0
  35. package/dist/index.d.ts +13 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +944 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/install.d.ts +66 -0
  40. package/dist/install.d.ts.map +1 -0
  41. package/dist/install.js +946 -0
  42. package/dist/install.js.map +1 -0
  43. package/dist/knowledge-graph/architecture.d.ts +213 -0
  44. package/dist/knowledge-graph/architecture.d.ts.map +1 -0
  45. package/dist/knowledge-graph/architecture.js +585 -0
  46. package/dist/knowledge-graph/architecture.js.map +1 -0
  47. package/dist/knowledge-graph/cypher.d.ts +113 -0
  48. package/dist/knowledge-graph/cypher.d.ts.map +1 -0
  49. package/dist/knowledge-graph/cypher.js +1051 -0
  50. package/dist/knowledge-graph/cypher.js.map +1 -0
  51. package/dist/knowledge-graph/dead-code.d.ts +121 -0
  52. package/dist/knowledge-graph/dead-code.d.ts.map +1 -0
  53. package/dist/knowledge-graph/dead-code.js +331 -0
  54. package/dist/knowledge-graph/dead-code.js.map +1 -0
  55. package/dist/knowledge-graph/flow-analyzer.d.ts +167 -0
  56. package/dist/knowledge-graph/flow-analyzer.d.ts.map +1 -0
  57. package/dist/knowledge-graph/flow-analyzer.js +739 -0
  58. package/dist/knowledge-graph/flow-analyzer.js.map +1 -0
  59. package/dist/knowledge-graph/graph.d.ts +291 -0
  60. package/dist/knowledge-graph/graph.d.ts.map +1 -0
  61. package/dist/knowledge-graph/graph.js +978 -0
  62. package/dist/knowledge-graph/graph.js.map +1 -0
  63. package/dist/knowledge-graph/index.d.ts +17 -0
  64. package/dist/knowledge-graph/index.d.ts.map +1 -0
  65. package/dist/knowledge-graph/index.js +14 -0
  66. package/dist/knowledge-graph/index.js.map +1 -0
  67. package/dist/knowledge-graph/indexer.d.ts +112 -0
  68. package/dist/knowledge-graph/indexer.d.ts.map +1 -0
  69. package/dist/knowledge-graph/indexer.js +506 -0
  70. package/dist/knowledge-graph/indexer.js.map +1 -0
  71. package/dist/knowledge-graph/pagerank.d.ts +141 -0
  72. package/dist/knowledge-graph/pagerank.d.ts.map +1 -0
  73. package/dist/knowledge-graph/pagerank.js +493 -0
  74. package/dist/knowledge-graph/pagerank.js.map +1 -0
  75. package/dist/knowledge-graph/parser.d.ts +55 -0
  76. package/dist/knowledge-graph/parser.d.ts.map +1 -0
  77. package/dist/knowledge-graph/parser.js +1090 -0
  78. package/dist/knowledge-graph/parser.js.map +1 -0
  79. package/dist/knowledge-graph/snapshot.d.ts +107 -0
  80. package/dist/knowledge-graph/snapshot.d.ts.map +1 -0
  81. package/dist/knowledge-graph/snapshot.js +435 -0
  82. package/dist/knowledge-graph/snapshot.js.map +1 -0
  83. package/dist/memory/decision-log.d.ts +151 -0
  84. package/dist/memory/decision-log.d.ts.map +1 -0
  85. package/dist/memory/decision-log.js +482 -0
  86. package/dist/memory/decision-log.js.map +1 -0
  87. package/dist/memory/persistent-memory.d.ts +182 -0
  88. package/dist/memory/persistent-memory.d.ts.map +1 -0
  89. package/dist/memory/persistent-memory.js +579 -0
  90. package/dist/memory/persistent-memory.js.map +1 -0
  91. package/dist/memory/session-memory.d.ts +165 -0
  92. package/dist/memory/session-memory.d.ts.map +1 -0
  93. package/dist/memory/session-memory.js +382 -0
  94. package/dist/memory/session-memory.js.map +1 -0
  95. package/dist/stress-test.d.ts +10 -0
  96. package/dist/stress-test.d.ts.map +1 -0
  97. package/dist/stress-test.js +258 -0
  98. package/dist/stress-test.js.map +1 -0
  99. package/dist/tools/advanced-tools.d.ts +32 -0
  100. package/dist/tools/advanced-tools.d.ts.map +1 -0
  101. package/dist/tools/advanced-tools.js +480 -0
  102. package/dist/tools/advanced-tools.js.map +1 -0
  103. package/dist/tools/change-tools.d.ts +76 -0
  104. package/dist/tools/change-tools.d.ts.map +1 -0
  105. package/dist/tools/change-tools.js +93 -0
  106. package/dist/tools/change-tools.js.map +1 -0
  107. package/dist/tools/context-tools.d.ts +68 -0
  108. package/dist/tools/context-tools.d.ts.map +1 -0
  109. package/dist/tools/context-tools.js +141 -0
  110. package/dist/tools/context-tools.js.map +1 -0
  111. package/dist/tools/debug-tools.d.ts +25 -0
  112. package/dist/tools/debug-tools.d.ts.map +1 -0
  113. package/dist/tools/debug-tools.js +286 -0
  114. package/dist/tools/debug-tools.js.map +1 -0
  115. package/dist/tools/evolving-tools.d.ts +23 -0
  116. package/dist/tools/evolving-tools.d.ts.map +1 -0
  117. package/dist/tools/evolving-tools.js +207 -0
  118. package/dist/tools/evolving-tools.js.map +1 -0
  119. package/dist/tools/flow-tools.d.ts +24 -0
  120. package/dist/tools/flow-tools.d.ts.map +1 -0
  121. package/dist/tools/flow-tools.js +265 -0
  122. package/dist/tools/flow-tools.js.map +1 -0
  123. package/dist/tools/graph-tools.d.ts +71 -0
  124. package/dist/tools/graph-tools.d.ts.map +1 -0
  125. package/dist/tools/graph-tools.js +165 -0
  126. package/dist/tools/graph-tools.js.map +1 -0
  127. package/dist/tools/memory-tools.d.ts +62 -0
  128. package/dist/tools/memory-tools.d.ts.map +1 -0
  129. package/dist/tools/memory-tools.js +195 -0
  130. package/dist/tools/memory-tools.js.map +1 -0
  131. package/dist/tools/smart-tools.d.ts +23 -0
  132. package/dist/tools/smart-tools.d.ts.map +1 -0
  133. package/dist/tools/smart-tools.js +482 -0
  134. package/dist/tools/smart-tools.js.map +1 -0
  135. package/dist/tools/snapshot-tools.d.ts +19 -0
  136. package/dist/tools/snapshot-tools.d.ts.map +1 -0
  137. package/dist/tools/snapshot-tools.js +149 -0
  138. package/dist/tools/snapshot-tools.js.map +1 -0
  139. package/dist/types.d.ts +181 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +45 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/utils/logger.d.ts +59 -0
  144. package/dist/utils/logger.d.ts.map +1 -0
  145. package/dist/utils/logger.js +142 -0
  146. package/dist/utils/logger.js.map +1 -0
  147. package/dist/utils/token-counter.d.ts +51 -0
  148. package/dist/utils/token-counter.d.ts.map +1 -0
  149. package/dist/utils/token-counter.js +181 -0
  150. package/dist/utils/token-counter.js.map +1 -0
  151. package/install.ps1 +321 -0
  152. package/install.sh +345 -0
  153. package/package.json +94 -0
  154. package/setup.bat +62 -0
@@ -0,0 +1,1090 @@
1
+ /**
2
+ * AI Mind Map — Tree-sitter AST Parser with Regex Fallback
3
+ *
4
+ * Extracts structural information (functions, classes, methods, interfaces,
5
+ * types, enums, constants, exports, imports) from source code files.
6
+ *
7
+ * Inspired by codebase-memory-mcp (158 languages) and Aider's repo map.
8
+ * Uses tree-sitter grammars for accurate parsing with a regex-based fallback
9
+ * when native bindings are unavailable or parsing fails.
10
+ */
11
+ import { createHash } from 'node:crypto';
12
+ import { readFile } from 'node:fs/promises';
13
+ import { extname, basename } from 'node:path';
14
+ // ============================================================
15
+ // Language Registry
16
+ // ============================================================
17
+ /** Maps file extensions to language identifiers */
18
+ const EXTENSION_MAP = {
19
+ '.js': 'javascript',
20
+ '.jsx': 'javascript',
21
+ '.mjs': 'javascript',
22
+ '.cjs': 'javascript',
23
+ '.ts': 'typescript',
24
+ '.tsx': 'typescript',
25
+ '.mts': 'typescript',
26
+ '.cts': 'typescript',
27
+ '.py': 'python',
28
+ '.pyw': 'python',
29
+ '.java': 'java',
30
+ '.go': 'go',
31
+ '.rs': 'rust',
32
+ '.c': 'c',
33
+ '.h': 'c',
34
+ '.cpp': 'cpp',
35
+ '.cxx': 'cpp',
36
+ '.cc': 'cpp',
37
+ '.hpp': 'cpp',
38
+ '.hxx': 'cpp',
39
+ '.cs': 'csharp',
40
+ '.rb': 'ruby',
41
+ '.php': 'php',
42
+ '.sh': 'bash',
43
+ '.bash': 'bash',
44
+ '.zsh': 'bash',
45
+ '.kt': 'kotlin',
46
+ '.kts': 'kotlin',
47
+ '.swift': 'swift',
48
+ '.dart': 'dart',
49
+ '.scala': 'scala',
50
+ '.sc': 'scala',
51
+ '.yaml': 'yaml',
52
+ '.yml': 'yaml',
53
+ '.toml': 'toml',
54
+ '.xml': 'xml',
55
+ '.xaml': 'xaml',
56
+ '.sql': 'sql',
57
+ '.proto': 'protobuf',
58
+ '.graphql': 'graphql',
59
+ '.gql': 'graphql',
60
+ };
61
+ /** Maps language identifiers to tree-sitter grammar package names */
62
+ const GRAMMAR_MAP = {
63
+ javascript: 'tree-sitter-javascript',
64
+ typescript: 'tree-sitter-typescript',
65
+ python: 'tree-sitter-python',
66
+ java: 'tree-sitter-java',
67
+ go: 'tree-sitter-go',
68
+ rust: 'tree-sitter-rust',
69
+ c: 'tree-sitter-c',
70
+ cpp: 'tree-sitter-cpp',
71
+ csharp: 'tree-sitter-c-sharp',
72
+ ruby: 'tree-sitter-ruby',
73
+ php: 'tree-sitter-php',
74
+ bash: 'tree-sitter-bash',
75
+ };
76
+ // ============================================================
77
+ // Helpers
78
+ // ============================================================
79
+ /** Generate a unique deterministic ID for a node */
80
+ export function generateNodeId(filePath, name, type) {
81
+ const hash = createHash('sha256')
82
+ .update(`${filePath}::${name}::${type}`)
83
+ .digest('hex');
84
+ return hash.substring(0, 16);
85
+ }
86
+ /** Generate a content hash for change detection */
87
+ export function generateContentHash(content) {
88
+ return createHash('sha256').update(content).digest('hex').substring(0, 16);
89
+ }
90
+ /** Detect language from file extension */
91
+ export function detectLanguage(filePath) {
92
+ const ext = extname(filePath).toLowerCase();
93
+ return EXTENSION_MAP[ext] ?? null;
94
+ }
95
+ /** Get all supported file extensions */
96
+ export function getSupportedExtensions() {
97
+ return Object.keys(EXTENSION_MAP);
98
+ }
99
+ /** Estimate token count for a string (rough: ~4 chars per token) */
100
+ function estimateTokens(text) {
101
+ return Math.ceil(text.length / 4);
102
+ }
103
+ /** Strip leading comment markers from doc comment lines */
104
+ function cleanDocComment(raw) {
105
+ return raw
106
+ .replace(/^\/\*\*?/m, '')
107
+ .replace(/\*\/$/m, '')
108
+ .replace(/^[ \t]*\*[ \t]?/gm, '')
109
+ .replace(/^[ \t]*\/\/\/? ?/gm, '')
110
+ .replace(/^[ \t]*#[ \t]?/gm, '')
111
+ .trim();
112
+ }
113
+ // ============================================================
114
+ // Tree-sitter Parser (primary)
115
+ // ============================================================
116
+ /** Cached tree-sitter Parser instance and loaded grammars */
117
+ let treeSitterParser = null;
118
+ const loadedGrammars = new Map();
119
+ let treeSitterAvailable = null;
120
+ /** Attempt to load the tree-sitter module and a language grammar */
121
+ async function getTreeSitterParser(language) {
122
+ // If we already determined tree-sitter is unavailable, skip
123
+ if (treeSitterAvailable === false) {
124
+ return null;
125
+ }
126
+ try {
127
+ if (!treeSitterParser) {
128
+ const TreeSitter = (await import('tree-sitter')).default;
129
+ treeSitterParser = new TreeSitter();
130
+ treeSitterAvailable = true;
131
+ }
132
+ if (!loadedGrammars.has(language)) {
133
+ const grammarPkg = GRAMMAR_MAP[language];
134
+ if (!grammarPkg)
135
+ return null;
136
+ let grammarModule = await import(grammarPkg);
137
+ let grammar = grammarModule.default ?? grammarModule;
138
+ // TypeScript grammar package exports { typescript, tsx }
139
+ if (language === 'typescript' && grammar.typescript) {
140
+ grammar = grammar.typescript;
141
+ }
142
+ loadedGrammars.set(language, grammar);
143
+ }
144
+ return { parser: treeSitterParser, grammar: loadedGrammars.get(language) };
145
+ }
146
+ catch {
147
+ treeSitterAvailable = false;
148
+ return null;
149
+ }
150
+ }
151
+ /** Extract nodes from a tree-sitter AST */
152
+ function extractFromTreeSitter(tree, source, filePath, language) {
153
+ const nodes = [];
154
+ const edges = [];
155
+ const sourceLines = source.split('\n');
156
+ const now = Date.now();
157
+ /** Get text for a node */
158
+ function nodeText(node) {
159
+ return node?.text ?? '';
160
+ }
161
+ /** Find the doc comment preceding a node */
162
+ function findDocComment(node) {
163
+ let prev = node.previousNamedSibling;
164
+ if (!prev) {
165
+ // Check parent's previous sibling for doc comments on methods inside classes
166
+ const parent = node.parent;
167
+ if (parent) {
168
+ // Scan unnamed children before our node
169
+ for (let i = 0; i < parent.namedChildCount; i++) {
170
+ const child = parent.namedChild(i);
171
+ if (child?.id === node.id)
172
+ break;
173
+ prev = child;
174
+ }
175
+ }
176
+ }
177
+ if (prev && prev.type === 'comment') {
178
+ const text = nodeText(prev);
179
+ if (text.startsWith('/**') || text.startsWith('///') || text.startsWith('# ')) {
180
+ return cleanDocComment(text);
181
+ }
182
+ }
183
+ // Python: look for expression_statement containing a string immediately after function/class def
184
+ if (language === 'python' && node.namedChildCount > 0) {
185
+ const body = node.childForFieldName('body');
186
+ if (body && body.namedChildCount > 0) {
187
+ const first = body.namedChild(0);
188
+ if (first?.type === 'expression_statement') {
189
+ const strNode = first.namedChild(0);
190
+ if (strNode && (strNode.type === 'string' || strNode.type === 'concatenated_string')) {
191
+ return cleanDocComment(nodeText(strNode).replace(/^['"`]{1,3}|['"`]{1,3}$/g, ''));
192
+ }
193
+ }
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+ /** Determine visibility from modifiers or naming conventions */
199
+ function getVisibility(node) {
200
+ // Check for explicit modifiers
201
+ const modifiers = collectModifiers(node);
202
+ if (modifiers.includes('public'))
203
+ return 'public';
204
+ if (modifiers.includes('private'))
205
+ return 'private';
206
+ if (modifiers.includes('protected'))
207
+ return 'protected';
208
+ if (modifiers.includes('internal'))
209
+ return 'internal';
210
+ // Python naming convention
211
+ if (language === 'python') {
212
+ const name = node.childForFieldName('name');
213
+ if (name) {
214
+ const nameText = nodeText(name);
215
+ if (nameText.startsWith('__') && !nameText.endsWith('__'))
216
+ return 'private';
217
+ if (nameText.startsWith('_'))
218
+ return 'protected';
219
+ }
220
+ }
221
+ return 'unknown';
222
+ }
223
+ /** Collect modifier keywords from a node */
224
+ function collectModifiers(node) {
225
+ const mods = [];
226
+ for (let i = 0; i < node.childCount; i++) {
227
+ const child = node.child(i);
228
+ if (!child)
229
+ continue;
230
+ const t = child.type;
231
+ if (t === 'public' || t === 'private' || t === 'protected' || t === 'internal' ||
232
+ t === 'static' || t === 'async' || t === 'abstract' || t === 'readonly' ||
233
+ t === 'export' || t === 'default' ||
234
+ t === 'accessibility_modifier' || t === 'modifiers') {
235
+ if (t === 'accessibility_modifier' || t === 'modifiers') {
236
+ mods.push(nodeText(child).trim());
237
+ }
238
+ else {
239
+ mods.push(t);
240
+ }
241
+ }
242
+ }
243
+ // Also check for export statement wrapping
244
+ if (node.parent?.type === 'export_statement' || node.parent?.type === 'export_declaration') {
245
+ mods.push('export');
246
+ }
247
+ return mods;
248
+ }
249
+ /** Check if a node is exported */
250
+ function isExported(node) {
251
+ const mods = collectModifiers(node);
252
+ if (mods.includes('export'))
253
+ return true;
254
+ if (node.parent?.type === 'export_statement' || node.parent?.type === 'export_declaration')
255
+ return true;
256
+ // Go: exported if name starts with uppercase
257
+ if (language === 'go') {
258
+ const name = node.childForFieldName('name');
259
+ if (name) {
260
+ const n = nodeText(name);
261
+ return n.length > 0 && n[0] === n[0].toUpperCase() && n[0] !== n[0].toLowerCase();
262
+ }
263
+ }
264
+ return false;
265
+ }
266
+ /** Extract function parameters */
267
+ function extractParameters(paramsNode) {
268
+ if (!paramsNode)
269
+ return [];
270
+ const params = [];
271
+ for (let i = 0; i < paramsNode.namedChildCount; i++) {
272
+ const param = paramsNode.namedChild(i);
273
+ if (!param)
274
+ continue;
275
+ const pType = param.type;
276
+ // Skip commas, parentheses
277
+ if (pType === ',' || pType === '(' || pType === ')')
278
+ continue;
279
+ let name = '';
280
+ let type = null;
281
+ let defaultValue = null;
282
+ let isOptional = false;
283
+ let isRest = false;
284
+ // Try field-based extraction
285
+ const nameNode = param.childForFieldName('name') ?? param.childForFieldName('pattern');
286
+ if (nameNode) {
287
+ name = nodeText(nameNode);
288
+ }
289
+ else if (param.type === 'identifier') {
290
+ name = nodeText(param);
291
+ }
292
+ else if (param.type === 'rest_pattern' || param.type === 'spread_element' || param.type === 'rest_parameter') {
293
+ isRest = true;
294
+ const inner = param.namedChild(0);
295
+ name = inner ? nodeText(inner) : nodeText(param).replace(/^\.\.\./, '');
296
+ }
297
+ else {
298
+ name = nodeText(param).split(':')[0]?.split('=')[0]?.replace(/^\.\.\./, '').trim() ?? '';
299
+ }
300
+ // Type annotation
301
+ const typeNode = param.childForFieldName('type');
302
+ if (typeNode) {
303
+ type = nodeText(typeNode);
304
+ }
305
+ // Default value
306
+ const valueNode = param.childForFieldName('value') ?? param.childForFieldName('default_value');
307
+ if (valueNode) {
308
+ defaultValue = nodeText(valueNode);
309
+ isOptional = true;
310
+ }
311
+ // Optional marker (TypeScript '?')
312
+ for (let j = 0; j < param.childCount; j++) {
313
+ const ch = param.child(j);
314
+ if (ch && nodeText(ch) === '?') {
315
+ isOptional = true;
316
+ break;
317
+ }
318
+ }
319
+ // Prefix '...' detection
320
+ if (name.startsWith('...')) {
321
+ isRest = true;
322
+ name = name.substring(3);
323
+ }
324
+ if (pType === 'rest_parameter' || pType === 'rest_pattern') {
325
+ isRest = true;
326
+ }
327
+ if (name) {
328
+ params.push({ name, type, defaultValue, isOptional, isRest });
329
+ }
330
+ }
331
+ return params;
332
+ }
333
+ /** Build a compact signature string from a node */
334
+ function buildSignature(node, name, nodeType) {
335
+ const mods = collectModifiers(node);
336
+ const parts = [];
337
+ // Prefix modifiers
338
+ const filteredMods = mods.filter(m => ['export', 'async', 'static', 'abstract', 'public', 'private', 'protected'].includes(m));
339
+ if (filteredMods.length > 0) {
340
+ parts.push(filteredMods.join(' '));
341
+ }
342
+ // Keyword
343
+ switch (nodeType) {
344
+ case 'function':
345
+ parts.push('function');
346
+ break;
347
+ case 'class':
348
+ parts.push('class');
349
+ break;
350
+ case 'method':
351
+ parts.push('');
352
+ break; // no keyword for methods
353
+ case 'interface':
354
+ parts.push('interface');
355
+ break;
356
+ case 'type_alias':
357
+ parts.push('type');
358
+ break;
359
+ case 'enum':
360
+ parts.push('enum');
361
+ break;
362
+ case 'constant':
363
+ parts.push('const');
364
+ break;
365
+ case 'variable':
366
+ parts.push('let');
367
+ break;
368
+ }
369
+ parts.push(name);
370
+ // Type parameters (generics)
371
+ const typeParams = node.childForFieldName('type_parameters');
372
+ if (typeParams) {
373
+ parts[parts.length - 1] += nodeText(typeParams);
374
+ }
375
+ // Parameters
376
+ const paramsNode = node.childForFieldName('parameters');
377
+ if (paramsNode) {
378
+ parts[parts.length - 1] += nodeText(paramsNode);
379
+ }
380
+ // Return type
381
+ const returnType = node.childForFieldName('return_type') ?? node.childForFieldName('result');
382
+ if (returnType) {
383
+ parts.push(':');
384
+ parts.push(nodeText(returnType));
385
+ }
386
+ // Superclass / implements
387
+ const superclass = node.childForFieldName('superclass');
388
+ if (superclass) {
389
+ parts.push('extends');
390
+ parts.push(nodeText(superclass));
391
+ }
392
+ return parts.filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
393
+ }
394
+ /** Create a GraphNode from a tree-sitter node */
395
+ function makeNode(tsNode, name, nType, parentId) {
396
+ const mods = collectModifiers(tsNode);
397
+ const signature = buildSignature(tsNode, name, nType);
398
+ const paramsNode = tsNode.childForFieldName('parameters');
399
+ const returnTypeNode = tsNode.childForFieldName('return_type') ?? tsNode.childForFieldName('result');
400
+ const startLine = tsNode.startPosition.row + 1;
401
+ const endLine = tsNode.endPosition.row + 1;
402
+ const sliceText = sourceLines.slice(startLine - 1, endLine).join('\n');
403
+ const gNode = {
404
+ id: generateNodeId(filePath, parentId ? `${parentId}::${name}` : name, nType),
405
+ type: nType,
406
+ name,
407
+ qualifiedName: parentId ? `${parentId}.${name}` : name,
408
+ filePath,
409
+ startLine,
410
+ endLine,
411
+ signature,
412
+ docComment: findDocComment(tsNode),
413
+ hash: generateContentHash(sliceText),
414
+ language,
415
+ visibility: getVisibility(tsNode),
416
+ isAsync: mods.includes('async'),
417
+ isStatic: mods.includes('static'),
418
+ isExported: isExported(tsNode),
419
+ parameters: paramsNode ? extractParameters(paramsNode) : undefined,
420
+ returnType: returnTypeNode ? nodeText(returnTypeNode).replace(/^:\s*/, '') : undefined,
421
+ updatedAt: now,
422
+ };
423
+ return gNode;
424
+ }
425
+ /** Recursively walk the AST and extract nodes */
426
+ function walk(tsNode, parentClassName) {
427
+ if (!tsNode)
428
+ return;
429
+ const t = tsNode.type;
430
+ const nameNode = tsNode.childForFieldName('name');
431
+ const nameStr = nameNode ? nodeText(nameNode) : '';
432
+ // --- Functions ---
433
+ if (t === 'function_declaration' || t === 'function_definition' ||
434
+ t === 'arrow_function' || t === 'function_item' /* Rust */) {
435
+ const funcName = nameStr || (tsNode.parent?.type === 'variable_declarator'
436
+ ? nodeText(tsNode.parent.childForFieldName('name'))
437
+ : `anonymous_${tsNode.startPosition.row}`);
438
+ const gNode = makeNode(tsNode, funcName, 'function');
439
+ nodes.push(gNode);
440
+ }
441
+ // --- Classes ---
442
+ else if (t === 'class_declaration' || t === 'class_definition' ||
443
+ t === 'class' || t === 'struct_item' /* Rust */ ||
444
+ t === 'struct_specifier' || t === 'class_specifier') {
445
+ const className = nameStr || `AnonymousClass_${tsNode.startPosition.row}`;
446
+ const gNode = makeNode(tsNode, className, 'class');
447
+ nodes.push(gNode);
448
+ // Process class body for methods/properties
449
+ const body = tsNode.childForFieldName('body');
450
+ if (body) {
451
+ for (let i = 0; i < body.namedChildCount; i++) {
452
+ const member = body.namedChild(i);
453
+ if (!member)
454
+ continue;
455
+ walkClassMember(member, className, gNode.id);
456
+ }
457
+ }
458
+ return; // Don't recurse children again
459
+ }
460
+ // --- Interfaces (TypeScript, Java, Go, C#) ---
461
+ else if (t === 'interface_declaration' || t === 'interface_definition') {
462
+ const gNode = makeNode(tsNode, nameStr, 'interface');
463
+ nodes.push(gNode);
464
+ // Process interface body
465
+ const body = tsNode.childForFieldName('body');
466
+ if (body) {
467
+ for (let i = 0; i < body.namedChildCount; i++) {
468
+ const member = body.namedChild(i);
469
+ if (!member)
470
+ continue;
471
+ walkInterfaceMember(member, nameStr, gNode.id);
472
+ }
473
+ }
474
+ return;
475
+ }
476
+ // --- Type aliases ---
477
+ else if (t === 'type_alias_declaration' || t === 'type_alias') {
478
+ const gNode = makeNode(tsNode, nameStr, 'type_alias');
479
+ nodes.push(gNode);
480
+ }
481
+ // --- Enums ---
482
+ else if (t === 'enum_declaration' || t === 'enum_definition' || t === 'enum_item') {
483
+ const gNode = makeNode(tsNode, nameStr, 'enum');
484
+ nodes.push(gNode);
485
+ }
486
+ // --- Variable declarations (constants) ---
487
+ else if (t === 'lexical_declaration' || t === 'variable_declaration') {
488
+ for (let i = 0; i < tsNode.namedChildCount; i++) {
489
+ const declarator = tsNode.namedChild(i);
490
+ if (!declarator || declarator.type !== 'variable_declarator')
491
+ continue;
492
+ const declName = declarator.childForFieldName('name');
493
+ if (!declName)
494
+ continue;
495
+ const varName = nodeText(declName);
496
+ // Check if it's const
497
+ const isConst = nodeText(tsNode).trimStart().startsWith('const');
498
+ const nodeType = isConst ? 'constant' : 'variable';
499
+ // Check if the value is a function (arrow function or function expression)
500
+ const valueNode = declarator.childForFieldName('value');
501
+ if (valueNode && (valueNode.type === 'arrow_function' ||
502
+ valueNode.type === 'function' ||
503
+ valueNode.type === 'function_expression')) {
504
+ const gNode = makeNode(valueNode, varName, 'function');
505
+ // Override signature to include const keyword
506
+ gNode.signature = `${isExported(tsNode) ? 'export ' : ''}const ${varName}${nodeText(valueNode).split('=>')[0]?.includes('(') ? nodeText(valueNode).split('=>')[0]?.trim().replace(/^[^(]*/, '') : '()'}`;
507
+ const paramsNode = valueNode.childForFieldName('parameters');
508
+ if (paramsNode) {
509
+ gNode.parameters = extractParameters(paramsNode);
510
+ }
511
+ gNode.isExported = isExported(tsNode);
512
+ nodes.push(gNode);
513
+ }
514
+ else {
515
+ const gNode = makeNode(tsNode, varName, nodeType);
516
+ gNode.isExported = isExported(tsNode);
517
+ // Build a cleaner signature for variables
518
+ const typeAnn = declarator.childForFieldName('type');
519
+ gNode.signature = `${isExported(tsNode) ? 'export ' : ''}${isConst ? 'const' : 'let'} ${varName}${typeAnn ? ': ' + nodeText(typeAnn) : ''}`;
520
+ if (typeAnn)
521
+ gNode.returnType = nodeText(typeAnn);
522
+ nodes.push(gNode);
523
+ }
524
+ }
525
+ }
526
+ // --- Import statements ---
527
+ else if (t === 'import_statement' || t === 'import_declaration') {
528
+ const sourceNode = tsNode.childForFieldName('source');
529
+ if (sourceNode) {
530
+ const importSource = nodeText(sourceNode).replace(/['"`]/g, '');
531
+ // Create edge from file to imported module
532
+ const fileNodeId = generateNodeId(filePath, basename(filePath), 'file');
533
+ edges.push({
534
+ sourceId: fileNodeId,
535
+ targetId: importSource, // Will be resolved later
536
+ type: 'imports',
537
+ metadata: { raw: nodeText(tsNode).trim() },
538
+ });
539
+ }
540
+ }
541
+ // --- Export statements ---
542
+ else if (t === 'export_statement' || t === 'export_declaration') {
543
+ // Process the declaration inside the export
544
+ for (let i = 0; i < tsNode.namedChildCount; i++) {
545
+ const child = tsNode.namedChild(i);
546
+ if (child)
547
+ walk(child, parentClassName);
548
+ }
549
+ return;
550
+ }
551
+ // --- Python-specific: decorated definitions ---
552
+ else if (t === 'decorated_definition') {
553
+ // Process the definition inside
554
+ const definition = tsNode.childForFieldName('definition');
555
+ if (definition)
556
+ walk(definition, parentClassName);
557
+ return;
558
+ }
559
+ // --- Go specific: function_declaration, method_declaration ---
560
+ else if (t === 'method_declaration') {
561
+ const methodName = nameStr;
562
+ const receiver = tsNode.childForFieldName('receiver');
563
+ const receiverType = receiver ? nodeText(receiver).replace(/[()]/g, '').split(/\s+/).pop() ?? '' : '';
564
+ if (methodName) {
565
+ const gNode = makeNode(tsNode, methodName, 'method', receiverType || undefined);
566
+ nodes.push(gNode);
567
+ if (receiverType) {
568
+ const parentId = generateNodeId(filePath, receiverType, 'class');
569
+ edges.push({ sourceId: parentId, targetId: gNode.id, type: 'contains' });
570
+ }
571
+ }
572
+ }
573
+ // --- Rust: impl blocks ---
574
+ else if (t === 'impl_item') {
575
+ const implType = tsNode.childForFieldName('type');
576
+ const implName = implType ? nodeText(implType) : '';
577
+ const body = tsNode.childForFieldName('body');
578
+ if (body && implName) {
579
+ for (let i = 0; i < body.namedChildCount; i++) {
580
+ const member = body.namedChild(i);
581
+ if (!member)
582
+ continue;
583
+ if (member.type === 'function_item') {
584
+ const mn = member.childForFieldName('name');
585
+ if (mn) {
586
+ const gNode = makeNode(member, nodeText(mn), 'method', implName);
587
+ nodes.push(gNode);
588
+ const parentId = generateNodeId(filePath, implName, 'class');
589
+ edges.push({ sourceId: parentId, targetId: gNode.id, type: 'contains' });
590
+ }
591
+ }
592
+ }
593
+ }
594
+ return;
595
+ }
596
+ // Recurse into children
597
+ for (let i = 0; i < tsNode.namedChildCount; i++) {
598
+ walk(tsNode.namedChild(i), parentClassName);
599
+ }
600
+ }
601
+ /** Walk class members */
602
+ function walkClassMember(member, className, classId) {
603
+ const mType = member.type;
604
+ const memberNameNode = member.childForFieldName('name');
605
+ const memberName = memberNameNode ? nodeText(memberNameNode) : '';
606
+ if (mType === 'method_definition' || mType === 'method_declaration' ||
607
+ mType === 'function_definition' || mType === 'function_declaration' ||
608
+ mType === 'function_item') {
609
+ if (memberName) {
610
+ const nType = memberName === 'constructor' || memberName === '__init__' ? 'constructor' : 'method';
611
+ const gNode = makeNode(member, memberName, nType, className);
612
+ nodes.push(gNode);
613
+ edges.push({ sourceId: classId, targetId: gNode.id, type: 'contains' });
614
+ }
615
+ }
616
+ else if (mType === 'public_field_definition' || mType === 'field_definition' ||
617
+ mType === 'property_declaration' || mType === 'field_declaration') {
618
+ if (memberName) {
619
+ const gNode = makeNode(member, memberName, 'property', className);
620
+ nodes.push(gNode);
621
+ edges.push({ sourceId: classId, targetId: gNode.id, type: 'contains' });
622
+ }
623
+ }
624
+ else {
625
+ // Recurse in case of decorated methods, etc.
626
+ if (mType === 'decorated_definition') {
627
+ const def = member.childForFieldName('definition');
628
+ if (def)
629
+ walkClassMember(def, className, classId);
630
+ }
631
+ }
632
+ }
633
+ /** Walk interface members */
634
+ function walkInterfaceMember(member, ifaceName, ifaceId) {
635
+ const memberNameNode = member.childForFieldName('name');
636
+ const memberName = memberNameNode ? nodeText(memberNameNode) : '';
637
+ if (memberName) {
638
+ // Interface methods are treated as method signatures
639
+ const nType = member.childForFieldName('parameters') ? 'method' : 'property';
640
+ const gNode = makeNode(member, memberName, nType, ifaceName);
641
+ nodes.push(gNode);
642
+ edges.push({ sourceId: ifaceId, targetId: gNode.id, type: 'contains' });
643
+ }
644
+ }
645
+ // Create the file node itself
646
+ const fileNode = {
647
+ id: generateNodeId(filePath, basename(filePath), 'file'),
648
+ type: 'file',
649
+ name: basename(filePath),
650
+ qualifiedName: filePath,
651
+ filePath,
652
+ startLine: 1,
653
+ endLine: sourceLines.length,
654
+ signature: filePath,
655
+ docComment: null,
656
+ hash: generateContentHash(source),
657
+ language,
658
+ visibility: 'public',
659
+ isAsync: false,
660
+ isStatic: false,
661
+ isExported: false,
662
+ updatedAt: now,
663
+ };
664
+ nodes.push(fileNode);
665
+ // Walk the AST
666
+ walk(tree.rootNode);
667
+ // Create 'contains' edges from file to all top-level symbols
668
+ for (const node of nodes) {
669
+ if (node.type !== 'file' && node.qualifiedName === node.name) {
670
+ edges.push({ sourceId: fileNode.id, targetId: node.id, type: 'contains' });
671
+ }
672
+ }
673
+ return { nodes, edges };
674
+ }
675
+ /** Language-specific regex patterns for fallback parsing */
676
+ const REGEX_PATTERNS = {
677
+ javascript: [
678
+ { pattern: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
679
+ { pattern: /^(?:export\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
680
+ { pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
681
+ { pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*function/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
682
+ { pattern: /^\s+(?:async\s+)?(\w+)\s*(\([^)]*\))\s*\{/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
683
+ ],
684
+ typescript: [
685
+ { pattern: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)(?:<[^>]*>)?\s*(\([^)]*\))(?:\s*:\s*[\w<>\[\]|&\s]+)?/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
686
+ { pattern: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?(?:\s+implements\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
687
+ { pattern: /^(?:export\s+)?interface\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
688
+ { pattern: /^(?:export\s+)?type\s+(\w+)(?:<[^>]*>)?\s*=/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
689
+ { pattern: /^(?:export\s+)?enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
690
+ { pattern: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[\w]+)\s*(?::\s*[\w<>\[\]|&\s]+)?\s*=>/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
691
+ { pattern: /^\s+(?:public|private|protected)?\s*(?:static\s+)?(?:async\s+)?(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
692
+ ],
693
+ python: [
694
+ { pattern: /^(?:async\s+)?def\s+(\w+)\s*(\([^)]*\))(?:\s*->\s*[\w\[\],\s|]+)?:/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
695
+ { pattern: /^class\s+(\w+)(?:\([^)]*\))?\s*:/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
696
+ { pattern: /^\s+(?:async\s+)?def\s+(\w+)\s*\(self[^)]*\)(?:\s*->\s*[\w\[\],\s|]+)?:/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
697
+ { pattern: /^\s+@(?:staticmethod|classmethod)\s*\n\s+def\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
698
+ ],
699
+ java: [
700
+ { pattern: /(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
701
+ { pattern: /(?:public|private|protected)\s+(?:static\s+)?interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
702
+ { pattern: /(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:abstract\s+)?(?:synchronized\s+)?[\w<>\[\],\s]+\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
703
+ { pattern: /(?:public|private|protected)\s+(?:static\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
704
+ ],
705
+ go: [
706
+ { pattern: /^func\s+(\w+)\s*(\([^)]*\))(?:\s*(?:\([^)]*\)|[\w*]+))?\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
707
+ { pattern: /^func\s+\([^)]+\)\s+(\w+)\s*(\([^)]*\))(?:\s*(?:\([^)]*\)|[\w*]+))?\s*\{/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
708
+ { pattern: /^type\s+(\w+)\s+struct\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
709
+ { pattern: /^type\s+(\w+)\s+interface\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
710
+ ],
711
+ rust: [
712
+ { pattern: /^(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*(\([^)]*\))(?:\s*->\s*[\w<>&\[\]]+)?\s*(?:where[^{]*)?\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
713
+ { pattern: /^(?:pub(?:\([^)]*\))?\s+)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
714
+ { pattern: /^(?:pub(?:\([^)]*\))?\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
715
+ { pattern: /^(?:pub(?:\([^)]*\))?\s+)?trait\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
716
+ { pattern: /^\s+(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+(\w+)(?:<[^>]*>)?\s*\(&?(?:mut\s+)?self[^)]*\)/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
717
+ ],
718
+ c: [
719
+ { pattern: /^(?:static\s+)?(?:inline\s+)?(?:extern\s+)?(?:const\s+)?[\w*\s]+\s+(\w+)\s*(\([^)]*\))\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
720
+ { pattern: /^(?:typedef\s+)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
721
+ { pattern: /^(?:typedef\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
722
+ ],
723
+ cpp: [
724
+ { pattern: /^(?:(?:virtual|static|inline|explicit|extern|const|constexpr)\s+)*[\w:<>*&\s]+\s+(\w+)\s*(\([^)]*\))(?:\s*(?:const|override|final|noexcept))*\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
725
+ { pattern: /^(?:template\s*<[^>]*>\s*)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
726
+ { pattern: /^(?:template\s*<[^>]*>\s*)?struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
727
+ { pattern: /^namespace\s+(\w+)\s*\{/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
728
+ ],
729
+ csharp: [
730
+ { pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?(?:partial\s+)?(?:abstract\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
731
+ { pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
732
+ { pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?(?:virtual\s+)?(?:override\s+)?(?:async\s+)?[\w<>\[\],?\s]+\s+(\w+)\s*(\([^)]*\))/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
733
+ { pattern: /(?:public|private|protected|internal)\s+(?:static\s+)?enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
734
+ { pattern: /^namespace\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
735
+ ],
736
+ ruby: [
737
+ { pattern: /^\s*def\s+(?:self\.)?(\w+[?!]?)(?:\(([^)]*)\))?/gm, type: 'method', nameGroup: 1, signatureGroup: 0 },
738
+ { pattern: /^\s*class\s+(\w+)(?:\s*<\s*\w+)?/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
739
+ { pattern: /^\s*module\s+(\w+)/gm, type: 'module', nameGroup: 1, signatureGroup: 0 },
740
+ ],
741
+ php: [
742
+ { pattern: /(?:public|private|protected)?\s*(?:static\s+)?function\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
743
+ { pattern: /(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+[\w,\s]+)?/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
744
+ { pattern: /interface\s+(\w+)(?:\s+extends\s+[\w,\s]+)?/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
745
+ { pattern: /trait\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
746
+ ],
747
+ bash: [
748
+ { pattern: /^(?:function\s+)?(\w+)\s*\(\)\s*\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
749
+ { pattern: /^(\w+)\s*=\s*/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
750
+ ],
751
+ kotlin: [
752
+ { pattern: /(?:public|private|protected|internal)?\s*(?:data\s+|sealed\s+|abstract\s+|open\s+|inner\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
753
+ { pattern: /(?:public|private|protected|internal)?\s*(?:fun)\s+(?:<[^>]*>\s+)?(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
754
+ { pattern: /(?:public|private|protected|internal)?\s*interface\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
755
+ { pattern: /(?:public|private|protected|internal)?\s*(?:enum\s+class|enum)\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
756
+ { pattern: /(?:public|private|protected|internal)?\s*object\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
757
+ { pattern: /(?:public|private|protected|internal)?\s*(?:val|var)\s+(\w+)\s*(?::\s*[\w<>\[\]?,\s]+)?\s*(?:=|$)/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
758
+ { pattern: /(?:public|private|protected|internal)?\s*typealias\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
759
+ { pattern: /^package\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
760
+ ],
761
+ swift: [
762
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*(?:final\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
763
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*struct\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
764
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*(?:static\s+|class\s+)?func\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
765
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*protocol\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
766
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*enum\s+(\w+)/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
767
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*extension\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
768
+ { pattern: /(?:public|private|fileprivate|internal|open)?\s*typealias\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
769
+ ],
770
+ dart: [
771
+ { pattern: /(?:abstract\s+)?class\s+(\w+)(?:<[^>]*>)?(?:\s+extends\s+[\w<>,\s]+)?(?:\s+(?:with|implements)\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
772
+ { pattern: /(?:Future<[^>]*>|void|int|double|bool|String|dynamic|[\w<>]+)\s+(\w+)\s*(\([^)]*\))\s*(?:async\s*)?\{/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
773
+ { pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
774
+ { pattern: /mixin\s+(\w+)(?:\s+on\s+[\w<>,\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
775
+ { pattern: /extension\s+(\w+)\s+on\s+[\w<>,\s]+\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
776
+ { pattern: /typedef\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
777
+ ],
778
+ scala: [
779
+ { pattern: /(?:sealed\s+|abstract\s+|final\s+)?(?:case\s+)?class\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
780
+ { pattern: /(?:private\s*(?:\[\w+\])?\s*|protected\s*(?:\[\w+\])?\s*)?def\s+(\w+)(?:\[.*?\])?\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
781
+ { pattern: /trait\s+(\w+)/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
782
+ { pattern: /object\s+(\w+)/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
783
+ { pattern: /(?:val|var)\s+(\w+)\s*(?::\s*[\w\[\]<>,\s]+)?\s*=/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
784
+ { pattern: /type\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
785
+ { pattern: /^package\s+([\w.]+)/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
786
+ ],
787
+ yaml: [
788
+ { pattern: /^(\w[\w-]*)\s*:/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
789
+ ],
790
+ toml: [
791
+ { pattern: /^\[([\w.-]+)\]/gm, type: 'namespace', nameGroup: 1, signatureGroup: 0 },
792
+ { pattern: /^(\w[\w-]*)\s*=/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
793
+ ],
794
+ xml: [
795
+ { pattern: /<(\w+:[\w.]+|\w+\.\w+)[^>]*(?:x:Class|x:Name)\s*=\s*"([^"]+)"/gm, type: 'class', nameGroup: 2, signatureGroup: 0 },
796
+ { pattern: /<(Window|Page|UserControl|ResourceDictionary|Application)[\s>]/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
797
+ ],
798
+ xaml: [
799
+ { pattern: /x:Class\s*=\s*"([^"]+)"/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
800
+ { pattern: /x:Name\s*=\s*"([^"]+)"/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
801
+ { pattern: /x:Key\s*=\s*"([^"]+)"/gm, type: 'variable', nameGroup: 1, signatureGroup: 0 },
802
+ { pattern: /(?:Click|Command|Loaded|Closing|TextChanged|SelectionChanged)\s*=\s*"([^"]+)"/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
803
+ { pattern: /<Style[^>]*TargetType\s*=\s*"\{?x:Type\s+([\w:]+)\}?"/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
804
+ ],
805
+ sql: [
806
+ { pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`?\w+`?\.)?`?(\w+)`?/gim, type: 'class', nameGroup: 1, signatureGroup: 0 },
807
+ { pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\s+(?:`?\w+`?\.)?`?(\w+)`?\s*(\([^)]*\))/gim, type: 'function', nameGroup: 1, signatureGroup: 0 },
808
+ { pattern: /CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:`?\w+`?\.)?`?(\w+)`?/gim, type: 'class', nameGroup: 1, signatureGroup: 0 },
809
+ { pattern: /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?/gim, type: 'variable', nameGroup: 1, signatureGroup: 0 },
810
+ { pattern: /CREATE\s+TRIGGER\s+(?:IF\s+NOT\s+EXISTS\s+)?`?(\w+)`?/gim, type: 'function', nameGroup: 1, signatureGroup: 0 },
811
+ ],
812
+ protobuf: [
813
+ { pattern: /message\s+(\w+)\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
814
+ { pattern: /service\s+(\w+)\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
815
+ { pattern: /rpc\s+(\w+)\s*(\([^)]*\))/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
816
+ { pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
817
+ ],
818
+ graphql: [
819
+ { pattern: /type\s+(\w+)(?:\s+implements\s+[\w&\s]+)?\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
820
+ { pattern: /input\s+(\w+)\s*\{/gm, type: 'class', nameGroup: 1, signatureGroup: 0 },
821
+ { pattern: /interface\s+(\w+)\s*\{/gm, type: 'interface', nameGroup: 1, signatureGroup: 0 },
822
+ { pattern: /enum\s+(\w+)\s*\{/gm, type: 'enum', nameGroup: 1, signatureGroup: 0 },
823
+ { pattern: /scalar\s+(\w+)/gm, type: 'type_alias', nameGroup: 1, signatureGroup: 0 },
824
+ { pattern: /(?:query|mutation|subscription)\s+(\w+)/gm, type: 'function', nameGroup: 1, signatureGroup: 0 },
825
+ ],
826
+ };
827
+ /** Detect doc comment above a line index */
828
+ function findRegexDocComment(lines, lineIndex) {
829
+ // Look backwards for comment blocks
830
+ let commentLines = [];
831
+ for (let i = lineIndex - 1; i >= 0 && i >= lineIndex - 30; i--) {
832
+ const line = lines[i].trim();
833
+ if (line === '') {
834
+ if (commentLines.length > 0)
835
+ break;
836
+ continue;
837
+ }
838
+ if (line.startsWith('/**') || line.startsWith('*') || line.startsWith('*/') ||
839
+ line.startsWith('//') || line.startsWith('///') ||
840
+ line.startsWith('#') || line.startsWith('"""') || line.startsWith("'''")) {
841
+ commentLines.unshift(lines[i]);
842
+ }
843
+ else {
844
+ break;
845
+ }
846
+ }
847
+ if (commentLines.length === 0)
848
+ return null;
849
+ return cleanDocComment(commentLines.join('\n'));
850
+ }
851
+ /** Regex-based fallback parser */
852
+ function parseWithRegex(source, filePath, language) {
853
+ const nodes = [];
854
+ const edges = [];
855
+ const lines = source.split('\n');
856
+ const now = Date.now();
857
+ // Create file node
858
+ const fileNode = {
859
+ id: generateNodeId(filePath, basename(filePath), 'file'),
860
+ type: 'file',
861
+ name: basename(filePath),
862
+ qualifiedName: filePath,
863
+ filePath,
864
+ startLine: 1,
865
+ endLine: lines.length,
866
+ signature: filePath,
867
+ docComment: null,
868
+ hash: generateContentHash(source),
869
+ language,
870
+ visibility: 'public',
871
+ isAsync: false,
872
+ isStatic: false,
873
+ isExported: false,
874
+ updatedAt: now,
875
+ };
876
+ nodes.push(fileNode);
877
+ const patterns = REGEX_PATTERNS[language] ?? REGEX_PATTERNS['javascript'] ?? [];
878
+ // Track matched names to avoid duplicates
879
+ const seen = new Set();
880
+ for (const { pattern, type, nameGroup } of patterns) {
881
+ // Reset regex lastIndex
882
+ const regex = new RegExp(pattern.source, pattern.flags);
883
+ let match;
884
+ while ((match = regex.exec(source)) !== null) {
885
+ const name = match[nameGroup];
886
+ if (!name)
887
+ continue;
888
+ const key = `${name}::${type}`;
889
+ if (seen.has(key))
890
+ continue;
891
+ seen.add(key);
892
+ // Calculate line number from match index
893
+ const startLine = source.substring(0, match.index).split('\n').length;
894
+ const matchText = match[0];
895
+ const endLine = startLine + matchText.split('\n').length - 1;
896
+ // Detect modifiers from signature
897
+ const sig = matchText.trim();
898
+ const isAsync = /\basync\b/.test(sig);
899
+ const isStatic = /\bstatic\b/.test(sig);
900
+ const isExportedMatch = /\bexport\b/.test(sig);
901
+ const isPublic = /\bpublic\b/.test(sig);
902
+ const isPrivate = /\bprivate\b/.test(sig);
903
+ const isProtected = /\bprotected\b/.test(sig);
904
+ let visibility = 'unknown';
905
+ if (isPublic)
906
+ visibility = 'public';
907
+ else if (isPrivate)
908
+ visibility = 'private';
909
+ else if (isProtected)
910
+ visibility = 'protected';
911
+ // Python convention
912
+ if (language === 'python' && name.startsWith('__') && !name.endsWith('__'))
913
+ visibility = 'private';
914
+ else if (language === 'python' && name.startsWith('_'))
915
+ visibility = 'protected';
916
+ // Go convention
917
+ if (language === 'go' && name[0] === name[0].toUpperCase() && name[0] !== name[0].toLowerCase())
918
+ visibility = 'public';
919
+ // Build a clean signature without body
920
+ const cleanSig = sig.replace(/\{[\s\S]*$/, '').replace(/:\s*$/, '').trim();
921
+ const gNode = {
922
+ id: generateNodeId(filePath, name, type),
923
+ type,
924
+ name,
925
+ qualifiedName: name,
926
+ filePath,
927
+ startLine,
928
+ endLine,
929
+ signature: cleanSig,
930
+ docComment: findRegexDocComment(lines, startLine - 1),
931
+ hash: generateContentHash(matchText),
932
+ language,
933
+ visibility,
934
+ isAsync,
935
+ isStatic,
936
+ isExported: isExportedMatch,
937
+ updatedAt: now,
938
+ };
939
+ nodes.push(gNode);
940
+ edges.push({ sourceId: fileNode.id, targetId: gNode.id, type: 'contains' });
941
+ }
942
+ }
943
+ // Extract import edges
944
+ const importPatterns = {
945
+ javascript: /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/gm,
946
+ typescript: /(?:import\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\))/gm,
947
+ python: /(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/gm,
948
+ java: /import\s+(?:static\s+)?([\w.]+);/gm,
949
+ go: /import\s+(?:\(\s*(?:"([^"]+)"[\s]*)+\)|"([^"]+)")/gm,
950
+ rust: /use\s+([\w:]+)/gm,
951
+ csharp: /using\s+(?:static\s+)?([\w.]+);/gm,
952
+ ruby: /require(?:_relative)?\s+['"]([^'"]+)['"]/gm,
953
+ php: /(?:use\s+([\w\\]+)|require(?:_once)?\s+['"]([^'"]+)['"]|include(?:_once)?\s+['"]([^'"]+)['"])/gm,
954
+ c: /#include\s+[<"]([^>"]+)[>"]/gm,
955
+ cpp: /#include\s+[<"]([^>"]+)[>"]/gm,
956
+ bash: /(?:source|\.)\s+['"]?([^'";\s]+)['"]?/gm,
957
+ kotlin: /import\s+([\w.]+)/gm,
958
+ swift: /import\s+(\w+)/gm,
959
+ dart: /import\s+['"]([^'"]+)['"]/gm,
960
+ scala: /import\s+([\w.{}]+)/gm,
961
+ sql: /-- no imports/gm,
962
+ };
963
+ const importRegex = importPatterns[language];
964
+ if (importRegex) {
965
+ const regex = new RegExp(importRegex.source, importRegex.flags);
966
+ let match;
967
+ while ((match = regex.exec(source)) !== null) {
968
+ const importSource = match[1] ?? match[2] ?? match[3] ?? '';
969
+ if (importSource) {
970
+ edges.push({
971
+ sourceId: fileNode.id,
972
+ targetId: importSource,
973
+ type: 'imports',
974
+ metadata: { raw: match[0].trim() },
975
+ });
976
+ }
977
+ }
978
+ }
979
+ return { nodes, edges };
980
+ }
981
+ // ============================================================
982
+ // Public API
983
+ // ============================================================
984
+ /**
985
+ * Parse a source code file and extract its structural information.
986
+ *
987
+ * Attempts tree-sitter parsing first, falls back to regex if unavailable.
988
+ *
989
+ * @param filePath - Absolute path to the source file
990
+ * @param source - Optional pre-read source code (reads from disk if not provided)
991
+ * @returns ParseResult with extracted nodes, edges, and any parse errors
992
+ */
993
+ export async function parseFile(filePath, source) {
994
+ const language = detectLanguage(filePath);
995
+ if (!language) {
996
+ return {
997
+ filePath,
998
+ language: 'unknown',
999
+ nodes: [],
1000
+ edges: [],
1001
+ parseErrors: [`Unsupported file extension: ${extname(filePath)}`],
1002
+ };
1003
+ }
1004
+ let content;
1005
+ try {
1006
+ content = source ?? await readFile(filePath, 'utf-8');
1007
+ }
1008
+ catch (err) {
1009
+ return {
1010
+ filePath,
1011
+ language,
1012
+ nodes: [],
1013
+ edges: [],
1014
+ parseErrors: [`Failed to read file: ${err instanceof Error ? err.message : String(err)}`],
1015
+ };
1016
+ }
1017
+ if (!content.trim()) {
1018
+ return {
1019
+ filePath,
1020
+ language,
1021
+ nodes: [],
1022
+ edges: [],
1023
+ parseErrors: [],
1024
+ };
1025
+ }
1026
+ const parseErrors = [];
1027
+ let nodes = [];
1028
+ let edges = [];
1029
+ // Try tree-sitter first
1030
+ const ts = await getTreeSitterParser(language);
1031
+ if (ts) {
1032
+ try {
1033
+ ts.parser.setLanguage(ts.grammar);
1034
+ const tree = ts.parser.parse(content);
1035
+ if (tree.rootNode.hasError) {
1036
+ parseErrors.push('Tree-sitter reported parse errors; results may be incomplete');
1037
+ }
1038
+ const result = extractFromTreeSitter(tree, content, filePath, language);
1039
+ nodes = result.nodes;
1040
+ edges = result.edges;
1041
+ }
1042
+ catch (err) {
1043
+ parseErrors.push(`Tree-sitter parsing failed: ${err instanceof Error ? err.message : String(err)}, using regex fallback`);
1044
+ const result = parseWithRegex(content, filePath, language);
1045
+ nodes = result.nodes;
1046
+ edges = result.edges;
1047
+ }
1048
+ }
1049
+ else {
1050
+ // Use regex fallback
1051
+ const result = parseWithRegex(content, filePath, language);
1052
+ nodes = result.nodes;
1053
+ edges = result.edges;
1054
+ }
1055
+ return { filePath, language, nodes, edges, parseErrors };
1056
+ }
1057
+ /**
1058
+ * Parse multiple files concurrently in batches.
1059
+ *
1060
+ * @param files - Array of file paths to parse
1061
+ * @param concurrency - Max concurrent parses (default 8)
1062
+ * @param onProgress - Optional progress callback (current, total)
1063
+ * @returns Array of ParseResults
1064
+ */
1065
+ export async function parseFiles(files, concurrency = 8, onProgress) {
1066
+ const results = [];
1067
+ const total = files.length;
1068
+ for (let i = 0; i < total; i += concurrency) {
1069
+ const batch = files.slice(i, i + concurrency);
1070
+ const batchResults = await Promise.all(batch.map(f => parseFile(f)));
1071
+ results.push(...batchResults);
1072
+ if (onProgress) {
1073
+ onProgress(Math.min(i + concurrency, total), total);
1074
+ }
1075
+ }
1076
+ return results;
1077
+ }
1078
+ /**
1079
+ * Check if a file extension is supported for parsing.
1080
+ */
1081
+ export function isSupportedFile(filePath) {
1082
+ return detectLanguage(filePath) !== null;
1083
+ }
1084
+ /**
1085
+ * Get the list of supported languages.
1086
+ */
1087
+ export function getSupportedLanguages() {
1088
+ return [...new Set(Object.values(EXTENSION_MAP))];
1089
+ }
1090
+ //# sourceMappingURL=parser.js.map