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.
Files changed (199) hide show
  1. package/README.md +228 -0
  2. package/dist/cli/commands/config_cmd.d.ts +2 -0
  3. package/dist/cli/commands/config_cmd.d.ts.map +1 -0
  4. package/dist/cli/commands/config_cmd.js +53 -0
  5. package/dist/cli/commands/config_cmd.js.map +1 -0
  6. package/dist/cli/commands/doctor_cmd.d.ts +2 -0
  7. package/dist/cli/commands/doctor_cmd.d.ts.map +1 -0
  8. package/dist/cli/commands/doctor_cmd.js +32 -0
  9. package/dist/cli/commands/doctor_cmd.js.map +1 -0
  10. package/dist/cli/commands/graph.d.ts +2 -0
  11. package/dist/cli/commands/graph.d.ts.map +1 -0
  12. package/dist/cli/commands/graph.js +26 -0
  13. package/dist/cli/commands/graph.js.map +1 -0
  14. package/dist/cli/commands/index_cmd.d.ts +2 -0
  15. package/dist/cli/commands/index_cmd.d.ts.map +1 -0
  16. package/dist/cli/commands/index_cmd.js +110 -0
  17. package/dist/cli/commands/index_cmd.js.map +1 -0
  18. package/dist/cli/commands/init.d.ts +2 -0
  19. package/dist/cli/commands/init.d.ts.map +1 -0
  20. package/dist/cli/commands/init.js +53 -0
  21. package/dist/cli/commands/init.js.map +1 -0
  22. package/dist/cli/commands/knowledge-graph.d.ts +8 -0
  23. package/dist/cli/commands/knowledge-graph.d.ts.map +1 -0
  24. package/dist/cli/commands/knowledge-graph.js +76 -0
  25. package/dist/cli/commands/knowledge-graph.js.map +1 -0
  26. package/dist/cli/commands/models.d.ts +2 -0
  27. package/dist/cli/commands/models.d.ts.map +1 -0
  28. package/dist/cli/commands/models.js +17 -0
  29. package/dist/cli/commands/models.js.map +1 -0
  30. package/dist/cli/commands/reset.d.ts +2 -0
  31. package/dist/cli/commands/reset.d.ts.map +1 -0
  32. package/dist/cli/commands/reset.js +35 -0
  33. package/dist/cli/commands/reset.js.map +1 -0
  34. package/dist/cli/commands/search.d.ts +2 -0
  35. package/dist/cli/commands/search.d.ts.map +1 -0
  36. package/dist/cli/commands/search.js +40 -0
  37. package/dist/cli/commands/search.js.map +1 -0
  38. package/dist/cli/commands/start.d.ts +2 -0
  39. package/dist/cli/commands/start.d.ts.map +1 -0
  40. package/dist/cli/commands/start.js +265 -0
  41. package/dist/cli/commands/start.js.map +1 -0
  42. package/dist/cli/commands/status.d.ts +2 -0
  43. package/dist/cli/commands/status.d.ts.map +1 -0
  44. package/dist/cli/commands/status.js +47 -0
  45. package/dist/cli/commands/status.js.map +1 -0
  46. package/dist/cli/index.d.ts +3 -0
  47. package/dist/cli/index.d.ts.map +1 -0
  48. package/dist/cli/index.js +121 -0
  49. package/dist/cli/index.js.map +1 -0
  50. package/dist/config/index.d.ts +28 -0
  51. package/dist/config/index.d.ts.map +1 -0
  52. package/dist/config/index.js +65 -0
  53. package/dist/config/index.js.map +1 -0
  54. package/dist/doctor/index.d.ts +8 -0
  55. package/dist/doctor/index.d.ts.map +1 -0
  56. package/dist/doctor/index.js +117 -0
  57. package/dist/doctor/index.js.map +1 -0
  58. package/dist/embeddings/provider.d.ts +9 -0
  59. package/dist/embeddings/provider.d.ts.map +1 -0
  60. package/dist/embeddings/provider.js +2 -0
  61. package/dist/embeddings/provider.js.map +1 -0
  62. package/dist/embeddings/transformers.d.ts +26 -0
  63. package/dist/embeddings/transformers.d.ts.map +1 -0
  64. package/dist/embeddings/transformers.js +120 -0
  65. package/dist/embeddings/transformers.js.map +1 -0
  66. package/dist/graph/build.d.ts +13 -0
  67. package/dist/graph/build.d.ts.map +1 -0
  68. package/dist/graph/build.js +33 -0
  69. package/dist/graph/build.js.map +1 -0
  70. package/dist/graph/builder.d.ts +24 -0
  71. package/dist/graph/builder.d.ts.map +1 -0
  72. package/dist/graph/builder.js +231 -0
  73. package/dist/graph/builder.js.map +1 -0
  74. package/dist/graph/render.d.ts +3 -0
  75. package/dist/graph/render.d.ts.map +1 -0
  76. package/dist/graph/render.js +60 -0
  77. package/dist/graph/render.js.map +1 -0
  78. package/dist/graph/serve.d.ts +6 -0
  79. package/dist/graph/serve.d.ts.map +1 -0
  80. package/dist/graph/serve.js +389 -0
  81. package/dist/graph/serve.js.map +1 -0
  82. package/dist/graph/service.d.ts +29 -0
  83. package/dist/graph/service.d.ts.map +1 -0
  84. package/dist/graph/service.js +73 -0
  85. package/dist/graph/service.js.map +1 -0
  86. package/dist/graph/storage.d.ts +33 -0
  87. package/dist/graph/storage.d.ts.map +1 -0
  88. package/dist/graph/storage.js +164 -0
  89. package/dist/graph/storage.js.map +1 -0
  90. package/dist/graph/svg.d.ts +27 -0
  91. package/dist/graph/svg.d.ts.map +1 -0
  92. package/dist/graph/svg.js +212 -0
  93. package/dist/graph/svg.js.map +1 -0
  94. package/dist/graph/types.d.ts +34 -0
  95. package/dist/graph/types.d.ts.map +1 -0
  96. package/dist/graph/types.js +9 -0
  97. package/dist/graph/types.js.map +1 -0
  98. package/dist/indexer/chunker.d.ts +18 -0
  99. package/dist/indexer/chunker.d.ts.map +1 -0
  100. package/dist/indexer/chunker.js +139 -0
  101. package/dist/indexer/chunker.js.map +1 -0
  102. package/dist/indexer/file-utils.d.ts +17 -0
  103. package/dist/indexer/file-utils.d.ts.map +1 -0
  104. package/dist/indexer/file-utils.js +123 -0
  105. package/dist/indexer/file-utils.js.map +1 -0
  106. package/dist/indexer/ignore.d.ts +5 -0
  107. package/dist/indexer/ignore.d.ts.map +1 -0
  108. package/dist/indexer/ignore.js +96 -0
  109. package/dist/indexer/ignore.js.map +1 -0
  110. package/dist/indexer/incremental.d.ts +16 -0
  111. package/dist/indexer/incremental.d.ts.map +1 -0
  112. package/dist/indexer/incremental.js +55 -0
  113. package/dist/indexer/incremental.js.map +1 -0
  114. package/dist/indexer/indexer.d.ts +36 -0
  115. package/dist/indexer/indexer.d.ts.map +1 -0
  116. package/dist/indexer/indexer.js +121 -0
  117. package/dist/indexer/indexer.js.map +1 -0
  118. package/dist/indexer/parser.d.ts +30 -0
  119. package/dist/indexer/parser.d.ts.map +1 -0
  120. package/dist/indexer/parser.js +225 -0
  121. package/dist/indexer/parser.js.map +1 -0
  122. package/dist/logging.d.ts +15 -0
  123. package/dist/logging.d.ts.map +1 -0
  124. package/dist/logging.js +42 -0
  125. package/dist/logging.js.map +1 -0
  126. package/dist/mcp/server.d.ts +4 -0
  127. package/dist/mcp/server.d.ts.map +1 -0
  128. package/dist/mcp/server.js +226 -0
  129. package/dist/mcp/server.js.map +1 -0
  130. package/dist/mcp-config.d.ts +4 -0
  131. package/dist/mcp-config.d.ts.map +1 -0
  132. package/dist/mcp-config.js +175 -0
  133. package/dist/mcp-config.js.map +1 -0
  134. package/dist/progress.d.ts +12 -0
  135. package/dist/progress.d.ts.map +1 -0
  136. package/dist/progress.js +16 -0
  137. package/dist/progress.js.map +1 -0
  138. package/dist/retrieval/bm25.d.ts +6 -0
  139. package/dist/retrieval/bm25.d.ts.map +1 -0
  140. package/dist/retrieval/bm25.js +20 -0
  141. package/dist/retrieval/bm25.js.map +1 -0
  142. package/dist/retrieval/reranker.d.ts +11 -0
  143. package/dist/retrieval/reranker.d.ts.map +1 -0
  144. package/dist/retrieval/reranker.js +46 -0
  145. package/dist/retrieval/reranker.js.map +1 -0
  146. package/dist/retrieval/search.d.ts +35 -0
  147. package/dist/retrieval/search.d.ts.map +1 -0
  148. package/dist/retrieval/search.js +95 -0
  149. package/dist/retrieval/search.js.map +1 -0
  150. package/dist/security.d.ts +5 -0
  151. package/dist/security.d.ts.map +1 -0
  152. package/dist/security.js +42 -0
  153. package/dist/security.js.map +1 -0
  154. package/dist/storage/fts.d.ts +30 -0
  155. package/dist/storage/fts.d.ts.map +1 -0
  156. package/dist/storage/fts.js +107 -0
  157. package/dist/storage/fts.js.map +1 -0
  158. package/dist/storage/metadata.d.ts +46 -0
  159. package/dist/storage/metadata.d.ts.map +1 -0
  160. package/dist/storage/metadata.js +133 -0
  161. package/dist/storage/metadata.js.map +1 -0
  162. package/dist/vector/lancedb.d.ts +25 -0
  163. package/dist/vector/lancedb.d.ts.map +1 -0
  164. package/dist/vector/lancedb.js +93 -0
  165. package/dist/vector/lancedb.js.map +1 -0
  166. package/dist/vector/store.d.ts +16 -0
  167. package/dist/vector/store.d.ts.map +1 -0
  168. package/dist/vector/store.js +2 -0
  169. package/dist/vector/store.js.map +1 -0
  170. package/dist/version.d.ts +12 -0
  171. package/dist/version.d.ts.map +1 -0
  172. package/dist/version.js +46 -0
  173. package/dist/version.js.map +1 -0
  174. package/dist/watcher/index.d.ts +15 -0
  175. package/dist/watcher/index.d.ts.map +1 -0
  176. package/dist/watcher/index.js +65 -0
  177. package/dist/watcher/index.js.map +1 -0
  178. package/package.json +35 -4
  179. package/src/cli/commands/graph.ts +30 -0
  180. package/src/cli/commands/init.ts +41 -3
  181. package/src/cli/commands/knowledge-graph.ts +85 -0
  182. package/src/cli/commands/reset.ts +40 -0
  183. package/src/cli/commands/start.ts +54 -21
  184. package/src/cli/index.ts +33 -0
  185. package/src/config/index.ts +13 -0
  186. package/src/graph/build.ts +45 -0
  187. package/src/graph/builder.ts +243 -0
  188. package/src/graph/render.ts +67 -0
  189. package/src/graph/serve.ts +399 -0
  190. package/src/graph/service.ts +84 -0
  191. package/src/graph/storage.ts +181 -0
  192. package/src/graph/svg.ts +241 -0
  193. package/src/graph/types.ts +68 -0
  194. package/src/mcp/server.ts +212 -85
  195. package/src/mcp-config.ts +185 -0
  196. package/src/storage/metadata.ts +9 -1
  197. package/.edhindexignore +0 -66
  198. package/BUILD_BRIEF.md +0 -228
  199. package/tsconfig.json +0 -21
