graph-indexer 1.0.0 → 1.0.2
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/LICENSE +21 -677
- package/PROMPT.md +160 -133
- package/README.md +244 -335
- package/core-engine.mjs +396 -121
- package/indexer.mjs +19 -19
- package/init.mjs +241 -0
- package/mcp-server.mjs +441 -117
- package/package.json +16 -4
- package/parser-utils.mjs +357 -17
- package/watch-daemon.mjs +25 -16
package/indexer.mjs
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* @description In-Memory Graph Indexer — Bootstrap Engine. Reads .ts/.tsx/.js/.jsx files → Tree-sitter AST → Local Embeddings → code-index.json. Zero external dependencies (only Tree-sitter). No ChromaDB.
|
|
5
5
|
* @author MaquinaTech <https://github.com/MaquinaTech>
|
|
6
6
|
* @copyright (c) 2026 MaquinaTech. All rights reserved.
|
|
7
|
-
* @license
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
import fs from 'fs';
|
|
19
19
|
import path from 'path';
|
|
@@ -61,7 +61,7 @@ async function main() {
|
|
|
61
61
|
for (const absolutePath of files) {
|
|
62
62
|
totalCheckedFiles++;
|
|
63
63
|
const relPath = path.relative(PROJECT_ROOT, absolutePath).replace(/\\/g, '/');
|
|
64
|
-
process.stdout.write(`\r⚡ Parsing AST: [${totalCheckedFiles}/${files.length}]
|
|
64
|
+
process.stdout.write(`\r⚡ Parsing AST: [${totalCheckedFiles}/${files.length}] Processing: ${relPath.slice(-40)} `);
|
|
65
65
|
|
|
66
66
|
if (relPath.includes('.bundle.') || relPath.includes('.min.')) continue;
|
|
67
67
|
|
|
@@ -76,7 +76,7 @@ async function main() {
|
|
|
76
76
|
const parser = getParserForFile(ext);
|
|
77
77
|
if (!parser) continue;
|
|
78
78
|
|
|
79
|
-
let tree = parser.parse(content);
|
|
79
|
+
let tree = parser.parse((offset) => offset < content.length ? content.slice(offset, offset + 4096) : null);
|
|
80
80
|
const rawImports = extractImportsFromAST(tree.rootNode, ext);
|
|
81
81
|
const imports = resolveLocalImports(rawImports, relPath, PROJECT_ROOT);
|
|
82
82
|
indexData.graph.dependencies[relPath] = imports;
|
|
@@ -92,14 +92,14 @@ async function main() {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
} catch (err) {
|
|
95
|
-
console.error(`\n💥 Error
|
|
95
|
+
console.error(`\n💥 Error in ${relPath}: ${err.message}`);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
const pendingChunks = indexData.chunksToEmbed || [];
|
|
100
100
|
console.log(`\n\n🧠 Embedding Generation (Ollama)`);
|
|
101
|
-
console.log(`Chunks
|
|
102
|
-
console.log(`
|
|
101
|
+
console.log(`Chunks reused from cache: ${indexData.chunks.length}`);
|
|
102
|
+
console.log(`New chunks to process: ${pendingChunks.length}`);
|
|
103
103
|
|
|
104
104
|
if (pendingChunks.length > 0) {
|
|
105
105
|
const BATCH_SIZE = 64;
|
|
@@ -124,11 +124,11 @@ async function main() {
|
|
|
124
124
|
? `This code architectural neighborhood connects with: ${cleanDeps.join(', ')}.`
|
|
125
125
|
: '';
|
|
126
126
|
|
|
127
|
-
// 🥇 BLEED PROTECTION: Flattened linear payload for Ollama without hidden indentation
|
|
128
127
|
return [
|
|
129
128
|
`File Location: ${c.file_path}`,
|
|
130
129
|
`Symbol Name: ${c.node_type} -> ${c.name}`,
|
|
131
130
|
c.docstring ? `Developer Documentation: ${c.docstring}` : '',
|
|
131
|
+
c.type_refs?.length ? `Type References: ${c.type_refs.join(', ')}` : '',
|
|
132
132
|
topologicalContext,
|
|
133
133
|
`--- Source Code ---`,
|
|
134
134
|
c.code_snippet
|
|
@@ -146,12 +146,12 @@ async function main() {
|
|
|
146
146
|
for (const chunk of batch) indexData.chunks.push(chunk);
|
|
147
147
|
}
|
|
148
148
|
completedChunksCount += batch.length;
|
|
149
|
-
process.stdout.write(`\r🤖 Embedding Progress: [${completedChunksCount}/${pendingChunks.length}] Chunks
|
|
149
|
+
process.stdout.write(`\r🤖 Embedding Progress: [${completedChunksCount}/${pendingChunks.length}] Chunks processed...`);
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
for (let i = 0; i < batches.length; i += CONCURRENCY) {
|
|
153
|
-
const
|
|
154
|
-
await Promise.all(
|
|
153
|
+
const batchGroup = batches.slice(i, i + CONCURRENCY);
|
|
154
|
+
await Promise.all(batchGroup.map(batch => worker(batch)));
|
|
155
155
|
}
|
|
156
156
|
console.timeEnd("Embedding Generation Duration");
|
|
157
157
|
}
|
package/init.mjs
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file init.mjs
|
|
4
|
+
* @description graph-indexer init CLI — auto-configures all detected IDEs/agents.
|
|
5
|
+
* @author MaquinaTech <https://github.com/MaquinaTech>
|
|
6
|
+
* @copyright (c) 2026 MaquinaTech. All rights reserved.
|
|
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
|
+
*/
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import os from 'os';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const isDryRun = process.argv.includes('--dry-run');
|
|
25
|
+
const PROJECT_ROOT = process.cwd();
|
|
26
|
+
|
|
27
|
+
// ─── MCP Server config block ──────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const SERVER_CONFIG = {
|
|
30
|
+
command: 'node',
|
|
31
|
+
args: [path.join(PROJECT_ROOT, 'node_modules', 'graph-indexer', 'mcp-server.mjs')],
|
|
32
|
+
env: { MCP_PROJECT_ROOT: PROJECT_ROOT }
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ─── Utilities ────────────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
function log(msg) { process.stdout.write(msg + '\n'); }
|
|
38
|
+
|
|
39
|
+
function writeFile(filePath, content) {
|
|
40
|
+
if (isDryRun) {
|
|
41
|
+
log(` [dry-run] Would write: ${filePath}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
45
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJsonSafe(filePath) {
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(filePath)) {
|
|
51
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
} catch { /* malformed JSON — start fresh */ }
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Deep-merge src into dst without duplicating top-level keys. */
|
|
58
|
+
function mergeJson(dst, src) {
|
|
59
|
+
for (const [k, v] of Object.entries(src)) {
|
|
60
|
+
if (dst[k] && typeof dst[k] === 'object' && !Array.isArray(dst[k]) && typeof v === 'object' && !Array.isArray(v)) {
|
|
61
|
+
mergeJson(dst[k], v);
|
|
62
|
+
} else {
|
|
63
|
+
dst[k] = v;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return dst;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── IDE Detectors & Configurators ───────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function configureVSCode() {
|
|
72
|
+
const vscodeDir = path.join(PROJECT_ROOT, '.vscode');
|
|
73
|
+
const configPath = path.join(vscodeDir, 'mcp.json');
|
|
74
|
+
|
|
75
|
+
const existing = readJsonSafe(configPath) || {};
|
|
76
|
+
if (!existing.servers) existing.servers = {};
|
|
77
|
+
if (existing.servers['graph-indexer']) return false; // already configured
|
|
78
|
+
|
|
79
|
+
existing.servers['graph-indexer'] = SERVER_CONFIG;
|
|
80
|
+
writeFile(configPath, JSON.stringify(existing, null, 2) + '\n');
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function configureCursor() {
|
|
85
|
+
// Check both project-level and global Cursor config
|
|
86
|
+
const candidates = [
|
|
87
|
+
path.join(PROJECT_ROOT, '.cursor', 'mcp.json'),
|
|
88
|
+
path.join(os.homedir(), '.cursor', 'mcp.json'),
|
|
89
|
+
];
|
|
90
|
+
const detected = candidates.some(p => fs.existsSync(path.dirname(p)));
|
|
91
|
+
|
|
92
|
+
// Prefer project-level config
|
|
93
|
+
const configPath = candidates[0];
|
|
94
|
+
const existing = readJsonSafe(configPath) || {};
|
|
95
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
96
|
+
if (existing.mcpServers['graph-indexer']) return false;
|
|
97
|
+
|
|
98
|
+
existing.mcpServers['graph-indexer'] = SERVER_CONFIG;
|
|
99
|
+
writeFile(configPath, JSON.stringify(existing, null, 2) + '\n');
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function configureClaudeDesktop() {
|
|
104
|
+
let configPath;
|
|
105
|
+
if (process.platform === 'win32') {
|
|
106
|
+
configPath = path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');
|
|
107
|
+
} else {
|
|
108
|
+
configPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(path.dirname(configPath))) return false; // Claude not installed
|
|
112
|
+
|
|
113
|
+
const existing = readJsonSafe(configPath) || {};
|
|
114
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
115
|
+
if (existing.mcpServers['graph-indexer']) return false;
|
|
116
|
+
|
|
117
|
+
existing.mcpServers['graph-indexer'] = SERVER_CONFIG;
|
|
118
|
+
writeFile(configPath, JSON.stringify(existing, null, 2) + '\n');
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function configureClaudeCode() {
|
|
123
|
+
// Claude Code stores MCP config in .mcp.json or .claude/settings.json at project root
|
|
124
|
+
const candidates = [
|
|
125
|
+
path.join(PROJECT_ROOT, '.claude', 'settings.json'),
|
|
126
|
+
path.join(PROJECT_ROOT, '.mcp.json'),
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
// Prefer .claude/settings.json if directory exists, else .mcp.json
|
|
130
|
+
const hasClaudeDir = fs.existsSync(path.join(PROJECT_ROOT, '.claude'));
|
|
131
|
+
const configPath = hasClaudeDir ? candidates[0] : candidates[1];
|
|
132
|
+
|
|
133
|
+
const existing = readJsonSafe(configPath) || {};
|
|
134
|
+
const serverKey = hasClaudeDir ? 'mcpServers' : 'mcpServers';
|
|
135
|
+
if (!existing[serverKey]) existing[serverKey] = {};
|
|
136
|
+
if (existing[serverKey]['graph-indexer']) return false;
|
|
137
|
+
|
|
138
|
+
existing[serverKey]['graph-indexer'] = SERVER_CONFIG;
|
|
139
|
+
writeFile(configPath, JSON.stringify(existing, null, 2) + '\n');
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── package.json scripts ─────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
function addPackageScripts() {
|
|
146
|
+
const pkgPath = path.join(PROJECT_ROOT, 'package.json');
|
|
147
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
148
|
+
|
|
149
|
+
const pkg = readJsonSafe(pkgPath);
|
|
150
|
+
if (!pkg) return false;
|
|
151
|
+
|
|
152
|
+
const scripts = pkg.scripts || {};
|
|
153
|
+
const toAdd = {
|
|
154
|
+
'mcp:index': 'idx-index --repo .',
|
|
155
|
+
'mcp:watch': 'idx-watch',
|
|
156
|
+
'mcp:start': 'idx-mcp',
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
let changed = false;
|
|
160
|
+
for (const [k, v] of Object.entries(toAdd)) {
|
|
161
|
+
if (!scripts[k]) { scripts[k] = v; changed = true; }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (changed) {
|
|
165
|
+
pkg.scripts = scripts;
|
|
166
|
+
writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
167
|
+
}
|
|
168
|
+
return changed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── .gitignore ──────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
function updateGitignore() {
|
|
174
|
+
const gitignorePath = path.join(PROJECT_ROOT, '.gitignore');
|
|
175
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
176
|
+
|
|
177
|
+
const entries = ['code-index.json', 'code-index.embeddings.bin'];
|
|
178
|
+
const toAdd = entries.filter(e => !existing.includes(e));
|
|
179
|
+
if (toAdd.length === 0) return false;
|
|
180
|
+
|
|
181
|
+
const newContent = existing.trimEnd() + '\n\n# graph-indexer runtime artifacts\n' + toAdd.join('\n') + '\n';
|
|
182
|
+
writeFile(gitignorePath, newContent);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
log('\n🔧 graph-indexer init' + (isDryRun ? ' [dry-run]' : '') + '\n');
|
|
189
|
+
log(`📂 Project: ${PROJECT_ROOT}\n`);
|
|
190
|
+
|
|
191
|
+
const configured = [];
|
|
192
|
+
const skipped = [];
|
|
193
|
+
|
|
194
|
+
// IDE configuration
|
|
195
|
+
const ides = [
|
|
196
|
+
{ name: 'VS Code', fn: configureVSCode },
|
|
197
|
+
{ name: 'Cursor', fn: configureCursor },
|
|
198
|
+
{ name: 'Claude Desktop', fn: configureClaudeDesktop },
|
|
199
|
+
{ name: 'Claude Code', fn: configureClaudeCode },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
log('🔍 Detecting IDEs / agents...\n');
|
|
203
|
+
for (const { name, fn } of ides) {
|
|
204
|
+
try {
|
|
205
|
+
const result = fn();
|
|
206
|
+
if (result === false) {
|
|
207
|
+
skipped.push(name + ' (already configured or not installed)');
|
|
208
|
+
} else {
|
|
209
|
+
configured.push(name);
|
|
210
|
+
}
|
|
211
|
+
} catch (e) {
|
|
212
|
+
skipped.push(name + ' (error: ' + e.message + ')');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Package scripts
|
|
217
|
+
const scriptsAdded = addPackageScripts();
|
|
218
|
+
if (scriptsAdded) configured.push('package.json scripts (mcp:index, mcp:watch, mcp:start)');
|
|
219
|
+
else skipped.push('package.json scripts (already present)');
|
|
220
|
+
|
|
221
|
+
// .gitignore
|
|
222
|
+
const gitignoreUpdated = updateGitignore();
|
|
223
|
+
if (gitignoreUpdated) configured.push('.gitignore (code-index.json, code-index.embeddings.bin)');
|
|
224
|
+
else skipped.push('.gitignore (already contains index entries)');
|
|
225
|
+
|
|
226
|
+
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
log('\n✅ Configured:\n');
|
|
229
|
+
if (configured.length) configured.forEach(c => log(' • ' + c));
|
|
230
|
+
else log(' (nothing new to configure)');
|
|
231
|
+
|
|
232
|
+
if (skipped.length) {
|
|
233
|
+
log('\n⏭️ Skipped:\n');
|
|
234
|
+
skipped.forEach(s => log(' • ' + s));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
log('\n📋 Next steps:\n');
|
|
238
|
+
log(' 1. Run `npm run mcp:index` to index this project');
|
|
239
|
+
log(' 2. Restart your IDE to activate MCP servers');
|
|
240
|
+
log(' 3. Run `npm run mcp:start` if your IDE needs the server started manually');
|
|
241
|
+
log('\n');
|