graph-indexer 1.0.0 → 1.0.1

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.
package/parser-utils.mjs CHANGED
@@ -3,16 +3,16 @@
3
3
  * @description Shared AST parsing, language registry, and embedding utilities.
4
4
  * @author MaquinaTech <https://github.com/MaquinaTech>
5
5
  * @copyright (c) 2026 MaquinaTech. All rights reserved.
6
- * @license GPL-3.0-only
7
- * * This program is free software: you can redistribute it and/or modify
8
- * it under the terms of the GNU General Public License as published by
9
- * the Free Software Foundation, either version 3 of the License, or
10
- * (at your option) any later version.
11
- *
12
- * This program is distributed in the hope that it will be useful,
13
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- * GNU General Public License for more details.
6
+ * @license MIT
7
+ * Copyright (c) 2026 MaquinaTech. All rights reserved.
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions: The above copyright
14
+ * notice and this permission notice shall be included in all copies or
15
+ * substantial portions of the Software.
16
16
  */
17
17
  import fs from 'fs';
18
18
  import path from 'path';
@@ -28,6 +28,10 @@ import Python from 'tree-sitter-python';
28
28
  import Rust from 'tree-sitter-rust';
29
29
  import Go from 'tree-sitter-go';
30
30
  import PHP from 'tree-sitter-php';
31
+ import Java from 'tree-sitter-java';
32
+ import Kotlin from 'tree-sitter-kotlin';
33
+ import CSharp from 'tree-sitter-c-sharp';
34
+ import Ruby from 'tree-sitter-ruby';
31
35
  import { truncateForEmbedding } from './core-engine.mjs';
32
36
 
33
37
  export const MAX_FILE_SIZE_BYTES = 500000;
@@ -39,6 +43,10 @@ const LANGUAGE_MAP = {
39
43
  '.css': CSS, '.scss': CSS,
40
44
  '.py': Python, '.rs': Rust, '.go': Go,
41
45
  '.php': PHP.php,
46
+ '.java': Java,
47
+ '.kt': Kotlin, '.kts': Kotlin,
48
+ '.cs': CSharp,
49
+ '.rb': Ruby,
42
50
  };
43
51
 
44
52
  // 🥇 ULTRA-GENERIC QUERIES: Immune to grammar changes between TS/JS/TSX
