viberag 0.3.2 → 0.4.0

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 (209) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/app.d.ts +3 -0
  3. package/dist/cli/app.js +100 -102
  4. package/dist/cli/commands/handlers.d.ts +8 -6
  5. package/dist/cli/commands/handlers.js +90 -32
  6. package/dist/cli/commands/useCommands.d.ts +20 -0
  7. package/dist/cli/commands/useCommands.js +189 -0
  8. package/dist/cli/commands/useRagCommands.d.ts +2 -5
  9. package/dist/cli/commands/useRagCommands.js +11 -18
  10. package/dist/cli/components/InitWizard.js +66 -27
  11. package/dist/cli/components/McpSetupWizard.js +23 -4
  12. package/dist/cli/components/SlotRow.d.ts +22 -0
  13. package/dist/cli/components/SlotRow.js +55 -0
  14. package/dist/cli/components/StatusBar.d.ts +14 -0
  15. package/dist/cli/components/StatusBar.js +156 -0
  16. package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
  17. package/dist/cli/contexts/DaemonStatusContext.js +106 -0
  18. package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
  19. package/dist/cli/hooks/useStatusPolling.js +121 -0
  20. package/dist/cli/store/app/selectors.d.ts +87 -0
  21. package/dist/cli/store/app/selectors.js +28 -0
  22. package/dist/cli/store/app/slice.d.ts +1013 -0
  23. package/dist/cli/store/app/slice.js +112 -0
  24. package/dist/cli/store/hooks.d.ts +22 -0
  25. package/dist/cli/store/hooks.js +17 -0
  26. package/dist/cli/store/store.d.ts +17 -0
  27. package/dist/cli/store/store.js +18 -0
  28. package/dist/cli/store/wizard/selectors.d.ts +115 -0
  29. package/dist/cli/store/wizard/selectors.js +36 -0
  30. package/dist/cli/store/wizard/slice.d.ts +523 -0
  31. package/dist/cli/store/wizard/slice.js +119 -0
  32. package/dist/cli/utils/error-handler.d.ts +55 -0
  33. package/dist/cli/utils/error-handler.js +92 -0
  34. package/dist/client/auto-start.d.ts +42 -0
  35. package/dist/client/auto-start.js +250 -0
  36. package/dist/client/connection.d.ts +48 -0
  37. package/dist/client/connection.js +200 -0
  38. package/dist/client/index.d.ts +93 -0
  39. package/dist/client/index.js +209 -0
  40. package/dist/client/types.d.ts +105 -0
  41. package/dist/client/types.js +7 -0
  42. package/dist/common/components/SlotRow.d.ts +22 -0
  43. package/dist/common/components/SlotRow.js +53 -0
  44. package/dist/common/components/StatusBar.js +82 -31
  45. package/dist/common/types.d.ts +12 -13
  46. package/dist/daemon/handlers.d.ts +15 -0
  47. package/dist/daemon/handlers.js +157 -0
  48. package/dist/daemon/index.d.ts +21 -0
  49. package/dist/daemon/index.js +123 -0
  50. package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
  51. package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
  52. package/dist/daemon/lib/chunker/index.d.ts +135 -0
  53. package/dist/daemon/lib/chunker/index.js +1370 -0
  54. package/dist/daemon/lib/chunker/types.d.ts +77 -0
  55. package/dist/daemon/lib/chunker/types.js +50 -0
  56. package/dist/daemon/lib/config.d.ts +73 -0
  57. package/dist/daemon/lib/config.js +149 -0
  58. package/dist/daemon/lib/constants.d.ts +75 -0
  59. package/dist/daemon/lib/constants.js +114 -0
  60. package/dist/daemon/lib/gitignore.d.ts +57 -0
  61. package/dist/daemon/lib/gitignore.js +246 -0
  62. package/dist/daemon/lib/logger.d.ts +51 -0
  63. package/dist/daemon/lib/logger.js +167 -0
  64. package/dist/daemon/lib/manifest.d.ts +58 -0
  65. package/dist/daemon/lib/manifest.js +116 -0
  66. package/dist/daemon/lib/merkle/diff.d.ts +32 -0
  67. package/dist/daemon/lib/merkle/diff.js +107 -0
  68. package/dist/daemon/lib/merkle/hash.d.ts +40 -0
  69. package/dist/daemon/lib/merkle/hash.js +180 -0
  70. package/dist/daemon/lib/merkle/index.d.ts +71 -0
  71. package/dist/daemon/lib/merkle/index.js +309 -0
  72. package/dist/daemon/lib/merkle/node.d.ts +55 -0
  73. package/dist/daemon/lib/merkle/node.js +82 -0
  74. package/dist/daemon/lifecycle.d.ts +50 -0
  75. package/dist/daemon/lifecycle.js +142 -0
  76. package/dist/daemon/owner.d.ts +175 -0
  77. package/dist/daemon/owner.js +609 -0
  78. package/dist/daemon/protocol.d.ts +100 -0
  79. package/dist/daemon/protocol.js +163 -0
  80. package/dist/daemon/providers/api-utils.d.ts +130 -0
  81. package/dist/daemon/providers/api-utils.js +248 -0
  82. package/dist/daemon/providers/gemini.d.ts +39 -0
  83. package/dist/daemon/providers/gemini.js +205 -0
  84. package/dist/daemon/providers/index.d.ts +14 -0
  85. package/dist/daemon/providers/index.js +14 -0
  86. package/dist/daemon/providers/local-4b.d.ts +28 -0
  87. package/dist/daemon/providers/local-4b.js +51 -0
  88. package/dist/daemon/providers/local.d.ts +36 -0
  89. package/dist/daemon/providers/local.js +166 -0
  90. package/dist/daemon/providers/mistral.d.ts +35 -0
  91. package/dist/daemon/providers/mistral.js +160 -0
  92. package/dist/daemon/providers/mock.d.ts +35 -0
  93. package/dist/daemon/providers/mock.js +69 -0
  94. package/dist/daemon/providers/openai.d.ts +41 -0
  95. package/dist/daemon/providers/openai.js +190 -0
  96. package/dist/daemon/providers/types.d.ts +68 -0
  97. package/dist/daemon/providers/types.js +6 -0
  98. package/dist/daemon/providers/validate.d.ts +30 -0
  99. package/dist/daemon/providers/validate.js +162 -0
  100. package/dist/daemon/server.d.ts +79 -0
  101. package/dist/daemon/server.js +293 -0
  102. package/dist/daemon/services/index.d.ts +11 -0
  103. package/dist/daemon/services/index.js +16 -0
  104. package/dist/daemon/services/indexing.d.ts +117 -0
  105. package/dist/daemon/services/indexing.js +573 -0
  106. package/dist/daemon/services/search/filters.d.ts +21 -0
  107. package/dist/daemon/services/search/filters.js +106 -0
  108. package/dist/daemon/services/search/fts.d.ts +32 -0
  109. package/dist/daemon/services/search/fts.js +61 -0
  110. package/dist/daemon/services/search/hybrid.d.ts +17 -0
  111. package/dist/daemon/services/search/hybrid.js +58 -0
  112. package/dist/daemon/services/search/index.d.ts +108 -0
  113. package/dist/daemon/services/search/index.js +417 -0
  114. package/dist/daemon/services/search/types.d.ts +126 -0
  115. package/dist/daemon/services/search/types.js +4 -0
  116. package/dist/daemon/services/search/vector.d.ts +25 -0
  117. package/dist/daemon/services/search/vector.js +44 -0
  118. package/dist/daemon/services/storage/index.d.ts +110 -0
  119. package/dist/daemon/services/storage/index.js +378 -0
  120. package/dist/daemon/services/storage/schema.d.ts +24 -0
  121. package/dist/daemon/services/storage/schema.js +51 -0
  122. package/dist/daemon/services/storage/types.d.ts +105 -0
  123. package/dist/daemon/services/storage/types.js +71 -0
  124. package/dist/daemon/services/types.d.ts +192 -0
  125. package/dist/daemon/services/types.js +53 -0
  126. package/dist/daemon/services/watcher.d.ts +98 -0
  127. package/dist/daemon/services/watcher.js +386 -0
  128. package/dist/daemon/state.d.ts +119 -0
  129. package/dist/daemon/state.js +161 -0
  130. package/dist/mcp/index.d.ts +1 -1
  131. package/dist/mcp/index.js +44 -60
  132. package/dist/mcp/server.d.ts +10 -14
  133. package/dist/mcp/server.js +75 -74
  134. package/dist/mcp/services/lazy-loader.d.ts +23 -0
  135. package/dist/mcp/services/lazy-loader.js +34 -0
  136. package/dist/mcp/warmup.d.ts +3 -3
  137. package/dist/mcp/warmup.js +39 -40
  138. package/dist/mcp/watcher.d.ts +5 -7
  139. package/dist/mcp/watcher.js +73 -64
  140. package/dist/rag/config/index.d.ts +2 -0
  141. package/dist/rag/constants.d.ts +30 -0
  142. package/dist/rag/constants.js +38 -0
  143. package/dist/rag/embeddings/api-utils.d.ts +121 -0
  144. package/dist/rag/embeddings/api-utils.js +259 -0
  145. package/dist/rag/embeddings/gemini.d.ts +4 -12
  146. package/dist/rag/embeddings/gemini.js +22 -72
  147. package/dist/rag/embeddings/index.d.ts +5 -3
  148. package/dist/rag/embeddings/index.js +5 -2
  149. package/dist/rag/embeddings/local-4b.d.ts +2 -2
  150. package/dist/rag/embeddings/local-4b.js +1 -1
  151. package/dist/rag/embeddings/local.d.ts +10 -3
  152. package/dist/rag/embeddings/local.js +58 -12
  153. package/dist/rag/embeddings/mistral.d.ts +4 -12
  154. package/dist/rag/embeddings/mistral.js +22 -72
  155. package/dist/rag/embeddings/mock.d.ts +35 -0
  156. package/dist/rag/embeddings/mock.js +69 -0
  157. package/dist/rag/embeddings/openai.d.ts +11 -13
  158. package/dist/rag/embeddings/openai.js +47 -75
  159. package/dist/rag/embeddings/types.d.ts +27 -1
  160. package/dist/rag/embeddings/validate.d.ts +9 -1
  161. package/dist/rag/embeddings/validate.js +17 -4
  162. package/dist/rag/index.d.ts +2 -2
  163. package/dist/rag/index.js +1 -1
  164. package/dist/rag/indexer/bounded-channel.d.ts +51 -0
  165. package/dist/rag/indexer/bounded-channel.js +138 -0
  166. package/dist/rag/indexer/indexer.d.ts +4 -14
  167. package/dist/rag/indexer/indexer.js +246 -169
  168. package/dist/rag/indexer/types.d.ts +1 -0
  169. package/dist/rag/logger/index.d.ts +22 -0
  170. package/dist/rag/logger/index.js +78 -1
  171. package/dist/rag/manifest/index.js +1 -2
  172. package/dist/rag/search/index.js +1 -1
  173. package/dist/rag/storage/schema.d.ts +2 -4
  174. package/dist/rag/storage/schema.js +3 -5
  175. package/dist/store/app/selectors.d.ts +87 -0
  176. package/dist/store/app/selectors.js +28 -0
  177. package/dist/store/app/slice.d.ts +1013 -0
  178. package/dist/store/app/slice.js +112 -0
  179. package/dist/store/hooks.d.ts +22 -0
  180. package/dist/store/hooks.js +17 -0
  181. package/dist/store/index.d.ts +12 -0
  182. package/dist/store/index.js +18 -0
  183. package/dist/store/indexing/listeners.d.ts +25 -0
  184. package/dist/store/indexing/listeners.js +46 -0
  185. package/dist/store/indexing/selectors.d.ts +195 -0
  186. package/dist/store/indexing/selectors.js +69 -0
  187. package/dist/store/indexing/slice.d.ts +309 -0
  188. package/dist/store/indexing/slice.js +113 -0
  189. package/dist/store/slot-progress/listeners.d.ts +23 -0
  190. package/dist/store/slot-progress/listeners.js +33 -0
  191. package/dist/store/slot-progress/selectors.d.ts +67 -0
  192. package/dist/store/slot-progress/selectors.js +36 -0
  193. package/dist/store/slot-progress/slice.d.ts +246 -0
  194. package/dist/store/slot-progress/slice.js +70 -0
  195. package/dist/store/store.d.ts +17 -0
  196. package/dist/store/store.js +18 -0
  197. package/dist/store/warmup/selectors.d.ts +109 -0
  198. package/dist/store/warmup/selectors.js +44 -0
  199. package/dist/store/warmup/slice.d.ts +137 -0
  200. package/dist/store/warmup/slice.js +72 -0
  201. package/dist/store/watcher/selectors.d.ts +115 -0
  202. package/dist/store/watcher/selectors.js +52 -0
  203. package/dist/store/watcher/slice.d.ts +269 -0
  204. package/dist/store/watcher/slice.js +100 -0
  205. package/dist/store/wizard/selectors.d.ts +115 -0
  206. package/dist/store/wizard/selectors.js +36 -0
  207. package/dist/store/wizard/slice.d.ts +523 -0
  208. package/dist/store/wizard/slice.js +119 -0
  209. package/package.json +10 -2
