graph-indexer 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graph-indexer",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Zero-DB, Air-gapped, In-Memory Graph Indexer MCP Server with Hybrid RRF Search and Cross-Language Topology",
5
5
  "type": "module",
6
6
  "main": "core-engine.mjs",
@@ -42,17 +42,6 @@
42
42
  "chokidar": "5.0.0",
43
43
  "ignore": "5.3.2",
44
44
  "tree-sitter": "0.21.1",
45
- "tree-sitter-c-sharp": "^0.21.3",
46
- "tree-sitter-css": "0.21.1",
47
- "tree-sitter-go": "0.21.0",
48
- "tree-sitter-java": "^0.21.0",
49
- "tree-sitter-javascript": "0.21.4",
50
- "tree-sitter-kotlin": "^0.3.8",
51
- "tree-sitter-php": "0.23.1",
52
- "tree-sitter-python": "0.21.0",
53
- "tree-sitter-ruby": "^0.23.1",
54
- "tree-sitter-rust": "0.21.0",
55
- "tree-sitter-typescript": "0.21.2",
56
45
  "zod": "3.23.8"
57
46
  },
58
47
  "keywords": [
@@ -88,6 +77,17 @@
88
77
  "access": "public"
89
78
  },
90
79
  "optionalDependencies": {
91
- "hnswlib-node": "^3.0.0"
80
+ "hnswlib-node": "^3.0.0",
81
+ "tree-sitter-c-sharp": "^0.21.3",
82
+ "tree-sitter-css": "0.21.1",
83
+ "tree-sitter-go": "0.21.0",
84
+ "tree-sitter-java": "^0.21.0",
85
+ "tree-sitter-javascript": "0.21.4",
86
+ "tree-sitter-kotlin": "^0.3.8",
87
+ "tree-sitter-php": "0.23.1",
88
+ "tree-sitter-python": "0.21.0",
89
+ "tree-sitter-ruby": "^0.23.1",
90
+ "tree-sitter-rust": "0.21.0",
91
+ "tree-sitter-typescript": "0.21.2"
92
92
  }
93
93
  }
package/parser-utils.mjs CHANGED
@@ -19,34 +19,65 @@ import path from 'path';
19
19
  import { createHash } from 'crypto';
20
20
  import ignore from 'ignore';
21
21
  import Parser from 'tree-sitter';
22
- const { Query } = Parser; // Importación robusta nativa
23
-
24
- import TypeScript from 'tree-sitter-typescript';
25
- import JavaScript from 'tree-sitter-javascript';
26
- import CSS from 'tree-sitter-css';
27
- import Python from 'tree-sitter-python';
28
- import Rust from 'tree-sitter-rust';
29
- import Go from 'tree-sitter-go';
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';
22
+ const { Query } = Parser; // Native parser query helper
23
+
35
24
  import { truncateForEmbedding } from './core-engine.mjs';
36
25
 
37
26
  export const MAX_FILE_SIZE_BYTES = 500000;
38
27
  export const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434";
39
28
 
29
+ // ─── Dynamic Language Loading ─────────────────────────────────────────────────
30
+
31
+ function _loadProjectConfig() {
32
+ const configPath = path.join(process.env.MCP_PROJECT_ROOT || process.cwd(), '.graph-indexer.json');
33
+ try {
34
+ if (fs.existsSync(configPath)) return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
35
+ } catch { /* ignore */ }
36
+ return null;
37
+ }
38
+
39
+ async function _tryLang(pkg, enabledLangs, key) {
40
+ if (enabledLangs && !enabledLangs.includes(key)) return null;
41
+ try {
42
+ return (await import(pkg)).default;
43
+ } catch {
44
+ process.stderr.write(`[graph-indexer] WARNING: ${pkg} not installed — ${key} files will be skipped\n`);
45
+ return null;
46
+ }
47
+ }
48
+
49
+ const _cfg = _loadProjectConfig();
50
+ const _enabled = _cfg?.languages ?? null; // null = all languages
51
+
52
+ const [
53
+ TypeScript, JavaScript, CSS, Python, Rust,
54
+ Go, PHP, Java, Kotlin, CSharp, Ruby
55
+ ] = await Promise.all([
56
+ _tryLang('tree-sitter-typescript', _enabled, 'typescript'),
57
+ _tryLang('tree-sitter-javascript', _enabled, 'javascript'),
58
+ _tryLang('tree-sitter-css', _enabled, 'css'),
59
+ _tryLang('tree-sitter-python', _enabled, 'python'),
60
+ _tryLang('tree-sitter-rust', _enabled, 'rust'),
61
+ _tryLang('tree-sitter-go', _enabled, 'go'),
62
+ _tryLang('tree-sitter-php', _enabled, 'php'),
63
+ _tryLang('tree-sitter-java', _enabled, 'java'),
64
+ _tryLang('tree-sitter-kotlin', _enabled, 'kotlin'),
65
+ _tryLang('tree-sitter-c-sharp', _enabled, 'csharp'),
66
+ _tryLang('tree-sitter-ruby', _enabled, 'ruby'),
67
+ ]);
68
+
40
69
  const LANGUAGE_MAP = {
41
- '.ts': TypeScript.typescript, '.tsx': TypeScript.tsx,
42
- '.js': JavaScript, '.jsx': JavaScript, '.mjs': JavaScript, '.cjs': JavaScript,
43
- '.css': CSS, '.scss': CSS,
44
- '.py': Python, '.rs': Rust, '.go': Go,
45
- '.php': PHP.php,
46
- '.java': Java,
47
- '.kt': Kotlin, '.kts': Kotlin,
48
- '.cs': CSharp,
49
- '.rb': Ruby,
70
+ ...(TypeScript ? { '.ts': TypeScript.typescript, '.tsx': TypeScript.tsx } : {}),
71
+ ...(JavaScript ? { '.js': JavaScript, '.jsx': JavaScript, '.mjs': JavaScript, '.cjs': JavaScript } : {}),
72
+ ...(CSS ? { '.css': CSS, '.scss': CSS } : {}),
73
+ ...(Python ? { '.py': Python } : {}),
74
+ ...(Rust ? { '.rs': Rust } : {}),
75
+ ...(Go ? { '.go': Go } : {}),
76
+ ...(PHP ? { '.php': PHP.php } : {}),
77
+ ...(Java ? { '.java': Java } : {}),
78
+ ...(Kotlin ? { '.kt': Kotlin, '.kts': Kotlin } : {}),
79
+ ...(CSharp ? { '.cs': CSharp } : {}),
80
+ ...(Ruby ? { '.rb': Ruby } : {}),
50
81
  };