@@ -73,6 +81,34 @@ const LANGUAGE_QUERIES = {
73
81
  (function_definition) @chunk
74
82
  (class_declaration) @chunk
75
83
  (expression_statement) @chunk
84
+ `,
85
+ java: `
86
+ (method_declaration) @chunk
87
+ (class_declaration) @chunk
88
+ (interface_declaration) @chunk
89
+ (constructor_declaration) @chunk
90
+ (enum_declaration) @chunk
91
+ `,
92
+ kotlin: `
93
+ (function_declaration) @chunk
94
+ (class_declaration) @chunk
95
+ (object_declaration) @chunk
96
+ (companion_object) @chunk
97
+ (secondary_constructor) @chunk
98
+ `,
99
+ cs: `
100
+ (method_declaration) @chunk
101
+ (class_declaration) @chunk
102
+ (interface_declaration) @chunk
103
+ (constructor_declaration) @chunk
104
+ (enum_declaration) @chunk
105
+ (property_declaration) @chunk
106
+ `,
107
+ rb: `
108
+ (method) @chunk
109
+ (singleton_method) @chunk
110
+ (class) @chunk
111
+ (module) @chunk
76
112
  `
77
113
  };
78
114
 
@@ -84,7 +120,15 @@ const CONTAINERS = new Set([
84
120
  'lexical_declaration', 'expression_statement', 'export_statement',
85
121
  'function_definition', 'class_definition', 'rule_set',
86
122
  'function_item', 'struct_item', 'enum_item', 'trait_item', 'impl_item',
87
- 'method_declaration', 'type_declaration'
123
+ 'method_declaration', 'type_declaration',
124
+ // Java / C#
125
+ 'interface_declaration', 'constructor_declaration', 'enum_declaration',
126
+ // Kotlin
127
+ 'object_declaration', 'companion_object', 'secondary_constructor',
128
+ // C#
129
+ 'property_declaration',
130
+ // Ruby
131
+ 'method', 'singleton_method', 'module'
88
132
  ]);
89
133
 
90
134
  export function getParserForFile(ext) {
@@ -183,6 +227,41 @@ export function extractImportsFromAST(rootNode, ext) {
183
227
  c.type === 'string' || c.type === 'encapsed_string');
184
228
  if (strNode) imports.add(strNode.text.replace(/['"]/g, ''));
185
229
  }
230
+ } // ── Java ─────────────────────────────────────────────────────────────
231
+ else if (ext === '.java') {
232
+ if (node.type === 'import_declaration') {
233
+ const scopedId = node.children.find(c => c.type === 'scoped_identifier' || c.type === 'identifier');
234
+ if (scopedId) imports.add(scopedId.text.replace(/\./g, '/'));
235
+ }
236
+ }
237
+ // ── Kotlin ───────────────────────────────────────────────────────────
238
+ else if (ext === '.kt' || ext === '.kts') {
239
+ if (node.type === 'import_header') {
240
+ const path = node.children.find(c => c.type === 'identifier' || c.type === 'user_type' || c.isNamed);
241
+ const raw = node.text.replace(/^import\s+/, '').replace(/\s*\.\*\s*$/, '').trim();
242
+ if (raw) imports.add(raw.replace(/\./g, '/'));
243
+ }
244
+ }
245
+ // ── C# ───────────────────────────────────────────────────────────────
246
+ else if (ext === '.cs') {
247
+ if (node.type === 'using_directive') {
248
+ const ns = node.children.find(c => c.type === 'qualified_name' || c.type === 'identifier' || c.type === 'name_equals');
249
+ if (ns) {
250
+ const raw = ns.text.replace(/\s*=\s*.*$/, '').trim();
251
+ if (raw) imports.add(raw.replace(/\./g, '/'));
252
+ }
253
+ }
254
+ }
255
+ // ── Ruby ──────────────────────────────────────────────────────────────
256
+ else if (ext === '.rb') {
257
+ if (node.type === 'call' || node.type === 'method_call') {
258
+ const method = node.childForFieldName?.('method') || node.children[0];
259
+ if (method && (method.text === 'require' || method.text === 'require_relative')) {
260
+ const args = node.childForFieldName?.('arguments') || node.children.find(c => c.type === 'argument_list');
261
+ const strArg = args?.children?.find(c => c.type === 'string' || c.type === 'simple_string');
262
+ if (strArg) imports.add(strArg.text.replace(/['"]/g, ''));
263
+ }
264
+ }
186
265
  }
187
266
  node.children.forEach(walk);
188
267
  }
@@ -196,7 +275,12 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
196
275
  if (!parser) return chunks;
197
276
 
198
277
  const JS_LIKE = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
199
- const langKey = JS_LIKE.includes(ext) ? 'ts' : (LANGUAGE_QUERIES[ext.slice(1)] ? ext.slice(1) : null);
278
+ const EXT_TO_LANG = {
279
+ '.java': 'java', '.kt': 'kotlin', '.kts': 'kotlin', '.cs': 'cs',
280
+ '.rb': 'rb'
281
+ };
282
+ const langKey = JS_LIKE.includes(ext) ? 'ts'
283
+ : (EXT_TO_LANG[ext] || (LANGUAGE_QUERIES[ext.slice(1)] ? ext.slice(1) : null));
200
284
  if (!langKey || !LANGUAGE_QUERIES[langKey]) return chunks;
201
285
 
202
286
  // 🥇 HEADER INHERITANCE: Extract top-of-file/global comments for module context
@@ -226,10 +310,14 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
226
310
  // Filter out very small fragments (simple variables, etc.)
227
311
  if (chunkNode.endPosition.row - chunkNode.startPosition.row < 2) continue;
228
312
 
229
- // 🥇 DEDUPLICATION LOGIC: Ignore nodes that are nested inside other container nodes
313
+ // 🥇 DEDUPLICATION LOGIC: Ignore nodes that are nested inside other container nodes.
314
+ // Stop at the actual tree root (parent === null) to be language-agnostic:
315
+ // Python root = 'module', JS root = 'program', Go root = 'source_file', Ruby root = 'program'.
316
+ // Stopping at 'program' alone was falsely marking top-level Python classes as nested
317
+ // because Ruby's 'module' keyword (also in CONTAINERS) shares the name with Python's root.
230
318
  let isNested = false;
231
319
  let currentParent = chunkNode.parent;
232
- while (currentParent && currentParent.type !== 'program') {
320
+ while (currentParent && currentParent.parent !== null) {
233
321
  if (CONTAINERS.has(currentParent.type)) {
234
322
  isNested = true;
235
323
  break;
@@ -306,6 +394,11 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
306
394
  const hash = generateChunkHash(docstring + snippet);
307
395
  const outgoingCalls = extractCalls(chunkNode);
308
396
 
397
+ // 🥇 PARAMETER / TYPE / CLASS CONTEXT ENRICHMENT (improves recall on undocumented code)
398
+ const params = extractParams(chunkNode, ext);
399
+ const returnType = extractReturnType(chunkNode, ext);
400
+ const classContext = extractClassContext(chunkNode);
401
+
309
402
  const id = createHash('sha256')
310
403
  .update(`${relPath}::${chunkNode.startPosition.row}::${chunkNode.startPosition.column}`)
311
404
  .digest('hex').slice(0, 24);
@@ -314,7 +407,8 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
314
407
  id, file_path: relPath, node_type: chunkNode.type, name: nameText,
315
408
  docstring: docstring, code_snippet: snippet, content_hash: hash,
316
409
  start_line: chunkNode.startPosition.row + 1, end_line: chunkNode.endPosition.row + 1,
317
- calls: outgoingCalls
410
+ calls: outgoingCalls,
411
+ params, return_type: returnType, class_context: classContext
318
412
  });
319
413
  }
320
414
  } catch (e) {
@@ -324,6 +418,104 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
324
418
  return chunks;
325
419
  }
326
420
 
421
+ // ─── Barrel export resolution ─────────────────────────────────────────────────
422
+
423
+ // Module-level cache: barrelAbsPath → Map<exportedName, sourceRelPath>
424
+ const _barrelCache = new Map();
425
+
426
+ /**
427
+ * Parses a barrel file (index.ts / index.js) and returns a map of
428
+ * exportedName → sourceFilePath (relative to projectRoot).
429
+ * e.g. { useAuthStore: 'src/stores/authStore.ts' }
430
+ */
431
+ export function resolveBarrelExports(barrelAbsPath, projectRoot) {
432
+ if (_barrelCache.has(barrelAbsPath)) return _barrelCache.get(barrelAbsPath);
433
+
434
+ const result = new Map();
435
+ _barrelCache.set(barrelAbsPath, result);
436
+
437
+ const ext = path.extname(barrelAbsPath);
438
+ if (!['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return result;
439
+
440
+ let content;
441
+ try { content = fs.readFileSync(barrelAbsPath, 'utf-8'); } catch { return result; }
442
+
443
+ const parser = getParserForFile(ext);
444
+ if (!parser) return result;
445
+
446
+ let tree;
447
+ try { tree = parser.parse((offset) => offset < content.length ? content.slice(offset, offset + 4096) : null); } catch { return result; }
448
+
449
+ const barrelDir = path.dirname(barrelAbsPath);
450
+
451
+ function walk(node) {
452
+ // export { X, Y as Z } from './source'
453
+ if (node.type === 'export_statement') {
454
+ const fromNode = node.children.find(c => c.type === 'string');
455
+ if (!fromNode) { node.children.forEach(walk); return; }
456
+
457
+ const rawSource = fromNode.text.replace(/['"]/g, '');
458
+ if (!rawSource.startsWith('.')) { node.children.forEach(walk); return; }
459
+
460
+ // Resolve the source file
461
+ const absSource = path.resolve(barrelDir, rawSource);
462
+ let finalAbs = null;
463
+ if (EXTENSIONS.has(path.extname(absSource)) && fs.existsSync(absSource)) {
464
+ finalAbs = absSource;
465
+ } else {
466
+ for (const e of EXTENSIONS) {
467
+ if (fs.existsSync(absSource + e)) { finalAbs = absSource + e; break; }
468
+ const idx = path.join(absSource, 'index' + e);
469
+ if (fs.existsSync(idx)) { finalAbs = idx; break; }
470
+ }
471
+ }
472
+ if (!finalAbs) { node.children.forEach(walk); return; }
473
+
474
+ const relSource = path.relative(projectRoot, finalAbs).replace(/\\/g, '/');
475
+
476
+ // Walk named exports
477
+ const namedExports = node.children.find(c => c.type === 'named_imports' || c.type === 'export_clause');
478
+ if (namedExports) {
479
+ for (const child of namedExports.children) {
480
+ if (child.type === 'import_specifier' || child.type === 'export_specifier') {
481
+ // `alias as exported` or just `name`
482
+ const names = child.children.filter(c => c.type === 'identifier');
483
+ if (names.length > 0) {
484
+ // The exported name is the last identifier (the alias if present)
485
+ result.set(names[names.length - 1].text, relSource);
486
+ }
487
+ }
488
+ }
489
+ }
490
+
491
+ // export * from './source' → map the source file itself
492
+ const starNode = node.children.find(c => c.text === '*');
493
+ if (starNode) {
494
+ result.set('*', relSource);
495
+ }
496
+ }
497
+ node.children.forEach(walk);
498
+ }
499
+ walk(tree.rootNode);
500
+ return result;
501
+ }
502
+
503
+ // ─── Go module-name cache (reads go.mod once per project root) ──────────────
504
+ const _goModCache = new Map();
505
+ function _readGoModuleName(projectRoot) {
506
+ if (_goModCache.has(projectRoot)) return _goModCache.get(projectRoot);
507
+ const modFile = path.join(projectRoot, 'go.mod');
508
+ let name = null;
509
+ if (fs.existsSync(modFile)) {
510
+ try {
511
+ const first = fs.readFileSync(modFile, 'utf-8').split('\n').find(l => l.trimStart().startsWith('module '));
512
+ if (first) name = first.trim().replace(/^module\s+/, '').split(/\s/)[0];
513
+ } catch { /* ignore */ }
514
+ }
515
+ _goModCache.set(projectRoot, name);
516
+ return name;
517
+ }
518
+
327
519
  export function resolveLocalImports(rawImports, fromFileRelPath, projectRoot) {
328
520
  const fileDir = path.dirname(path.join(projectRoot, fromFileRelPath));
329
521
  const ext = path.extname(fromFileRelPath);
@@ -348,7 +540,46 @@ export function resolveLocalImports(rawImports, fromFileRelPath, projectRoot) {
348
540
  }
349
541
  }
350
542
  }
351
- if (finalAbs) resolved.push(path.relative(projectRoot, finalAbs).replace(/\\/g, '/'));
543
+ if (finalAbs) {
544
+ const relPath = path.relative(projectRoot, finalAbs).replace(/\\/g, '/');
545
+ const baseName = path.basename(finalAbs, path.extname(finalAbs));
546
+ // 🥇 BARREL RESOLUTION: If the resolved file is an index file, expand barrel exports
547
+ if (baseName === 'index' && ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(path.extname(finalAbs))) {
548
+ const barrelMap = resolveBarrelExports(finalAbs, projectRoot);
549
+ if (barrelMap.size > 0) {
550
+ // Add all unique source files referenced by this barrel
551
+ const sources = new Set(barrelMap.values());
552
+ for (const src of sources) {
553
+ if (!resolved.includes(src)) resolved.push(src);
554
+ }
555
+ } else {
556
+ // Barrel has no re-exports — keep the barrel file itself
557
+ if (!resolved.includes(relPath)) resolved.push(relPath);
558
+ }
559
+ } else {
560
+ if (!resolved.includes(relPath)) resolved.push(relPath);
561
+ }
562
+ }
563
+ }
564
+ // ── Go intra-module: github.com/owner/repo/sub → sub/*.go ──────────
565
+ // Go import paths use the module name as prefix; map them to local dirs.
566
+ else if (ext === '.go') {
567
+ const modName = _readGoModuleName(projectRoot);
568
+ if (modName && raw.startsWith(modName + '/')) {
569
+ const subPkg = raw.slice(modName.length + 1); // e.g. 'render'
570
+ const absDir = path.join(projectRoot, subPkg);
571
+ if (fs.existsSync(absDir)) {
572
+ try {
573
+ const goFiles = fs.readdirSync(absDir)
574
+ .filter(f => f.endsWith('.go') && !f.includes('_test'))
575
+ .slice(0, 5); // cap: take a representative sample
576
+ for (const gof of goFiles) {
577
+ const rel = path.relative(projectRoot, path.join(absDir, gof)).replace(/\\/g, '/');
578
+ if (!resolved.includes(rel)) resolved.push(rel);
579
+ }
580
+ } catch { /* directory unreadable — skip */ }
581
+ }
582
+ }
352
583
  }
353
584
  // ── Rust crate-local: crate::module::item → src/module.rs ──────────
354
585
  else if (ext === '.rs' && raw.startsWith('crate::')) {
@@ -419,6 +650,65 @@ export async function getLocalEmbeddingsBatch(texts, graceful = true) {
419
650
  return null;
420
651
  }
421
652
 
653
+ // ─── Enrichment helpers (param names, return type, class context) ─────────────
654
+
655
+ export function extractParams(chunkNode, ext) {
656
+ const params = [];
657
+ const JS_LIKE = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
658
+ const paramTypes = JS_LIKE.includes(ext)
659
+ ? ['required_parameter', 'optional_parameter', 'formal_parameters', 'identifier']
660
+ : ['parameter', 'formal_parameter', 'identifier'];
661
+
662
+ function walkParams(node) {
663
+ if (node.type === 'formal_parameters' || node.type === 'parameters' || node.type === 'parameter_list') {
664
+ for (const child of node.children) {
665
+ // TS: required_parameter / optional_parameter have an identifier child
666
+ if (child.type === 'required_parameter' || child.type === 'optional_parameter' || child.type === 'formal_parameter') {
667
+ const id = child.childForFieldName?.('pattern') || child.childForFieldName?.('name') ||
668
+ child.children.find(c => c.type === 'identifier');
669
+ if (id) params.push(id.text);
670
+ // also grab type annotation text
671
+ const typeAnnotation = child.childForFieldName?.('type');
672
+ if (typeAnnotation) {
673
+ const typeText = typeAnnotation.text.replace(/^:\s*/, '').trim();
674
+ if (typeText) params.push(typeText);
675
+ }
676
+ } else if (child.type === 'identifier') {
677
+ params.push(child.text);
678
+ }
679
+ }
680
+ }
681
+ for (const child of node.children) walkParams(child);
682
+ }
683
+
684
+ // Only walk the direct params node to avoid deep recursion into body
685
+ const paramsNode = chunkNode.childForFieldName?.('parameters') || chunkNode.childForFieldName?.('formal_parameters');
686
+ if (paramsNode) walkParams(paramsNode);
687
+ return [...new Set(params)].filter(p => p && p.length > 1).slice(0, 15);
688
+ }
689
+
690
+ export function extractReturnType(chunkNode, ext) {
691
+ const JS_LIKE = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
692
+ if (JS_LIKE.includes(ext)) {
693
+ const retTypeNode = chunkNode.childForFieldName?.('return_type');
694
+ if (retTypeNode) return retTypeNode.text.replace(/^:\s*/, '').trim().slice(0, 80);
695
+ }
696
+ return '';
697
+ }
698
+
699
+ export function extractClassContext(chunkNode) {
700
+ let parent = chunkNode.parent;
701
+ while (parent) {
702
+ if (parent.type === 'class_declaration' || parent.type === 'class_definition' ||
703
+ parent.type === 'class_body' || parent.type === 'impl_item') {
704
+ const nameNode = parent.childForFieldName?.('name');
705
+ if (nameNode) return nameNode.text;
706
+ }
707
+ parent = parent.parent;
708
+ }
709
+ return '';
710
+ }
711
+
422
712
  export function extractCalls(rootNode) {
423
713
  const calls = new Set();
424
714
  function walk(node) {
@@ -450,6 +740,16 @@ export function extractCalls(rootNode) {
450
740
  const macroNode = node.childForFieldName?.('macro') || node.children[0];
451
741
  if (macroNode && macroNode.type === 'identifier') calls.add(macroNode.text + '!');
452
742
  }
743
+ // Java / C#: method_invocation
744
+ else if (node.type === 'method_invocation') {
745
+ const nameNode = node.childForFieldName?.('name') || node.children.find(c => c.type === 'identifier');
746
+ if (nameNode) calls.add(nameNode.text);
747
+ }
748
+ // Ruby: method_call / call
749
+ else if (node.type === 'method_call') {
750
+ const method = node.childForFieldName?.('method') || node.children.find(c => c.type === 'identifier');
751
+ if (method && method.type === 'identifier') calls.add(method.text);
752
+ }
453
753
  node.children.forEach(walk);
454
754
  }
455
755
  walk(rootNode);
package/watch-daemon.mjs CHANGED
@@ -4,16 +4,16 @@
4
4
  * @description Native FileSystem Watcher Daemon to maintain an in-memory graph index. Utilizes Tree-sitter for AST parsing and local embedding generation.
5
5
  * @author MaquinaTech <https://github.com/MaquinaTech>
6
6
  * @copyright (c) 2026 MaquinaTech. All rights reserved.
7
- * @license GPL-3.0-only
8
- * This program is free software: you can redistribute it and/or modify
9
- * it under the terms of the GNU General Public License as published by
10
- * the Free Software Foundation, either version 3 of the License, or
11
- * (at your option) any later version.
12
- *
13
- * This program is distributed with the hope that it will be beneficial,
14
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- * GNU General Public License for more details.
7
+ * @license MIT
8
+ * Copyright (c) 2026 MaquinaTech. All rights reserved.
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions: The above copyright
15
+ * notice and this permission notice shall be included in all copies or
16
+ * substantial portions of the Software.
17
17
  */
18
18
 
19
19
  import fs from 'fs';
@@ -62,7 +62,7 @@ async function processFileChange(absolutePath) {
62
62
  const parser = getParserForFile(ext);
63
63
  if (!parser) return;
64
64
 
65
- const tree = parser.parse(content);
65
+ const tree = parser.parse((offset) => offset < content.length ? content.slice(offset, offset + 4096) : null);
66
66
  const imports = resolveLocalImports(extractImportsFromAST(tree.rootNode, ext), filename, PROJECT_ROOT);
67
67
  db.updateFileGraph(filename, imports);
68
68
 
@@ -114,9 +114,18 @@ async function processFileChange(absolutePath) {
114
114
 
115
115
  // Unified lexical re-indexing of neighborhood
116
116
  const cleanDeps = imports.map(d => d.split('/').pop().split('.')[0]);
117
- const enrichedContext = `${chunk.name} ${chunk.docstring || ''} ${cleanDeps.join(' ')} ${(chunk.calls || []).join(' ')} ${chunk.code_snippet}`;
118
-
119
- db._indexLexical(chunk.id, enrichedContext);
117
+ const enrichedContext = [
118
+ chunk.name,
119
+ chunk.docstring || '',
120
+ cleanDeps.join(' '),
121
+ (chunk.calls || []).join(' '),
122
+ (chunk.params || []).join(' '),
123
+ chunk.return_type || '',
124
+ chunk.class_context ? `${chunk.class_context}.${chunk.name}` : '',
125
+ chunk.code_snippet
126
+ ].join(' ');
127
+
128
+ db._indexLexical(chunk.id, enrichedContext, chunk.file_path);
120
129
  db.chunks.set(chunk.id, chunk);
121
130
  }
122
131