neuronlayer 0.1.7 → 0.1.9

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.

@@ -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;
@@ -7,10 +7,10 @@ import { ASTParser } from './ast.js';
7
7
  import { FileWatcher, type FileEvent } from './watcher.js';
8
8
  import { Tier2Storage } from '../storage/tier2.js';
9
9
  import { isCodeFile, detectLanguage, hashContent, getPreview, countLines } from '../utils/files.js';
10
- import type { MemoryLayerConfig, IndexingProgress } from '../types/index.js';
10
+ import type { NeuronLayerConfig, IndexingProgress } from '../types/index.js';
11
11
 
12
12
  export class Indexer extends EventEmitter {
13
- private config: MemoryLayerConfig;
13
+ private config: NeuronLayerConfig;
14
14
  private embeddingGenerator: EmbeddingGenerator;
15
15
  private astParser: ASTParser;
16
16
  private watcher: FileWatcher;
@@ -19,7 +19,7 @@ export class Indexer extends EventEmitter {
19
19
  private pendingFiles: Set<string> = new Set();
20
20
  private processTimeout: NodeJS.Timeout | null = null;
21
21
 
22
- constructor(config: MemoryLayerConfig, tier2: Tier2Storage) {
22
+ constructor(config: NeuronLayerConfig, tier2: Tier2Storage) {
23
23
  super();
24
24
  this.config = config;
25
25
  this.tier2 = tier2;
@@ -149,6 +149,17 @@ export class Indexer extends EventEmitter {
149
149
  const exportsWithFileId = parsed.exports.map(e => ({ ...e, fileId }));
150
150
  this.tier2.insertExports(exportsWithFileId);
151
151
  }
152
+
153
+ // Build dependency edges from imports
154
+ if (parsed.imports.length > 0) {
155
+ this.tier2.clearDependencies(fileId);
156
+ for (const imp of parsed.imports) {
157
+ const targetFile = this.tier2.resolveImportToFile(relativePath, imp.importedFrom);
158
+ if (targetFile) {
159
+ this.tier2.addDependency(fileId, targetFile.id, 'imports');
160
+ }
161
+ }
162
+ }
152
163
  }
153
164
  } catch (astError) {
154
165
  // AST parsing is optional, don't fail the whole index
@@ -156,6 +167,20 @@ export class Indexer extends EventEmitter {
156
167
  }
157
168
 
158
169
  this.emit('fileIndexed', relativePath);
170
+
171
+ // Emit impact warning for changed files (not during initial indexing)
172
+ if (!this.isIndexing) {
173
+ const dependents = this.tier2.getFileDependents(relativePath);
174
+ if (dependents.length > 0) {
175
+ this.emit('fileImpact', {
176
+ file: relativePath,
177
+ affectedFiles: dependents.map(d => d.file),
178
+ affectedCount: dependents.length,
179
+ imports: dependents.map(d => ({ file: d.file, symbols: d.imports }))
180
+ });
181
+ }
182
+ }
183
+
159
184
  return true; // Actually indexed
160
185
  } catch (error) {
161
186
  console.error(`Error indexing ${absolutePath}:`, error);