51
82
 
52
83
  // 🥇 ULTRA-GENERIC QUERIES: Immune to grammar changes between TS/JS/TSX
@@ -398,6 +429,7 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
398
429
  const params = extractParams(chunkNode, ext);
399
430
  const returnType = extractReturnType(chunkNode, ext);
400
431
  const classContext = extractClassContext(chunkNode);
432
+ const typeRefs = extractTypeAnnotations(chunkNode, ext);
401
433
 
402
434
  const id = createHash('sha256')
403
435
  .update(`${relPath}::${chunkNode.startPosition.row}::${chunkNode.startPosition.column}`)
@@ -408,7 +440,8 @@ export function extractSemanticChunks(rootNode, relPath, sourceCode, ext) {
408
440
  docstring: docstring, code_snippet: snippet, content_hash: hash,
409
441
  start_line: chunkNode.startPosition.row + 1, end_line: chunkNode.endPosition.row + 1,
410
442
  calls: outgoingCalls,
411
- params, return_type: returnType, class_context: classContext
443
+ params, return_type: returnType, class_context: classContext,
444
+ type_refs: typeRefs,
412
445
  });
413
446
  }
414
447
  } catch (e) {
@@ -709,6 +742,44 @@ export function extractClassContext(chunkNode) {
709
742
  return '';
710
743
  }
711
744
 
745
+ /**
746
+ * Frontier 2: Extract TypeScript/Python type annotation names from a chunk node.
747
+ * Returns simple type names (e.g. ['User', 'AuthToken', 'PaymentService'])
748
+ * used to enrich the inverted index and the type_refs chunk field.
749
+ */
750
+ export function extractTypeAnnotations(chunkNode, ext) {
751
+ const types = new Set();
752
+ const JS_LIKE = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
753
+ if (!JS_LIKE.includes(ext) && ext !== '.py') return [];
754
+
755
+ function walk(node) {
756
+ // TypeScript: type_annotation nodes contain the type text
757
+ if (node.type === 'type_annotation') {
758
+ const typeText = node.text.replace(/^:\s*/, '').trim();
759
+ // Extract simple identifiers from the type (skip primitives)
760
+ const PRIMITIVES = new Set(['string','number','boolean','void','any','unknown','never','null','undefined','object','symbol','bigint']);
761
+ for (const match of typeText.matchAll(/\b([A-Z][A-Za-z0-9]*)\b/g)) {
762
+ if (!PRIMITIVES.has(match[1].toLowerCase())) types.add(match[1]);
763
+ }
764
+ }
765
+ // TypeScript: generic_type, predefined_type (string, number…) skip, named types keep
766
+ else if (node.type === 'type_identifier' || node.type === 'generic_type') {
767
+ const name = node.children[0]?.text || node.text;
768
+ if (name && /^[A-Z]/.test(name)) types.add(name);
769
+ }
770
+ // Python: type comments or annotations (annotation nodes)
771
+ else if (node.type === 'annotation' && ext === '.py') {
772
+ const typeText = node.text.replace(/^->\s*|^:\s*/, '').trim();
773
+ for (const match of typeText.matchAll(/\b([A-Z][A-Za-z0-9]*)\b/g)) {
774
+ types.add(match[1]);
775
+ }
776
+ }
777
+ for (const child of node.children) walk(child);
778
+ }
779
+ walk(chunkNode);
780
+ return Array.from(types).slice(0, 20);
781
+ }
782
+
712
783
  export function extractCalls(rootNode) {
713
784
  const calls = new Set();
714
785
  function walk(node) {
package/watch-daemon.mjs CHANGED
@@ -132,11 +132,11 @@ async function processFileChange(absolutePath) {
132
132
  db.saveDebounced();
133
133
  process.stderr.write(`[daemon] 🔄 Synced: ${filename} (Cache Hit: ${newChunks.length - chunksToEmbed.length}/${newChunks.length})\n`);
134
134
  } catch (err) {
135
- process.stderr.write(`[daemon] ❌ Error en ${filename}: ${err.message}\n`);
135
+ process.stderr.write(`[daemon] ❌ Error in ${filename}: ${err.message}\n`);
136
136
  }
137
137
  }
138
138
 
139
- process.stderr.write(`🚀 Chokidar Watcher Daemon iniciado en: ${PROJECT_ROOT}\n`);
139
+ process.stderr.write(`🚀 Chokidar Watcher Daemon started in: ${PROJECT_ROOT}\n`);
140
140
  const watcher = chokidar.watch(PROJECT_ROOT, {
141
141
  ignored: /(^|[\/\\])\../, persistent: true,
142
142
  awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }