neuronlayer 0.1.6 → 0.1.8

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.

Potentially problematic release.


This version of neuronlayer might be problematic. Click here for more details.

package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "neuronlayer",
3
- "version": "0.1.6",
4
- "description": "Persistent memory layer for AI coding assistants - MCP server that makes AI truly understand your codebase",
3
+ "version": "0.1.8",
4
+ "description": "Code intelligence layer disguised as memory - MCP server giving AI persistent understanding of your codebase",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -159,13 +159,19 @@ export function exportDecisions(
159
159
  };
160
160
  }
161
161
 
162
- // Open database and get decisions
163
- const dbPath = join(projectInfo.dataDir, 'memorylayer.db');
162
+ // Open database and get decisions (check both new and old names)
163
+ let dbPath = join(projectInfo.dataDir, 'neuronlayer.db');
164
164
  if (!existsSync(dbPath)) {
165
- return {
166
- success: false,
167
- message: `Project database not found. Has the project been indexed?`
168
- };
165
+ // Fall back to old name for backwards compatibility
166
+ const oldDbPath = join(projectInfo.dataDir, 'memorylayer.db');
167
+ if (existsSync(oldDbPath)) {
168
+ dbPath = oldDbPath;
169
+ } else {
170
+ return {
171
+ success: false,
172
+ message: `Project database not found. Has the project been indexed?`
173
+ };
174
+ }
169
175
  }
170
176
 
171
177
  const db = initializeDatabase(dbPath);
@@ -1,5 +1,5 @@
1
1
  import { join, basename } from 'path';
2
- import { existsSync, mkdirSync, readFileSync, statSync } from 'fs';
2
+ import { existsSync, mkdirSync, readFileSync, statSync, renameSync } from 'fs';
3
3
  import { initializeDatabase, closeDatabase } from '../storage/database.js';
4
4
  import { Tier1Storage } from '../storage/tier1.js';
5
5
  import { Tier2Storage } from '../storage/tier2.js';
@@ -68,8 +68,22 @@ export class MemoryLayerEngine {
68
68
  mkdirSync(config.dataDir, { recursive: true });
69
69
  }
70
70
 
71
- // Initialize database
72
- const dbPath = join(config.dataDir, 'memorylayer.db');
71
+ // Initialize database (with migration from old name)
72
+ let dbPath = join(config.dataDir, 'neuronlayer.db');
73
+ const oldDbPath = join(config.dataDir, 'memorylayer.db');
74
+
75
+ // Migrate from old database name if it exists
76
+ if (!existsSync(dbPath) && existsSync(oldDbPath)) {
77
+ try {
78
+ console.error('Migrating database from memorylayer.db to neuronlayer.db...');
79
+ renameSync(oldDbPath, dbPath);
80
+ } catch (err) {
81
+ // If rename fails (file locked), use the old database path
82
+ console.error('Migration skipped (file in use), using existing database');
83
+ dbPath = oldDbPath;
84
+ }
85
+ }
86
+
73
87
  this.db = initializeDatabase(dbPath);
74
88
 
75
89
  // Initialize storage tiers
@@ -214,6 +228,16 @@ export class MemoryLayerEngine {
214
228
  this.updateProjectStats();
215
229
  // Extract decisions from git and comments
216
230
  this.extractDecisions().catch(err => console.error('Decision extraction error:', err));
231
+
232
+ // Index tests after code indexing is complete
233
+ try {
234
+ const testResult = this.testAwareness.refreshIndex();
235
+ if (testResult.testsIndexed > 0) {
236
+ console.error(`Test index: ${testResult.testsIndexed} tests (${testResult.framework})`);
237
+ }
238
+ } catch (err) {
239
+ console.error('Test indexing error:', err);
240
+ }
217
241
  });
218
242
 