package/dist/mcp/index.js CHANGED
@@ -4,78 +4,62 @@
4
4
  *
5
5
  * Exposes VibeRAG functionality via Model Context Protocol.
6
6
  * Uses current working directory as project root.
7
- * Includes file watcher for automatic incremental indexing.
7
+ * Connects to the daemon for all RAG operations.
8
8
  *
9
9
  * Usage with Claude Code:
10
10
  * claude mcp add viberag -- npx viberag-mcp
11
11
  */
12
12
  import { createMcpServer } from './server.js';
13
- import { configExists, Indexer } from '../rag/index.js';
13
+ import { configExists } from '../daemon/lib/config.js';
14
14
  // Use current working directory as project root (same behavior as CLI)
15
15
  const projectRoot = process.cwd();
16
- const { server, startWatcher, stopWatcher, startWarmup, warmupManager } = createMcpServer(projectRoot);
16
+ const { server, connectDaemon, disconnectDaemon } = createMcpServer(projectRoot);
17
17
  // Handle shutdown signals
18
18
  async function shutdown(signal) {
19
19
  console.error(`[viberag-mcp] Received ${signal}, shutting down...`);
20
- await stopWatcher();
20
+ await disconnectDaemon();
21
21
  process.exit(0);
22
22
  }
23
- process.on('SIGINT', () => shutdown('SIGINT'));
24
- process.on('SIGTERM', () => shutdown('SIGTERM'));
25
- // Start the server, then start the watcher
26
- server.start({
27
- transportType: 'stdio',
23
+ process.on('SIGINT', () => {
24
+ void shutdown('SIGINT');
28
25
  });
29
- // Start warmup, watcher, and sync index after server is running
30
- // Use setImmediate to ensure server.start() completes first
31
- setImmediate(async () => {
32
- // Start warmup FIRST (most important for tool responsiveness)
33
- // This runs in background - tools will wait for it to complete
34
- try {
35
- if (await configExists(projectRoot)) {
36
- startWarmup();
37
- console.error('[viberag-mcp] Warmup started');
38
- // Monitor warmup completion for logging (non-blocking)
39
- warmupManager.getWarmupPromise()?.catch(error => {
40
- console.error('[viberag-mcp] Warmup failed:', error instanceof Error ? error.message : error);
41
- });
42
- }
43
- }
44
- catch (error) {
45
- console.error('[viberag-mcp] Failed to start warmup:', error instanceof Error ? error.message : error);
46
- }
47
- // Start watcher (will queue any changes during sync)
48
- try {
49
- await startWatcher();
50
- }
51
- catch (error) {
52
- // Watcher errors shouldn't crash the server
53
- console.error('[viberag-mcp] Failed to start watcher:', error instanceof Error ? error.message : error);
54
- }
55
- // Sync index on startup if project is initialized
56
- // This catches any changes made while MCP server was offline
57
- try {
58
- if (await configExists(projectRoot)) {
59
- console.error('[viberag-mcp] Running startup sync...');
60
- const indexer = new Indexer(projectRoot);
61
- try {
62
- const stats = await indexer.index({ force: false });
63
- if (stats.filesNew > 0 ||
64
- stats.filesModified > 0 ||
65
- stats.filesDeleted > 0) {
66
- console.error(`[viberag-mcp] Startup sync complete: ${stats.filesNew} new, ${stats.filesModified} modified, ${stats.filesDeleted} deleted`);
67
- }
68
- else {
69
- console.error('[viberag-mcp] Startup sync: index up to date');
70
- }
71
- }
72
- finally {
73
- indexer.close();
74
- }
75
- }
76
- }
77
- catch (error) {
78
- // Sync errors shouldn't crash the server
79
- console.error('[viberag-mcp] Startup sync failed:', error instanceof Error ? error.message : error);
26
+ process.on('SIGTERM', () => {
27
+ void shutdown('SIGTERM');
28
+ });
29
+ /**
30
+ * Run startup tasks after MCP client connects.
31
+ * This is deferred to ensure the MCP handshake completes first.
32
+ */
33
+ async function runStartupTasks() {
34
+ // Check if project is initialized
35
+ const isInitialized = await configExists(projectRoot);
36
+ if (!isInitialized) {
37
+ console.error('[viberag-mcp] Project not initialized.');
38
+ console.error('[viberag-mcp] Run "npx viberag" in this directory and use /init to configure.');
39
+ console.error('[viberag-mcp] Use viberag_status tool for details on how to initialize.');
40
+ // For uninitialized projects, we're done
41
+ return;
80
42
  }
43
+ // Connect to daemon (starts it if needed)
44
+ // The daemon handles warmup, watcher, and startup sync
45
+ await connectDaemon();
46
+ }
47
+ // Wait for client to connect before running startup tasks
48
+ // This ensures the MCP handshake completes before any file I/O
49
+ server.on('connect', () => {
50
+ // Run startup tasks in the background (don't block the connection)
51
+ runStartupTasks().catch(error => {
52
+ // Pass Error object directly to preserve stack trace (ADR-011)
53
+ console.error('[viberag-mcp] Startup tasks failed:', error);
54
+ });
55
+ });
56
+ // Start the server (await to ensure transport is ready)
57
+ server
58
+ .start({
59
+ transportType: 'stdio',
60
+ })
61
+ .catch(error => {
62
+ // Pass Error object directly to preserve stack trace (ADR-011)
63
+ console.error('[viberag-mcp] Failed to start server:', error);
64
+ process.exit(1);
81
65
  });
@@ -7,23 +7,19 @@
7
7
  * Includes file watcher for automatic incremental indexing.
8
8
  */
9
9
  import { FastMCP } from 'fastmcp';
10
- import { FileWatcher } from './watcher.js';
11
- import { WarmupManager } from './warmup.js';
10
+ import { DaemonClient } from '../client/index.js';
12
11
  /**
13
- * MCP server with file watcher and warmup manager.
12
+ * MCP server with daemon client.
14
13
  */
15
- export interface McpServerWithWatcher {
14
+ export interface McpServerWithDaemon {
16
15
  server: FastMCP;
17
- watcher: FileWatcher;
18
- warmupManager: WarmupManager;
19
- /** Start the watcher (call after server.start) */
20
- startWatcher: () => Promise<void>;
21
- /** Stop the watcher (call before exit) */
22
- stopWatcher: () => Promise<void>;
23
- /** Start warmup (call after server.start) */
24
- startWarmup: () => void;
16
+ client: DaemonClient;
17
+ /** Connect to daemon (call after server.start) */
18
+ connectDaemon: () => Promise<void>;
19
+ /** Disconnect from daemon (call before exit) */
20
+ disconnectDaemon: () => Promise<void>;
25
21
  }
26
22
  /**
27
- * Create and configure the MCP server with file watcher and warmup manager.
23
+ * Create and configure the MCP server with daemon client.
28
24
  */
29
- export declare function createMcpServer(projectRoot: string): McpServerWithWatcher;
25
+ export declare function createMcpServer(projectRoot: string): McpServerWithDaemon;
@@ -9,9 +9,11 @@
9
9
  import { createRequire } from 'node:module';
10
10
  import { FastMCP } from 'fastmcp';
11
11
  import { z } from 'zod';
12
- import { Indexer, configExists, loadManifest, manifestExists, loadConfig, saveConfig, PROVIDER_CONFIGS, getSchemaVersionInfo, } from '../rag/index.js';
13
- import { FileWatcher } from './watcher.js';
14
- import { WarmupManager } from './warmup.js';
12
+ // Direct imports from daemon for fast startup (avoid barrel file)
13
+ import { configExists, loadConfig } from '../daemon/lib/config.js';
14
+ import { loadManifest, manifestExists, getSchemaVersionInfo, } from '../daemon/lib/manifest.js';
15
+ import { DaemonClient } from '../client/index.js';
16
+ import { createServiceLogger } from '../daemon/lib/logger.js';
15
17
  const require = createRequire(import.meta.url);
16
18
  const pkg = require('../../package.json');
17
19
  /**
@@ -20,7 +22,7 @@ const pkg = require('../../package.json');
20
22
  class NotInitializedError extends Error {
21
23
  constructor(projectRoot) {
22
24
  super(`VibeRAG not initialized in ${projectRoot}. ` +
23
- `Run 'npx viberag' and use /init command first.`);
25
+ `Use viberag_status tool for details, or run 'npx viberag' CLI and use /init command.`);
24
26
  this.name = 'NotInitializedError';
25
27
  }
26
28
  }
@@ -187,17 +189,23 @@ function formatIndexStats(stats) {
187
189
  });
188
190
  }
189
191
  /**
190
- * Create and configure the MCP server with file watcher and warmup manager.
192
+ * Create and configure the MCP server with daemon client.
191
193
  */
192
194
  export function createMcpServer(projectRoot) {
193
195
  const server = new FastMCP({
194
196
  name: 'viberag',
195
197
  version: pkg.version,
196
198
  });
197
- // Create file watcher
198
- const watcher = new FileWatcher(projectRoot);
199
- // Create warmup manager for shared SearchEngine
200
- const warmupManager = new WarmupManager(projectRoot);
199
+ // Create daemon client (auto-starts daemon if needed)
200
+ const client = new DaemonClient(projectRoot);
201
+ // Create logger for error tracking (writes to .viberag/logs/mcp/)
202
+ let logger = null;
203
+ const getLogger = () => {
204
+ if (!logger) {
205
+ logger = createServiceLogger(projectRoot, 'mcp');
206
+ }
207
+ return logger;
208
+ };
201
209
  // Filters schema for transparent, AI-controlled filtering
202
210
  const filtersSchema = z
203
211
  .object({
@@ -408,16 +416,15 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
408
416
  hasDocstring: args.filters.has_docstring,
409
417
  }
410
418
  : undefined;
411
- // Get shared search engine from warmup manager (waits for warmup if needed)
412
- const engine = await warmupManager.getSearchEngine();
413
419
  // Determine if debug info should be returned
414
420
  const returnDebug = args.return_debug ??
415
421
  (args.mode === 'hybrid' || args.mode === undefined);
416
- const results = await engine.search(args.query, {
422
+ // Search via daemon client
423
+ const results = await client.search(args.query, {
417
424
  mode: args.mode,
418
425
  limit: args.limit,
419
426
  minScore: args.min_score,
420
- filters,
427
+ filters: filters,
421
428
  codeSnippet: args.code_snippet,
422
429
  symbolName: args.symbol_name,
423
430
  bm25Weight: args.bm25_weight,
@@ -425,7 +432,6 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
425
432
  autoBoostThreshold: args.auto_boost_threshold,
426
433
  returnDebug,
427
434
  });
428
- // Don't close engine - it's shared across calls
429
435
  return formatSearchResults(results, returnDebug, args.max_response_size);
430
436
  },
431
437
  });
@@ -446,29 +452,9 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
446
452
  }),
447
453
  execute: async (args) => {
448
454
  await ensureInitialized(projectRoot);
449
- // When forcing reindex, sync config dimensions with current provider settings
450
- // This handles cases where PROVIDER_CONFIGS dimensions changed (e.g., Gemini 768→1536)
451
- if (args.force) {
452
- const config = await loadConfig(projectRoot);
453
- const currentDimensions = PROVIDER_CONFIGS[config.embeddingProvider]?.dimensions;
454
- if (currentDimensions &&
455
- config.embeddingDimensions !== currentDimensions) {
456
- const updatedConfig = {
457
- ...config,
458
- embeddingDimensions: currentDimensions,
459
- embeddingModel: PROVIDER_CONFIGS[config.embeddingProvider].model,
460
- };
461
- await saveConfig(projectRoot, updatedConfig);
462
- }
463
- }
464
- const indexer = new Indexer(projectRoot);
465
- try {
466
- const stats = await indexer.index({ force: args.force });
467
- return formatIndexStats(stats);
468
- }
469
- finally {
470
- indexer.close();
471
- }
455
+ // Index via daemon client (handles dimension sync internally)
456
+ const stats = await client.index({ force: args.force });
457
+ return formatIndexStats(stats);
472
458
  },
473
459
  });
474
460
  // Tool: viberag_status
@@ -476,10 +462,24 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
476
462
  name: 'viberag_status',
477
463
  description: 'Get index status including file count, chunk count, embedding provider, schema version, and last update time. ' +
478
464
  'If schema version is outdated, run viberag_index with force=true to reindex. ' +
479
- 'TIP: Check status before delegating exploration to sub-agents to ensure the index is current.',
465
+ 'TIP: Check status before delegating exploration to sub-agents to ensure the index is current. ' +
466
+ 'This tool also works when the project is not initialized, providing guidance on how to set up.',
480
467
  parameters: z.object({}),
481
468
  execute: async () => {
482
- await ensureInitialized(projectRoot);
469
+ // Check if project is initialized (don't throw, return helpful status)
470
+ if (!(await configExists(projectRoot))) {
471
+ return JSON.stringify({
472
+ status: 'not_initialized',
473
+ message: 'VibeRAG is not initialized in this project.',
474
+ projectRoot,
475
+ instructions: {
476
+ step1: 'Run "npx viberag" in a terminal in this project directory',
477
+ step2: 'Use the /init command to configure an embedding provider',
478
+ providers: 'Choose from: Gemini (free tier), OpenAI, Mistral, or Local (no API key)',
479
+ note: 'After initialization, run viberag_index to create the search index',
480
+ },
481
+ });
482
+ }
483
483
  if (!(await manifestExists(projectRoot))) {
484
484
  return JSON.stringify({
485
485
  status: 'not_indexed',
@@ -507,16 +507,25 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
507
507
  `Schema version ${schemaInfo.current} is outdated (current: ${schemaInfo.required}). ` +
508
508
  `Run viberag_index with force=true to reindex and enable new features.`;
509
509
  }
510
- // Add warmup state
511
- const warmupState = warmupManager.getState();
512
- response['warmup'] = {
513
- status: warmupState.status,
514
- provider: warmupState.provider,
515
- startedAt: warmupState.startedAt,
516
- readyAt: warmupState.readyAt,
517
- elapsedMs: warmupState.elapsedMs,
518
- error: warmupState.error,
519
- };
510
+ // Add warmup state from daemon status
511
+ try {
512
+ const daemonStatus = await client.status();
513
+ response['warmup'] = {
514
+ status: daemonStatus.warmupStatus,
515
+ elapsedMs: daemonStatus.warmupElapsedMs,
516
+ };
517
+ }
518
+ catch (error) {
519
+ // Log unexpected errors (expected: daemon not running)
520
+ const message = error instanceof Error ? error.message : String(error);
521
+ const isExpected = message.includes('ENOENT') || message.includes('ECONNREFUSED');
522
+ if (!isExpected) {
523
+ // Log to both stderr and debug.log - pass Error object for full stack
524
+ console.error('[mcp] Failed to get daemon warmup status:', error);
525
+ getLogger().error('MCP', 'Failed to get daemon warmup status', error instanceof Error ? error : new Error(message));
526
+ }
527
+ response['warmup'] = { status: 'unknown' };
528
+ }
520
529
  return JSON.stringify(response);
521
530
  },
522
531
  });
@@ -527,7 +536,7 @@ Production code: { path_not_contains: ["test", "mock", "fixture"], is_exported:
527
536
  'how many files are being watched, pending changes, and last update time.',
528
537
  parameters: z.object({}),
529
538
  execute: async () => {
530
- const status = watcher.getStatus();
539
+ const status = await client.watchStatus();
531
540
  return JSON.stringify(status);
532
541
  },
533
542
  });
@@ -666,14 +675,12 @@ Metadata filters:
666
675
  .max(MAX_RESPONSE_SIZE)
667
676
  .optional()
668
677
  .default(DEFAULT_MAX_RESPONSE_SIZE)
669
- .describe('Maximum response size in bytes (default: 100KB). ' +
678
+ .describe('Maximum response size in bytes (default: 50KB, max: 100KB). ' +
670
679
  'Reduces merged result count to fit; does NOT truncate text.'),
671
680
  }),
672
681
  execute: async (args) => {
673
682
  await ensureInitialized(projectRoot);
674
- // Get shared search engine from warmup manager (waits for warmup if needed)
675
- const engine = await warmupManager.getSearchEngine();
676
- // Run all searches in parallel
683
+ // Run all searches in parallel via daemon client
677
684
  const searchPromises = args.searches.map(async (config, index) => {
678
685
  const filters = config.filters
679
686
  ? {
@@ -687,10 +694,10 @@ Metadata filters:
687
694
  hasDocstring: config.filters.has_docstring,
688
695
  }
689
696
  : undefined;
690
- const results = await engine.search(config.query, {
697
+ const results = await client.search(config.query, {
691
698
  mode: config.mode,
692
699
  limit: config.limit,
693
- filters,
700
+ filters: filters,
694
701
  bm25Weight: config.bm25_weight,
695
702
  autoBoost: config.auto_boost,
696
703
  returnDebug: true,
@@ -813,7 +820,6 @@ Metadata filters:
813
820
  results: mergedResults,
814
821
  };
815
822
  }
816
- // Don't close engine - it's shared across calls
817
823
  return JSON.stringify({
818
824
  searchCount: args.searches.length,
819
825
  individual,
@@ -823,26 +829,21 @@ Metadata filters:
823
829
  });
824
830
  return {
825
831
  server,
826
- watcher,
827
- warmupManager,
828
- startWatcher: async () => {
829
- // Only start watcher if project is initialized
832
+ client,
833
+ connectDaemon: async () => {
834
+ // Only connect if project is initialized
830
835
  if (await configExists(projectRoot)) {
831
- await watcher.start();
836
+ try {
837
+ await client.connect();
838
+ console.error('[viberag-mcp] Connected to daemon');
839
+ }
840
+ catch (error) {
841
+ console.error('[viberag-mcp] Failed to connect to daemon:', error instanceof Error ? error.message : error);
842
+ }
832
843
  }
833
844
  },
834
- stopWatcher: async () => {
835
- await watcher.stop();
836
- warmupManager.close();
837
- },
838
- startWarmup: () => {
839
- warmupManager.startWarmup({
840
- onProgress: state => {
841
- if (state.status === 'ready') {
842
- console.error(`[viberag-mcp] Warmup complete (${state.elapsedMs}ms)`);
843
- }
844
- },
845
- });
845
+ disconnectDaemon: async () => {
846
+ await client.disconnect();
846
847
  },
847
848
  };
848
849
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Lazy Loader Service
3
+ *
4
+ * Provides lazy-loaded access to heavy RAG modules that have native dependencies.
5
+ * These modules (Indexer, SearchEngine) load tree-sitter WASM and lancedb native bindings,
6
+ * which takes ~500-1000ms. By loading them lazily, the MCP server can respond to
7
+ * the initialize handshake immediately.
8
+ *
9
+ * Pattern: Singleton with memoization
10
+ * Thread-safety: Safe - single-threaded Node.js environment
11
+ */
12
+ import type { Indexer as IndexerType } from '../../rag/indexer/index.js';
13
+ import type { SearchEngine as SearchEngineType } from '../../rag/search/index.js';
14
+ /**
15
+ * Get the Indexer class, loading the module on first access.
16
+ * Subsequent calls return the cached reference.
17
+ */
18
+ export declare function getIndexer(): Promise<typeof IndexerType>;
19
+ /**
20
+ * Get the SearchEngine class, loading the module on first access.
21
+ * Subsequent calls return the cached reference.
22
+ */
23
+ export declare function getSearchEngine(): Promise<typeof SearchEngineType>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Lazy Loader Service
3
+ *
4
+ * Provides lazy-loaded access to heavy RAG modules that have native dependencies.
5
+ * These modules (Indexer, SearchEngine) load tree-sitter WASM and lancedb native bindings,
6
+ * which takes ~500-1000ms. By loading them lazily, the MCP server can respond to
7
+ * the initialize handshake immediately.
8
+ *
9
+ * Pattern: Singleton with memoization
10
+ * Thread-safety: Safe - single-threaded Node.js environment
11
+ */
12
+ // Memoized module references
13
+ let indexerModule = null;
14
+ let searchModule = null;
15
+ /**
16
+ * Get the Indexer class, loading the module on first access.
17
+ * Subsequent calls return the cached reference.
18
+ */
19
+ export async function getIndexer() {
20
+ if (!indexerModule) {
21
+ indexerModule = await import('../../rag/indexer/index.js');
22
+ }
23
+ return indexerModule.Indexer;
24
+ }
25
+ /**
26
+ * Get the SearchEngine class, loading the module on first access.
27
+ * Subsequent calls return the cached reference.
28
+ */
29
+ export async function getSearchEngine() {
30
+ if (!searchModule) {
31
+ searchModule = await import('../../rag/search/index.js');
32
+ }
33
+ return searchModule.SearchEngine;
34
+ }
@@ -4,12 +4,13 @@
4
4
  * Manages singleton SearchEngine initialization and state.
5
5
  * Ensures embedding model is loaded once and shared across tool calls.
6
6
  *
7
+ * State is tracked in Redux for consistency with the rest of the codebase.
7
8
  * The idempotent promise pattern solves the race condition:
8
9
  * - First call: Creates the warmup promise, starts initialization
9
10
  * - Concurrent calls: All await the SAME promise (no duplicate work)
10
11
  * - After ready: Returns cached SearchEngine immediately
11
12
  */
12
- import { SearchEngine } from '../rag/index.js';
13
+ import type { SearchEngine } from '../rag/search/index.js';
13
14
  /**
14
15
  * Warmup status enum.
15
16
  */
@@ -41,10 +42,9 @@ export declare class WarmupManager {
41
42
  private readonly projectRoot;
42
43
  private searchEngine;
43
44
  private warmupPromise;
44
- private state;
45
45
  constructor(projectRoot: string);
46
46
  /**
47
- * Get current warmup state.
47
+ * Get current warmup state from Redux.
48
48
  */
49
49
  getState(): WarmupState;
50
50
  /**