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
package/package.json
CHANGED
|
@@ -1,21 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edhindex",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Local-first hybrid code search engine with MCP",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Local-first hybrid code search engine with knowledge graph — BM25 + vector + rerank, tree-sitter parsing, MCP server",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"code-search",
|
|
7
|
+
"code-index",
|
|
8
|
+
"semantic-search",
|
|
9
|
+
"hybrid-search",
|
|
10
|
+
"knowledge-graph",
|
|
11
|
+
"mcp",
|
|
12
|
+
"tree-sitter",
|
|
13
|
+
"codebase-search",
|
|
14
|
+
"local-first",
|
|
15
|
+
"offline"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/krishnasfdcc-a11y/edhIndex.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/krishnasfdcc-a11y/edhIndex#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/krishnasfdcc-a11y/edhIndex/issues"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "edhdev (https://github.com/krishnasfdcc-a11y)",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
5
30
|
"type": "module",
|
|
6
31
|
"bin": {
|
|
7
|
-
"edhindex": "
|
|
32
|
+
"edhindex": "dist/cli/index.js"
|
|
8
33
|
},
|
|
9
34
|
"exports": {
|
|
10
35
|
".": "./dist/index.js"
|
|
11
36
|
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src"
|
|
40
|
+
],
|
|
12
41
|
"scripts": {
|
|
42
|
+
"postinstall": "npx tsc || true",
|
|
13
43
|
"build": "tsc",
|
|
44
|
+
"prepublishOnly": "npm run build",
|
|
14
45
|
"start": "node dist/cli/index.js",
|
|
15
46
|
"dev": "npx tsx src/cli/index.ts"
|
|
16
47
|
},
|
|
17
48
|
"dependencies": {
|
|
18
|
-
"@huggingface/transformers": "^
|
|
49
|
+
"@huggingface/transformers": "^4.2.0",
|
|
19
50
|
"@lancedb/lancedb": "^0.31.0",
|
|
20
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
52
|
"better-sqlite3": "^12.11.1",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { MetadataStore } from '../../storage/metadata.js';
|
|
4
|
+
import { buildGraphData } from '../../graph/build.js';
|
|
5
|
+
import { generateSVG } from '../../graph/svg.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
export async function graphCommand(rootPath: string, outputPath?: string) {
|
|
9
|
+
const indexDir = join(rootPath, '.edhindex');
|
|
10
|
+
if (!existsSync(indexDir)) {
|
|
11
|
+
console.log(chalk.yellow('No EDHIndex index found. Run "edhindex start" first.'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const store = new MetadataStore(indexDir);
|
|
16
|
+
const { nodes, edges, fileCount, edgeCount } = buildGraphData(store);
|
|
17
|
+
store.close();
|
|
18
|
+
|
|
19
|
+
if (fileCount === 0) {
|
|
20
|
+
console.log(chalk.yellow('No indexed files found.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const svg = generateSVG(rootPath, nodes, edges);
|
|
25
|
+
const outFile = outputPath || join(rootPath, 'edhindex-graph.svg');
|
|
26
|
+
writeFileSync(outFile, svg, 'utf-8');
|
|
27
|
+
|
|
28
|
+
console.log(chalk.green(`Graph generated: ${outFile}`));
|
|
29
|
+
console.log(chalk.dim(`${fileCount} files, ${edgeCount} dependencies`));
|
|
30
|
+
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
-
import { loadConfig, saveConfig, defaultConfig } from '../../config/index.js';
|
|
3
|
+
import { loadConfig, saveConfig, defaultConfig, MODEL_CONFIGS, type ModelTier } from '../../config/index.js';
|
|
4
|
+
import { promptAndWriteMCPConfigs } from '../../mcp-config.js';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
4
6
|
import { logger } from '../../logging.js';
|
|
5
7
|
import chalk from 'chalk';
|
|
6
8
|
|
|
9
|
+
function isInteractive(): boolean {
|
|
10
|
+
return process.stdin.isTTY && process.stdout.isTTY;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function askModel(): Promise<ModelTier> {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
if (!isInteractive()) {
|
|
16
|
+
resolve('balanced');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
const tiers: ModelTier[] = ['light', 'balanced', 'max'];
|
|
21
|
+
const lines = tiers.map((t, i) => {
|
|
22
|
+
const c = MODEL_CONFIGS[t];
|
|
23
|
+
return ` ${i + 1}. ${c.displayName} (${c.dimensions}d, ${c.size})`;
|
|
24
|
+
});
|
|
25
|
+
rl.question(
|
|
26
|
+
`\n${chalk.bold('Embedding Model')}\nWhich embedding model do you want to use?\n${lines.join('\n')}\n\nChoose (1-3) [2]: `,
|
|
27
|
+
(answer) => {
|
|
28
|
+
rl.close();
|
|
29
|
+
const idx = parseInt(answer.trim(), 10);
|
|
30
|
+
if (idx >= 1 && idx <= 3) {
|
|
31
|
+
resolve(tiers[idx - 1]);
|
|
32
|
+
} else {
|
|
33
|
+
resolve('balanced');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
export async function initCommand(rootPath: string) {
|
|
8
41
|
const indexDir = join(rootPath, '.edhindex');
|
|
9
42
|
|
|
@@ -16,10 +49,15 @@ export async function initCommand(rootPath: string) {
|
|
|
16
49
|
|
|
17
50
|
mkdirSync(indexDir, { recursive: true });
|
|
18
51
|
const config = defaultConfig();
|
|
52
|
+
config.model = await askModel();
|
|
19
53
|
saveConfig(indexDir, config);
|
|
20
54
|
|
|
21
55
|
console.log(chalk.green('EDHIndex initialized.'));
|
|
22
|
-
|
|
23
|
-
console.log(`
|
|
56
|
+
const mc = MODEL_CONFIGS[config.model];
|
|
57
|
+
console.log(`Model: ${mc.displayName} (${mc.dimensions}d, ${mc.size})`);
|
|
58
|
+
console.log(`Languages: ${config.languages.join(', ')}`);
|
|
59
|
+
|
|
60
|
+
await promptAndWriteMCPConfigs(rootPath);
|
|
61
|
+
|
|
24
62
|
console.log(chalk.dim('Run "edhindex start" to build the index and start the MCP server.'));
|
|
25
63
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { GraphService } from '../../graph/service.js';
|
|
4
|
+
import { startGraphServer, writeViewerFile } from '../../graph/serve.js';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
export async function kgCommand(rootPath: string, flags: { rebuild?: boolean; stats?: boolean; serve?: boolean; write?: boolean; port?: string }) {
|
|
9
|
+
const indexDir = join(rootPath, '.edhindex');
|
|
10
|
+
if (!existsSync(indexDir)) {
|
|
11
|
+
console.log(chalk.yellow('No index found. Run "edhindex start" first.'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (flags.write) {
|
|
16
|
+
console.log(chalk.dim('Writing graph viewer...'));
|
|
17
|
+
const outFile = join(rootPath, '.edhindex', 'knowledge-graph.html');
|
|
18
|
+
writeViewerFile(rootPath, outFile);
|
|
19
|
+
console.log(chalk.green(`\n Viewer written: ${outFile}`));
|
|
20
|
+
console.log(chalk.dim('\n Open in VS Code:'));
|
|
21
|
+
console.log(chalk.dim(' 1. File > Open File... > select the file'));
|
|
22
|
+
console.log(chalk.dim(' 2. Or drag into VS Code and click "Preview"'));
|
|
23
|
+
console.log(chalk.dim(' 3. Or right-click → "Open with Live Server"'));
|
|
24
|
+
console.log(chalk.dim('\n Or open in browser:'));
|
|
25
|
+
console.log(chalk.dim(` file://${outFile}`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (flags.serve) {
|
|
30
|
+
const port = flags.port ? parseInt(flags.port, 10) : 0;
|
|
31
|
+
console.log(chalk.dim('Starting graph server...'));
|
|
32
|
+
const { port: actualPort, close } = await startGraphServer(rootPath, port);
|
|
33
|
+
const url = `http://127.0.0.1:${actualPort}`;
|
|
34
|
+
console.log(chalk.green(`\n Knowledge Graph: ${url}`));
|
|
35
|
+
console.log(chalk.dim('\n Press Ctrl+C to stop.\n'));
|
|
36
|
+
|
|
37
|
+
// Try to open browser
|
|
38
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
39
|
+
try {
|
|
40
|
+
const child = spawn(cmd, [url], { detached: true, stdio: 'ignore' });
|
|
41
|
+
child.unref();
|
|
42
|
+
} catch { /* browser open not critical */ }
|
|
43
|
+
|
|
44
|
+
// Keep alive
|
|
45
|
+
process.on('SIGINT', () => { close(); process.exit(0); });
|
|
46
|
+
process.on('SIGTERM', () => { close(); process.exit(0); });
|
|
47
|
+
await new Promise(() => {}); // hang
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const svc = new GraphService(rootPath);
|
|
53
|
+
|
|
54
|
+
if (flags.rebuild) {
|
|
55
|
+
console.log(chalk.dim('Building knowledge graph...'));
|
|
56
|
+
svc.build();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stats = svc.getStats();
|
|
60
|
+
svc.close();
|
|
61
|
+
|
|
62
|
+
console.log(chalk.bold('\nKnowledge Graph'));
|
|
63
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
64
|
+
console.log(` Nodes: ${chalk.cyan(stats.nodeCount)}`);
|
|
65
|
+
console.log(` Edges: ${chalk.cyan(stats.edgeCount)}`);
|
|
66
|
+
|
|
67
|
+
if (Object.keys(stats.typeCounts).length > 0) {
|
|
68
|
+
console.log(chalk.dim('\n By type:'));
|
|
69
|
+
for (const [type, count] of Object.entries(stats.typeCounts).sort((a, b) => b[1] - a[1])) {
|
|
70
|
+
console.log(` ${type.padEnd(14)} ${count}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(chalk.dim('\n Commands:'));
|
|
75
|
+
console.log(chalk.dim(' edhindex kg serve — Interactive graph in browser'));
|
|
76
|
+
console.log(chalk.dim(' edhindex graph — Static SVG graph'));
|
|
77
|
+
|
|
78
|
+
console.log(chalk.dim('\n MCP tools:'));
|
|
79
|
+
console.log(chalk.dim(' get_graph — Full graph data'));
|
|
80
|
+
console.log(chalk.dim(' get_node — Node details + neighbors'));
|
|
81
|
+
console.log(chalk.dim(' search_graph — Search nodes by name'));
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(chalk.red('Failed:'), e instanceof Error ? e.message : e);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
function isInteractive(): boolean {
|
|
7
|
+
return process.stdin.isTTY && process.stdout.isTTY;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function confirm(message: string): Promise<boolean> {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
if (!isInteractive()) {
|
|
13
|
+
resolve(false);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
18
|
+
rl.close();
|
|
19
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function resetCommand(rootPath: string) {
|
|
25
|
+
const indexDir = join(rootPath, '.edhindex');
|
|
26
|
+
|
|
27
|
+
if (!existsSync(indexDir)) {
|
|
28
|
+
console.log(chalk.yellow('No EDHIndex index found in this directory.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ok = await confirm(chalk.red('Delete the entire EDHIndex index (config + vectors + metadata)?'));
|
|
33
|
+
if (!ok) {
|
|
34
|
+
console.log(chalk.dim('Cancelled.'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
rmSync(indexDir, { recursive: true, force: true });
|
|
39
|
+
console.log(chalk.green(`Deleted: ${indexDir}`));
|
|
40
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { loadConfig, MODEL_CONFIGS } from '../../config/index.js';
|
|
4
4
|
import { logger, setLogLevel, LogLevel, getLogLevel } from '../../logging.js';
|
|
5
5
|
import { emitProgress, onProgress } from '../../progress.js';
|
|
@@ -12,6 +12,10 @@ import { Indexer } from '../../indexer/indexer.js';
|
|
|
12
12
|
import { SearchEngine } from '../../retrieval/search.js';
|
|
13
13
|
import { createMCPServer } from '../../mcp/server.js';
|
|
14
14
|
import { FileWatcher } from '../../watcher/index.js';
|
|
15
|
+
import { buildGraphData } from '../../graph/build.js';
|
|
16
|
+
import { generateSVG } from '../../graph/svg.js';
|
|
17
|
+
import { GraphBuilder } from '../../graph/builder.js';
|
|
18
|
+
import { GraphStore } from '../../graph/storage.js';
|
|
15
19
|
import cliProgress from 'cli-progress';
|
|
16
20
|
import chalk from 'chalk';
|
|
17
21
|
|
|
@@ -32,23 +36,23 @@ export async function startCommand(rootPath: string) {
|
|
|
32
36
|
};
|
|
33
37
|
setLogLevel(logLevelMap[config.logLevel] || LogLevel.Info);
|
|
34
38
|
|
|
35
|
-
console.
|
|
36
|
-
console.
|
|
37
|
-
console.
|
|
38
|
-
console.
|
|
39
|
-
console.
|
|
39
|
+
console.error(chalk.bold('EDHIndex v1.0.0'));
|
|
40
|
+
console.error(`Workspace: ${rootPath}`);
|
|
41
|
+
console.error(`Model: ${modelCfg.displayName} (${modelCfg.dimensions}d)`);
|
|
42
|
+
console.error(`Languages: ${config.languages.join(', ')}`);
|
|
43
|
+
console.error('');
|
|
40
44
|
|
|
41
45
|
// Check index version
|
|
42
46
|
const version = loadVersion(indexDir);
|
|
43
47
|
if (needsRebuild(version, config.model)) {
|
|
44
48
|
if (version) {
|
|
45
|
-
console.
|
|
46
|
-
console.
|
|
49
|
+
console.error(chalk.yellow('Index version mismatch — full rebuild required.'));
|
|
50
|
+
console.error(chalk.dim(` Expected model: ${config.model}, existing: ${version.embeddingModel}`));
|
|
47
51
|
} else {
|
|
48
|
-
console.
|
|
52
|
+
console.error(chalk.dim('No existing index found — building from scratch.'));
|
|
49
53
|
}
|
|
50
54
|
} else {
|
|
51
|
-
console.
|
|
55
|
+
console.error(chalk.dim('Existing index is compatible — incremental update.'));
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
// Setup progress bar
|
|
@@ -66,7 +70,7 @@ export async function startCommand(rootPath: string) {
|
|
|
66
70
|
});
|
|
67
71
|
|
|
68
72
|
// Initialize stores
|
|
69
|
-
console.
|
|
73
|
+
console.error(chalk.dim('Initializing storage...'));
|
|
70
74
|
const metadataStore = new MetadataStore(indexDir);
|
|
71
75
|
const ftsStore = new FTSStore(indexDir);
|
|
72
76
|
|
|
@@ -76,13 +80,13 @@ export async function startCommand(rootPath: string) {
|
|
|
76
80
|
|
|
77
81
|
// Initialize embedding provider
|
|
78
82
|
const embedder = new TransformersEmbeddingProvider(config.model);
|
|
79
|
-
console.
|
|
83
|
+
console.error(chalk.dim(`Loading embedding model (${modelCfg.displayName})...`));
|
|
80
84
|
await embedder.load();
|
|
81
85
|
|
|
82
86
|
// Initialize reranker if enabled
|
|
83
87
|
let reranker: TransformersReranker | null = null;
|
|
84
88
|
if (config.rerank) {
|
|
85
|
-
console.
|
|
89
|
+
console.error(chalk.dim('Loading reranker model...'));
|
|
86
90
|
reranker = new TransformersReranker();
|
|
87
91
|
await reranker.load();
|
|
88
92
|
}
|
|
@@ -150,7 +154,7 @@ export async function startCommand(rootPath: string) {
|
|
|
150
154
|
const doFullRebuild = needsRebuild(version, config.model) || existingFileHashes.size === 0;
|
|
151
155
|
|
|
152
156
|
// Index
|
|
153
|
-
console.
|
|
157
|
+
console.error('');
|
|
154
158
|
progressBar = new cliProgress.SingleBar({
|
|
155
159
|
format: '{phase} | {bar} | {percentage}% | {value}/{total}',
|
|
156
160
|
barCompleteChar: '\u2588',
|
|
@@ -160,20 +164,20 @@ export async function startCommand(rootPath: string) {
|
|
|
160
164
|
|
|
161
165
|
if (doFullRebuild) {
|
|
162
166
|
progressBar.start(100, 0, { phase: 'Scanning' });
|
|
163
|
-
console.
|
|
167
|
+
console.error(chalk.dim('Running full index...'));
|
|
164
168
|
const chunks = await indexer.runFullIndex();
|
|
165
169
|
progressBar.stop();
|
|
166
|
-
console.
|
|
170
|
+
console.error(chalk.green(`Indexed ${chunks.length} chunks.`));
|
|
167
171
|
|
|
168
172
|
// Save version
|
|
169
173
|
const newVersion = getExpectedVersion(config.model);
|
|
170
174
|
saveVersion(indexDir, newVersion);
|
|
171
175
|
} else {
|
|
172
176
|
progressBar.start(100, 0, { phase: 'Scanning' });
|
|
173
|
-
console.
|
|
177
|
+
console.error(chalk.dim('Running incremental index...'));
|
|
174
178
|
const result = await indexer.runIncremental(existingFileHashes, existingChunkHashes);
|
|
175
179
|
progressBar.stop();
|
|
176
|
-
console.
|
|
180
|
+
console.error(chalk.green(`Indexed ${result.chunks.length} chunks, removed ${result.removed.length} files.`));
|
|
177
181
|
|
|
178
182
|
// Update version
|
|
179
183
|
const existingVersion = version!;
|
|
@@ -227,11 +231,40 @@ export async function startCommand(rootPath: string) {
|
|
|
227
231
|
}
|
|
228
232
|
});
|
|
229
233
|
await watcher.start();
|
|
230
|
-
console.
|
|
234
|
+
console.error(chalk.green('File watcher started.'));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Generate dependency graph SVG
|
|
238
|
+
try {
|
|
239
|
+
const { nodes, edges, fileCount, edgeCount } = buildGraphData(metadataStore);
|
|
240
|
+
if (fileCount > 0) {
|
|
241
|
+
const svg = generateSVG(rootPath, nodes, edges);
|
|
242
|
+
writeFileSync(join(rootPath, 'edhindex-graph.svg'), svg, 'utf-8');
|
|
243
|
+
logger.verbose(`SVG graph: ${fileCount} files, ${edgeCount} edges`);
|
|
244
|
+
}
|
|
245
|
+
} catch (e) {
|
|
246
|
+
logger.debug('Failed to generate graph SVG:', e);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Build knowledge graph
|
|
250
|
+
if (config.graphEnabled) {
|
|
251
|
+
try {
|
|
252
|
+
logger.info('Building knowledge graph...');
|
|
253
|
+
const graphStore = new GraphStore(indexDir);
|
|
254
|
+
const builder = new GraphBuilder(rootPath, metadataStore, graphStore);
|
|
255
|
+
builder.buildFull();
|
|
256
|
+
const { nodeCount, edgeCount } = { nodeCount: graphStore.getNodeCount(), edgeCount: graphStore.getEdgeCount() };
|
|
257
|
+
graphStore.close();
|
|
258
|
+
console.error(chalk.dim(`\n Knowledge Graph: ${nodeCount} nodes, ${edgeCount} edges`));
|
|
259
|
+
console.error(chalk.dim(` View in browser: edhindex kg --serve`));
|
|
260
|
+
console.error(chalk.dim(` View in VS Code: edhindex kg --write`));
|
|
261
|
+
} catch (e) {
|
|
262
|
+
logger.debug('Failed to build knowledge graph:', e);
|
|
263
|
+
}
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
// Start MCP server
|
|
234
|
-
console.
|
|
267
|
+
console.error(chalk.green('Starting MCP server on stdio...'));
|
|
235
268
|
const mcpServer = createMCPServer(searchEngine, rootPath);
|
|
236
269
|
|
|
237
270
|
const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
@@ -240,7 +273,7 @@ export async function startCommand(rootPath: string) {
|
|
|
240
273
|
|
|
241
274
|
// Handle shutdown
|
|
242
275
|
const shutdown = async () => {
|
|
243
|
-
console.
|
|
276
|
+
console.error(chalk.yellow('\nShutting down...'));
|
|
244
277
|
if (watcher) await watcher.stop();
|
|
245
278
|
vectorStore.close();
|
|
246
279
|
metadataStore.close();
|
package/src/cli/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { statusCommand } from './commands/status.js';
|
|
|
9
9
|
import { configCommand } from './commands/config_cmd.js';
|
|
10
10
|
import { modelsCommand } from './commands/models.js';
|
|
11
11
|
import { doctorCommand } from './commands/doctor_cmd.js';
|
|
12
|
+
import { resetCommand } from './commands/reset.js';
|
|
13
|
+
import { graphCommand } from './commands/graph.js';
|
|
14
|
+
import { kgCommand } from './commands/knowledge-graph.js';
|
|
12
15
|
import { setLogLevel, LogLevel } from '../logging.js';
|
|
13
16
|
|
|
14
17
|
const program = new Command();
|
|
@@ -96,4 +99,34 @@ program
|
|
|
96
99
|
await doctorCommand(rootPath, indexDir);
|
|
97
100
|
});
|
|
98
101
|
|
|
102
|
+
program
|
|
103
|
+
.command('reset')
|
|
104
|
+
.description('Delete the index and all stored data')
|
|
105
|
+
.action(async () => {
|
|
106
|
+
const rootPath = process.cwd();
|
|
107
|
+
await resetCommand(rootPath);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
program
|
|
111
|
+
.command('graph')
|
|
112
|
+
.description('Generate a dependency graph (SVG)')
|
|
113
|
+
.option('-o, --output <path>', 'Output file path')
|
|
114
|
+
.action(async (opts: { output?: string }) => {
|
|
115
|
+
const rootPath = process.cwd();
|
|
116
|
+
await graphCommand(rootPath, opts.output);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
program
|
|
120
|
+
.command('kg')
|
|
121
|
+
.description('Knowledge graph — build, stats, serve, write, and query')
|
|
122
|
+
.option('-r, --rebuild', 'Rebuild graph from index')
|
|
123
|
+
.option('-s, --stats', 'Show graph statistics')
|
|
124
|
+
.option('--serve', 'Start interactive graph viewer (opens browser)')
|
|
125
|
+
.option('-w, --write', 'Write graph viewer to .edhindex/knowledge-graph.html')
|
|
126
|
+
.option('-p, --port <number>', 'Port for graph viewer (default: random)')
|
|
127
|
+
.action(async (opts: { rebuild?: boolean; stats?: boolean; serve?: boolean; write?: boolean; port?: string }) => {
|
|
128
|
+
const rootPath = process.cwd();
|
|
129
|
+
await kgCommand(rootPath, opts);
|
|
130
|
+
});
|
|
131
|
+
|
|
99
132
|
program.parse(process.argv);
|
package/src/config/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { logger } from '../logging.js';
|
|
|
4
4
|
|
|
5
5
|
export type ModelTier = 'light' | 'balanced' | 'max';
|
|
6
6
|
export type LogLevelConfig = 'silent' | 'info' | 'verbose' | 'debug';
|
|
7
|
+
export type GraphLayoutConfig = 'force' | 'hierarchical' | 'circular' | 'grid';
|
|
7
8
|
|
|
8
9
|
export interface Config {
|
|
9
10
|
model: ModelTier;
|
|
@@ -12,6 +13,12 @@ export interface Config {
|
|
|
12
13
|
rerank: boolean;
|
|
13
14
|
maxResults: number;
|
|
14
15
|
logLevel: LogLevelConfig;
|
|
16
|
+
graphEnabled: boolean;
|
|
17
|
+
graphLayout: GraphLayoutConfig;
|
|
18
|
+
graphAnimations: boolean;
|
|
19
|
+
graphShowFolders: boolean;
|
|
20
|
+
graphShowFiles: boolean;
|
|
21
|
+
graphShowFunctions: boolean;
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
const DEFAULT_CONFIG: Config = {
|
|
@@ -21,6 +28,12 @@ const DEFAULT_CONFIG: Config = {
|
|
|
21
28
|
rerank: true,
|
|
22
29
|
maxResults: 10,
|
|
23
30
|
logLevel: 'info',
|
|
31
|
+
graphEnabled: true,
|
|
32
|
+
graphLayout: 'force',
|
|
33
|
+
graphAnimations: true,
|
|
34
|
+
graphShowFolders: true,
|
|
35
|
+
graphShowFiles: true,
|
|
36
|
+
graphShowFunctions: true,
|
|
24
37
|
};
|
|
25
38
|
|
|
26
39
|
export function defaultConfig(): Config {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MetadataStore } from '../storage/metadata.js';
|
|
2
|
+
import { FileNode, parseImportPaths, resolveImport } from './svg.js';
|
|
3
|
+
|
|
4
|
+
export interface GraphData {
|
|
5
|
+
nodes: FileNode[];
|
|
6
|
+
edges: { source: string; target: string }[];
|
|
7
|
+
fileCount: number;
|
|
8
|
+
edgeCount: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildGraphData(store: MetadataStore): GraphData {
|
|
12
|
+
const files = store.getAllFilesWithImports();
|
|
13
|
+
|
|
14
|
+
const nodeMap = new Map<string, FileNode>();
|
|
15
|
+
for (const row of files) {
|
|
16
|
+
if (!nodeMap.has(row.file)) {
|
|
17
|
+
nodeMap.set(row.file, {
|
|
18
|
+
id: row.file,
|
|
19
|
+
label: row.file,
|
|
20
|
+
language: row.language,
|
|
21
|
+
chunkCount: row.chunkCount,
|
|
22
|
+
imports: parseImportPaths(row.imports),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const edgeSet = new Set<string>();
|
|
28
|
+
const indexedFiles = new Set(nodeMap.keys());
|
|
29
|
+
for (const node of nodeMap.values()) {
|
|
30
|
+
for (const imp of node.imports) {
|
|
31
|
+
const resolved = resolveImport(node.id, imp, indexedFiles);
|
|
32
|
+
if (resolved && resolved !== node.id) {
|
|
33
|
+
edgeSet.add(`${node.id}|${resolved}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nodes = [...nodeMap.values()];
|
|
39
|
+
const edges = [...edgeSet].map((e) => {
|
|
40
|
+
const [source, target] = e.split('|');
|
|
41
|
+
return { source, target };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return { nodes, edges, fileCount: nodes.length, edgeCount: edges.length };
|
|
45
|
+
}
|