219
243
  this.indexer.on('fileIndexed', (path) => {
@@ -224,6 +248,22 @@ export class MemoryLayerEngine {
224
248
  this.summarizer.invalidateSummaryByPath(path);
225
249
  });
226
250
 
251
+ this.indexer.on('fileImpact', (impact: { file: string; affectedFiles: string[]; affectedCount: number }) => {
252
+ // Log impact warning for file changes
253
+ if (impact.affectedCount > 0) {
254
+ console.error(`[Impact] ${impact.file} changed → ${impact.affectedCount} file(s) may be affected`);
255
+ if (impact.affectedCount <= 5) {
256
+ impact.affectedFiles.forEach(f => console.error(` → ${f}`));
257
+ } else {
258
+ impact.affectedFiles.slice(0, 3).forEach(f => console.error(` → ${f}`));
259
+ console.error(` ... and ${impact.affectedCount - 3} more`);
260
+ }
261
+
262
+ // Track impact in ghost mode for proactive warnings
263
+ this.ghostMode.onFileImpact(impact.file, impact.affectedFiles);
264
+ }
265
+ });
266
+
227
267
  this.indexer.on('error', (error) => {
228
268
  console.error('Indexer error:', error);
229
269
  });
@@ -727,6 +767,16 @@ export class MemoryLayerEngine {
727
767
  return { imports, importedBy, symbols };
728
768
  }
729
769
 
770
+ // Find circular dependencies in the project
771
+ findCircularDependencies(): Array<string[]> {
772
+ return this.tier2.findCircularDependencies();
773
+ }
774
+
775
+ // Get transitive dependents (all files affected by changing a file)
776
+ getTransitiveDependents(filePath: string, maxDepth: number = 3): Array<{ file: string; depth: number; imports: string[] }> {
777
+ return this.tier2.getTransitiveDependents(filePath, maxDepth);
778
+ }
779
+
730
780
  // Phase 2: Get symbol count
731
781
  getSymbolCount(): number {
732
782
  return this.tier2.getSymbolCount();
@@ -64,9 +64,16 @@ const TECH_PATTERNS = [
64
64
  { pattern: /\b(sass|scss|less)\b/i, category: 'styling', term: 'CSS preprocessor' },
65
65
  ];
66
66
 
67
+ export interface FileImpact {
68
+ changedFile: string;
69
+ affectedFiles: string[];
70
+ timestamp: Date;
71
+ }
72
+
67
73
  export class GhostMode {
68
74
  private activeFiles: Map<string, FileContext> = new Map();
69
75
  private recentDecisions: Map<string, Decision[]> = new Map();
76
+ private recentImpacts: Map<string, FileImpact> = new Map(); // Track file impacts
70
77
  private tier2: Tier2Storage;
71
78
  private embeddingGenerator: EmbeddingGenerator;
72
79
 
@@ -74,12 +81,58 @@ export class GhostMode {
74
81
  private readonly MAX_ACTIVE_FILES = 20;
75
82
  private readonly FILE_TTL_MS = 60 * 60 * 1000; // 1 hour
76
83
  private readonly DECISION_CACHE_SIZE = 50;
84
+ private readonly IMPACT_TTL_MS = 30 * 60 * 1000; // 30 minutes
77
85
 
78
86
  constructor(tier2: Tier2Storage, embeddingGenerator: EmbeddingGenerator) {
79
87
  this.tier2 = tier2;
80
88
  this.embeddingGenerator = embeddingGenerator;
81
89
  }
82
90
 
91
+ /**
92
+ * Called when a file change impacts other files
93
+ */
94
+ onFileImpact(changedFile: string, affectedFiles: string[]): void {
95
+ const impact: FileImpact = {
96
+ changedFile,
97
+ affectedFiles,
98
+ timestamp: new Date()
99
+ };
100
+
101
+ // Store impact for each affected file
102
+ for (const file of affectedFiles) {
103
+ this.recentImpacts.set(file, impact);
104
+ }
105
+
106
+ // Evict old impacts
107
+ this.evictStaleImpacts();
108
+ }
109
+
110
+ /**
111
+ * Check if a file was recently impacted by changes to another file
112
+ */
113
+ getImpactWarning(filePath: string): FileImpact | null {
114
+ const impact = this.recentImpacts.get(filePath);
115
+ if (!impact) return null;
116
+
117
+ // Check if still within TTL
118
+ const age = Date.now() - impact.timestamp.getTime();
119
+ if (age > this.IMPACT_TTL_MS) {
120
+ this.recentImpacts.delete(filePath);
121
+ return null;
122
+ }
123
+
124
+ return impact;
125
+ }
126
+
127
+ private evictStaleImpacts(): void {
128
+ const now = Date.now();
129
+ for (const [file, impact] of this.recentImpacts) {
130
+ if (now - impact.timestamp.getTime() > this.IMPACT_TTL_MS) {
131
+ this.recentImpacts.delete(file);
132
+ }
133
+ }
134
+ }
135
+
83
136
  /**
84
137
  * Called when any file is read - silently track and pre-fetch decisions
85
138
  */
@@ -269,7 +269,11 @@ export class ProjectManager {
269
269
  const result: Array<{ project: ProjectInfo; db: Database.Database }> = [];
270
270
 
271
271
  for (const project of this.listProjects()) {
272
- const dbPath = join(project.dataDir, 'memorylayer.db');
272
+ // Check both new and old database names
273
+ let dbPath = join(project.dataDir, 'neuronlayer.db');
274
+ if (!existsSync(dbPath)) {
275
+ dbPath = join(project.dataDir, 'memorylayer.db');
276
+ }
273
277
 
274
278
  if (existsSync(dbPath)) {
275
279
  try {
@@ -80,6 +80,30 @@ const LANGUAGE_CONFIGS: Record<string, LanguageConfig> = {
80
80
  (import_from_statement) @import
81
81
  `
82
82
  }
83
+ },
84
+ go: {
85
+ wasmFile: 'tree-sitter-go.wasm',
86
+ extensions: ['.go'],
87
+ queries: {
88
+ functions: `(function_declaration name: (identifier) @name) @func`,
89
+ classes: `(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @class`
90
+ }
91
+ },
92
+ rust: {
93
+ wasmFile: 'tree-sitter-rust.wasm',
94
+ extensions: ['.rs'],
95
+ queries: {
96
+ functions: `(function_item name: (identifier) @name) @func`,
97
+ classes: `(struct_item name: (type_identifier) @name) @class`
98
+ }
99
+ },
100
+ java: {
101
+ wasmFile: 'tree-sitter-java.wasm',
102
+ extensions: ['.java'],
103
+ queries: {
104
+ functions: `(method_declaration name: (identifier) @name) @func`,
105
+ classes: `(class_declaration name: (identifier) @name) @class`
106
+ }
83
107
  }
84
108
  };
85
109
 
@@ -95,41 +119,15 @@ export class ASTParser {
95
119
 
96
120
  async initialize(): Promise<void> {
97
121
  if (this.initialized) return;
98
-
99
- try {
100
- await Parser.init();
101
- this.parser = new Parser();
102
- this.initialized = true;
103
- console.error('AST Parser initialized');
104
- } catch (error) {
105
- console.error('Failed to initialize AST parser:', error);
106
- throw error;
107
- }
122
+ // Using regex-based parsing for reliable cross-platform support
123
+ // Tree-sitter WASM support reserved for future enhancement
124
+ this.initialized = true;
108
125
  }
109
126
 
110
- private async loadLanguage(langName: string): Promise<Parser.Language | null> {
111
- if (this.languages.has(langName)) {
112
- return this.languages.get(langName)!;
113
- }
114
-
115
- const config = LANGUAGE_CONFIGS[langName];
116
- if (!config) {
117
- return null;
118
- }
119
-
120
- try {
121
- // Try to load from node_modules or bundled location
122
- const wasmDir = join(this.dataDir, 'wasm');
123
- const wasmPath = join(wasmDir, config.wasmFile);
124
-
125
- // For now, we'll use a simplified approach without external WASM files
126
- // In production, you'd download these from tree-sitter releases
127
- console.error(`Language ${langName} WASM not available yet`);
128
- return null;
129
- } catch (error) {
130
- console.error(`Failed to load language ${langName}:`, error);
131
- return null;
132
- }
127
+ private async loadLanguage(_langName: string): Promise<Parser.Language | null> {
128
+ // Tree-sitter WASM loading not implemented - using regex fallback
129
+ // Language configs are retained for future tree-sitter support
130
+ return null;
133
131
  }
134
132
 
135
133
  getLanguageForFile(filePath: string): string | null {
@@ -152,11 +150,13 @@ export class ASTParser {
152
150
  await this.initialize();
153
151
  }
154
152
 
155
- // Use regex-based parsing as fallback since WASM loading is complex
153
+ // Regex-based parsing - reliable cross-platform symbol extraction
156
154
  return this.parseWithRegex(filePath, content);
157
155
  }
158
156
 
159
- // Regex-based parsing fallback (works without WASM)
157
+ // Regex-based parsing for symbol extraction
158
+ // Handles: functions, classes, interfaces, types, imports, exports
159
+ // Supports: TypeScript, JavaScript, Python, Go, Rust, Java
160
160
  private parseWithRegex(filePath: string, content: string): {
161
161
  symbols: CodeSymbol[];
162
162
  imports: Import[];
@@ -172,6 +172,12 @@ export class ASTParser {
172
172
  this.parseTypeScriptJS(filePath, content, lines, symbols, imports, exports);
173
173
  } else if (lang === 'python') {
174
174
  this.parsePython(filePath, content, lines, symbols, imports, exports);
175
+ } else if (lang === 'go') {
176
+ this.parseGo(filePath, content, lines, symbols, imports, exports);
177
+ } else if (lang === 'rust') {
178
+ this.parseRust(filePath, content, lines, symbols, imports, exports);
179
+ } else if (lang === 'java') {
180
+ this.parseJava(filePath, content, lines, symbols, imports, exports);
175
181
  }
176
182
 
177
183
  return { symbols, imports, exports };
@@ -187,37 +193,60 @@ export class ASTParser {
187
193
  ): void {
188
194
  // Patterns for TypeScript/JavaScript
189
195
  const patterns = {
190
- // Functions: function name(), const name = () =>, const name = function()
191
- function: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/,
192
- arrowFunc: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/,
196
+ // Functions: function name(), export default function name(), const name = () =>
197
+ function: /^(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+(\w+)/,
198
+ // Arrow functions: handles type annotations and destructured params
199
+ arrowFunc: /^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_]\w*)\s*(?::\s*[^=]+)?\s*=>/,
193
200
  // Classes
194
- class: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
201
+ class: /^(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+(\w+)/,
195
202
  // Interfaces (TS only)
196
203
  interface: /^(?:export\s+)?interface\s+(\w+)/,
197
204
  // Types (TS only)
198
- type: /^(?:export\s+)?type\s+(\w+)\s*=/,
199
- // Imports
200
- import: /^import\s+(?:(\w+)(?:\s*,\s*)?)?(?:\{([^}]+)\})?\s*from\s*['"]([^'"]+)['"]/,
201
- importAll: /^import\s+\*\s+as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/,
205
+ type: /^(?:export\s+)?type\s+(\w+)\s*(?:<[^>]*>)?\s*=/,
206
+ // Imports - supports 'import type'
207
+ import: /^import\s+(?:type\s+)?(?:(\w+)(?:\s*,\s*)?)?(?:\{([^}]+)\})?\s*from\s*['"]([^'"]+)['"]/,
208
+ importAll: /^import\s+(?:type\s+)?\*\s+as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/,
202
209
  importSideEffect: /^import\s*['"]([^'"]+)['"]/,
203
210
  // Exports
204
- exportNamed: /^export\s+\{([^}]+)\}/,
211
+ exportNamed: /^export\s+(?:type\s+)?\{([^}]+)\}/,
205
212
  exportDefault: /^export\s+default\s+(?:class|function|const|let|var)?\s*(\w+)?/,
206
213
  exportDirect: /^export\s+(?:const|let|var|function|class|interface|type|enum|async\s+function)\s+(\w+)/,
207
214
  // Enums (TS)
208
215
  enum: /^(?:export\s+)?(?:const\s+)?enum\s+(\w+)/,
209
- // Methods inside classes (simplified)
210
- method: /^\s+(?:async\s+)?(?:static\s+)?(?:private\s+|public\s+|protected\s+)?(\w+)\s*\([^)]*\)\s*[:{]/,
216
+ // Methods inside classes - handles generics, readonly, and all modifiers
217
+ method: /^\s+(?:async\s+)?(?:static\s+)?(?:readonly\s+)?(?:private\s+|public\s+|protected\s+)?(?:get\s+|set\s+)?(\w+)\s*(?:<[^>]+>)?\s*\(/,
211
218
  };
212
219
 
213
220
  let currentClass: { name: string; startLine: number } | null = null;
214
221
  let braceDepth = 0;
222
+ let inBlockComment = false;
215
223
 
216
224
  for (let i = 0; i < lines.length; i++) {
217
225
  const line = lines[i] || '';
218
226
  const trimmed = line.trim();
219
227
  const lineNum = i + 1;
220
228
 
229
+ // Track block comments properly
230
+ if (inBlockComment) {
231
+ if (trimmed.includes('*/')) {
232
+ inBlockComment = false;
233
+ }
234
+ continue;
235
+ }
236
+
237
+ // Skip single-line comments
238
+ if (trimmed.startsWith('//')) {
239
+ continue;
240
+ }
241
+
242
+ // Handle block comment start
243
+ if (trimmed.startsWith('/*')) {
244
+ if (!trimmed.includes('*/')) {
245
+ inBlockComment = true;
246
+ }
247
+ continue;
248
+ }
249
+
221
250
  // Track brace depth for class scope
222
251
  braceDepth += (line.match(/\{/g) || []).length;
223
252
  braceDepth -= (line.match(/\}/g) || []).length;
@@ -231,11 +260,6 @@ export class ASTParser {
231
260
  currentClass = null;
232
261
  }
233
262
 
234
- // Skip comments
235
- if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
236
- continue;
237
- }
238
-
239
263
  // Functions
240
264
  let match = trimmed.match(patterns.function);
241
265
  if (match && match[1]) {
@@ -511,6 +535,287 @@ export class ASTParser {
511
535
  }
512
536
  }
513
537
 
538
+ private parseGo(
539
+ filePath: string,
540
+ content: string,
541
+ lines: string[],
542
+ symbols: CodeSymbol[],
543
+ imports: Import[],
544
+ exports: Export[]
545
+ ): void {
546
+ let inImportBlock = false;
547
+
548
+ for (let i = 0; i < lines.length; i++) {
549
+ const line = lines[i] || '';
550
+ const trimmed = line.trim();
551
+ const lineNum = i + 1;
552
+
553
+ // Skip comments
554
+ if (trimmed.startsWith('//')) continue;
555
+
556
+ // Handle import blocks
557
+ if (trimmed === 'import (') {
558
+ inImportBlock = true;
559
+ continue;
560
+ }
561
+ if (inImportBlock && trimmed === ')') {
562
+ inImportBlock = false;
563
+ continue;
564
+ }
565
+
566
+ // Single import or import block item
567
+ const importMatch = inImportBlock
568
+ ? trimmed.match(/^(?:(\w+)\s+)?"([^"]+)"/)
569
+ : trimmed.match(/^import\s+(?:(\w+)\s+)?"([^"]+)"/);
570
+ if (importMatch) {
571
+ const alias = importMatch[1];
572
+ const path = importMatch[2] || '';
573
+ const pkg = alias || path.split('/').pop() || '';
574
+ imports.push({
575
+ fileId: 0,
576
+ filePath,
577
+ importedFrom: path,
578
+ importedSymbols: [pkg],
579
+ isDefault: false,
580
+ isNamespace: false,
581
+ lineNumber: lineNum
582
+ });
583
+ continue;
584
+ }
585
+
586
+ // Functions and methods
587
+ const funcMatch = trimmed.match(/^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?(\w+)\s*\(/);
588
+ if (funcMatch) {
589
+ const receiver = funcMatch[2];
590
+ const name = receiver ? `${receiver}.${funcMatch[3]}` : (funcMatch[3] || '');
591
+ symbols.push({
592
+ fileId: 0,
593
+ filePath,
594
+ kind: receiver ? 'method' : 'function',
595
+ name,
596
+ lineStart: lineNum,
597
+ lineEnd: this.findBlockEnd(lines, i),
598
+ exported: /^[A-Z]/.test(funcMatch[3] || ''),
599
+ signature: trimmed.split('{')[0]?.trim()
600
+ });
601
+ continue;
602
+ }
603
+
604
+ // Structs
605
+ const structMatch = trimmed.match(/^type\s+(\w+)\s+struct\s*\{?/);
606
+ if (structMatch) {
607
+ symbols.push({
608
+ fileId: 0,
609
+ filePath,
610
+ kind: 'class',
611
+ name: structMatch[1] || '',
612
+ lineStart: lineNum,
613
+ lineEnd: this.findBlockEnd(lines, i),
614
+ exported: /^[A-Z]/.test(structMatch[1] || '')
615
+ });
616
+ continue;
617
+ }
618
+
619
+ // Interfaces
620
+ const ifaceMatch = trimmed.match(/^type\s+(\w+)\s+interface\s*\{?/);
621
+ if (ifaceMatch) {
622
+ symbols.push({
623
+ fileId: 0,
624
+ filePath,
625
+ kind: 'interface',
626
+ name: ifaceMatch[1] || '',
627
+ lineStart: lineNum,
628
+ lineEnd: this.findBlockEnd(lines, i),
629
+ exported: /^[A-Z]/.test(ifaceMatch[1] || '')
630
+ });
631
+ }
632
+ }
633
+ }
634
+
635
+ private parseRust(
636
+ filePath: string,
637
+ content: string,
638
+ lines: string[],
639
+ symbols: CodeSymbol[],
640
+ imports: Import[],
641
+ exports: Export[]
642
+ ): void {
643
+ for (let i = 0; i < lines.length; i++) {
644
+ const line = lines[i] || '';
645
+ const trimmed = line.trim();
646
+ const lineNum = i + 1;
647
+
648
+ // Skip comments
649
+ if (trimmed.startsWith('//')) continue;
650
+
651
+ // Functions
652
+ const fnMatch = trimmed.match(/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
653
+ if (fnMatch) {
654
+ symbols.push({
655
+ fileId: 0,
656
+ filePath,
657
+ kind: 'function',
658
+ name: fnMatch[1] || '',
659
+ lineStart: lineNum,
660
+ lineEnd: this.findBlockEnd(lines, i),
661
+ exported: trimmed.startsWith('pub'),
662
+ signature: trimmed.split('{')[0]?.trim()
663
+ });
664
+ continue;
665
+ }
666
+
667
+ // Structs
668
+ const structMatch = trimmed.match(/^(?:pub\s+)?struct\s+(\w+)/);
669
+ if (structMatch) {
670
+ symbols.push({
671
+ fileId: 0,
672
+ filePath,
673
+ kind: 'class',
674
+ name: structMatch[1] || '',
675
+ lineStart: lineNum,
676
+ lineEnd: this.findBlockEnd(lines, i),
677
+ exported: trimmed.startsWith('pub')
678
+ });
679
+ continue;
680
+ }
681
+
682
+ // Enums
683
+ const enumMatch = trimmed.match(/^(?:pub\s+)?enum\s+(\w+)/);
684
+ if (enumMatch) {
685
+ symbols.push({
686
+ fileId: 0,
687
+ filePath,
688
+ kind: 'enum',
689
+ name: enumMatch[1] || '',
690
+ lineStart: lineNum,
691
+ lineEnd: this.findBlockEnd(lines, i),
692
+ exported: trimmed.startsWith('pub')
693
+ });
694
+ continue;
695
+ }
696
+
697
+ // Traits (similar to interfaces)
698
+ const traitMatch = trimmed.match(/^(?:pub\s+)?trait\s+(\w+)/);
699
+ if (traitMatch) {
700
+ symbols.push({
701
+ fileId: 0,
702
+ filePath,
703
+ kind: 'interface',
704
+ name: traitMatch[1] || '',
705
+ lineStart: lineNum,
706
+ lineEnd: this.findBlockEnd(lines, i),
707
+ exported: trimmed.startsWith('pub')
708
+ });
709
+ continue;
710
+ }
711
+
712
+ // Impl blocks
713
+ const implMatch = trimmed.match(/^impl\s+(?:<[^>]+>\s+)?(?:(\w+)\s+for\s+)?(\w+)/);
714
+ if (implMatch) {
715
+ const traitName = implMatch[1];
716
+ const typeName = implMatch[2] || '';
717
+ const name = traitName ? `${traitName} for ${typeName}` : typeName;
718
+ symbols.push({
719
+ fileId: 0,
720
+ filePath,
721
+ kind: 'class',
722
+ name: `impl ${name}`,
723
+ lineStart: lineNum,
724
+ lineEnd: this.findBlockEnd(lines, i),
725
+ exported: false
726
+ });
727
+ continue;
728
+ }
729
+
730
+ // use statements
731
+ const useMatch = trimmed.match(/^(?:pub\s+)?use\s+(.+);/);
732
+ if (useMatch) {
733
+ const path = (useMatch[1] || '').replace(/::/g, '/');
734
+ imports.push({
735
+ fileId: 0,
736
+ filePath,
737
+ importedFrom: path,
738
+ importedSymbols: [path.split('/').pop()?.replace(/[{}]/g, '') || ''],
739
+ isDefault: false,
740
+ isNamespace: path.includes('*'),
741
+ lineNumber: lineNum
742
+ });
743
+ }
744
+ }
745
+ }
746
+
747
+ private parseJava(
748
+ filePath: string,
749
+ content: string,
750
+ lines: string[],
751
+ symbols: CodeSymbol[],
752
+ imports: Import[],
753
+ exports: Export[]
754
+ ): void {
755
+ let currentClass: string | null = null;
756
+
757
+ for (let i = 0; i < lines.length; i++) {
758
+ const line = lines[i] || '';
759
+ const trimmed = line.trim();
760
+ const lineNum = i + 1;
761
+
762
+ // Skip comments
763
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) continue;
764
+
765
+ // Imports
766
+ const importMatch = trimmed.match(/^import\s+(?:static\s+)?([^;]+);/);
767
+ if (importMatch) {
768
+ const path = importMatch[1] || '';
769
+ imports.push({
770
+ fileId: 0,
771
+ filePath,
772
+ importedFrom: path,
773
+ importedSymbols: [path.split('.').pop() || ''],
774
+ isDefault: false,
775
+ isNamespace: path.endsWith('*'),
776
+ lineNumber: lineNum
777
+ });
778
+ continue;
779
+ }
780
+
781
+ // Classes and interfaces
782
+ const classMatch = trimmed.match(/^(?:public\s+|private\s+|protected\s+)?(?:abstract\s+)?(?:final\s+)?(class|interface|enum)\s+(\w+)/);
783
+ if (classMatch) {
784
+ currentClass = classMatch[2] || '';
785
+ symbols.push({
786
+ fileId: 0,
787
+ filePath,
788
+ kind: classMatch[1] === 'interface' ? 'interface' : classMatch[1] === 'enum' ? 'enum' : 'class',
789
+ name: currentClass,
790
+ lineStart: lineNum,
791
+ lineEnd: this.findBlockEnd(lines, i),
792
+ exported: trimmed.includes('public')
793
+ });
794
+ continue;
795
+ }
796
+
797
+ // Methods
798
+ const methodMatch = trimmed.match(/^(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?(?:<[^>]+>\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\(/);
799
+ if (methodMatch && currentClass && !['if', 'for', 'while', 'switch', 'catch', 'class', 'interface', 'enum'].includes(methodMatch[2] || '')) {
800
+ const returnType = methodMatch[1];
801
+ const methodName = methodMatch[2] || '';
802
+ // Skip constructors (name matches class name)
803
+ if (methodName !== currentClass) {
804
+ symbols.push({
805
+ fileId: 0,
806
+ filePath,
807
+ kind: 'method',
808
+ name: `${currentClass}.${methodName}`,
809
+ lineStart: lineNum,
810
+ lineEnd: this.findBlockEnd(lines, i),
811
+ exported: trimmed.includes('public'),
812
+ signature: `${returnType} ${methodName}(...)`
813
+ });
814
+ }
815
+ }
816
+ }
817
+ }
818
+
514
819
  private findBlockEnd(lines: string[], startIndex: number): number {
515
820
  let braceCount = 0;
516
821
  let started = false;