neuro-cli 4.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,1956 @@
1
+ // ============================================================
2
+ // NeuroCLI - Tree-sitter Integration
3
+ // Regex-based AST parser for repository mapping
4
+ // Inspired by Aider's lightweight "good enough" approach
5
+ // No external dependencies - uses only Node.js built-ins
6
+ // ============================================================
7
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
8
+ import { join, extname, relative, basename, dirname } from 'path';
9
+ import { execSync } from 'child_process';
10
+ // ============================================================
11
+ // Language Configuration
12
+ // ============================================================
13
+ const LANGUAGE_EXTENSIONS = {
14
+ '.ts': 'typescript',
15
+ '.tsx': 'typescript',
16
+ '.js': 'javascript',
17
+ '.jsx': 'javascript',
18
+ '.mjs': 'javascript',
19
+ '.cjs': 'javascript',
20
+ '.py': 'python',
21
+ '.pyi': 'python',
22
+ '.pyx': 'python',
23
+ '.go': 'go',
24
+ '.rs': 'rust',
25
+ '.java': 'java',
26
+ '.kt': 'kotlin',
27
+ '.css': 'css',
28
+ '.scss': 'css',
29
+ '.less': 'css',
30
+ '.html': 'html',
31
+ '.htm': 'html',
32
+ '.vue': 'vue',
33
+ '.svelte': 'svelte',
34
+ };
35
+ const LANGUAGE_NAMES = {
36
+ 'typescript': 'TypeScript',
37
+ 'javascript': 'JavaScript',
38
+ 'python': 'Python',
39
+ 'go': 'Go',
40
+ 'rust': 'Rust',
41
+ 'java': 'Java',
42
+ 'kotlin': 'Kotlin',
43
+ 'css': 'CSS',
44
+ 'html': 'HTML',
45
+ 'vue': 'Vue',
46
+ 'svelte': 'Svelte',
47
+ };
48
+ const SKIP_DIRS = new Set([
49
+ 'node_modules', '.git', '__pycache__', '.next', 'dist', '.turbo', '.cache',
50
+ 'build', 'out', 'target', '.cargo', 'vendor', '.venv', 'venv', 'env',
51
+ '.tox', '.mypy_cache', '.pytest_cache', '.hg', '.svn', 'coverage',
52
+ ]);
53
+ const BINARY_EXTENSIONS = new Set([
54
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp',
55
+ '.mp3', '.mp4', '.wav', '.avi', '.mov', '.mkv', '.flac',
56
+ '.zip', '.tar', '.gz', '.bz2', '.xz', '.7z', '.rar',
57
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
58
+ '.woff', '.woff2', '.ttf', '.eot', '.otf',
59
+ '.wasm', '.so', '.dll', '.dylib', '.exe',
60
+ ]);
61
+ // ---- TypeScript / JavaScript Parser ----
62
+ function parseTypeScript(content) {
63
+ const lines = content.split('\n');
64
+ const symbols = [];
65
+ const imports = [];
66
+ const exports = [];
67
+ for (let i = 0; i < lines.length; i++) {
68
+ const line = lines[i];
69
+ const lineNum = i + 1;
70
+ // ---- Imports ----
71
+ const importMatch = line.match(/^import\s+(?:type\s+)?(?:(\{[^}]*\})|(\*\s+as\s+\w+)|(\w+))\s+from\s+['"]([^'"]+)['"]/);
72
+ if (importMatch) {
73
+ const [, namedImports, namespaceImport, defaultImport, source] = importMatch;
74
+ const items = [];
75
+ if (namedImports) {
76
+ items.push(...namedImports.replace(/[{}]/g, '').split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean));
77
+ }
78
+ if (namespaceImport) {
79
+ items.push(namespaceImport.replace(/\*\s+as\s+/, '').trim());
80
+ }
81
+ if (defaultImport) {
82
+ items.push(defaultImport.trim());
83
+ }
84
+ imports.push({
85
+ source,
86
+ items,
87
+ line: lineNum,
88
+ isTypeOnly: line.includes('import type'),
89
+ });
90
+ continue;
91
+ }
92
+ // Side-effect import
93
+ const sideEffectImport = line.match(/^import\s+['"]([^'"]+)['"]/);
94
+ if (sideEffectImport) {
95
+ imports.push({
96
+ source: sideEffectImport[1],
97
+ items: [],
98
+ line: lineNum,
99
+ isTypeOnly: false,
100
+ });
101
+ continue;
102
+ }
103
+ // Require import
104
+ const requireMatch = line.match(/(?:const|let|var)\s+(?:\{([^}]*)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
105
+ if (requireMatch) {
106
+ const [, namedImports, defaultImport, source] = requireMatch;
107
+ const items = [];
108
+ if (namedImports) {
109
+ items.push(...namedImports.split(',').map(s => s.trim().split(/\s*:\s*/)[0].trim()).filter(Boolean));
110
+ }
111
+ if (defaultImport) {
112
+ items.push(defaultImport.trim());
113
+ }
114
+ imports.push({ source, items, line: lineNum, isTypeOnly: false });
115
+ }
116
+ // ---- Exports ----
117
+ const exportDefaultMatch = line.match(/^export\s+default\s+(?:function\s+)?(\w+)/);
118
+ if (exportDefaultMatch) {
119
+ exports.push({
120
+ name: exportDefaultMatch[1],
121
+ kind: 'function',
122
+ line: lineNum,
123
+ isDefault: true,
124
+ isReexport: false,
125
+ });
126
+ }
127
+ const reexportMatch = line.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
128
+ if (reexportMatch) {
129
+ const items = reexportMatch[1].split(',').map(s => {
130
+ const parts = s.trim().split(/\s+as\s+/);
131
+ return parts[parts.length - 1].trim();
132
+ });
133
+ for (const item of items) {
134
+ exports.push({
135
+ name: item,
136
+ kind: 'reexport',
137
+ line: lineNum,
138
+ isDefault: false,
139
+ isReexport: true,
140
+ });
141
+ }
142
+ continue;
143
+ }
144
+ // ---- Classes ----
145
+ const classMatch = line.match(/^(export\s+)?(default\s+)?(declare\s+)?(abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
146
+ if (classMatch) {
147
+ const [, exportKw, defaultKw, , abstractKw, name, extendsCls, implementsIf] = classMatch;
148
+ const modifiers = [];
149
+ if (exportKw)
150
+ modifiers.push('export');
151
+ if (defaultKw)
152
+ modifiers.push('default');
153
+ if (abstractKw)
154
+ modifiers.push('abstract');
155
+ const endLine = findBlockEnd(lines, i);
156
+ const signature = line.trim().replace(/\{$/, '').trim();
157
+ const symbol = {
158
+ name,
159
+ kind: 'class',
160
+ line: lineNum,
161
+ endLine,
162
+ signature,
163
+ modifiers,
164
+ children: [],
165
+ };
166
+ // Parse class members (methods, properties)
167
+ const classBody = lines.slice(i, endLine);
168
+ for (let j = 0; j < classBody.length; j++) {
169
+ const memberLine = classBody[j];
170
+ const methodMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|abstract|readonly|async|override|declare)\s+)*(?:(?:get|set)\s+)?(\w+)\s*[<(]/);
171
+ if (methodMatch) {
172
+ const methodModifiers = extractModifiers(memberLine);
173
+ const methodName = methodMatch[1];
174
+ // Skip constructor - it's implicit
175
+ if (methodName === 'constructor') {
176
+ symbol.children.push({
177
+ name: 'constructor',
178
+ kind: 'method',
179
+ line: lineNum + j,
180
+ endLine: findBlockEnd(classBody, j) + lineNum,
181
+ signature: memberLine.trim().replace(/\{$/, '').trim(),
182
+ modifiers: methodModifiers,
183
+ });
184
+ continue;
185
+ }
186
+ // Determine if it's a getter/setter or regular method
187
+ const isAccessor = /^\s+(?:public|private|protected|static)\s+(?:get|set)\s+/.test(memberLine);
188
+ symbol.children.push({
189
+ name: methodName,
190
+ kind: isAccessor ? 'method' : 'method',
191
+ line: lineNum + j,
192
+ endLine: findBlockEnd(classBody, j) + lineNum,
193
+ signature: memberLine.trim().replace(/\{$/, '').trim(),
194
+ modifiers: methodModifiers,
195
+ });
196
+ }
197
+ // Class properties
198
+ const propMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|abstract|readonly|declare)\s+)+(\w+)\s*[?:!]/);
199
+ if (propMatch && !memberLine.includes('(')) {
200
+ symbol.children.push({
201
+ name: propMatch[1],
202
+ kind: 'variable',
203
+ line: lineNum + j,
204
+ endLine: lineNum + j,
205
+ modifiers: extractModifiers(memberLine),
206
+ });
207
+ }
208
+ }
209
+ symbols.push(symbol);
210
+ if (exportKw) {
211
+ exports.push({
212
+ name,
213
+ kind: 'class',
214
+ line: lineNum,
215
+ isDefault: !!defaultKw,
216
+ isReexport: false,
217
+ });
218
+ }
219
+ continue;
220
+ }
221
+ // ---- Interfaces ----
222
+ const interfaceMatch = line.match(/^(export\s+)?(declare\s+)?interface\s+(\w+)(?:\s+extends\s+([^{]+))?/);
223
+ if (interfaceMatch) {
224
+ const [, exportKw, , name, extendsIf] = interfaceMatch;
225
+ const modifiers = [];
226
+ if (exportKw)
227
+ modifiers.push('export');
228
+ const endLine = findBlockEnd(lines, i);
229
+ const signature = line.trim().replace(/\{$/, '').trim();
230
+ const symbol = {
231
+ name,
232
+ kind: 'interface',
233
+ line: lineNum,
234
+ endLine,
235
+ signature,
236
+ modifiers,
237
+ children: [],
238
+ };
239
+ // Parse interface members
240
+ const ifaceBody = lines.slice(i, endLine);
241
+ for (let j = 0; j < ifaceBody.length; j++) {
242
+ const memberLine = ifaceBody[j];
243
+ const methodMatch = memberLine.match(/^\s+(\w+)\s*[<(]/);
244
+ if (methodMatch) {
245
+ symbol.children.push({
246
+ name: methodMatch[1],
247
+ kind: 'method',
248
+ line: lineNum + j,
249
+ endLine: lineNum + j,
250
+ signature: memberLine.trim().replace(/[;,{]$/, '').trim(),
251
+ });
252
+ }
253
+ const propMatch = memberLine.match(/^\s+(\w+)\s*[?!:]/);
254
+ if (propMatch && !memberLine.includes('(')) {
255
+ symbol.children.push({
256
+ name: propMatch[1],
257
+ kind: 'variable',
258
+ line: lineNum + j,
259
+ endLine: lineNum + j,
260
+ signature: memberLine.trim().replace(/[;,{]$/, '').trim(),
261
+ });
262
+ }
263
+ }
264
+ symbols.push(symbol);
265
+ if (exportKw) {
266
+ exports.push({
267
+ name,
268
+ kind: 'interface',
269
+ line: lineNum,
270
+ isDefault: false,
271
+ isReexport: false,
272
+ });
273
+ }
274
+ continue;
275
+ }
276
+ // ---- Type Aliases ----
277
+ const typeMatch = line.match(/^(export\s+)?(declare\s+)?type\s+(\w+)\s*(?:<[^>]+>)?\s*=/);
278
+ if (typeMatch) {
279
+ const [, exportKw, , name] = typeMatch;
280
+ const endLine = findTypeEnd(lines, i);
281
+ symbols.push({
282
+ name,
283
+ kind: 'type',
284
+ line: lineNum,
285
+ endLine,
286
+ signature: line.trim().slice(0, 120),
287
+ modifiers: exportKw ? ['export'] : [],
288
+ });
289
+ if (exportKw) {
290
+ exports.push({
291
+ name,
292
+ kind: 'type',
293
+ line: lineNum,
294
+ isDefault: false,
295
+ isReexport: false,
296
+ });
297
+ }
298
+ continue;
299
+ }
300
+ // ---- Enums ----
301
+ const enumMatch = line.match(/^(export\s+)?(declare\s+)?(?:const\s+)?enum\s+(\w+)/);
302
+ if (enumMatch) {
303
+ const [, exportKw, , name] = enumMatch;
304
+ const endLine = findBlockEnd(lines, i);
305
+ symbols.push({
306
+ name,
307
+ kind: 'enum',
308
+ line: lineNum,
309
+ endLine,
310
+ signature: line.trim().replace(/\{$/, '').trim(),
311
+ modifiers: exportKw ? ['export'] : [],
312
+ });
313
+ if (exportKw) {
314
+ exports.push({
315
+ name,
316
+ kind: 'enum',
317
+ line: lineNum,
318
+ isDefault: false,
319
+ isReexport: false,
320
+ });
321
+ }
322
+ continue;
323
+ }
324
+ // ---- Functions ----
325
+ const funcMatch = line.match(/^(export\s+)?(default\s+)?(declare\s+)?(async\s+)?function\s+(\w+)\s*([<(])/);
326
+ if (funcMatch) {
327
+ const [, exportKw, defaultKw, , asyncKw, name] = funcMatch;
328
+ const modifiers = [];
329
+ if (exportKw)
330
+ modifiers.push('export');
331
+ if (defaultKw)
332
+ modifiers.push('default');
333
+ if (asyncKw)
334
+ modifiers.push('async');
335
+ const endLine = findBlockEnd(lines, i);
336
+ const signature = line.trim().replace(/\{$/, '').trim();
337
+ symbols.push({
338
+ name,
339
+ kind: 'function',
340
+ line: lineNum,
341
+ endLine,
342
+ signature: signature.slice(0, 200),
343
+ modifiers,
344
+ });
345
+ if (exportKw) {
346
+ exports.push({
347
+ name,
348
+ kind: 'function',
349
+ line: lineNum,
350
+ isDefault: !!defaultKw,
351
+ isReexport: false,
352
+ });
353
+ }
354
+ continue;
355
+ }
356
+ // ---- Arrow Functions / Const Declarations ----
357
+ const constArrowMatch = line.match(/^(export\s+)?(declare\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/);
358
+ if (constArrowMatch) {
359
+ const [, exportKw, , name] = constArrowMatch;
360
+ const modifiers = [];
361
+ if (exportKw)
362
+ modifiers.push('export');
363
+ symbols.push({
364
+ name,
365
+ kind: 'function',
366
+ line: lineNum,
367
+ endLine: findStatementEnd(lines, i),
368
+ signature: line.trim().slice(0, 200),
369
+ modifiers,
370
+ });
371
+ if (exportKw) {
372
+ exports.push({
373
+ name,
374
+ kind: 'function',
375
+ line: lineNum,
376
+ isDefault: false,
377
+ isReexport: false,
378
+ });
379
+ }
380
+ continue;
381
+ }
382
+ // ---- Regular Const/Let/Var ----
383
+ const varMatch = line.match(/^(export\s+)?(declare\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=/);
384
+ if (varMatch) {
385
+ const [, exportKw, , name] = varMatch;
386
+ // Skip if it's an arrow function (already handled)
387
+ if (!line.includes('=>')) {
388
+ symbols.push({
389
+ name,
390
+ kind: 'constant',
391
+ line: lineNum,
392
+ endLine: lineNum,
393
+ signature: line.trim().slice(0, 120),
394
+ modifiers: exportKw ? ['export'] : [],
395
+ });
396
+ if (exportKw) {
397
+ exports.push({
398
+ name,
399
+ kind: 'constant',
400
+ line: lineNum,
401
+ isDefault: false,
402
+ isReexport: false,
403
+ });
404
+ }
405
+ }
406
+ continue;
407
+ }
408
+ const letMatch = line.match(/^(export\s+)?let\s+(\w+)\s*(?::\s*[^=]+)?\s*=/);
409
+ if (letMatch) {
410
+ const [, exportKw, name] = letMatch;
411
+ symbols.push({
412
+ name,
413
+ kind: 'variable',
414
+ line: lineNum,
415
+ endLine: lineNum,
416
+ modifiers: exportKw ? ['export'] : [],
417
+ });
418
+ continue;
419
+ }
420
+ // ---- Namespaces ----
421
+ const namespaceMatch = line.match(/^(export\s+)?namespace\s+(\w+)/);
422
+ if (namespaceMatch) {
423
+ const [, exportKw, name] = namespaceMatch;
424
+ const endLine = findBlockEnd(lines, i);
425
+ symbols.push({
426
+ name,
427
+ kind: 'namespace',
428
+ line: lineNum,
429
+ endLine,
430
+ signature: line.trim().replace(/\{$/, '').trim(),
431
+ modifiers: exportKw ? ['export'] : [],
432
+ });
433
+ continue;
434
+ }
435
+ }
436
+ return { symbols, imports, exports };
437
+ }
438
+ // ---- Python Parser ----
439
+ function parsePython(content) {
440
+ const lines = content.split('\n');
441
+ const symbols = [];
442
+ const imports = [];
443
+ const exports = [];
444
+ for (let i = 0; i < lines.length; i++) {
445
+ const line = lines[i];
446
+ const lineNum = i + 1;
447
+ const indent = line.match(/^(\s*)/)?.[1].length || 0;
448
+ // Skip comments and empty lines
449
+ if (line.trim().startsWith('#') || line.trim() === '')
450
+ continue;
451
+ // ---- Imports ----
452
+ const importMatch = line.match(/^(\s*)import\s+([^\n]+)/);
453
+ if (importMatch) {
454
+ const modules = importMatch[2].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim());
455
+ for (const mod of modules) {
456
+ imports.push({ source: mod, items: [], line: lineNum, isTypeOnly: false });
457
+ }
458
+ continue;
459
+ }
460
+ const fromImportMatch = line.match(/^(\s*)from\s+([^\s]+)\s+import\s+([^\n]+)/);
461
+ if (fromImportMatch) {
462
+ const source = fromImportMatch[2];
463
+ const items = fromImportMatch[3].split(',').map(s => {
464
+ const parts = s.trim().split(/\s+as\s+/);
465
+ return parts[0].trim();
466
+ }).filter(Boolean);
467
+ imports.push({ source, items, line: lineNum, isTypeOnly: false });
468
+ continue;
469
+ }
470
+ // Only process top-level symbols (indent === 0)
471
+ if (indent > 0)
472
+ continue;
473
+ // ---- Decorators ----
474
+ const decorators = [];
475
+ let j = i;
476
+ while (j < lines.length && lines[j].trimStart().startsWith('@')) {
477
+ const decMatch = lines[j].trim().match(/@(\w+)/);
478
+ if (decMatch)
479
+ decorators.push(decMatch[1]);
480
+ j++;
481
+ }
482
+ // Adjust i if we consumed decorators
483
+ if (j > i) {
484
+ i = j - 1;
485
+ continue; // The actual def/class will be on next iteration
486
+ }
487
+ // ---- Classes ----
488
+ const classMatch = line.match(/^class\s+(\w+)(?:\(([^)]*)\))?\s*:/);
489
+ if (classMatch) {
490
+ const name = classMatch[1];
491
+ const baseClasses = classMatch[2]?.split(',').map(s => s.trim()).filter(Boolean) || [];
492
+ const endLine = findPythonBlockEnd(lines, i);
493
+ // Collect decorators from lines above
494
+ const classDecorators = [];
495
+ for (let k = i - 1; k >= 0; k--) {
496
+ if (lines[k].trimStart().startsWith('@')) {
497
+ const dMatch = lines[k].trim().match(/@(\w+)/);
498
+ if (dMatch)
499
+ classDecorators.unshift(dMatch[1]);
500
+ }
501
+ else {
502
+ break;
503
+ }
504
+ }
505
+ const symbol = {
506
+ name,
507
+ kind: 'class',
508
+ line: lineNum,
509
+ endLine,
510
+ signature: line.trim().replace(/:\s*$/, ''),
511
+ modifiers: classDecorators.length > 0 ? classDecorators : undefined,
512
+ children: [],
513
+ };
514
+ // Parse class methods
515
+ const classBody = lines.slice(i + 1, endLine);
516
+ let methodIndent = -1;
517
+ for (let k = 0; k < classBody.length; k++) {
518
+ const mLine = classBody[k];
519
+ const mIndent = mLine.match(/^(\s*)/)?.[1].length || 0;
520
+ if (methodIndent === -1 && mLine.trim().startsWith('def ')) {
521
+ methodIndent = mIndent;
522
+ }
523
+ if (mIndent === methodIndent && mLine.trim().startsWith('def ')) {
524
+ const methodMatch = mLine.match(/^\s+(?:(?:async|staticmethod|classmethod)\s+)*def\s+(\w+)\s*\(([^)]*)\)/);
525
+ if (methodMatch) {
526
+ const methodName = methodMatch[1];
527
+ const methodModifiers = [];
528
+ // Check decorators above method
529
+ for (let d = k - 1; d >= 0; d--) {
530
+ if (classBody[d].trimStart().startsWith('@')) {
531
+ const dMatch = classBody[d].trim().match(/@(\w+)/);
532
+ if (dMatch)
533
+ methodModifiers.unshift(dMatch[1]);
534
+ }
535
+ else {
536
+ break;
537
+ }
538
+ }
539
+ if (mLine.includes('async '))
540
+ methodModifiers.push('async');
541
+ symbol.children.push({
542
+ name: methodName,
543
+ kind: 'method',
544
+ line: lineNum + k + 1,
545
+ endLine: findPythonBlockEnd(lines, i + 1 + k),
546
+ signature: mLine.trim().replace(/:\s*$/, ''),
547
+ modifiers: methodModifiers.length > 0 ? methodModifiers : undefined,
548
+ });
549
+ }
550
+ }
551
+ }
552
+ symbols.push(symbol);
553
+ continue;
554
+ }
555
+ // ---- Functions ----
556
+ const funcMatch = line.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
557
+ if (funcMatch) {
558
+ const name = funcMatch[1];
559
+ const isAsync = line.includes('async ');
560
+ const endLine = findPythonBlockEnd(lines, i);
561
+ // Collect decorators
562
+ const funcDecorators = [];
563
+ for (let k = i - 1; k >= 0; k--) {
564
+ if (lines[k].trimStart().startsWith('@')) {
565
+ const dMatch = lines[k].trim().match(/@(\w+)/);
566
+ if (dMatch)
567
+ funcDecorators.unshift(dMatch[1]);
568
+ }
569
+ else {
570
+ break;
571
+ }
572
+ }
573
+ const modifiers = [];
574
+ if (isAsync)
575
+ modifiers.push('async');
576
+ if (funcDecorators.length > 0)
577
+ modifiers.push(...funcDecorators);
578
+ symbols.push({
579
+ name,
580
+ kind: 'function',
581
+ line: lineNum,
582
+ endLine,
583
+ signature: line.trim().replace(/:\s*$/, ''),
584
+ modifiers: modifiers.length > 0 ? modifiers : undefined,
585
+ });
586
+ continue;
587
+ }
588
+ // ---- Top-level Variables ----
589
+ const varMatch = line.match(/^([A-Z_][A-Z_0-9]*)\s*=/);
590
+ if (varMatch) {
591
+ symbols.push({
592
+ name: varMatch[1],
593
+ kind: 'constant',
594
+ line: lineNum,
595
+ endLine: lineNum,
596
+ });
597
+ continue;
598
+ }
599
+ const pyVarMatch = line.match(/^(\w+)\s*[:=]\s*(?:(?:async\s+)?def|class)\b/);
600
+ if (!pyVarMatch) {
601
+ const simpleVarMatch = line.match(/^(\w+)\s*=\s*/);
602
+ if (simpleVarMatch && !line.trim().startsWith('_')) {
603
+ symbols.push({
604
+ name: simpleVarMatch[1],
605
+ kind: 'variable',
606
+ line: lineNum,
607
+ endLine: lineNum,
608
+ });
609
+ }
610
+ }
611
+ }
612
+ // Python doesn't have explicit exports - use __all__ if present
613
+ const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/);
614
+ if (allMatch) {
615
+ const items = allMatch[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean);
616
+ for (const item of items) {
617
+ exports.push({
618
+ name: item,
619
+ kind: 'unknown',
620
+ line: content.substring(0, content.indexOf('__all__')).split('\n').length,
621
+ isDefault: false,
622
+ isReexport: false,
623
+ });
624
+ }
625
+ }
626
+ return { symbols, imports, exports };
627
+ }
628
+ // ---- Go Parser ----
629
+ function parseGo(content) {
630
+ const lines = content.split('\n');
631
+ const symbols = [];
632
+ const imports = [];
633
+ const exports = [];
634
+ for (let i = 0; i < lines.length; i++) {
635
+ const line = lines[i];
636
+ const lineNum = i + 1;
637
+ // ---- Package ----
638
+ const pkgMatch = line.match(/^package\s+(\w+)/);
639
+ if (pkgMatch) {
640
+ symbols.push({
641
+ name: pkgMatch[1],
642
+ kind: 'namespace',
643
+ line: lineNum,
644
+ endLine: lineNum,
645
+ signature: line.trim(),
646
+ });
647
+ continue;
648
+ }
649
+ // ---- Imports ----
650
+ const singleImport = line.match(/^import\s+["']([^"']+)["']/);
651
+ if (singleImport) {
652
+ imports.push({ source: singleImport[1], items: [], line: lineNum, isTypeOnly: false });
653
+ continue;
654
+ }
655
+ // Multi-line import block
656
+ if (line.match(/^import\s*\(/)) {
657
+ let j = i + 1;
658
+ while (j < lines.length && !lines[j].includes(')')) {
659
+ const impMatch = lines[j].match(/^\s*(?:(\w+)\s+)?"([^"]+)"/);
660
+ if (impMatch) {
661
+ imports.push({
662
+ source: impMatch[2],
663
+ items: impMatch[1] ? [impMatch[1]] : [],
664
+ line: j + 1,
665
+ isTypeOnly: false,
666
+ });
667
+ }
668
+ j++;
669
+ }
670
+ continue;
671
+ }
672
+ // ---- Functions ----
673
+ const funcMatch = line.match(/^func\s+(?:\(([^)]+)\)\s+)?(\w+)\s*\(([^)]*)\)/);
674
+ if (funcMatch) {
675
+ const [, receiver, name, params] = funcMatch;
676
+ const isExported = /^[A-Z]/.test(name);
677
+ const endLine = findBlockEnd(lines, i, '{');
678
+ const signature = line.trim().replace(/\{$/, '').trim();
679
+ const symbol = {
680
+ name,
681
+ kind: receiver ? 'method' : 'function',
682
+ line: lineNum,
683
+ endLine,
684
+ signature: signature.slice(0, 200),
685
+ modifiers: isExported ? ['exported'] : [],
686
+ };
687
+ symbols.push(symbol);
688
+ if (isExported) {
689
+ exports.push({
690
+ name: receiver ? `(${receiver}).${name}` : name,
691
+ kind: 'function',
692
+ line: lineNum,
693
+ isDefault: false,
694
+ isReexport: false,
695
+ });
696
+ }
697
+ continue;
698
+ }
699
+ // ---- Types (structs, interfaces) ----
700
+ const typeMatch = line.match(/^type\s+(\w+)\s+(struct|interface)/);
701
+ if (typeMatch) {
702
+ const name = typeMatch[1];
703
+ const kind = typeMatch[2];
704
+ const isExported = /^[A-Z]/.test(name);
705
+ const endLine = findBlockEnd(lines, i, '{');
706
+ const symbol = {
707
+ name,
708
+ kind: kind === 'struct' ? 'class' : 'interface',
709
+ line: lineNum,
710
+ endLine,
711
+ signature: line.trim().replace(/\{$/, '').trim(),
712
+ modifiers: isExported ? ['exported'] : [],
713
+ children: [],
714
+ };
715
+ // Parse struct/interface members
716
+ const body = lines.slice(i + 1, endLine);
717
+ for (let k = 0; k < body.length; k++) {
718
+ const memberLine = body[k].trim();
719
+ // Struct field
720
+ const fieldMatch = memberLine.match(/^(\w+)\s+[\w*.\[\]]+/);
721
+ if (fieldMatch && !memberLine.startsWith('//') && kind === 'struct') {
722
+ symbol.children.push({
723
+ name: fieldMatch[1],
724
+ kind: 'variable',
725
+ line: lineNum + k + 1,
726
+ endLine: lineNum + k + 1,
727
+ });
728
+ }
729
+ // Interface method
730
+ const methodMatch = memberLine.match(/^(\w+)\s*\(/);
731
+ if (methodMatch && !memberLine.startsWith('//') && kind === 'interface') {
732
+ symbol.children.push({
733
+ name: methodMatch[1],
734
+ kind: 'method',
735
+ line: lineNum + k + 1,
736
+ endLine: lineNum + k + 1,
737
+ signature: memberLine.replace(/,$/, '').trim(),
738
+ });
739
+ }
740
+ }
741
+ symbols.push(symbol);
742
+ if (isExported) {
743
+ exports.push({
744
+ name,
745
+ kind: kind === 'struct' ? 'class' : 'interface',
746
+ line: lineNum,
747
+ isDefault: false,
748
+ isReexport: false,
749
+ });
750
+ }
751
+ continue;
752
+ }
753
+ // ---- Type aliases ----
754
+ const typeAliasMatch = line.match(/^type\s+(\w+)\s+[^{]/);
755
+ if (typeAliasMatch && !typeMatch) {
756
+ const name = typeAliasMatch[1];
757
+ const isExported = /^[A-Z]/.test(name);
758
+ symbols.push({
759
+ name,
760
+ kind: 'type',
761
+ line: lineNum,
762
+ endLine: lineNum,
763
+ signature: line.trim(),
764
+ modifiers: isExported ? ['exported'] : [],
765
+ });
766
+ continue;
767
+ }
768
+ // ---- Variables ----
769
+ const varMatch = line.match(/^var\s+(\w+)\s+/);
770
+ if (varMatch) {
771
+ const name = varMatch[1];
772
+ const isExported = /^[A-Z]/.test(name);
773
+ symbols.push({
774
+ name,
775
+ kind: 'variable',
776
+ line: lineNum,
777
+ endLine: lineNum,
778
+ modifiers: isExported ? ['exported'] : [],
779
+ });
780
+ continue;
781
+ }
782
+ // ---- Constants ----
783
+ const constMatch = line.match(/^const\s+(?:\(\s*)?(\w+)/);
784
+ if (constMatch) {
785
+ const name = constMatch[1];
786
+ const isExported = /^[A-Z]/.test(name);
787
+ symbols.push({
788
+ name,
789
+ kind: 'constant',
790
+ line: lineNum,
791
+ endLine: lineNum,
792
+ modifiers: isExported ? ['exported'] : [],
793
+ });
794
+ }
795
+ }
796
+ return { symbols, imports, exports };
797
+ }
798
+ // ---- Rust Parser ----
799
+ function parseRust(content) {
800
+ const lines = content.split('\n');
801
+ const symbols = [];
802
+ const imports = [];
803
+ const exports = [];
804
+ for (let i = 0; i < lines.length; i++) {
805
+ const line = lines[i];
806
+ const lineNum = i + 1;
807
+ // ---- Use statements ----
808
+ const useMatch = line.match(/^use\s+([^;]+);/);
809
+ if (useMatch) {
810
+ const usePath = useMatch[1].replace(/\{[^}]*\}/, '').trim();
811
+ const items = [];
812
+ const namedItems = useMatch[1].match(/\{([^}]+)\}/);
813
+ if (namedItems) {
814
+ items.push(...namedItems[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean));
815
+ }
816
+ imports.push({
817
+ source: usePath.replace(/::\{.*$/, ''),
818
+ items,
819
+ line: lineNum,
820
+ isTypeOnly: false,
821
+ });
822
+ continue;
823
+ }
824
+ // ---- Functions ----
825
+ const funcMatch = line.match(/^(pub\s+)?(?:async\s+)?(?:const\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*[<(]/);
826
+ if (funcMatch) {
827
+ const [, pubKw, name] = funcMatch;
828
+ const endLine = findBlockEnd(lines, i, '{');
829
+ const signature = line.trim().replace(/\{$/, '').trim();
830
+ symbols.push({
831
+ name,
832
+ kind: 'function',
833
+ line: lineNum,
834
+ endLine,
835
+ signature: signature.slice(0, 200),
836
+ modifiers: pubKw ? ['pub'] : [],
837
+ });
838
+ if (pubKw) {
839
+ exports.push({
840
+ name,
841
+ kind: 'function',
842
+ line: lineNum,
843
+ isDefault: false,
844
+ isReexport: false,
845
+ });
846
+ }
847
+ continue;
848
+ }
849
+ // ---- Structs ----
850
+ const structMatch = line.match(/^(pub\s+)?struct\s+(\w+)/);
851
+ if (structMatch) {
852
+ const [, pubKw, name] = structMatch;
853
+ const endLine = findBlockEnd(lines, i, '{');
854
+ const symbol = {
855
+ name,
856
+ kind: 'class',
857
+ line: lineNum,
858
+ endLine,
859
+ signature: line.trim().replace(/\{$/, '').trim(),
860
+ modifiers: pubKw ? ['pub'] : [],
861
+ children: [],
862
+ };
863
+ // Parse struct fields
864
+ const body = lines.slice(i + 1, endLine);
865
+ for (let k = 0; k < body.length; k++) {
866
+ const fieldMatch = body[k].match(/^\s+(pub\s+)?(\w+)\s*:/);
867
+ if (fieldMatch) {
868
+ symbol.children.push({
869
+ name: fieldMatch[2],
870
+ kind: 'variable',
871
+ line: lineNum + k + 1,
872
+ endLine: lineNum + k + 1,
873
+ modifiers: fieldMatch[1] ? ['pub'] : [],
874
+ });
875
+ }
876
+ }
877
+ symbols.push(symbol);
878
+ if (pubKw) {
879
+ exports.push({
880
+ name,
881
+ kind: 'class',
882
+ line: lineNum,
883
+ isDefault: false,
884
+ isReexport: false,
885
+ });
886
+ }
887
+ continue;
888
+ }
889
+ // ---- Enums ----
890
+ const enumMatch = line.match(/^(pub\s+)?enum\s+(\w+)/);
891
+ if (enumMatch) {
892
+ const [, pubKw, name] = enumMatch;
893
+ const endLine = findBlockEnd(lines, i, '{');
894
+ const symbol = {
895
+ name,
896
+ kind: 'enum',
897
+ line: lineNum,
898
+ endLine,
899
+ signature: line.trim().replace(/\{$/, '').trim(),
900
+ modifiers: pubKw ? ['pub'] : [],
901
+ children: [],
902
+ };
903
+ // Parse enum variants
904
+ const body = lines.slice(i + 1, endLine);
905
+ for (let k = 0; k < body.length; k++) {
906
+ const variantMatch = body[k].match(/^\s+(\w+)/);
907
+ if (variantMatch && !body[k].trim().startsWith('//')) {
908
+ symbol.children.push({
909
+ name: variantMatch[1],
910
+ kind: 'constant',
911
+ line: lineNum + k + 1,
912
+ endLine: lineNum + k + 1,
913
+ });
914
+ }
915
+ }
916
+ symbols.push(symbol);
917
+ if (pubKw) {
918
+ exports.push({
919
+ name,
920
+ kind: 'enum',
921
+ line: lineNum,
922
+ isDefault: false,
923
+ isReexport: false,
924
+ });
925
+ }
926
+ continue;
927
+ }
928
+ // ---- Traits ----
929
+ const traitMatch = line.match(/^(pub\s+)?trait\s+(\w+)/);
930
+ if (traitMatch) {
931
+ const [, pubKw, name] = traitMatch;
932
+ const endLine = findBlockEnd(lines, i, '{');
933
+ const symbol = {
934
+ name,
935
+ kind: 'interface',
936
+ line: lineNum,
937
+ endLine,
938
+ signature: line.trim().replace(/\{$/, '').trim(),
939
+ modifiers: pubKw ? ['pub'] : [],
940
+ children: [],
941
+ };
942
+ // Parse trait methods
943
+ const body = lines.slice(i + 1, endLine);
944
+ for (let k = 0; k < body.length; k++) {
945
+ const methodMatch = body[k].match(/^\s+(?:async\s+)?fn\s+(\w+)/);
946
+ if (methodMatch) {
947
+ symbol.children.push({
948
+ name: methodMatch[1],
949
+ kind: 'method',
950
+ line: lineNum + k + 1,
951
+ endLine: findBlockEnd(body, k, '{') + lineNum + 1,
952
+ signature: body[k].trim().replace(/\{$/, '').trim(),
953
+ });
954
+ }
955
+ }
956
+ symbols.push(symbol);
957
+ if (pubKw) {
958
+ exports.push({
959
+ name,
960
+ kind: 'interface',
961
+ line: lineNum,
962
+ isDefault: false,
963
+ isReexport: false,
964
+ });
965
+ }
966
+ continue;
967
+ }
968
+ // ---- Impl blocks ----
969
+ const implMatch = line.match(/^(pub\s+)?impl\s+(?:<[^>]+>\s*)?(?:trait\s+\w+\s+for\s+)?(\w+)/);
970
+ if (implMatch) {
971
+ const name = implMatch[2];
972
+ const endLine = findBlockEnd(lines, i, '{');
973
+ symbols.push({
974
+ name: `impl ${name}`,
975
+ kind: 'namespace',
976
+ line: lineNum,
977
+ endLine,
978
+ signature: line.trim().replace(/\{$/, '').trim(),
979
+ modifiers: implMatch[1] ? ['pub'] : [],
980
+ });
981
+ continue;
982
+ }
983
+ // ---- Type aliases ----
984
+ const typeAliasMatch = line.match(/^(pub\s+)?type\s+(\w+)\s*(?:<[^>]+>)?\s*=/);
985
+ if (typeAliasMatch) {
986
+ const [, pubKw, name] = typeAliasMatch;
987
+ symbols.push({
988
+ name,
989
+ kind: 'type',
990
+ line: lineNum,
991
+ endLine: lineNum,
992
+ signature: line.trim(),
993
+ modifiers: pubKw ? ['pub'] : [],
994
+ });
995
+ continue;
996
+ }
997
+ // ---- Static/Const ----
998
+ const staticMatch = line.match(/^(pub\s+)?(?:static|const)\s+(?:mut\s+)?(\w+)/);
999
+ if (staticMatch) {
1000
+ const [, pubKw, name] = staticMatch;
1001
+ symbols.push({
1002
+ name,
1003
+ kind: 'constant',
1004
+ line: lineNum,
1005
+ endLine: lineNum,
1006
+ modifiers: pubKw ? ['pub'] : [],
1007
+ });
1008
+ }
1009
+ }
1010
+ return { symbols, imports, exports };
1011
+ }
1012
+ // ---- Java Parser ----
1013
+ function parseJava(content) {
1014
+ const lines = content.split('\n');
1015
+ const symbols = [];
1016
+ const imports = [];
1017
+ const exports = [];
1018
+ for (let i = 0; i < lines.length; i++) {
1019
+ const line = lines[i];
1020
+ const lineNum = i + 1;
1021
+ // ---- Imports ----
1022
+ const importMatch = line.match(/^import\s+(?:static\s+)?([^;]+);/);
1023
+ if (importMatch) {
1024
+ const source = importMatch[1];
1025
+ const parts = source.split('.');
1026
+ const isStatic = line.includes('import static');
1027
+ imports.push({
1028
+ source: isStatic ? parts.slice(0, -1).join('.') : source,
1029
+ items: isStatic ? [parts[parts.length - 1]] : [],
1030
+ line: lineNum,
1031
+ isTypeOnly: false,
1032
+ });
1033
+ continue;
1034
+ }
1035
+ // ---- Package ----
1036
+ const pkgMatch = line.match(/^package\s+([^;]+);/);
1037
+ if (pkgMatch) {
1038
+ symbols.push({
1039
+ name: pkgMatch[1],
1040
+ kind: 'namespace',
1041
+ line: lineNum,
1042
+ endLine: lineNum,
1043
+ signature: line.trim(),
1044
+ });
1045
+ continue;
1046
+ }
1047
+ // ---- Classes ----
1048
+ const classMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:final\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/);
1049
+ if (classMatch) {
1050
+ const name = classMatch[1];
1051
+ const isPublic = line.includes('public ');
1052
+ const endLine = findBlockEnd(lines, i, '{');
1053
+ const modifiers = extractJavaModifiers(line);
1054
+ const signature = line.trim().replace(/\{$/, '').trim();
1055
+ const symbol = {
1056
+ name,
1057
+ kind: 'class',
1058
+ line: lineNum,
1059
+ endLine,
1060
+ signature: signature.slice(0, 200),
1061
+ modifiers,
1062
+ children: [],
1063
+ };
1064
+ // Parse class members
1065
+ const body = lines.slice(i + 1, endLine);
1066
+ for (let k = 0; k < body.length; k++) {
1067
+ const memberLine = body[k];
1068
+ // Methods
1069
+ const methodMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|final|abstract|synchronized|native|strictfp)\s+)+(?:<[^>]+>\s+)?(?:\w+(?:\[\])*\s+)?(\w+)\s*\(/);
1070
+ if (methodMatch && !memberLine.includes('new ') && !memberLine.match(/^\s+(?:if|while|for|switch|catch)\s*\(/)) {
1071
+ const methodModifiers = extractJavaModifiers(memberLine);
1072
+ symbol.children.push({
1073
+ name: methodMatch[1],
1074
+ kind: 'method',
1075
+ line: lineNum + k + 1,
1076
+ endLine: findBlockEnd(body, k, '{') + lineNum + 1,
1077
+ signature: memberLine.trim().replace(/\{$/, '').trim().slice(0, 200),
1078
+ modifiers: methodModifiers,
1079
+ });
1080
+ }
1081
+ // Fields (simplified)
1082
+ const fieldMatch = memberLine.match(/^\s+(?:(?:public|private|protected|static|final|volatile|transient)\s+)+(?:\w+(?:<[^>]+>)?(?:\[\])*\s+)(\w+)\s*[;=]/);
1083
+ if (fieldMatch) {
1084
+ symbol.children.push({
1085
+ name: fieldMatch[1],
1086
+ kind: 'variable',
1087
+ line: lineNum + k + 1,
1088
+ endLine: lineNum + k + 1,
1089
+ modifiers: extractJavaModifiers(memberLine),
1090
+ });
1091
+ }
1092
+ }
1093
+ symbols.push(symbol);
1094
+ if (isPublic) {
1095
+ exports.push({
1096
+ name,
1097
+ kind: 'class',
1098
+ line: lineNum,
1099
+ isDefault: false,
1100
+ isReexport: false,
1101
+ });
1102
+ }
1103
+ continue;
1104
+ }
1105
+ // ---- Interfaces ----
1106
+ const ifaceMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?interface\s+(\w+)/);
1107
+ if (ifaceMatch) {
1108
+ const name = ifaceMatch[1];
1109
+ const isPublic = line.includes('public ');
1110
+ const endLine = findBlockEnd(lines, i, '{');
1111
+ const modifiers = extractJavaModifiers(line);
1112
+ const symbol = {
1113
+ name,
1114
+ kind: 'interface',
1115
+ line: lineNum,
1116
+ endLine,
1117
+ signature: line.trim().replace(/\{$/, '').trim(),
1118
+ modifiers,
1119
+ children: [],
1120
+ };
1121
+ // Parse interface methods
1122
+ const body = lines.slice(i + 1, endLine);
1123
+ for (let k = 0; k < body.length; k++) {
1124
+ const methodMatch = body[k].match(/^\s+(?:default\s+|static\s+)?(?:<[^>]+>\s+)?(?:\w+\s+)?(\w+)\s*\(/);
1125
+ if (methodMatch) {
1126
+ symbol.children.push({
1127
+ name: methodMatch[1],
1128
+ kind: 'method',
1129
+ line: lineNum + k + 1,
1130
+ endLine: findBlockEnd(body, k, '{') + lineNum + 1,
1131
+ signature: body[k].trim().replace(/[;{]$/, '').trim(),
1132
+ });
1133
+ }
1134
+ }
1135
+ symbols.push(symbol);
1136
+ if (isPublic) {
1137
+ exports.push({
1138
+ name,
1139
+ kind: 'interface',
1140
+ line: lineNum,
1141
+ isDefault: false,
1142
+ isReexport: false,
1143
+ });
1144
+ }
1145
+ continue;
1146
+ }
1147
+ // ---- Enums ----
1148
+ const enumMatch = line.match(/(?:(?:public|private|protected)\s+)?(?:static\s+)?enum\s+(\w+)/);
1149
+ if (enumMatch) {
1150
+ const name = enumMatch[1];
1151
+ const endLine = findBlockEnd(lines, i, '{');
1152
+ const modifiers = extractJavaModifiers(line);
1153
+ const symbol = {
1154
+ name,
1155
+ kind: 'enum',
1156
+ line: lineNum,
1157
+ endLine,
1158
+ signature: line.trim().replace(/\{$/, '').trim(),
1159
+ modifiers,
1160
+ children: [],
1161
+ };
1162
+ // Parse enum constants
1163
+ const body = lines.slice(i + 1, endLine);
1164
+ for (let k = 0; k < body.length; k++) {
1165
+ const constantMatch = body[k].match(/^\s+([A-Z_][A-Z_0-9]*)(?:\s*[,(;])/);
1166
+ if (constantMatch) {
1167
+ symbol.children.push({
1168
+ name: constantMatch[1],
1169
+ kind: 'constant',
1170
+ line: lineNum + k + 1,
1171
+ endLine: lineNum + k + 1,
1172
+ });
1173
+ }
1174
+ }
1175
+ symbols.push(symbol);
1176
+ }
1177
+ }
1178
+ return { symbols, imports, exports };
1179
+ }
1180
+ // ---- CSS Parser ----
1181
+ function parseCSS(content) {
1182
+ const lines = content.split('\n');
1183
+ const symbols = [];
1184
+ const imports = [];
1185
+ const exports = [];
1186
+ for (let i = 0; i < lines.length; i++) {
1187
+ const line = lines[i];
1188
+ const lineNum = i + 1;
1189
+ // ---- Imports ----
1190
+ const importMatch = line.match(/@import\s+(?:url\()?['"]?([^'")\s;]+)['"]?\)?/);
1191
+ if (importMatch) {
1192
+ imports.push({
1193
+ source: importMatch[1],
1194
+ items: [],
1195
+ line: lineNum,
1196
+ isTypeOnly: false,
1197
+ });
1198
+ continue;
1199
+ }
1200
+ // ---- Selectors ----
1201
+ const selectorMatch = line.match(/^([.#][\w-]+|[\w-]+\s*\{)/);
1202
+ if (selectorMatch) {
1203
+ const name = selectorMatch[1].replace(/\s*\{$/, '').trim();
1204
+ const kind = name.startsWith('.') ? 'variable' : name.startsWith('#') ? 'constant' : 'class';
1205
+ const endLine = findBlockEnd(lines, i, '{');
1206
+ symbols.push({
1207
+ name,
1208
+ kind,
1209
+ line: lineNum,
1210
+ endLine,
1211
+ signature: line.trim(),
1212
+ });
1213
+ continue;
1214
+ }
1215
+ // ---- Media queries ----
1216
+ const mediaMatch = line.match(/^@media\s+([^{]+)\s*\{/);
1217
+ if (mediaMatch) {
1218
+ const endLine = findBlockEnd(lines, i, '{');
1219
+ symbols.push({
1220
+ name: `@media ${mediaMatch[1].trim()}`,
1221
+ kind: 'namespace',
1222
+ line: lineNum,
1223
+ endLine,
1224
+ signature: line.trim(),
1225
+ });
1226
+ continue;
1227
+ }
1228
+ // ---- Keyframes ----
1229
+ const keyframesMatch = line.match(/^@keyframes\s+([\w-]+)/);
1230
+ if (keyframesMatch) {
1231
+ const endLine = findBlockEnd(lines, i, '{');
1232
+ symbols.push({
1233
+ name: keyframesMatch[1],
1234
+ kind: 'function',
1235
+ line: lineNum,
1236
+ endLine,
1237
+ signature: line.trim(),
1238
+ });
1239
+ }
1240
+ }
1241
+ return { symbols, imports, exports };
1242
+ }
1243
+ // ---- HTML Parser ----
1244
+ function parseHTML(content) {
1245
+ const lines = content.split('\n');
1246
+ const symbols = [];
1247
+ const imports = [];
1248
+ const exports = [];
1249
+ for (let i = 0; i < lines.length; i++) {
1250
+ const line = lines[i];
1251
+ const lineNum = i + 1;
1252
+ // ---- Script imports ----
1253
+ const scriptSrc = line.match(/<script[^>]+src=["']([^"']+)["']/);
1254
+ if (scriptSrc) {
1255
+ imports.push({
1256
+ source: scriptSrc[1],
1257
+ items: [],
1258
+ line: lineNum,
1259
+ isTypeOnly: false,
1260
+ });
1261
+ continue;
1262
+ }
1263
+ // ---- Style imports ----
1264
+ const styleHref = line.match(/<link[^>]+href=["']([^"']+)["'][^>]*rel=["']stylesheet["']/);
1265
+ if (styleHref) {
1266
+ imports.push({
1267
+ source: styleHref[1],
1268
+ items: [],
1269
+ line: lineNum,
1270
+ isTypeOnly: false,
1271
+ });
1272
+ continue;
1273
+ }
1274
+ // ---- IDs ----
1275
+ const idMatch = line.match(/id=["']([^"']+)["']/);
1276
+ if (idMatch) {
1277
+ symbols.push({
1278
+ name: `#${idMatch[1]}`,
1279
+ kind: 'constant',
1280
+ line: lineNum,
1281
+ endLine: lineNum,
1282
+ });
1283
+ }
1284
+ // ---- Template/component tags ----
1285
+ const templateMatch = line.match(/<template\s+id=["']([^"']+)["']/);
1286
+ if (templateMatch) {
1287
+ const endLine = findHTMLTagEnd(lines, i, 'template');
1288
+ symbols.push({
1289
+ name: templateMatch[1],
1290
+ kind: 'class',
1291
+ line: lineNum,
1292
+ endLine,
1293
+ signature: line.trim(),
1294
+ });
1295
+ }
1296
+ }
1297
+ return { symbols, imports, exports };
1298
+ }
1299
+ // ============================================================
1300
+ // Helper Functions for Block Detection
1301
+ // ============================================================
1302
+ function findBlockEnd(lines, startLine, openChar = '{') {
1303
+ let depth = 0;
1304
+ const closeChar = openChar === '{' ? '}' : openChar === '(' ? ')' : openChar === '[' ? ']' : '';
1305
+ for (let i = startLine; i < lines.length; i++) {
1306
+ const line = lines[i];
1307
+ // Skip strings (rough approximation)
1308
+ const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '');
1309
+ for (const ch of cleaned) {
1310
+ if (ch === openChar)
1311
+ depth++;
1312
+ if (ch === closeChar)
1313
+ depth--;
1314
+ }
1315
+ if (depth <= 0 && i > startLine) {
1316
+ return i + 1;
1317
+ }
1318
+ // Handle single-line blocks
1319
+ if (i === startLine && depth === 0) {
1320
+ return i + 1;
1321
+ }
1322
+ }
1323
+ return lines.length;
1324
+ }
1325
+ function findTypeEnd(lines, startLine) {
1326
+ // Type aliases can span multiple lines with unions/intersections
1327
+ let depth = 0;
1328
+ let foundEquals = false;
1329
+ for (let i = startLine; i < lines.length; i++) {
1330
+ const line = lines[i];
1331
+ if (line.includes('='))
1332
+ foundEquals = true;
1333
+ if (foundEquals) {
1334
+ // Count parentheses and brackets for generics
1335
+ for (const ch of line) {
1336
+ if (ch === '(' || ch === '[' || ch === '{')
1337
+ depth++;
1338
+ if (ch === ')' || ch === ']' || ch === '}')
1339
+ depth--;
1340
+ }
1341
+ // End of type when depth is 0 and line doesn't end with continuation
1342
+ if (depth <= 0 && !line.trim().endsWith('|') && !line.trim().endsWith('&') && i > startLine) {
1343
+ return i + 1;
1344
+ }
1345
+ // Simple single-line type
1346
+ if (depth === 0 && i === startLine && !line.trim().endsWith('|') && !line.trim().endsWith('&')) {
1347
+ return i + 1;
1348
+ }
1349
+ }
1350
+ }
1351
+ return lines.length;
1352
+ }
1353
+ function findStatementEnd(lines, startLine) {
1354
+ // For const/let arrow functions, find the end of the statement
1355
+ let depth = 0;
1356
+ let parenDepth = 0;
1357
+ for (let i = startLine; i < lines.length; i++) {
1358
+ const line = lines[i];
1359
+ const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '');
1360
+ for (const ch of cleaned) {
1361
+ if (ch === '{')
1362
+ depth++;
1363
+ if (ch === '}')
1364
+ depth--;
1365
+ if (ch === '(')
1366
+ parenDepth++;
1367
+ if (ch === ')')
1368
+ parenDepth--;
1369
+ }
1370
+ if (depth === 0 && parenDepth === 0 && i > startLine) {
1371
+ return i + 1;
1372
+ }
1373
+ }
1374
+ return Math.min(startLine + 30, lines.length);
1375
+ }
1376
+ function findPythonBlockEnd(lines, startLine) {
1377
+ if (startLine >= lines.length)
1378
+ return lines.length;
1379
+ // Find the indentation of the block start
1380
+ const startIndent = lines[startLine].match(/^(\s*)/)?.[1].length || 0;
1381
+ for (let i = startLine + 1; i < lines.length; i++) {
1382
+ const line = lines[i];
1383
+ if (line.trim() === '')
1384
+ continue;
1385
+ const currentIndent = line.match(/^(\s*)/)?.[1].length || 0;
1386
+ if (currentIndent <= startIndent && line.trim() !== '') {
1387
+ return i;
1388
+ }
1389
+ }
1390
+ return lines.length;
1391
+ }
1392
+ function findHTMLTagEnd(lines, startLine, tagName) {
1393
+ const closeTag = `</${tagName}>`;
1394
+ for (let i = startLine + 1; i < lines.length; i++) {
1395
+ if (lines[i].includes(closeTag)) {
1396
+ return i + 1;
1397
+ }
1398
+ }
1399
+ return lines.length;
1400
+ }
1401
+ function extractModifiers(line) {
1402
+ const modifiers = [];
1403
+ const modifierKeywords = ['export', 'default', 'declare', 'abstract', 'async', 'static', 'public', 'private', 'protected', 'readonly', 'override', 'const', 'let', 'var'];
1404
+ for (const kw of modifierKeywords) {
1405
+ // Use word boundary check
1406
+ const regex = new RegExp(`\\b${kw}\\b`);
1407
+ if (regex.test(line)) {
1408
+ modifiers.push(kw);
1409
+ }
1410
+ }
1411
+ return modifiers;
1412
+ }
1413
+ function extractJavaModifiers(line) {
1414
+ const modifiers = [];
1415
+ const modifierKeywords = ['public', 'private', 'protected', 'static', 'final', 'abstract', 'synchronized', 'native', 'strictfp', 'volatile', 'transient', 'default'];
1416
+ for (const kw of modifierKeywords) {
1417
+ const regex = new RegExp(`\\b${kw}\\b`);
1418
+ if (regex.test(line)) {
1419
+ modifiers.push(kw);
1420
+ }
1421
+ }
1422
+ return modifiers;
1423
+ }
1424
+ // ============================================================
1425
+ // Main Integration Class
1426
+ // ============================================================
1427
+ export class TreeSitterIntegration {
1428
+ projectRoot;
1429
+ fileCache = new Map();
1430
+ repoMapCache = null;
1431
+ repoMapCacheTime = 0;
1432
+ CACHE_TTL = 60000; // 1 minute
1433
+ constructor(projectRoot) {
1434
+ this.projectRoot = projectRoot;
1435
+ }
1436
+ // ---- Core Analysis Methods ----
1437
+ analyzeFile(filePath) {
1438
+ const absolutePath = this.resolvePath(filePath);
1439
+ if (!existsSync(absolutePath))
1440
+ return null;
1441
+ const content = this.readFile(absolutePath);
1442
+ if (content === null)
1443
+ return null;
1444
+ const language = this.detectLanguage(absolutePath);
1445
+ if (language === 'unknown')
1446
+ return null;
1447
+ const parsed = this.parseContent(content, language);
1448
+ const summary = this.generateFileSummary(absolutePath, language, parsed);
1449
+ return {
1450
+ path: relative(this.projectRoot, absolutePath),
1451
+ language,
1452
+ symbols: parsed.symbols,
1453
+ imports: parsed.imports,
1454
+ exports: parsed.exports,
1455
+ summary,
1456
+ };
1457
+ }
1458
+ analyzeDirectory(dirPath, maxDepth = 5) {
1459
+ const absoluteDir = this.resolvePath(dirPath);
1460
+ if (!existsSync(absoluteDir) || !statSync(absoluteDir).isDirectory())
1461
+ return [];
1462
+ const files = [];
1463
+ this.walkDirectory(absoluteDir, files, 0, maxDepth);
1464
+ return files;
1465
+ }
1466
+ buildRepoMap(projectRoot) {
1467
+ const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
1468
+ // Check cache
1469
+ if (this.repoMapCache && Date.now() - this.repoMapCacheTime < this.CACHE_TTL) {
1470
+ return this.repoMapCache;
1471
+ }
1472
+ const files = this.analyzeDirectory(root);
1473
+ const languageBreakdown = {};
1474
+ let totalSymbols = 0;
1475
+ for (const file of files) {
1476
+ const langName = LANGUAGE_NAMES[file.language] || file.language;
1477
+ languageBreakdown[langName] = (languageBreakdown[langName] || 0) + 1;
1478
+ totalSymbols += file.symbols.length;
1479
+ }
1480
+ this.repoMapCache = {
1481
+ root,
1482
+ files,
1483
+ totalSymbols,
1484
+ totalFiles: files.length,
1485
+ languageBreakdown,
1486
+ generatedAt: Date.now(),
1487
+ };
1488
+ this.repoMapCacheTime = Date.now();
1489
+ return this.repoMapCache;
1490
+ }
1491
+ // ---- Symbol Methods ----
1492
+ getSymbols(filePath) {
1493
+ const fileMap = this.analyzeFile(filePath);
1494
+ return fileMap?.symbols || [];
1495
+ }
1496
+ getCallGraph(filePath) {
1497
+ const absolutePath = this.resolvePath(filePath);
1498
+ const content = this.readFile(absolutePath);
1499
+ if (!content)
1500
+ return [];
1501
+ const language = this.detectLanguage(absolutePath);
1502
+ const parsed = this.parseContent(content, language);
1503
+ const nodes = [];
1504
+ for (const symbol of parsed.symbols) {
1505
+ if (symbol.kind === 'function' || symbol.kind === 'method') {
1506
+ // Extract function calls from the function body
1507
+ const lines = content.split('\n');
1508
+ const body = lines.slice(symbol.line - 1, symbol.endLine).join('\n');
1509
+ const calls = this.extractCalls(body, language);
1510
+ nodes.push({
1511
+ name: symbol.name,
1512
+ kind: symbol.kind,
1513
+ line: symbol.line,
1514
+ calls: Array.from(new Set(calls)),
1515
+ calledBy: [], // Filled in by cross-referencing
1516
+ });
1517
+ }
1518
+ }
1519
+ // Cross-reference: who calls whom
1520
+ const allCalls = new Map();
1521
+ for (const node of nodes) {
1522
+ for (const call of node.calls) {
1523
+ if (!allCalls.has(call))
1524
+ allCalls.set(call, new Set());
1525
+ allCalls.get(call).add(node.name);
1526
+ }
1527
+ }
1528
+ for (const node of nodes) {
1529
+ const callers = allCalls.get(node.name);
1530
+ node.calledBy = callers ? Array.from(callers) : [];
1531
+ }
1532
+ return nodes;
1533
+ }
1534
+ getDependencies(filePath) {
1535
+ const fileMap = this.analyzeFile(filePath);
1536
+ return fileMap?.imports || [];
1537
+ }
1538
+ findDefinition(symbol, projectRoot) {
1539
+ const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
1540
+ const files = this.analyzeDirectory(root);
1541
+ const results = [];
1542
+ for (const file of files) {
1543
+ for (const sym of file.symbols) {
1544
+ if (sym.name === symbol) {
1545
+ results.push({
1546
+ file: file.path,
1547
+ line: sym.line,
1548
+ kind: sym.kind,
1549
+ });
1550
+ }
1551
+ // Check children
1552
+ if (sym.children) {
1553
+ for (const child of sym.children) {
1554
+ if (child.name === symbol) {
1555
+ results.push({
1556
+ file: file.path,
1557
+ line: child.line,
1558
+ kind: child.kind,
1559
+ });
1560
+ }
1561
+ }
1562
+ }
1563
+ }
1564
+ }
1565
+ return results;
1566
+ }
1567
+ findReferences(symbol, projectRoot) {
1568
+ const root = projectRoot ? this.resolvePath(projectRoot) : this.projectRoot;
1569
+ const results = [];
1570
+ // Try using ripgrep first for speed
1571
+ try {
1572
+ const rgResult = execSync(`rg --line-number --color never --max-count 50 "\\b${symbol}\\b" "${root}"`, { encoding: 'utf-8', timeout: 30000, cwd: root });
1573
+ for (const line of rgResult.trim().split('\n')) {
1574
+ const match = line.match(/^(.+?):(\d+):(.+)$/);
1575
+ if (match) {
1576
+ const [, file, lineNum, context] = match;
1577
+ // Skip binary files and common non-source directories
1578
+ const ext = extname(file);
1579
+ if (!BINARY_EXTENSIONS.has(ext) && !SKIP_DIRS.has(dirname(file).split('/')[0])) {
1580
+ results.push({
1581
+ file: relative(root, file),
1582
+ line: parseInt(lineNum, 10),
1583
+ context: context.trim(),
1584
+ });
1585
+ }
1586
+ }
1587
+ }
1588
+ }
1589
+ catch {
1590
+ // Fallback: search in analyzed files
1591
+ const files = this.analyzeDirectory(root);
1592
+ for (const file of files) {
1593
+ const absolutePath = join(root, file.path);
1594
+ const content = this.readFile(absolutePath);
1595
+ if (!content)
1596
+ continue;
1597
+ const lines = content.split('\n');
1598
+ for (let i = 0; i < lines.length; i++) {
1599
+ if (new RegExp(`\\b${symbol}\\b`).test(lines[i])) {
1600
+ results.push({
1601
+ file: file.path,
1602
+ line: i + 1,
1603
+ context: lines[i].trim(),
1604
+ });
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+ return results.slice(0, 100);
1610
+ }
1611
+ getOutline(filePath) {
1612
+ const fileMap = this.analyzeFile(filePath);
1613
+ if (!fileMap)
1614
+ return [];
1615
+ return fileMap.symbols.map(sym => this.symbolToOutline(sym));
1616
+ }
1617
+ detectLanguage(filePath) {
1618
+ const ext = extname(filePath).toLowerCase();
1619
+ return LANGUAGE_EXTENSIONS[ext] || 'unknown';
1620
+ }
1621
+ getDiagnostics(filePath) {
1622
+ const absolutePath = this.resolvePath(filePath);
1623
+ const content = this.readFile(absolutePath);
1624
+ if (!content)
1625
+ return [];
1626
+ const language = this.detectLanguage(absolutePath);
1627
+ const diagnostics = [];
1628
+ const lines = content.split('\n');
1629
+ // Basic syntax checks - common issues across languages
1630
+ // 1. Unmatched brackets
1631
+ const bracketStack = [];
1632
+ const pairs = { '(': ')', '[': ']', '{': '}' };
1633
+ for (let i = 0; i < lines.length; i++) {
1634
+ const line = lines[i];
1635
+ // Skip string content (rough)
1636
+ const cleaned = line.replace(/(["'`])(?:(?!\1).|\\\1)*\1/g, '').replace(/\/\/.*$|\/\*[\s\S]*?\*\//g, '');
1637
+ for (let j = 0; j < cleaned.length; j++) {
1638
+ const ch = cleaned[j];
1639
+ if (pairs[ch]) {
1640
+ bracketStack.push({ char: ch, line: i + 1, col: j + 1 });
1641
+ }
1642
+ else if (Object.values(pairs).includes(ch)) {
1643
+ const last = bracketStack.pop();
1644
+ if (!last) {
1645
+ diagnostics.push({
1646
+ line: i + 1,
1647
+ column: j + 1,
1648
+ severity: 'warning',
1649
+ message: `Unexpected closing bracket '${ch}'`,
1650
+ rule: 'unmatched-bracket',
1651
+ });
1652
+ }
1653
+ else if (pairs[last.char] !== ch) {
1654
+ diagnostics.push({
1655
+ line: i + 1,
1656
+ column: j + 1,
1657
+ severity: 'warning',
1658
+ message: `Mismatched bracket: expected '${pairs[last.char]}' but found '${ch}' (opened at line ${last.line})`,
1659
+ rule: 'mismatched-bracket',
1660
+ });
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ // Remaining unclosed brackets
1666
+ for (const bracket of bracketStack) {
1667
+ diagnostics.push({
1668
+ line: bracket.line,
1669
+ column: bracket.col,
1670
+ severity: 'warning',
1671
+ message: `Unclosed bracket '${bracket.char}'`,
1672
+ rule: 'unclosed-bracket',
1673
+ });
1674
+ }
1675
+ // 2. Language-specific checks
1676
+ if (language === 'typescript' || language === 'javascript') {
1677
+ // Check for missing semicolons in specific patterns
1678
+ for (let i = 0; i < lines.length; i++) {
1679
+ const trimmed = lines[i].trim();
1680
+ if (trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('*') && !trimmed.startsWith('/*')) {
1681
+ // Detect potential issues
1682
+ if (/^(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*[^;{]+$/.test(trimmed) && !trimmed.endsWith(',')) {
1683
+ // This could be a missing semicolon, but it's too noisy to report all
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ if (language === 'python') {
1689
+ // Check for mixed tabs/spaces
1690
+ let usesTabs = false;
1691
+ let usesSpaces = false;
1692
+ for (const line of lines) {
1693
+ if (line.startsWith('\t'))
1694
+ usesTabs = true;
1695
+ if (line.startsWith(' '))
1696
+ usesSpaces = true;
1697
+ }
1698
+ if (usesTabs && usesSpaces) {
1699
+ diagnostics.push({
1700
+ line: 1,
1701
+ column: 1,
1702
+ severity: 'warning',
1703
+ message: 'Mixed tabs and spaces for indentation',
1704
+ rule: 'mixed-indentation',
1705
+ });
1706
+ }
1707
+ }
1708
+ return diagnostics;
1709
+ }
1710
+ formatRepoMap(projectRoot, maxTokens) {
1711
+ const repoMap = this.buildRepoMap(projectRoot);
1712
+ const maxChars = (maxTokens || 4000) * 4; // ~4 chars per token
1713
+ const lines = [];
1714
+ lines.push(`# Repository Map: ${basename(repoMap.root)}`);
1715
+ lines.push(`# ${repoMap.totalFiles} files | ${repoMap.totalSymbols} symbols | Generated: ${new Date(repoMap.generatedAt).toISOString()}`);
1716
+ lines.push('');
1717
+ // Language breakdown
1718
+ const langEntries = Object.entries(repoMap.languageBreakdown)
1719
+ .sort(([, a], [, b]) => b - a);
1720
+ lines.push(`Languages: ${langEntries.map(([l, c]) => `${l}(${c})`).join(', ')}`);
1721
+ lines.push('');
1722
+ // File listing with symbols
1723
+ for (const file of repoMap.files) {
1724
+ const langName = LANGUAGE_NAMES[file.language] || file.language;
1725
+ const relPath = file.path;
1726
+ // File header
1727
+ const importCount = file.imports.length;
1728
+ const exportCount = file.exports.length;
1729
+ lines.push(`## ${relPath} [${langName}]`);
1730
+ if (importCount > 0 || exportCount > 0) {
1731
+ lines.push(` imports: ${importCount} | exports: ${exportCount}`);
1732
+ }
1733
+ // Symbols
1734
+ for (const sym of file.symbols) {
1735
+ const modifier = sym.modifiers?.length ? `[${sym.modifiers.join(',')}] ` : '';
1736
+ const icon = this.getSymbolIcon(sym.kind);
1737
+ const lineRange = sym.line === sym.endLine ? `:${sym.line}` : `:${sym.line}-${sym.endLine}`;
1738
+ const sig = sym.signature ? ` — ${sym.signature.slice(0, 80)}` : '';
1739
+ lines.push(` ${icon} ${modifier}${sym.kind} ${sym.name}${lineRange}${sig}`);
1740
+ // Children
1741
+ if (sym.children) {
1742
+ for (const child of sym.children) {
1743
+ const childMod = child.modifiers?.length ? `[${child.modifiers.join(',')}] ` : '';
1744
+ const childIcon = this.getSymbolIcon(child.kind);
1745
+ const childRange = child.line === child.endLine ? `:${child.line}` : `:${child.line}-${child.endLine}`;
1746
+ lines.push(` ${childIcon} ${childMod}${child.kind} ${child.name}${childRange}`);
1747
+ }
1748
+ }
1749
+ }
1750
+ lines.push('');
1751
+ // Check if we've exceeded the token limit
1752
+ const currentLength = lines.join('\n').length;
1753
+ if (currentLength > maxChars) {
1754
+ lines.push(`... [truncated, ${repoMap.files.length - repoMap.files.indexOf(file) - 1} more files]`);
1755
+ break;
1756
+ }
1757
+ }
1758
+ return lines.join('\n');
1759
+ }
1760
+ // ---- Cache Management ----
1761
+ invalidateCache(filePath) {
1762
+ if (filePath) {
1763
+ this.fileCache.delete(this.resolvePath(filePath));
1764
+ }
1765
+ else {
1766
+ this.fileCache.clear();
1767
+ }
1768
+ this.repoMapCache = null;
1769
+ this.repoMapCacheTime = 0;
1770
+ }
1771
+ // ---- Private Methods ----
1772
+ resolvePath(filePath) {
1773
+ if (filePath.startsWith('/'))
1774
+ return filePath;
1775
+ return join(this.projectRoot, filePath);
1776
+ }
1777
+ readFile(absolutePath) {
1778
+ try {
1779
+ if (!existsSync(absolutePath))
1780
+ return null;
1781
+ const stat = statSync(absolutePath);
1782
+ if (stat.isDirectory())
1783
+ return null;
1784
+ if (stat.size > 1024 * 1024)
1785
+ return null; // Skip files > 1MB
1786
+ return readFileSync(absolutePath, 'utf-8');
1787
+ }
1788
+ catch {
1789
+ return null;
1790
+ }
1791
+ }
1792
+ parseContent(content, language) {
1793
+ switch (language) {
1794
+ case 'typescript':
1795
+ case 'javascript':
1796
+ return parseTypeScript(content);
1797
+ case 'python':
1798
+ return parsePython(content);
1799
+ case 'go':
1800
+ return parseGo(content);
1801
+ case 'rust':
1802
+ return parseRust(content);
1803
+ case 'java':
1804
+ case 'kotlin':
1805
+ return parseJava(content);
1806
+ case 'css':
1807
+ return parseCSS(content);
1808
+ case 'html':
1809
+ return parseHTML(content);
1810
+ default:
1811
+ return { symbols: [], imports: [], exports: [] };
1812
+ }
1813
+ }
1814
+ walkDirectory(dir, results, depth, maxDepth) {
1815
+ if (depth > maxDepth)
1816
+ return;
1817
+ let entries;
1818
+ try {
1819
+ entries = readdirSync(dir, { withFileTypes: true });
1820
+ }
1821
+ catch {
1822
+ return;
1823
+ }
1824
+ for (const entry of entries) {
1825
+ if (SKIP_DIRS.has(entry.name))
1826
+ continue;
1827
+ if (entry.name.startsWith('.') && entry.name !== '.env')
1828
+ continue;
1829
+ const fullPath = join(dir, entry.name);
1830
+ if (entry.isDirectory()) {
1831
+ this.walkDirectory(fullPath, results, depth + 1, maxDepth);
1832
+ }
1833
+ else if (entry.isFile()) {
1834
+ const ext = extname(entry.name).toLowerCase();
1835
+ if (BINARY_EXTENSIONS.has(ext))
1836
+ continue;
1837
+ const language = LANGUAGE_EXTENSIONS[ext];
1838
+ if (!language)
1839
+ continue;
1840
+ const fileMap = this.analyzeFile(fullPath);
1841
+ if (fileMap) {
1842
+ results.push(fileMap);
1843
+ }
1844
+ }
1845
+ }
1846
+ }
1847
+ generateFileSummary(filePath, language, parsed) {
1848
+ const parts = [];
1849
+ const langName = LANGUAGE_NAMES[language] || language;
1850
+ parts.push(langName);
1851
+ if (parsed.symbols.length > 0) {
1852
+ const kinds = parsed.symbols.map(s => s.kind);
1853
+ const classCount = kinds.filter(k => k === 'class').length;
1854
+ const funcCount = kinds.filter(k => k === 'function').length;
1855
+ const ifaceCount = kinds.filter(k => k === 'interface').length;
1856
+ const typeCount = kinds.filter(k => k === 'type').length;
1857
+ const enumCount = kinds.filter(k => k === 'enum').length;
1858
+ const details = [];
1859
+ if (classCount)
1860
+ details.push(`${classCount} class${classCount > 1 ? 'es' : ''}`);
1861
+ if (funcCount)
1862
+ details.push(`${funcCount} fn${funcCount > 1 ? 's' : ''}`);
1863
+ if (ifaceCount)
1864
+ details.push(`${ifaceCount} iface${ifaceCount > 1 ? 's' : ''}`);
1865
+ if (typeCount)
1866
+ details.push(`${typeCount} type${typeCount > 1 ? 's' : ''}`);
1867
+ if (enumCount)
1868
+ details.push(`${enumCount} enum${enumCount > 1 ? 's' : ''}`);
1869
+ if (details.length > 0) {
1870
+ parts.push(details.join(', '));
1871
+ }
1872
+ }
1873
+ if (parsed.imports.length > 0) {
1874
+ parts.push(`${parsed.imports.length} import${parsed.imports.length > 1 ? 's' : ''}`);
1875
+ }
1876
+ if (parsed.exports.length > 0) {
1877
+ parts.push(`${parsed.exports.length} export${parsed.exports.length > 1 ? 's' : ''}`);
1878
+ }
1879
+ return parts.join(' | ');
1880
+ }
1881
+ extractCalls(body, language) {
1882
+ const calls = [];
1883
+ // Generic function call pattern
1884
+ const callPatterns = [
1885
+ /(\w+)\s*\(/g, // Direct calls: foo()
1886
+ /(\w+)\.\s*(\w+)\s*\(/g, // Method calls: obj.method()
1887
+ ];
1888
+ // Language-specific patterns
1889
+ switch (language) {
1890
+ case 'python':
1891
+ callPatterns.push(/self\.(\w+)\s*\(/g);
1892
+ break;
1893
+ case 'go':
1894
+ callPatterns.push(/(\w+)\.\s*(\w+)\s*\(/g);
1895
+ break;
1896
+ case 'rust':
1897
+ callPatterns.push(/(\w+)::(\w+)\s*\(/g);
1898
+ break;
1899
+ }
1900
+ // Keywords to exclude
1901
+ const keywords = new Set([
1902
+ 'if', 'else', 'for', 'while', 'switch', 'case', 'return', 'new',
1903
+ 'typeof', 'instanceof', 'throw', 'try', 'catch', 'finally',
1904
+ 'class', 'function', 'const', 'let', 'var', 'import', 'export',
1905
+ 'async', 'await', 'yield', 'from', 'as', 'extends', 'implements',
1906
+ 'interface', 'type', 'enum', 'namespace', 'module', 'require',
1907
+ 'console', 'process', 'Math', 'JSON', 'Object', 'Array', 'String',
1908
+ 'Number', 'Boolean', 'Promise', 'Map', 'Set', 'Error', 'self',
1909
+ 'super', 'this', 'def', 'elif', 'except', 'lambda', 'with',
1910
+ 'assert', 'raise', 'pass', 'break', 'continue', 'del', 'global',
1911
+ 'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict',
1912
+ 'tuple', 'set', 'bool', 'type', 'isinstance', 'hasattr', 'getattr',
1913
+ 'setattr', 'iter', 'next', 'enumerate', 'zip', 'map', 'filter',
1914
+ 'sorted', 'reversed', 'min', 'max', 'sum', 'abs', 'round',
1915
+ 'append', 'extend', 'insert', 'remove', 'pop', 'clear',
1916
+ 'keys', 'values', 'items', 'get', 'update',
1917
+ ]);
1918
+ for (const pattern of callPatterns) {
1919
+ let match;
1920
+ while ((match = pattern.exec(body)) !== null) {
1921
+ const name = match[1] || match[2] || match[0];
1922
+ if (!keywords.has(name) && name.length > 1 && /^[a-zA-Z_]/.test(name)) {
1923
+ calls.push(name);
1924
+ }
1925
+ }
1926
+ }
1927
+ return Array.from(new Set(calls));
1928
+ }
1929
+ symbolToOutline(sym) {
1930
+ return {
1931
+ name: sym.name,
1932
+ kind: sym.kind,
1933
+ line: sym.line,
1934
+ endLine: sym.endLine,
1935
+ icon: this.getSymbolIcon(sym.kind),
1936
+ children: sym.children?.map(c => this.symbolToOutline(c)) || [],
1937
+ signature: sym.signature,
1938
+ };
1939
+ }
1940
+ getSymbolIcon(kind) {
1941
+ const icons = {
1942
+ class: '🔷',
1943
+ function: '⚡',
1944
+ method: '🔹',
1945
+ variable: '📌',
1946
+ interface: '🔌',
1947
+ type: '🏷️',
1948
+ enum: '🎯',
1949
+ constant: '🔒',
1950
+ namespace: '📦',
1951
+ import: '📥',
1952
+ };
1953
+ return icons[kind] || '📄';
1954
+ }
1955
+ }
1956
+ //# sourceMappingURL=tree-sitter.js.map