edhindex 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/README.md +228 -0
- package/dist/cli/commands/config_cmd.d.ts +2 -0
- package/dist/cli/commands/config_cmd.d.ts.map +1 -0
- package/dist/cli/commands/config_cmd.js +53 -0
- package/dist/cli/commands/config_cmd.js.map +1 -0
- package/dist/cli/commands/doctor_cmd.d.ts +2 -0
- package/dist/cli/commands/doctor_cmd.d.ts.map +1 -0
- package/dist/cli/commands/doctor_cmd.js +32 -0
- package/dist/cli/commands/doctor_cmd.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +2 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +26 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/index_cmd.d.ts +2 -0
- package/dist/cli/commands/index_cmd.d.ts.map +1 -0
- package/dist/cli/commands/index_cmd.js +110 -0
- package/dist/cli/commands/index_cmd.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +53 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/knowledge-graph.d.ts +8 -0
- package/dist/cli/commands/knowledge-graph.d.ts.map +1 -0
- package/dist/cli/commands/knowledge-graph.js +76 -0
- package/dist/cli/commands/knowledge-graph.js.map +1 -0
- package/dist/cli/commands/models.d.ts +2 -0
- package/dist/cli/commands/models.d.ts.map +1 -0
- package/dist/cli/commands/models.js +17 -0
- package/dist/cli/commands/models.js.map +1 -0
- package/dist/cli/commands/reset.d.ts +2 -0
- package/dist/cli/commands/reset.d.ts.map +1 -0
- package/dist/cli/commands/reset.js +35 -0
- package/dist/cli/commands/reset.js.map +1 -0
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +40 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +265 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +47 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +121 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/index.d.ts +28 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +65 -0
- package/dist/config/index.js.map +1 -0
- package/dist/doctor/index.d.ts +8 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +117 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/embeddings/provider.d.ts +9 -0
- package/dist/embeddings/provider.d.ts.map +1 -0
- package/dist/embeddings/provider.js +2 -0
- package/dist/embeddings/provider.js.map +1 -0
- package/dist/embeddings/transformers.d.ts +26 -0
- package/dist/embeddings/transformers.d.ts.map +1 -0
- package/dist/embeddings/transformers.js +120 -0
- package/dist/embeddings/transformers.js.map +1 -0
- package/dist/graph/build.d.ts +13 -0
- package/dist/graph/build.d.ts.map +1 -0
- package/dist/graph/build.js +33 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/builder.d.ts +24 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +231 -0
- package/dist/graph/builder.js.map +1 -0
- package/dist/graph/render.d.ts +3 -0
- package/dist/graph/render.d.ts.map +1 -0
- package/dist/graph/render.js +60 -0
- package/dist/graph/render.js.map +1 -0
- package/dist/graph/serve.d.ts +6 -0
- package/dist/graph/serve.d.ts.map +1 -0
- package/dist/graph/serve.js +389 -0
- package/dist/graph/serve.js.map +1 -0
- package/dist/graph/service.d.ts +29 -0
- package/dist/graph/service.d.ts.map +1 -0
- package/dist/graph/service.js +73 -0
- package/dist/graph/service.js.map +1 -0
- package/dist/graph/storage.d.ts +33 -0
- package/dist/graph/storage.d.ts.map +1 -0
- package/dist/graph/storage.js +164 -0
- package/dist/graph/storage.js.map +1 -0
- package/dist/graph/svg.d.ts +27 -0
- package/dist/graph/svg.d.ts.map +1 -0
- package/dist/graph/svg.js +212 -0
- package/dist/graph/svg.js.map +1 -0
- package/dist/graph/types.d.ts +34 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +9 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/indexer/chunker.d.ts +18 -0
- package/dist/indexer/chunker.d.ts.map +1 -0
- package/dist/indexer/chunker.js +139 -0
- package/dist/indexer/chunker.js.map +1 -0
- package/dist/indexer/file-utils.d.ts +17 -0
- package/dist/indexer/file-utils.d.ts.map +1 -0
- package/dist/indexer/file-utils.js +123 -0
- package/dist/indexer/file-utils.js.map +1 -0
- package/dist/indexer/ignore.d.ts +5 -0
- package/dist/indexer/ignore.d.ts.map +1 -0
- package/dist/indexer/ignore.js +96 -0
- package/dist/indexer/ignore.js.map +1 -0
- package/dist/indexer/incremental.d.ts +16 -0
- package/dist/indexer/incremental.d.ts.map +1 -0
- package/dist/indexer/incremental.js +55 -0
- package/dist/indexer/incremental.js.map +1 -0
- package/dist/indexer/indexer.d.ts +36 -0
- package/dist/indexer/indexer.d.ts.map +1 -0
- package/dist/indexer/indexer.js +121 -0
- package/dist/indexer/indexer.js.map +1 -0
- package/dist/indexer/parser.d.ts +30 -0
- package/dist/indexer/parser.d.ts.map +1 -0
- package/dist/indexer/parser.js +225 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/logging.d.ts +15 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +42 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +226 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp-config.d.ts +4 -0
- package/dist/mcp-config.d.ts.map +1 -0
- package/dist/mcp-config.js +175 -0
- package/dist/mcp-config.js.map +1 -0
- package/dist/progress.d.ts +12 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +16 -0
- package/dist/progress.js.map +1 -0
- package/dist/retrieval/bm25.d.ts +6 -0
- package/dist/retrieval/bm25.d.ts.map +1 -0
- package/dist/retrieval/bm25.js +20 -0
- package/dist/retrieval/bm25.js.map +1 -0
- package/dist/retrieval/reranker.d.ts +11 -0
- package/dist/retrieval/reranker.d.ts.map +1 -0
- package/dist/retrieval/reranker.js +46 -0
- package/dist/retrieval/reranker.js.map +1 -0
- package/dist/retrieval/search.d.ts +35 -0
- package/dist/retrieval/search.d.ts.map +1 -0
- package/dist/retrieval/search.js +95 -0
- package/dist/retrieval/search.js.map +1 -0
- package/dist/security.d.ts +5 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +42 -0
- package/dist/security.js.map +1 -0
- package/dist/storage/fts.d.ts +30 -0
- package/dist/storage/fts.d.ts.map +1 -0
- package/dist/storage/fts.js +107 -0
- package/dist/storage/fts.js.map +1 -0
- package/dist/storage/metadata.d.ts +46 -0
- package/dist/storage/metadata.d.ts.map +1 -0
- package/dist/storage/metadata.js +133 -0
- package/dist/storage/metadata.js.map +1 -0
- package/dist/vector/lancedb.d.ts +25 -0
- package/dist/vector/lancedb.d.ts.map +1 -0
- package/dist/vector/lancedb.js +93 -0
- package/dist/vector/lancedb.js.map +1 -0
- package/dist/vector/store.d.ts +16 -0
- package/dist/vector/store.d.ts.map +1 -0
- package/dist/vector/store.js +2 -0
- package/dist/vector/store.js.map +1 -0
- package/dist/version.d.ts +12 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +46 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher/index.d.ts +15 -0
- package/dist/watcher/index.d.ts.map +1 -0
- package/dist/watcher/index.js +65 -0
- package/dist/watcher/index.js.map +1 -0
- package/package.json +35 -4
- package/src/cli/commands/graph.ts +30 -0
- package/src/cli/commands/init.ts +41 -3
- package/src/cli/commands/knowledge-graph.ts +85 -0
- package/src/cli/commands/reset.ts +40 -0
- package/src/cli/commands/start.ts +54 -21
- package/src/cli/index.ts +33 -0
- package/src/config/index.ts +13 -0
- package/src/graph/build.ts +45 -0
- package/src/graph/builder.ts +243 -0
- package/src/graph/render.ts +67 -0
- package/src/graph/serve.ts +399 -0
- package/src/graph/service.ts +84 -0
- package/src/graph/storage.ts +181 -0
- package/src/graph/svg.ts +241 -0
- package/src/graph/types.ts +68 -0
- package/src/mcp/server.ts +212 -85
- package/src/mcp-config.ts +185 -0
- package/src/storage/metadata.ts +9 -1
- package/.edhindexignore +0 -66
- package/BUILD_BRIEF.md +0 -228
- package/tsconfig.json +0 -21
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
export type MCPClient =
|
|
7
|
+
| 'opencode'
|
|
8
|
+
| 'cline'
|
|
9
|
+
| 'roo-code'
|
|
10
|
+
| 'claude-code'
|
|
11
|
+
| 'cursor'
|
|
12
|
+
| 'windsurf'
|
|
13
|
+
| 'github-copilot'
|
|
14
|
+
| 'continue'
|
|
15
|
+
| 'none';
|
|
16
|
+
|
|
17
|
+
interface ClientConfig {
|
|
18
|
+
key: MCPClient;
|
|
19
|
+
label: string;
|
|
20
|
+
description: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CLIENT_OPTIONS: ClientConfig[] = [
|
|
24
|
+
{ key: 'opencode', label: 'OpenCode', description: 'opencode.json at project root' },
|
|
25
|
+
{ key: 'cline', label: 'Cline', description: '.cline/mcp.json at project root' },
|
|
26
|
+
{ key: 'roo-code', label: 'Roo Code', description: '.roo/mcp.json at project root' },
|
|
27
|
+
{ key: 'claude-code', label: 'Claude Code', description: 'claude.jsonc at project root' },
|
|
28
|
+
{ key: 'cursor', label: 'Cursor', description: '.cursor/mcp.json at project root' },
|
|
29
|
+
{ key: 'windsurf', label: 'Windsurf', description: '.windsurf/mcp_config.json at project root' },
|
|
30
|
+
{ key: 'github-copilot', label: 'GitHub Copilot', description: '.vscode/settings.json (VS Code workspace)' },
|
|
31
|
+
{ key: 'continue', label: 'Continue', description: '.continue/config.json at project root' },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function isInteractive(): boolean {
|
|
35
|
+
return process.stdin.isTTY && process.stdout.isTTY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function askClients(): Promise<MCPClient[]> {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
if (!isInteractive()) {
|
|
41
|
+
resolve([]);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
45
|
+
const lines = CLIENT_OPTIONS.map((opt, i) => ` ${i + 1}. ${opt.label} — ${opt.description}`);
|
|
46
|
+
const prompt = [
|
|
47
|
+
`\n${chalk.bold('MCP Client Configuration')}`,
|
|
48
|
+
'Which MCP clients do you want to configure?',
|
|
49
|
+
'Enter numbers separated by commas (e.g. 1,3,5) or press Enter to skip:',
|
|
50
|
+
'',
|
|
51
|
+
...lines,
|
|
52
|
+
'',
|
|
53
|
+
].join('\n');
|
|
54
|
+
rl.question(prompt, (answer) => {
|
|
55
|
+
rl.close();
|
|
56
|
+
const trimmed = answer.trim();
|
|
57
|
+
if (!trimmed) {
|
|
58
|
+
resolve([]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const indices = trimmed
|
|
62
|
+
.split(/[, ]+/)
|
|
63
|
+
.map((s) => parseInt(s, 10))
|
|
64
|
+
.filter((n) => !isNaN(n) && n >= 1 && n <= CLIENT_OPTIONS.length);
|
|
65
|
+
const selected = [...new Set(indices.map((i) => CLIENT_OPTIONS[i - 1].key))];
|
|
66
|
+
resolve(selected);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function writeSingleConfig(rootPath: string, client: MCPClient): string {
|
|
72
|
+
const mcpServer = {
|
|
73
|
+
mcpServers: {
|
|
74
|
+
edhindex: {
|
|
75
|
+
command: 'edhindex',
|
|
76
|
+
args: ['start'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const mcpServerCopilot = {
|
|
81
|
+
edhindex: {
|
|
82
|
+
command: 'edhindex',
|
|
83
|
+
args: ['start'],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
switch (client) {
|
|
88
|
+
case 'opencode': {
|
|
89
|
+
const fp = join(rootPath, 'opencode.json');
|
|
90
|
+
const cfg: Record<string, unknown> = { $schema: 'https://opencode.ai/config.json' };
|
|
91
|
+
if (existsSync(fp)) {
|
|
92
|
+
try { Object.assign(cfg, JSON.parse(readFileSync(fp, 'utf-8'))); } catch { /* merge */ }
|
|
93
|
+
}
|
|
94
|
+
cfg.mcp = {
|
|
95
|
+
edhindex: { type: 'local', command: ['edhindex', 'start'], enabled: true, timeout: 120000 },
|
|
96
|
+
};
|
|
97
|
+
writeFileSync(fp, JSON.stringify(cfg, null, 2) + '\n', 'utf-8');
|
|
98
|
+
return fp;
|
|
99
|
+
}
|
|
100
|
+
case 'cline': {
|
|
101
|
+
const dir = join(rootPath, '.cline');
|
|
102
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
103
|
+
const fp = join(dir, 'mcp.json');
|
|
104
|
+
writeFileSync(fp, JSON.stringify(mcpServer, null, 2) + '\n', 'utf-8');
|
|
105
|
+
return fp;
|
|
106
|
+
}
|
|
107
|
+
case 'roo-code': {
|
|
108
|
+
const dir = join(rootPath, '.roo');
|
|
109
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
110
|
+
const fp = join(dir, 'mcp.json');
|
|
111
|
+
writeFileSync(fp, JSON.stringify(mcpServer, null, 2) + '\n', 'utf-8');
|
|
112
|
+
return fp;
|
|
113
|
+
}
|
|
114
|
+
case 'claude-code': {
|
|
115
|
+
const fp = join(rootPath, 'claude.jsonc');
|
|
116
|
+
writeFileSync(fp, JSON.stringify(mcpServer, null, 2) + '\n', 'utf-8');
|
|
117
|
+
return fp;
|
|
118
|
+
}
|
|
119
|
+
case 'cursor': {
|
|
120
|
+
const dir = join(rootPath, '.cursor');
|
|
121
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
122
|
+
const fp = join(dir, 'mcp.json');
|
|
123
|
+
writeFileSync(fp, JSON.stringify(mcpServer, null, 2) + '\n', 'utf-8');
|
|
124
|
+
return fp;
|
|
125
|
+
}
|
|
126
|
+
case 'windsurf': {
|
|
127
|
+
const dir = join(rootPath, '.windsurf');
|
|
128
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
129
|
+
const fp = join(dir, 'mcp_config.json');
|
|
130
|
+
writeFileSync(fp, JSON.stringify(mcpServer, null, 2) + '\n', 'utf-8');
|
|
131
|
+
return fp;
|
|
132
|
+
}
|
|
133
|
+
case 'github-copilot': {
|
|
134
|
+
const dir = join(rootPath, '.vscode');
|
|
135
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
136
|
+
const fp = join(dir, 'settings.json');
|
|
137
|
+
const settings: Record<string, unknown> = {};
|
|
138
|
+
if (existsSync(fp)) {
|
|
139
|
+
try {
|
|
140
|
+
Object.assign(settings, JSON.parse(readFileSync(fp, 'utf-8')));
|
|
141
|
+
} catch { /* start fresh if corrupt */ }
|
|
142
|
+
}
|
|
143
|
+
settings['github.copilot.chat.mcpServers'] = mcpServerCopilot;
|
|
144
|
+
writeFileSync(fp, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
145
|
+
return fp;
|
|
146
|
+
}
|
|
147
|
+
case 'continue': {
|
|
148
|
+
const dir = join(rootPath, '.continue');
|
|
149
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
150
|
+
const fp = join(dir, 'config.json');
|
|
151
|
+
const cfg: Record<string, unknown> = {};
|
|
152
|
+
if (existsSync(fp)) {
|
|
153
|
+
try {
|
|
154
|
+
Object.assign(cfg, JSON.parse(readFileSync(fp, 'utf-8')));
|
|
155
|
+
} catch { /* start fresh if corrupt */ }
|
|
156
|
+
}
|
|
157
|
+
const existing = (cfg.mcpServers as Record<string, unknown>[]) || [];
|
|
158
|
+
if (!existing.find((s: Record<string, unknown>) => s.name === 'edhindex')) {
|
|
159
|
+
cfg.mcpServers = [...existing, { name: 'edhindex', command: 'edhindex', args: ['start'] }];
|
|
160
|
+
}
|
|
161
|
+
writeFileSync(fp, JSON.stringify(cfg, null, 2) + '\n', 'utf-8');
|
|
162
|
+
return fp;
|
|
163
|
+
}
|
|
164
|
+
default:
|
|
165
|
+
throw new Error(`Unknown MCP client: ${client}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function writeMCPConfigs(rootPath: string, clients: MCPClient[]): string[] {
|
|
170
|
+
const paths: string[] = [];
|
|
171
|
+
for (const client of clients) {
|
|
172
|
+
if (client === 'none') continue;
|
|
173
|
+
paths.push(writeSingleConfig(rootPath, client));
|
|
174
|
+
}
|
|
175
|
+
return paths;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function promptAndWriteMCPConfigs(rootPath: string) {
|
|
179
|
+
const clients = await askClients();
|
|
180
|
+
if (clients.length === 0) return;
|
|
181
|
+
const paths = writeMCPConfigs(rootPath, clients);
|
|
182
|
+
for (const fp of paths) {
|
|
183
|
+
console.log(chalk.green(`MCP config written: ${fp}`));
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/storage/metadata.ts
CHANGED
|
@@ -84,6 +84,14 @@ export class MetadataStore {
|
|
|
84
84
|
this.db.exec('ROLLBACK');
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
getAllFilesWithImports(): { file: string; language: string; imports: string; exports: string; chunkCount: number }[] {
|
|
88
|
+
return this.db.prepare(`
|
|
89
|
+
SELECT c.file, c.language, c.imports, c.exports, COUNT(*) as chunkCount
|
|
90
|
+
FROM chunks c
|
|
91
|
+
GROUP BY c.file
|
|
92
|
+
`).all() as { file: string; language: string; imports: string; exports: string; chunkCount: number }[];
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
upsertChunk(chunk: ChunkMetadata) {
|
|
88
96
|
const stmt = this.db.prepare(`
|
|
89
97
|
INSERT OR REPLACE INTO chunks
|
|
@@ -143,7 +151,7 @@ export class MetadataStore {
|
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
getChunksForFile(file: string): ChunkMetadata[] {
|
|
146
|
-
return this.db.prepare('SELECT * FROM chunks WHERE file = ?').all() as ChunkMetadata[];
|
|
154
|
+
return this.db.prepare('SELECT * FROM chunks WHERE file = ?').all(file) as ChunkMetadata[];
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
getChunkCount(): number {
|
package/.edhindexignore
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
node_modules
|
|
2
|
-
.git
|
|
3
|
-
dist
|
|
4
|
-
build
|
|
5
|
-
coverage
|
|
6
|
-
.next
|
|
7
|
-
out
|
|
8
|
-
vendor
|
|
9
|
-
bin
|
|
10
|
-
obj
|
|
11
|
-
target
|
|
12
|
-
tmp
|
|
13
|
-
.cache
|
|
14
|
-
.turbo
|
|
15
|
-
.edhindex
|
|
16
|
-
*.exe
|
|
17
|
-
*.dll
|
|
18
|
-
*.so
|
|
19
|
-
*.dylib
|
|
20
|
-
*.bin
|
|
21
|
-
*.wasm
|
|
22
|
-
*.o
|
|
23
|
-
*.a
|
|
24
|
-
*.lib
|
|
25
|
-
*.pyc
|
|
26
|
-
*.class
|
|
27
|
-
*.jar
|
|
28
|
-
*.war
|
|
29
|
-
*.png
|
|
30
|
-
*.jpg
|
|
31
|
-
*.jpeg
|
|
32
|
-
*.gif
|
|
33
|
-
*.svg
|
|
34
|
-
*.ico
|
|
35
|
-
*.webp
|
|
36
|
-
*.bmp
|
|
37
|
-
*.tiff
|
|
38
|
-
*.ico
|
|
39
|
-
*.pdf
|
|
40
|
-
*.doc
|
|
41
|
-
*.docx
|
|
42
|
-
*.xls
|
|
43
|
-
*.xlsx
|
|
44
|
-
*.ppt
|
|
45
|
-
*.pptx
|
|
46
|
-
*.zip
|
|
47
|
-
*.tar
|
|
48
|
-
*.gz
|
|
49
|
-
*.bz2
|
|
50
|
-
*.7z
|
|
51
|
-
*.rar
|
|
52
|
-
*.mp4
|
|
53
|
-
*.avi
|
|
54
|
-
*.mov
|
|
55
|
-
*.wmv
|
|
56
|
-
*.flv
|
|
57
|
-
*.mkv
|
|
58
|
-
*.mp3
|
|
59
|
-
*.wav
|
|
60
|
-
*.flac
|
|
61
|
-
*.ogg
|
|
62
|
-
*.min.js
|
|
63
|
-
*.min.css
|
|
64
|
-
package-lock.json
|
|
65
|
-
yarn.lock
|
|
66
|
-
pnpm-lock.yaml
|
package/BUILD_BRIEF.md
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
# EDHIndex — build brief
|
|
2
|
-
|
|
3
|
-
## How to use this
|
|
4
|
-
Save this file as `BUILD_BRIEF.md` in the repo root. Works with whatever AI coding agent you're using — GitHub Copilot, Claude Code, Cursor, Windsurf, or anything else. Reference it however that tool supports. Build the v1 section fully, end to end, before opening the v2 section — v2 exists so an eager agent doesn't try to build everything at once.
|
|
5
|
-
|
|
6
|
-
## What this is
|
|
7
|
-
EDHIndex is a standalone local tool that indexes a codebase and exposes fast, hybrid code search (keyword + semantic + reranked) as an MCP server. Any MCP-compatible client can connect and call it. Self-contained — no dependency on any editor, terminal, or agent application.
|
|
8
|
-
|
|
9
|
-
## Think of it as a code intelligence engine, not "a RAG project"
|
|
10
|
-
RAG is one consumer of this engine, not the whole identity of it. Layers, top to bottom:
|
|
11
|
-
```
|
|
12
|
-
CLI
|
|
13
|
-
|
|
|
14
|
-
Configuration
|
|
15
|
-
|
|
|
16
|
-
Workspace manager
|
|
17
|
-
|
|
|
18
|
-
Indexer (tree-sitter parser, file watcher, git integration)
|
|
19
|
-
|
|
|
20
|
-
Storage (SQLite: metadata + FTS5 | LanceDB: embeddings)
|
|
21
|
-
|
|
|
22
|
-
Retrieval engine (BM25, vector search, rerank)
|
|
23
|
-
|
|
|
24
|
-
MCP server
|
|
25
|
-
```
|
|
26
|
-
This separation is what lets you later build a CLI grep tool, a VS Code extension, or anything else on the same core without touching the indexing or retrieval layers.
|
|
27
|
-
|
|
28
|
-
## Naming conventions
|
|
29
|
-
- Repo / npm package / CLI command: `edhindex` — no exact match found on npm at time of writing, but double-check on npmjs.com right before first publish since registry search results can lag.
|
|
30
|
-
- Local index folder inside any indexed project: `.edhindex/`
|
|
31
|
-
- Ignore file: `.edhindexignore` (same syntax as `.gitignore`)
|
|
32
|
-
|
|
33
|
-
## Design principles
|
|
34
|
-
These are the non-negotiable values the rest of this brief is derived from. When a build decision isn't spelled out explicitly elsewhere, default back to these:
|
|
35
|
-
- Local-first — the tool works fully on the user's machine
|
|
36
|
-
- Offline-first — no network dependency after the embedding model is downloaded once
|
|
37
|
-
- Zero telemetry
|
|
38
|
-
- No vendor lock-in — interfaces over concrete libraries, everywhere it matters
|
|
39
|
-
- Deterministic indexing — same input produces the same index
|
|
40
|
-
- Incremental updates only — never re-do work that isn't stale
|
|
41
|
-
- Safe-by-default — security guardrails are load-bearing, not optional
|
|
42
|
-
- Extensible without rewriting core components
|
|
43
|
-
|
|
44
|
-
## Distribution model — git repo, not a hosted service
|
|
45
|
-
- User runs `git clone`, then `npm install`
|
|
46
|
-
- `npx edhindex start` launches it locally, pointed at whichever folder the user opens
|
|
47
|
-
- Everything happens inside that folder — nothing hosted, nothing leaves the machine
|
|
48
|
-
|
|
49
|
-
## Model tiers — user picks one on first run
|
|
50
|
-
- **Light** — bge-small-en-v1.5 (~130MB) or all-MiniLM-L6-v2 (~46MB). Old/weak hardware.
|
|
51
|
-
- **Balanced** (default) — nomic-embed-text-v1.5/v2 (~270MB). 8,192 token context, best size-to-quality tradeoff.
|
|
52
|
-
- **Max quality** — BGE-M3 (~1.1-2.2GB). Matches commercial APIs on benchmarks, needs more RAM/CPU.
|
|
53
|
-
|
|
54
|
-
All run locally via `@huggingface/transformers` — no API call, no account, no ongoing cost at any tier. Switching tiers later requires a full re-index — vectors from different models aren't compatible with each other.
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
# V1 — build this first, ship it, then stop
|
|
59
|
-
|
|
60
|
-
## Supported languages (v1)
|
|
61
|
-
TypeScript, JavaScript, Go, Python. Nothing else yet — don't let an agent try to wire up every Tree-sitter grammar that exists.
|
|
62
|
-
|
|
63
|
-
## Never index these (v1)
|
|
64
|
-
`node_modules`, `.git`, `dist`, `build`, `coverage`, `.next`, `out`, `vendor`, `bin`, `obj`, `target`, `tmp`, `.cache`, `.turbo` — plus whatever the user adds to `.edhindexignore`.
|
|
65
|
-
|
|
66
|
-
## File limits (v1)
|
|
67
|
-
Skip anything over 10MB. Skip binaries, images, PDFs, videos, archives, and anything that looks generated.
|
|
68
|
-
|
|
69
|
-
## Chunking strategy (v1)
|
|
70
|
-
Priority order: function → method → class → interface → enum → module → file. If a unit exceeds the embedding model's token limit, split it recursively rather than truncating silently.
|
|
71
|
-
|
|
72
|
-
## Metadata schema (v1)
|
|
73
|
-
Store per chunk in SQLite: `id, file, language, symbol, kind, parent, hash, git_commit, start_line, end_line, imports, exports, embedding_model, last_indexed, checksum`. Define this once, up front — retrofitting new fields into an existing index later is painful.
|
|
74
|
-
|
|
75
|
-
## Index versioning (v1)
|
|
76
|
-
`.edhindex/version.json`, written on every index build:
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"schema": 1,
|
|
80
|
-
"embeddingModel": "nomic-v2",
|
|
81
|
-
"chunkStrategy": 1,
|
|
82
|
-
"createdAt": "2026-07-19T00:00:00Z",
|
|
83
|
-
"lastIndexed": "2026-07-19T00:00:00Z"
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
On startup, compare this against the current build's expected values. Any mismatch (schema bump, model change, chunk strategy change) → trigger a full rebuild automatically rather than surfacing a confusing runtime error. This is the mechanism that makes "switching tiers requires a full re-index" (above) something the tool detects and handles, not something the user has to remember.
|
|
87
|
-
|
|
88
|
-
## Incremental indexing (v1)
|
|
89
|
-
Hash each file (SHA256). Chokidar triggers a check on save; only re-parse and re-embed if the hash actually changed. Don't re-embed unchanged files just because a save event fired.
|
|
90
|
-
Also hash each chunk individually. When a file's hash changes, diff at the chunk level and only re-embed the chunks whose hash actually changed — not the whole file. A one-function edit in a 2,000-line file should cost one embedding call, not fifty.
|
|
91
|
-
|
|
92
|
-
## Startup flow (v1)
|
|
93
|
-
- No index found → full scan → build → ready
|
|
94
|
-
- Existing index found → load it → compare hashes → update only what changed → ready
|
|
95
|
-
|
|
96
|
-
## Ranking formula (v1)
|
|
97
|
-
Top 30 BM25 candidates + top 30 vector candidates → deduplicate → rerank the merged set → return top 10 (configurable). Concrete numbers, not "combine and rerank" left vague. Full pipeline, in order:
|
|
98
|
-
```
|
|
99
|
-
Query
|
|
100
|
-
|
|
|
101
|
-
Normalize
|
|
102
|
-
|
|
|
103
|
-
BM25 search (top 30) + Vector search (top 30)
|
|
104
|
-
|
|
|
105
|
-
Merge
|
|
106
|
-
|
|
|
107
|
-
Deduplicate
|
|
108
|
-
|
|
|
109
|
-
Rerank
|
|
110
|
-
|
|
|
111
|
-
Top K (default 10)
|
|
112
|
-
|
|
|
113
|
-
MCP response
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Embedding and storage abstraction (v1 — cheap now, expensive to retrofit later)
|
|
117
|
-
Define interfaces, don't hardcode the concrete libraries:
|
|
118
|
-
```
|
|
119
|
-
EmbeddingProvider: embed(), dimensions(), name(), load(), dispose()
|
|
120
|
-
VectorStore: insert(), delete(), search(), compact()
|
|
121
|
-
```
|
|
122
|
-
Ship transformers.js and LanceDB as the default implementations of these interfaces. This is what lets you swap to Ollama, llama.cpp, Qdrant, or anything else later without rewriting the code that calls them.
|
|
123
|
-
|
|
124
|
-
## Config file (v1)
|
|
125
|
-
`.edhindex/config.json`:
|
|
126
|
-
```json
|
|
127
|
-
{
|
|
128
|
-
"model": "balanced",
|
|
129
|
-
"languages": ["ts", "js", "go", "py"],
|
|
130
|
-
"watch": true,
|
|
131
|
-
"rerank": true,
|
|
132
|
-
"maxResults": 10
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Progress reporting (v1)
|
|
137
|
-
Indexing must never look hung. Emit structured progress events (not just log lines) that a CLI progress bar or an MCP client can both consume — e.g. `{ phase: "scanning", current: 118, total: 624 }`, then `{ phase: "embedding", percent: 42 }`, then `{ phase: "building_fts" }`, then `{ phase: "ready" }`.
|
|
138
|
-
|
|
139
|
-
## Cancellation (v1)
|
|
140
|
-
Indexing must be safely interruptible (Ctrl+C, or an MCP-triggered stop). On interrupt: finish the file currently being processed, flush metadata to SQLite, close the DB cleanly, then exit. Never leave a half-written index — a killed process should produce either the old valid state or the new valid state, never something in between.
|
|
141
|
-
|
|
142
|
-
## Logging levels (v1)
|
|
143
|
-
Four levels, fixed from the start so an agent doesn't scatter ad hoc `console.log` calls: `silent`, `info` (default), `verbose`, `debug`. Configurable via CLI flag and config file.
|
|
144
|
-
|
|
145
|
-
## CLI (v1)
|
|
146
|
-
```
|
|
147
|
-
edhindex init
|
|
148
|
-
edhindex start
|
|
149
|
-
edhindex index
|
|
150
|
-
edhindex search <query>
|
|
151
|
-
edhindex status
|
|
152
|
-
edhindex config
|
|
153
|
-
edhindex models
|
|
154
|
-
edhindex doctor
|
|
155
|
-
```
|
|
156
|
-
`doctor` checks SQLite, LanceDB, the embedding model, the MCP server, disk space, permissions, and active file watchers. Build this in v1 — it pays for itself the very first time something breaks and you need to know where.
|
|
157
|
-
|
|
158
|
-
## Security — non-negotiable, build this in from the start, not after
|
|
159
|
-
- Never execute any indexed code
|
|
160
|
-
- Never follow symlinks that point outside the selected workspace
|
|
161
|
-
- Never transmit source code over the network — enforce this in code, not just as a policy, unless the user explicitly opts into a future remote embedding provider
|
|
162
|
-
- Sanitize every path to prevent directory traversal
|
|
163
|
-
- Restrict every MCP tool operation strictly to the indexed workspace — a calling agent should never be able to reach outside the folder it was pointed at
|
|
164
|
-
|
|
165
|
-
This matters because once this runs as an MCP server, it can be driven by an autonomous agent acting on file contents it reads — path traversal and symlink escapes are the first thing that will get tried against a tool like this, intentionally or not.
|
|
166
|
-
|
|
167
|
-
## MCP tools exposed (v1)
|
|
168
|
-
`search_codebase` only. Everything else is v2.
|
|
169
|
-
|
|
170
|
-
## Non-goals (v1)
|
|
171
|
-
Explicit so an eager agent doesn't add any of these on its own initiative:
|
|
172
|
-
- No chat interface
|
|
173
|
-
- No code generation
|
|
174
|
-
- No cloud sync
|
|
175
|
-
- No telemetry
|
|
176
|
-
- No authentication
|
|
177
|
-
- No web UI
|
|
178
|
-
- No editor integration
|
|
179
|
-
- No remote database
|
|
180
|
-
- No hosted service
|
|
181
|
-
|
|
182
|
-
## Corrections worth keeping in mind
|
|
183
|
-
- MCP was created by Anthropic (Nov 2024), not OpenAI — now an open, vendor-neutral standard under the Linux Foundation. Use `@modelcontextprotocol/sdk` or FastMCP.
|
|
184
|
-
- `tiktoken` matches OpenAI's tokenizer specifically — treat any token counts here as approximate for other models.
|
|
185
|
-
|
|
186
|
-
## Recommended stack (v1)
|
|
187
|
-
Tree-sitter (AST), SQLite (metadata + FTS5), LanceDB (vectors), transformers.js (embeddings + reranker, local), chokidar (file watching), simple-git (git integration), MCP server (FastMCP or TypeScript SDK).
|
|
188
|
-
|
|
189
|
-
Note: Language Server Protocol integration is intentionally **not** in v1. Tree-sitter alone (function/class/import/export boundaries) is enough for a strong index and search experience. LSP adds per-language server lifecycle management, installation, and cross-platform quirks — real complexity that isn't needed until the persisted symbol/call graph (v2) makes it worth it. Don't let an agent reach for LSP in v1.
|
|
190
|
-
|
|
191
|
-
## Build order (v1)
|
|
192
|
-
1. Repo scaffold, config file, CLI skeleton, ignore rules, file size limits
|
|
193
|
-
2. AST indexing — Tree-sitter, chunking strategy, metadata schema, incremental hashing
|
|
194
|
-
3. Embeddings and vector search — via the `EmbeddingProvider` interface, tier selection on first run
|
|
195
|
-
4. Keyword search — SQLite FTS5
|
|
196
|
-
5. Ranking — the top-30/top-30/dedupe/rerank/top-10 formula
|
|
197
|
-
6. MCP server exposing `search_codebase`, with the security guardrails enforced here
|
|
198
|
-
7. File watching and the cold-start/warm-start flow
|
|
199
|
-
8. `doctor` command and basic recovery (if SQLite fails to open, rebuild from scratch rather than crash)
|
|
200
|
-
|
|
201
|
-
## Success criteria (v1)
|
|
202
|
-
v1 is done when all of the following hold:
|
|
203
|
-
- Indexes a 100k-line repository successfully
|
|
204
|
-
- Updates incrementally on file save, without a full re-index
|
|
205
|
-
- Returns search results in a reasonable time on a warm index
|
|
206
|
-
- Runs completely offline after the embedding model is downloaded
|
|
207
|
-
- Emits zero network requests beyond that one-time model download
|
|
208
|
-
- Recovers cleanly if `.edhindex/` is deleted (falls back to full scan)
|
|
209
|
-
- Runs on Windows, macOS, and Linux
|
|
210
|
-
- `search_codebase` works end to end through an MCP client
|
|
211
|
-
|
|
212
|
-
(Hard performance numbers — search latency, index update time, cold start, memory ceiling — are deliberately a v2 tuning target, not a v1 gate. Don't block shipping v1 on numbers that haven't been measured yet.)
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
# V2 — deliberately deferred, don't touch until v1 works end to end
|
|
217
|
-
|
|
218
|
-
- **Persisted symbol/call graph** (LSP-derived) — enables "expand from a chunk to its related symbols" as part of retrieval. This is the single biggest quality upgrade available, and also the most complex — earn it after the simpler retrieval loop is proven, not before.
|
|
219
|
-
- **Additional MCP tools**: `find_symbol`, `find_references`, `find_definition`, `recent_changes`, `workspace_stats`, `reindex`, `index_status`
|
|
220
|
-
- **Parallel indexing via worker threads** — only once you've actually confirmed indexing speed is a bottleneck on a real repo, not preemptively
|
|
221
|
-
- **Additional languages** beyond the v1 four (Java, C#, Rust, C++, Kotlin, Swift)
|
|
222
|
-
- **Formal test suite** as its own tracked effort — unit, integration, large-repo, corrupt-index, model-switch, recovery, watcher tests
|
|
223
|
-
- **Sophisticated corruption recovery** — attempt repair before falling back to a full rebuild
|
|
224
|
-
- **Full plugin system** — v1 already reserves the extension points via the two interfaces above; a real plugin loader can wait
|
|
225
|
-
- **Performance target tuning** — search under 300ms, index update under 2s, cold startup under 10s, memory under 1GB. Good targets to measure against once v1 exists, not a blocker before it ships
|
|
226
|
-
|
|
227
|
-
## Scope check
|
|
228
|
-
This is the entire scope of EDHIndex: index a codebase, keep the index live, expose it over MCP, safely. It does not include an editor, a terminal, or a chat interface — those are separate projects, if built at all.
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ES2022",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"declaration": true,
|
|
15
|
-
"declarationMap": true,
|
|
16
|
-
"sourceMap": true,
|
|
17
|
-
"isolatedModules": true
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*"],
|
|
20
|
-
"exclude": ["node_modules", "dist"]
|
|
21
|
-
}
|