package/package.json CHANGED
@@ -1,21 +1,52 @@
1
1
  {
2
2
  "name": "edhindex",
3
- "version": "1.0.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": "./dist/cli/index.js"
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": "^3.8.1",
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
+ }
@@ -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
- console.log(`Index directory: ${indexDir}`);
23
- console.log(`Configuration: model=${config.model}, languages=[${config.languages.join(', ')}]`);
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.log(chalk.bold('EDHIndex v1.0.0'));
36
- console.log(`Workspace: ${rootPath}`);
37
- console.log(`Model: ${modelCfg.displayName} (${modelCfg.dimensions}d)`);
38
- console.log(`Languages: ${config.languages.join(', ')}`);
39
- console.log('');
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.log(chalk.yellow('Index version mismatch — full rebuild required.'));
46
- console.log(chalk.dim(` Expected model: ${config.model}, existing: ${version.embeddingModel}`));
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.log(chalk.dim('No existing index found — building from scratch.'));
52
+ console.error(chalk.dim('No existing index found — building from scratch.'));
49
53
  }
50
54
  } else {
51
- console.log(chalk.dim('Existing index is compatible — incremental update.'));
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.log(chalk.dim('Initializing storage...'));
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.log(chalk.dim(`Loading embedding model (${modelCfg.displayName})...`));
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.log(chalk.dim('Loading reranker model...'));
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.log('');
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.log(chalk.dim('Running full index...'));
167
+ console.error(chalk.dim('Running full index...'));
164
168
  const chunks = await indexer.runFullIndex();
165
169
  progressBar.stop();
166
- console.log(chalk.green(`Indexed ${chunks.length} chunks.`));
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.log(chalk.dim('Running incremental index...'));
177
+ console.error(chalk.dim('Running incremental index...'));
174
178
  const result = await indexer.runIncremental(existingFileHashes, existingChunkHashes);
175
179
  progressBar.stop();
176
- console.log(chalk.green(`Indexed ${result.chunks.length} chunks, removed ${result.removed.length} files.`));
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.log(chalk.green('File watcher started.'));
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.log(chalk.green('Starting MCP server on stdio...'));
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.log(chalk.yellow('\nShutting down...'));
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);
@@ -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
+ }