cursor-recursive-rag 0.2.0-alpha.2 → 0.2.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 (210) hide show
  1. package/README.md +179 -203
  2. package/dist/adapters/llm/anthropic.d.ts +27 -0
  3. package/dist/adapters/llm/anthropic.d.ts.map +1 -0
  4. package/dist/adapters/llm/anthropic.js +287 -0
  5. package/dist/adapters/llm/anthropic.js.map +1 -0
  6. package/dist/adapters/llm/base.d.ts +62 -0
  7. package/dist/adapters/llm/base.d.ts.map +1 -0
  8. package/dist/adapters/llm/base.js +140 -0
  9. package/dist/adapters/llm/base.js.map +1 -0
  10. package/dist/adapters/llm/deepseek.d.ts +24 -0
  11. package/dist/adapters/llm/deepseek.d.ts.map +1 -0
  12. package/dist/adapters/llm/deepseek.js +228 -0
  13. package/dist/adapters/llm/deepseek.js.map +1 -0
  14. package/dist/adapters/llm/groq.d.ts +25 -0
  15. package/dist/adapters/llm/groq.d.ts.map +1 -0
  16. package/dist/adapters/llm/groq.js +265 -0
  17. package/dist/adapters/llm/groq.js.map +1 -0
  18. package/dist/adapters/llm/index.d.ts +62 -0
  19. package/dist/adapters/llm/index.d.ts.map +1 -0
  20. package/dist/adapters/llm/index.js +380 -0
  21. package/dist/adapters/llm/index.js.map +1 -0
  22. package/dist/adapters/llm/ollama.d.ts +23 -0
  23. package/dist/adapters/llm/ollama.d.ts.map +1 -0
  24. package/dist/adapters/llm/ollama.js +261 -0
  25. package/dist/adapters/llm/ollama.js.map +1 -0
  26. package/dist/adapters/llm/openai.d.ts +22 -0
  27. package/dist/adapters/llm/openai.d.ts.map +1 -0
  28. package/dist/adapters/llm/openai.js +232 -0
  29. package/dist/adapters/llm/openai.js.map +1 -0
  30. package/dist/adapters/llm/openrouter.d.ts +27 -0
  31. package/dist/adapters/llm/openrouter.d.ts.map +1 -0
  32. package/dist/adapters/llm/openrouter.js +305 -0
  33. package/dist/adapters/llm/openrouter.js.map +1 -0
  34. package/dist/adapters/vector/index.d.ts.map +1 -1
  35. package/dist/adapters/vector/index.js +8 -0
  36. package/dist/adapters/vector/index.js.map +1 -1
  37. package/dist/adapters/vector/redis-native.d.ts +35 -0
  38. package/dist/adapters/vector/redis-native.d.ts.map +1 -0
  39. package/dist/adapters/vector/redis-native.js +170 -0
  40. package/dist/adapters/vector/redis-native.js.map +1 -0
  41. package/dist/cli/commands/chat.d.ts +4 -0
  42. package/dist/cli/commands/chat.d.ts.map +1 -0
  43. package/dist/cli/commands/chat.js +374 -0
  44. package/dist/cli/commands/chat.js.map +1 -0
  45. package/dist/cli/commands/maintenance.d.ts +4 -0
  46. package/dist/cli/commands/maintenance.d.ts.map +1 -0
  47. package/dist/cli/commands/maintenance.js +237 -0
  48. package/dist/cli/commands/maintenance.js.map +1 -0
  49. package/dist/cli/commands/rules.d.ts +9 -0
  50. package/dist/cli/commands/rules.d.ts.map +1 -0
  51. package/dist/cli/commands/rules.js +639 -0
  52. package/dist/cli/commands/rules.js.map +1 -0
  53. package/dist/cli/commands/setup.js +5 -4
  54. package/dist/cli/commands/setup.js.map +1 -1
  55. package/dist/cli/index.js +6 -0
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/config/memoryConfig.d.ts +427 -0
  58. package/dist/config/memoryConfig.d.ts.map +1 -0
  59. package/dist/config/memoryConfig.js +258 -0
  60. package/dist/config/memoryConfig.js.map +1 -0
  61. package/dist/config/rulesConfig.d.ts +486 -0
  62. package/dist/config/rulesConfig.d.ts.map +1 -0
  63. package/dist/config/rulesConfig.js +345 -0
  64. package/dist/config/rulesConfig.js.map +1 -0
  65. package/dist/dashboard/coreTools.d.ts +14 -0
  66. package/dist/dashboard/coreTools.d.ts.map +1 -0
  67. package/dist/dashboard/coreTools.js +413 -0
  68. package/dist/dashboard/coreTools.js.map +1 -0
  69. package/dist/dashboard/public/index.html +1982 -13
  70. package/dist/dashboard/server.d.ts +1 -8
  71. package/dist/dashboard/server.d.ts.map +1 -1
  72. package/dist/dashboard/server.js +846 -13
  73. package/dist/dashboard/server.js.map +1 -1
  74. package/dist/dashboard/toolRegistry.d.ts +192 -0
  75. package/dist/dashboard/toolRegistry.d.ts.map +1 -0
  76. package/dist/dashboard/toolRegistry.js +322 -0
  77. package/dist/dashboard/toolRegistry.js.map +1 -0
  78. package/dist/proxy/index.d.ts +1 -1
  79. package/dist/proxy/index.d.ts.map +1 -1
  80. package/dist/proxy/index.js +9 -6
  81. package/dist/proxy/index.js.map +1 -1
  82. package/dist/server/index.js +21 -0
  83. package/dist/server/index.js.map +1 -1
  84. package/dist/server/tools/crawl.d.ts.map +1 -1
  85. package/dist/server/tools/crawl.js +8 -0
  86. package/dist/server/tools/crawl.js.map +1 -1
  87. package/dist/server/tools/index.d.ts.map +1 -1
  88. package/dist/server/tools/index.js +19 -1
  89. package/dist/server/tools/index.js.map +1 -1
  90. package/dist/server/tools/ingest.d.ts.map +1 -1
  91. package/dist/server/tools/ingest.js +5 -0
  92. package/dist/server/tools/ingest.js.map +1 -1
  93. package/dist/server/tools/memory.d.ts +250 -0
  94. package/dist/server/tools/memory.d.ts.map +1 -0
  95. package/dist/server/tools/memory.js +472 -0
  96. package/dist/server/tools/memory.js.map +1 -0
  97. package/dist/server/tools/recursive-query.d.ts.map +1 -1
  98. package/dist/server/tools/recursive-query.js +6 -0
  99. package/dist/server/tools/recursive-query.js.map +1 -1
  100. package/dist/server/tools/search.d.ts.map +1 -1
  101. package/dist/server/tools/search.js +6 -0
  102. package/dist/server/tools/search.js.map +1 -1
  103. package/dist/services/activity-log.d.ts +10 -0
  104. package/dist/services/activity-log.d.ts.map +1 -0
  105. package/dist/services/activity-log.js +53 -0
  106. package/dist/services/activity-log.js.map +1 -0
  107. package/dist/services/categoryManager.d.ts +110 -0
  108. package/dist/services/categoryManager.d.ts.map +1 -0
  109. package/dist/services/categoryManager.js +549 -0
  110. package/dist/services/categoryManager.js.map +1 -0
  111. package/dist/services/contextEnvironment.d.ts +206 -0
  112. package/dist/services/contextEnvironment.d.ts.map +1 -0
  113. package/dist/services/contextEnvironment.js +481 -0
  114. package/dist/services/contextEnvironment.js.map +1 -0
  115. package/dist/services/conversationProcessor.d.ts +99 -0
  116. package/dist/services/conversationProcessor.d.ts.map +1 -0
  117. package/dist/services/conversationProcessor.js +311 -0
  118. package/dist/services/conversationProcessor.js.map +1 -0
  119. package/dist/services/cursorChatReader.d.ts +129 -0
  120. package/dist/services/cursorChatReader.d.ts.map +1 -0
  121. package/dist/services/cursorChatReader.js +419 -0
  122. package/dist/services/cursorChatReader.js.map +1 -0
  123. package/dist/services/decayCalculator.d.ts +85 -0
  124. package/dist/services/decayCalculator.d.ts.map +1 -0
  125. package/dist/services/decayCalculator.js +182 -0
  126. package/dist/services/decayCalculator.js.map +1 -0
  127. package/dist/services/enhancedVectorStore.d.ts +102 -0
  128. package/dist/services/enhancedVectorStore.d.ts.map +1 -0
  129. package/dist/services/enhancedVectorStore.js +245 -0
  130. package/dist/services/enhancedVectorStore.js.map +1 -0
  131. package/dist/services/hybridScorer.d.ts +120 -0
  132. package/dist/services/hybridScorer.d.ts.map +1 -0
  133. package/dist/services/hybridScorer.js +334 -0
  134. package/dist/services/hybridScorer.js.map +1 -0
  135. package/dist/services/knowledgeExtractor.d.ts +45 -0
  136. package/dist/services/knowledgeExtractor.d.ts.map +1 -0
  137. package/dist/services/knowledgeExtractor.js +436 -0
  138. package/dist/services/knowledgeExtractor.js.map +1 -0
  139. package/dist/services/knowledgeStorage.d.ts +102 -0
  140. package/dist/services/knowledgeStorage.d.ts.map +1 -0
  141. package/dist/services/knowledgeStorage.js +383 -0
  142. package/dist/services/knowledgeStorage.js.map +1 -0
  143. package/dist/services/maintenanceScheduler.d.ts +89 -0
  144. package/dist/services/maintenanceScheduler.d.ts.map +1 -0
  145. package/dist/services/maintenanceScheduler.js +479 -0
  146. package/dist/services/maintenanceScheduler.js.map +1 -0
  147. package/dist/services/memoryMetadataStore.d.ts +62 -0
  148. package/dist/services/memoryMetadataStore.d.ts.map +1 -0
  149. package/dist/services/memoryMetadataStore.js +570 -0
  150. package/dist/services/memoryMetadataStore.js.map +1 -0
  151. package/dist/services/recursiveRetrieval.d.ts +122 -0
  152. package/dist/services/recursiveRetrieval.d.ts.map +1 -0
  153. package/dist/services/recursiveRetrieval.js +443 -0
  154. package/dist/services/recursiveRetrieval.js.map +1 -0
  155. package/dist/services/relationshipGraph.d.ts +77 -0
  156. package/dist/services/relationshipGraph.d.ts.map +1 -0
  157. package/dist/services/relationshipGraph.js +411 -0
  158. package/dist/services/relationshipGraph.js.map +1 -0
  159. package/dist/services/rlmSafeguards.d.ts +273 -0
  160. package/dist/services/rlmSafeguards.d.ts.map +1 -0
  161. package/dist/services/rlmSafeguards.js +705 -0
  162. package/dist/services/rlmSafeguards.js.map +1 -0
  163. package/dist/services/rulesAnalyzer.d.ts +119 -0
  164. package/dist/services/rulesAnalyzer.d.ts.map +1 -0
  165. package/dist/services/rulesAnalyzer.js +768 -0
  166. package/dist/services/rulesAnalyzer.js.map +1 -0
  167. package/dist/services/rulesMerger.d.ts +75 -0
  168. package/dist/services/rulesMerger.d.ts.map +1 -0
  169. package/dist/services/rulesMerger.js +404 -0
  170. package/dist/services/rulesMerger.js.map +1 -0
  171. package/dist/services/rulesParser.d.ts +127 -0
  172. package/dist/services/rulesParser.d.ts.map +1 -0
  173. package/dist/services/rulesParser.js +594 -0
  174. package/dist/services/rulesParser.js.map +1 -0
  175. package/dist/services/smartChunker.d.ts +110 -0
  176. package/dist/services/smartChunker.d.ts.map +1 -0
  177. package/dist/services/smartChunker.js +520 -0
  178. package/dist/services/smartChunker.js.map +1 -0
  179. package/dist/types/categories.d.ts +105 -0
  180. package/dist/types/categories.d.ts.map +1 -0
  181. package/dist/types/categories.js +108 -0
  182. package/dist/types/categories.js.map +1 -0
  183. package/dist/types/extractedKnowledge.d.ts +233 -0
  184. package/dist/types/extractedKnowledge.d.ts.map +1 -0
  185. package/dist/types/extractedKnowledge.js +56 -0
  186. package/dist/types/extractedKnowledge.js.map +1 -0
  187. package/dist/types/index.d.ts +9 -2
  188. package/dist/types/index.d.ts.map +1 -1
  189. package/dist/types/index.js +12 -1
  190. package/dist/types/index.js.map +1 -1
  191. package/dist/types/llmProvider.d.ts +282 -0
  192. package/dist/types/llmProvider.d.ts.map +1 -0
  193. package/dist/types/llmProvider.js +48 -0
  194. package/dist/types/llmProvider.js.map +1 -0
  195. package/dist/types/memory.d.ts +227 -0
  196. package/dist/types/memory.d.ts.map +1 -0
  197. package/dist/types/memory.js +76 -0
  198. package/dist/types/memory.js.map +1 -0
  199. package/dist/types/relationships.d.ts +167 -0
  200. package/dist/types/relationships.d.ts.map +1 -0
  201. package/dist/types/relationships.js +106 -0
  202. package/dist/types/relationships.js.map +1 -0
  203. package/dist/types/rulesOptimizer.d.ts +345 -0
  204. package/dist/types/rulesOptimizer.d.ts.map +1 -0
  205. package/dist/types/rulesOptimizer.js +22 -0
  206. package/dist/types/rulesOptimizer.js.map +1 -0
  207. package/docs/cursor-recursive-rag-memory-spec.md +4569 -0
  208. package/docs/cursor-recursive-rag-tasks.md +1355 -0
  209. package/package.json +6 -3
  210. package/restart-rag.sh +16 -0
@@ -7,21 +7,19 @@ import { loadConfig, writeConfig } from '../services/config.js';
7
7
  import { createVectorStore } from '../adapters/vector/index.js';
8
8
  import { createEmbedder } from '../adapters/embeddings/index.js';
9
9
  import { createOpenSkillsClient } from '../integrations/openskills.js';
10
+ import { logActivity as sharedLogActivity, getActivityLog } from '../services/activity-log.js';
11
+ import { getToolRegistry, JobStatus } from './toolRegistry.js';
12
+ import { registerCoreTools } from './coreTools.js';
13
+ import { loadRulesConfig, saveRulesConfig, validatePattern, testPattern, EXAMPLE_PATTERNS, RulesAnalyzerConfigSchema, LLM_PROVIDERS, } from '../config/rulesConfig.js';
14
+ import { createProvider } from '../adapters/llm/index.js';
15
+ import { RulesParser } from '../services/rulesParser.js';
16
+ import { getRulesAnalyzer } from '../services/rulesAnalyzer.js';
17
+ import { getRulesMerger } from '../services/rulesMerger.js';
18
+ import { writeFileSync, unlinkSync, mkdirSync, copyFileSync } from 'fs';
10
19
  const __filename = fileURLToPath(import.meta.url);
11
20
  const __dirname = dirname(__filename);
12
- // In-memory activity log (would use a proper store in production)
13
- const activityLog = [];
14
21
  export function logActivity(type, message, details) {
15
- activityLog.unshift({
16
- timestamp: new Date().toISOString(),
17
- type,
18
- message,
19
- details
20
- });
21
- // Keep only last 100 entries
22
- if (activityLog.length > 100) {
23
- activityLog.pop();
24
- }
22
+ sharedLogActivity(type, message, details);
25
23
  }
26
24
  const MIME_TYPES = {
27
25
  '.html': 'text/html',
@@ -122,7 +120,41 @@ async function handleAPI(req, res, path) {
122
120
  return;
123
121
  }
124
122
  if (path === '/api/activity' && req.method === 'GET') {
125
- res.end(JSON.stringify(activityLog));
123
+ const activities = getActivityLog();
124
+ res.end(JSON.stringify(activities));
125
+ return;
126
+ }
127
+ if (path === '/api/health' && req.method === 'GET') {
128
+ const config = loadConfig();
129
+ const status = {
130
+ vectorStore: { type: config.vectorStore, status: 'unknown', error: null, count: 0 },
131
+ embeddings: { type: config.embeddings, status: 'unknown', error: null }
132
+ };
133
+ // Test vector store connection
134
+ try {
135
+ const vectorStore = createVectorStore(config.vectorStore, config);
136
+ const count = await vectorStore.count();
137
+ status.vectorStore.status = 'connected';
138
+ status.vectorStore.count = count;
139
+ if (vectorStore.disconnect) {
140
+ await vectorStore.disconnect();
141
+ }
142
+ }
143
+ catch (e) {
144
+ status.vectorStore.status = 'error';
145
+ status.vectorStore.error = e instanceof Error ? e.message : 'Connection failed';
146
+ }
147
+ // Test embeddings
148
+ try {
149
+ const embedder = await createEmbedder(config.embeddings, config);
150
+ await embedder.embed('test');
151
+ status.embeddings.status = 'connected';
152
+ }
153
+ catch (e) {
154
+ status.embeddings.status = 'error';
155
+ status.embeddings.error = e instanceof Error ? e.message : 'Embeddings failed';
156
+ }
157
+ res.end(JSON.stringify(status));
126
158
  return;
127
159
  }
128
160
  if (path === '/api/search' && req.method === 'POST') {
@@ -219,6 +251,805 @@ async function handleAPI(req, res, path) {
219
251
  }
220
252
  return;
221
253
  }
254
+ // Tools API endpoints
255
+ if (path === '/api/tools' && req.method === 'GET') {
256
+ const registry = getToolRegistry();
257
+ const tools = registry.getTools();
258
+ const categoriesWithCounts = registry.getCategoriesWithCounts();
259
+ res.end(JSON.stringify({
260
+ tools: tools.map(t => ({
261
+ ...t,
262
+ schema: registry.getParameterSchema(t.name),
263
+ })),
264
+ categories: Object.entries(categoriesWithCounts).map(([name, count]) => ({
265
+ name,
266
+ count,
267
+ })),
268
+ totalTools: tools.length,
269
+ }));
270
+ return;
271
+ }
272
+ // Get tools by category
273
+ const toolsByCategoryMatch = path.match(/^\/api\/tools\/category\/([^/]+)$/);
274
+ if (toolsByCategoryMatch && req.method === 'GET') {
275
+ const category = toolsByCategoryMatch[1];
276
+ const registry = getToolRegistry();
277
+ const tools = registry.getToolsByCategory(category);
278
+ res.end(JSON.stringify({
279
+ category,
280
+ tools: tools.map(t => ({
281
+ ...t,
282
+ schema: registry.getParameterSchema(t.name),
283
+ })),
284
+ }));
285
+ return;
286
+ }
287
+ // Get single tool with schema
288
+ const toolDetailMatch = path.match(/^\/api\/tools\/([^/]+)$/);
289
+ if (toolDetailMatch && req.method === 'GET') {
290
+ const toolName = toolDetailMatch[1];
291
+ const registry = getToolRegistry();
292
+ const tool = registry.getTool(toolName);
293
+ if (!tool) {
294
+ res.statusCode = 404;
295
+ res.end(JSON.stringify({ error: `Tool '${toolName}' not found` }));
296
+ return;
297
+ }
298
+ res.end(JSON.stringify({
299
+ ...tool,
300
+ schema: registry.getParameterSchema(toolName),
301
+ }));
302
+ return;
303
+ }
304
+ // Execute tool
305
+ const toolExecuteMatch = path.match(/^\/api\/tools\/([^/]+)\/execute$/);
306
+ if (toolExecuteMatch && req.method === 'POST') {
307
+ const toolName = toolExecuteMatch[1];
308
+ const registry = getToolRegistry();
309
+ if (!registry.hasTool(toolName)) {
310
+ res.statusCode = 404;
311
+ res.end(JSON.stringify({ error: `Tool '${toolName}' not found` }));
312
+ return;
313
+ }
314
+ let body = '';
315
+ req.on('data', chunk => body += chunk);
316
+ req.on('end', async () => {
317
+ try {
318
+ const params = body ? JSON.parse(body) : {};
319
+ const tool = registry.getTool(toolName);
320
+ // For long-running tools, execute async and return job ID
321
+ if (tool?.isLongRunning) {
322
+ const jobId = registry.executeAsync(toolName, params);
323
+ res.end(JSON.stringify({
324
+ async: true,
325
+ jobId,
326
+ message: `Tool '${toolName}' started. Check status at /api/tools/${toolName}/status/${jobId}`,
327
+ }));
328
+ return;
329
+ }
330
+ // Execute synchronously
331
+ const result = await registry.execute(toolName, params);
332
+ logActivity('query', `Tool executed: ${toolName}`, { params, success: result.success });
333
+ res.end(JSON.stringify(result));
334
+ }
335
+ catch (e) {
336
+ res.statusCode = 400;
337
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Invalid request' }));
338
+ }
339
+ });
340
+ return;
341
+ }
342
+ // Get job status
343
+ const jobStatusMatch = path.match(/^\/api\/tools\/([^/]+)\/status\/([^/]+)$/);
344
+ if (jobStatusMatch && req.method === 'GET') {
345
+ const [, toolName, jobId] = jobStatusMatch;
346
+ const registry = getToolRegistry();
347
+ const job = registry.getJob(jobId);
348
+ if (!job) {
349
+ res.statusCode = 404;
350
+ res.end(JSON.stringify({ error: `Job '${jobId}' not found` }));
351
+ return;
352
+ }
353
+ res.end(JSON.stringify({
354
+ id: job.id,
355
+ toolName: job.toolName,
356
+ status: job.status,
357
+ progress: job.progress,
358
+ progressMessage: job.progressMessage,
359
+ startedAt: job.startedAt.toISOString(),
360
+ completedAt: job.completedAt?.toISOString(),
361
+ result: job.status === JobStatus.COMPLETED || job.status === JobStatus.FAILED ? job.result : undefined,
362
+ }));
363
+ return;
364
+ }
365
+ // Get recent jobs
366
+ if (path === '/api/tools/jobs' && req.method === 'GET') {
367
+ const registry = getToolRegistry();
368
+ const jobs = registry.getRecentJobs(20);
369
+ res.end(JSON.stringify({
370
+ jobs: jobs.map(j => ({
371
+ id: j.id,
372
+ toolName: j.toolName,
373
+ status: j.status,
374
+ startedAt: j.startedAt.toISOString(),
375
+ completedAt: j.completedAt?.toISOString(),
376
+ success: j.result?.success,
377
+ })),
378
+ }));
379
+ return;
380
+ }
381
+ // =========================================
382
+ // Rules Analyzer Configuration API
383
+ // =========================================
384
+ // Get rules config
385
+ if (path === '/api/rules/config' && req.method === 'GET') {
386
+ try {
387
+ const config = loadRulesConfig();
388
+ res.end(JSON.stringify({
389
+ config,
390
+ examples: EXAMPLE_PATTERNS,
391
+ }));
392
+ }
393
+ catch (e) {
394
+ res.statusCode = 500;
395
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Failed to load config' }));
396
+ }
397
+ return;
398
+ }
399
+ // Save rules config
400
+ if (path === '/api/rules/config' && req.method === 'PUT') {
401
+ let body = '';
402
+ req.on('data', chunk => body += chunk);
403
+ req.on('end', async () => {
404
+ try {
405
+ const data = JSON.parse(body);
406
+ const validated = RulesAnalyzerConfigSchema.parse(data);
407
+ saveRulesConfig(validated);
408
+ logActivity('query', 'Rules analyzer config updated');
409
+ res.end(JSON.stringify({ success: true, config: validated }));
410
+ }
411
+ catch (e) {
412
+ res.statusCode = 400;
413
+ res.end(JSON.stringify({
414
+ error: e instanceof Error ? e.message : 'Invalid config',
415
+ details: e instanceof Error && 'issues' in e ? e.issues : undefined,
416
+ }));
417
+ }
418
+ });
419
+ return;
420
+ }
421
+ // Validate a regex pattern
422
+ if (path === '/api/rules/validate-pattern' && req.method === 'POST') {
423
+ let body = '';
424
+ req.on('data', chunk => body += chunk);
425
+ req.on('end', () => {
426
+ try {
427
+ const { pattern } = JSON.parse(body);
428
+ const result = validatePattern(pattern);
429
+ res.end(JSON.stringify(result));
430
+ }
431
+ catch (e) {
432
+ res.statusCode = 400;
433
+ res.end(JSON.stringify({ valid: false, error: 'Invalid request' }));
434
+ }
435
+ });
436
+ return;
437
+ }
438
+ // Test a pattern against sample content
439
+ if (path === '/api/rules/test-pattern' && req.method === 'POST') {
440
+ let body = '';
441
+ req.on('data', chunk => body += chunk);
442
+ req.on('end', () => {
443
+ try {
444
+ const { pattern, content } = JSON.parse(body);
445
+ const result = testPattern(pattern, content);
446
+ res.end(JSON.stringify(result));
447
+ }
448
+ catch (e) {
449
+ res.statusCode = 400;
450
+ res.end(JSON.stringify({ matches: false, error: 'Invalid request' }));
451
+ }
452
+ });
453
+ return;
454
+ }
455
+ // Get example patterns (templates)
456
+ if (path === '/api/rules/examples' && req.method === 'GET') {
457
+ res.end(JSON.stringify(EXAMPLE_PATTERNS));
458
+ return;
459
+ }
460
+ // Get available LLM providers
461
+ if (path === '/api/rules/llm/providers' && req.method === 'GET') {
462
+ res.end(JSON.stringify({ providers: LLM_PROVIDERS }));
463
+ return;
464
+ }
465
+ // Test LLM connection and get available models
466
+ if (path === '/api/rules/llm/test' && req.method === 'POST') {
467
+ let body = '';
468
+ req.on('data', chunk => body += chunk);
469
+ req.on('end', async () => {
470
+ try {
471
+ const { provider, apiKey, baseUrl } = JSON.parse(body);
472
+ if (!provider) {
473
+ res.statusCode = 400;
474
+ res.end(JSON.stringify({ success: false, error: 'Provider is required' }));
475
+ return;
476
+ }
477
+ // Build provider config
478
+ const config = {
479
+ provider,
480
+ apiKey,
481
+ baseUrl,
482
+ };
483
+ try {
484
+ const llmProvider = createProvider(config);
485
+ const isAvailable = await llmProvider.isAvailable();
486
+ if (!isAvailable) {
487
+ res.end(JSON.stringify({
488
+ success: false,
489
+ error: provider === 'ollama'
490
+ ? 'Ollama is not running. Start it with: ollama serve'
491
+ : 'Invalid API key or provider not available'
492
+ }));
493
+ return;
494
+ }
495
+ // Get available models
496
+ const models = await llmProvider.listModels();
497
+ res.end(JSON.stringify({
498
+ success: true,
499
+ models: models.map(m => ({
500
+ id: m.id,
501
+ name: m.name,
502
+ contextLength: m.capabilities.contextLength,
503
+ supportsJsonMode: m.capabilities.supportsJsonMode,
504
+ }))
505
+ }));
506
+ }
507
+ catch (providerError) {
508
+ res.end(JSON.stringify({
509
+ success: false,
510
+ error: providerError instanceof Error ? providerError.message : 'Connection failed'
511
+ }));
512
+ }
513
+ }
514
+ catch (e) {
515
+ res.statusCode = 400;
516
+ res.end(JSON.stringify({ success: false, error: 'Invalid request' }));
517
+ }
518
+ });
519
+ return;
520
+ }
521
+ // Save LLM configuration
522
+ if (path === '/api/rules/llm/config' && req.method === 'PUT') {
523
+ let body = '';
524
+ req.on('data', chunk => body += chunk);
525
+ req.on('end', () => {
526
+ try {
527
+ const { provider, apiKey, model, baseUrl } = JSON.parse(body);
528
+ const config = loadRulesConfig();
529
+ config.llm = {
530
+ provider: provider || undefined,
531
+ apiKey: apiKey || undefined,
532
+ model: model || undefined,
533
+ baseUrl: baseUrl || undefined,
534
+ };
535
+ // If LLM is configured, enable useLLM
536
+ if (provider && (apiKey || provider === 'ollama')) {
537
+ config.analysis.useLLM = true;
538
+ }
539
+ saveRulesConfig(config);
540
+ logActivity('query', `LLM provider configured: ${provider}`);
541
+ // Return config without the API key for security
542
+ const safeConfig = { ...config };
543
+ if (safeConfig.llm.apiKey) {
544
+ safeConfig.llm.apiKey = '***configured***';
545
+ }
546
+ res.end(JSON.stringify({ success: true, config: safeConfig }));
547
+ }
548
+ catch (e) {
549
+ res.statusCode = 400;
550
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Invalid request' }));
551
+ }
552
+ });
553
+ return;
554
+ }
555
+ // Get LLM configuration (without exposing API key)
556
+ if (path === '/api/rules/llm/config' && req.method === 'GET') {
557
+ try {
558
+ const config = loadRulesConfig();
559
+ const safeConfig = {
560
+ provider: config.llm?.provider,
561
+ model: config.llm?.model,
562
+ baseUrl: config.llm?.baseUrl,
563
+ hasApiKey: !!config.llm?.apiKey,
564
+ };
565
+ res.end(JSON.stringify(safeConfig));
566
+ }
567
+ catch (e) {
568
+ res.statusCode = 500;
569
+ res.end(JSON.stringify({ error: 'Failed to load config' }));
570
+ }
571
+ return;
572
+ }
573
+ // Add a version check pattern
574
+ if (path === '/api/rules/config/version-checks' && req.method === 'POST') {
575
+ let body = '';
576
+ req.on('data', chunk => body += chunk);
577
+ req.on('end', () => {
578
+ try {
579
+ const newCheck = JSON.parse(body);
580
+ const config = loadRulesConfig();
581
+ // Validate the pattern
582
+ const validation = validatePattern(newCheck.pattern);
583
+ if (!validation.valid) {
584
+ res.statusCode = 400;
585
+ res.end(JSON.stringify({ error: `Invalid pattern: ${validation.error}` }));
586
+ return;
587
+ }
588
+ config.versionChecks.push({
589
+ ...newCheck,
590
+ enabled: newCheck.enabled ?? true,
591
+ });
592
+ saveRulesConfig(config);
593
+ res.end(JSON.stringify({ success: true, config }));
594
+ }
595
+ catch (e) {
596
+ res.statusCode = 400;
597
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Invalid request' }));
598
+ }
599
+ });
600
+ return;
601
+ }
602
+ // Add a deprecation pattern
603
+ if (path === '/api/rules/config/deprecation-patterns' && req.method === 'POST') {
604
+ let body = '';
605
+ req.on('data', chunk => body += chunk);
606
+ req.on('end', () => {
607
+ try {
608
+ const newPattern = JSON.parse(body);
609
+ const config = loadRulesConfig();
610
+ // Validate the pattern
611
+ const validation = validatePattern(newPattern.pattern);
612
+ if (!validation.valid) {
613
+ res.statusCode = 400;
614
+ res.end(JSON.stringify({ error: `Invalid pattern: ${validation.error}` }));
615
+ return;
616
+ }
617
+ config.deprecationPatterns.push({
618
+ ...newPattern,
619
+ enabled: newPattern.enabled ?? true,
620
+ });
621
+ saveRulesConfig(config);
622
+ res.end(JSON.stringify({ success: true, config }));
623
+ }
624
+ catch (e) {
625
+ res.statusCode = 400;
626
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Invalid request' }));
627
+ }
628
+ });
629
+ return;
630
+ }
631
+ // Delete a version check or deprecation pattern by index
632
+ const deletePatternMatch = path.match(/^\/api\/rules\/config\/(version-checks|deprecation-patterns)\/(\d+)$/);
633
+ if (deletePatternMatch && req.method === 'DELETE') {
634
+ const [, patternType, indexStr] = deletePatternMatch;
635
+ const index = parseInt(indexStr, 10);
636
+ try {
637
+ const config = loadRulesConfig();
638
+ const array = patternType === 'version-checks'
639
+ ? config.versionChecks
640
+ : config.deprecationPatterns;
641
+ if (index < 0 || index >= array.length) {
642
+ res.statusCode = 404;
643
+ res.end(JSON.stringify({ error: 'Pattern not found' }));
644
+ return;
645
+ }
646
+ array.splice(index, 1);
647
+ saveRulesConfig(config);
648
+ res.end(JSON.stringify({ success: true, config }));
649
+ }
650
+ catch (e) {
651
+ res.statusCode = 500;
652
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Failed to delete' }));
653
+ }
654
+ return;
655
+ }
656
+ // Toggle pattern enabled/disabled
657
+ const togglePatternMatch = path.match(/^\/api\/rules\/config\/(version-checks|deprecation-patterns)\/(\d+)\/toggle$/);
658
+ if (togglePatternMatch && req.method === 'POST') {
659
+ const [, patternType, indexStr] = togglePatternMatch;
660
+ const index = parseInt(indexStr, 10);
661
+ try {
662
+ const config = loadRulesConfig();
663
+ const array = patternType === 'version-checks'
664
+ ? config.versionChecks
665
+ : config.deprecationPatterns;
666
+ if (index < 0 || index >= array.length) {
667
+ res.statusCode = 404;
668
+ res.end(JSON.stringify({ error: 'Pattern not found' }));
669
+ return;
670
+ }
671
+ array[index].enabled = !array[index].enabled;
672
+ saveRulesConfig(config);
673
+ res.end(JSON.stringify({ success: true, enabled: array[index].enabled, config }));
674
+ }
675
+ catch (e) {
676
+ res.statusCode = 500;
677
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Failed to toggle' }));
678
+ }
679
+ return;
680
+ }
681
+ // Get system home directory
682
+ if (path === '/api/system/home' && req.method === 'GET') {
683
+ const home = process.env.HOME || process.env.USERPROFILE || '~';
684
+ res.end(JSON.stringify({ home }));
685
+ return;
686
+ }
687
+ // List directories (for folder browsing)
688
+ if (path === '/api/system/browse' && req.method === 'POST') {
689
+ let body = '';
690
+ req.on('data', chunk => body += chunk);
691
+ req.on('end', async () => {
692
+ try {
693
+ const { directory, showHidden = true } = JSON.parse(body);
694
+ const { readdirSync, statSync, existsSync } = await import('fs');
695
+ const { join, resolve, dirname } = await import('path');
696
+ // Expand ~ to home directory
697
+ const home = process.env.HOME || process.env.USERPROFILE || '';
698
+ let expandedDir = directory.replace(/^~/, home);
699
+ // Resolve to absolute path
700
+ expandedDir = resolve(expandedDir);
701
+ // Check if directory exists
702
+ if (!existsSync(expandedDir)) {
703
+ res.statusCode = 400;
704
+ res.end(JSON.stringify({ error: `Directory not found: ${expandedDir}` }));
705
+ return;
706
+ }
707
+ const entries = readdirSync(expandedDir, { withFileTypes: true });
708
+ const folders = entries
709
+ .filter(e => {
710
+ if (!e.isDirectory())
711
+ return false;
712
+ // Show important hidden folders like .cursor, .codex, .config
713
+ if (e.name.startsWith('.')) {
714
+ const importantHidden = ['.cursor', '.codex', '.config', '.local', '.npm', '.vscode', '.git'];
715
+ return showHidden && importantHidden.some(h => e.name === h || e.name.startsWith(h + '-'));
716
+ }
717
+ return true;
718
+ })
719
+ .map(e => ({
720
+ name: e.name,
721
+ path: join(expandedDir, e.name),
722
+ }))
723
+ .sort((a, b) => {
724
+ // Sort: hidden folders first, then alphabetically
725
+ const aHidden = a.name.startsWith('.');
726
+ const bHidden = b.name.startsWith('.');
727
+ if (aHidden && !bHidden)
728
+ return -1;
729
+ if (!aHidden && bHidden)
730
+ return 1;
731
+ return a.name.localeCompare(b.name);
732
+ })
733
+ .slice(0, 100); // Limit results
734
+ const parent = dirname(expandedDir);
735
+ res.end(JSON.stringify({
736
+ current: expandedDir,
737
+ folders,
738
+ parent: parent !== expandedDir ? parent : '', // Empty if at root
739
+ }));
740
+ }
741
+ catch (e) {
742
+ res.statusCode = 400;
743
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Failed to browse' }));
744
+ }
745
+ });
746
+ return;
747
+ }
748
+ // ==================== RULES OPTIMIZER ENDPOINTS ====================
749
+ // Analyze rules in a folder (step 1)
750
+ if (path === '/api/rules/analyze' && req.method === 'POST') {
751
+ let body = '';
752
+ req.on('data', chunk => body += chunk);
753
+ req.on('end', async () => {
754
+ try {
755
+ const { folder } = JSON.parse(body);
756
+ if (!folder) {
757
+ res.statusCode = 400;
758
+ res.end(JSON.stringify({ error: 'folder is required' }));
759
+ return;
760
+ }
761
+ const ragConfig = loadConfig();
762
+ const parser = new RulesParser();
763
+ const analyzer = getRulesAnalyzer(ragConfig);
764
+ const rules = parser.parseDirectory(folder);
765
+ const duplicates = await analyzer.findDuplicates(rules);
766
+ const conflicts = await analyzer.findConflicts(rules);
767
+ const outdated = analyzer.findOutdatedRules(rules);
768
+ res.end(JSON.stringify({
769
+ success: true,
770
+ folder,
771
+ stats: {
772
+ totalRules: rules.length,
773
+ duplicates: duplicates.length,
774
+ conflicts: conflicts.length,
775
+ outdated: outdated.length,
776
+ },
777
+ rules: rules.map((r) => ({
778
+ title: r.title,
779
+ path: r.sourceFile.path,
780
+ tokens: r.tokenCount,
781
+ tags: r.tags,
782
+ })),
783
+ duplicates: duplicates.map(d => ({
784
+ rule1: { title: d.rule1.title, path: d.rule1.sourceFile.path },
785
+ rule2: { title: d.rule2.title, path: d.rule2.sourceFile.path },
786
+ similarity: d.similarity,
787
+ matchType: d.matchType,
788
+ })),
789
+ conflicts,
790
+ outdated,
791
+ }));
792
+ }
793
+ catch (e) {
794
+ res.statusCode = 500;
795
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Analysis failed' }));
796
+ }
797
+ });
798
+ return;
799
+ }
800
+ // Auto-optimize rules (analyze + merge + cleanup in one step)
801
+ if (path === '/api/rules/auto-optimize' && req.method === 'POST') {
802
+ let body = '';
803
+ req.on('data', chunk => body += chunk);
804
+ req.on('end', async () => {
805
+ try {
806
+ const { folder, dryRun = true } = JSON.parse(body);
807
+ if (!folder) {
808
+ res.statusCode = 400;
809
+ res.end(JSON.stringify({ error: 'folder is required' }));
810
+ return;
811
+ }
812
+ const config = loadRulesConfig();
813
+ const useLLM = config.analysis.useLLM && config.llm.provider && config.llm.apiKey;
814
+ // If LLM is enabled but not configured, return error
815
+ if (config.analysis.useLLM && (!config.llm.provider || !config.llm.apiKey)) {
816
+ res.statusCode = 400;
817
+ res.end(JSON.stringify({
818
+ error: 'LLM is enabled but not configured. Either disable "Use LLM for Analysis" in Settings, or configure an LLM provider.'
819
+ }));
820
+ return;
821
+ }
822
+ const ragConfig = loadConfig();
823
+ const parser = new RulesParser();
824
+ const analyzer = getRulesAnalyzer(ragConfig);
825
+ // Step 1: Parse all rules
826
+ const rules = parser.parseDirectory(folder);
827
+ if (rules.length === 0) {
828
+ res.end(JSON.stringify({
829
+ success: true,
830
+ dryRun,
831
+ message: 'No rules found in the specified folder',
832
+ stats: { totalRules: 0, duplicates: 0, conflicts: 0, outdated: 0, merged: 0, deleted: 0 },
833
+ actions: [],
834
+ }));
835
+ return;
836
+ }
837
+ // Step 2: Find issues using pattern matching (always works, no LLM needed)
838
+ const duplicates = await analyzer.findDuplicates(rules);
839
+ const conflicts = await analyzer.findConflicts(rules);
840
+ const outdated = analyzer.findOutdatedRules(rules);
841
+ const actions = [];
842
+ let merged = 0;
843
+ let deleted = 0;
844
+ // Add outdated rule warnings
845
+ for (const issue of outdated) {
846
+ actions.push({
847
+ type: 'outdated',
848
+ path: issue.rule.sourceFile.path,
849
+ reason: issue.reason,
850
+ severity: 'warning',
851
+ });
852
+ }
853
+ // Add conflict warnings
854
+ for (const conflict of conflicts) {
855
+ actions.push({
856
+ type: 'warning',
857
+ path: conflict.rule1.sourceFile.path,
858
+ reason: `Conflicts with ${conflict.rule2.title}: ${conflict.description}`,
859
+ severity: 'error',
860
+ });
861
+ }
862
+ // Step 3: Handle duplicates
863
+ if (duplicates.length > 0) {
864
+ if (useLLM) {
865
+ // Use LLM to intelligently merge duplicates
866
+ const merger = getRulesMerger();
867
+ // Group duplicates into clusters for merging
868
+ const processed = new Set();
869
+ const clusters = [];
870
+ for (const dup of duplicates) {
871
+ const path1 = dup.rule1.sourceFile.path;
872
+ const path2 = dup.rule2.sourceFile.path;
873
+ if (processed.has(path1) && processed.has(path2))
874
+ continue;
875
+ let cluster = clusters.find(c => c.rules.some((r) => r.sourceFile.path === path1 || r.sourceFile.path === path2));
876
+ if (!cluster) {
877
+ cluster = { rules: [], similarity: dup.similarity };
878
+ clusters.push(cluster);
879
+ }
880
+ if (!cluster.rules.some((r) => r.sourceFile.path === path1)) {
881
+ const rule = rules.find((r) => r.sourceFile.path === path1);
882
+ if (rule)
883
+ cluster.rules.push(rule);
884
+ }
885
+ if (!cluster.rules.some((r) => r.sourceFile.path === path2)) {
886
+ const rule = rules.find((r) => r.sourceFile.path === path2);
887
+ if (rule)
888
+ cluster.rules.push(rule);
889
+ }
890
+ processed.add(path1);
891
+ processed.add(path2);
892
+ }
893
+ // Merge each cluster using LLM
894
+ for (const cluster of clusters) {
895
+ if (cluster.rules.length < 2)
896
+ continue;
897
+ try {
898
+ const mergeResult = await merger.mergeRules(cluster.rules, {
899
+ context: `These rules have ${Math.round(cluster.similarity * 100)}% similarity.`,
900
+ });
901
+ if (mergeResult.success && mergeResult.mergedContent) {
902
+ const keepPath = cluster.rules[0].sourceFile.path;
903
+ const deletePaths = cluster.rules.slice(1).map((r) => r.sourceFile.path);
904
+ actions.push({
905
+ type: 'merge',
906
+ path: keepPath,
907
+ reason: `Merged ${cluster.rules.length} similar rules (${Math.round(cluster.similarity * 100)}% similarity)`,
908
+ content: mergeResult.mergedContent,
909
+ });
910
+ for (const delPath of deletePaths) {
911
+ actions.push({
912
+ type: 'delete',
913
+ path: delPath,
914
+ reason: `Content merged into ${keepPath}`,
915
+ });
916
+ }
917
+ merged += cluster.rules.length;
918
+ deleted += deletePaths.length;
919
+ }
920
+ }
921
+ catch (mergeError) {
922
+ console.error(`Failed to merge cluster:`, mergeError);
923
+ }
924
+ }
925
+ }
926
+ else {
927
+ // Without LLM: just report duplicates as warnings
928
+ for (const dup of duplicates) {
929
+ actions.push({
930
+ type: 'warning',
931
+ path: dup.rule1.sourceFile.path,
932
+ reason: `Duplicate of "${dup.rule2.title}" (${Math.round(dup.similarity * 100)}% similar) - enable LLM to auto-merge`,
933
+ severity: 'warning',
934
+ });
935
+ }
936
+ }
937
+ }
938
+ // Step 4: Apply changes if not dry run and LLM was used for merging
939
+ if (!dryRun && useLLM && actions.some(a => a.type === 'merge' || a.type === 'delete')) {
940
+ const backupFolder = join(folder, '.rules-backup-' + Date.now());
941
+ mkdirSync(backupFolder, { recursive: true });
942
+ for (const action of actions) {
943
+ try {
944
+ if (action.type === 'merge' && action.content) {
945
+ const filename = action.path.split('/').pop() || 'rule';
946
+ copyFileSync(action.path, join(backupFolder, filename));
947
+ writeFileSync(action.path, action.content, 'utf-8');
948
+ }
949
+ else if (action.type === 'delete') {
950
+ const filename = action.path.split('/').pop() || 'rule';
951
+ copyFileSync(action.path, join(backupFolder, filename));
952
+ unlinkSync(action.path);
953
+ }
954
+ }
955
+ catch (fileError) {
956
+ console.error(`Failed to apply action for ${action.path}:`, fileError);
957
+ }
958
+ }
959
+ }
960
+ const totalIssues = duplicates.length + conflicts.length + outdated.length;
961
+ let message;
962
+ if (totalIssues === 0) {
963
+ message = `Analyzed ${rules.length} rules - no issues found!`;
964
+ }
965
+ else if (useLLM && !dryRun) {
966
+ message = `Applied ${actions.filter(a => a.type === 'merge' || a.type === 'delete').length} optimizations`;
967
+ }
968
+ else if (useLLM && dryRun) {
969
+ message = `Found ${totalIssues} issues (dry run - no changes made)`;
970
+ }
971
+ else {
972
+ message = `Found ${totalIssues} issues. Enable LLM to auto-merge duplicates.`;
973
+ }
974
+ res.end(JSON.stringify({
975
+ success: true,
976
+ dryRun,
977
+ usedLLM: useLLM,
978
+ message,
979
+ stats: {
980
+ totalRules: rules.length,
981
+ duplicates: duplicates.length,
982
+ conflicts: conflicts.length,
983
+ outdated: outdated.length,
984
+ merged,
985
+ deleted,
986
+ },
987
+ actions: actions.map(a => ({
988
+ type: a.type,
989
+ path: a.path,
990
+ reason: a.reason,
991
+ severity: a.severity,
992
+ hasContent: !!a.content,
993
+ })),
994
+ }));
995
+ }
996
+ catch (e) {
997
+ res.statusCode = 500;
998
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Auto-optimize failed' }));
999
+ }
1000
+ });
1001
+ return;
1002
+ }
1003
+ // Preview a merge (without applying)
1004
+ if (path === '/api/rules/preview-merge' && req.method === 'POST') {
1005
+ let body = '';
1006
+ req.on('data', chunk => body += chunk);
1007
+ req.on('end', async () => {
1008
+ try {
1009
+ const { paths } = JSON.parse(body);
1010
+ if (!paths || !Array.isArray(paths) || paths.length < 2) {
1011
+ res.statusCode = 400;
1012
+ res.end(JSON.stringify({ error: 'At least 2 rule paths are required' }));
1013
+ return;
1014
+ }
1015
+ const config = loadRulesConfig();
1016
+ if (!config.llm.provider || !config.llm.apiKey) {
1017
+ res.statusCode = 400;
1018
+ res.end(JSON.stringify({ error: 'LLM not configured' }));
1019
+ return;
1020
+ }
1021
+ const parser = new RulesParser();
1022
+ const merger = getRulesMerger();
1023
+ // Parse the specific rules
1024
+ const rules = [];
1025
+ for (const p of paths) {
1026
+ const ruleFile = parser.readRuleFile(p);
1027
+ if (ruleFile) {
1028
+ const parsed = parser.parseFile(ruleFile);
1029
+ rules.push(...parsed);
1030
+ }
1031
+ }
1032
+ if (rules.length < 2) {
1033
+ res.statusCode = 400;
1034
+ res.end(JSON.stringify({ error: 'Could not parse enough rules' }));
1035
+ return;
1036
+ }
1037
+ const result = await merger.mergeRules(rules);
1038
+ res.end(JSON.stringify({
1039
+ success: result.success,
1040
+ mergedContent: result.mergedContent,
1041
+ mergedTitle: result.mergedTitle,
1042
+ originalRules: rules.map((r) => ({ title: r.title, path: r.sourceFile.path, tokens: r.tokenCount })),
1043
+ }));
1044
+ }
1045
+ catch (e) {
1046
+ res.statusCode = 500;
1047
+ res.end(JSON.stringify({ error: e instanceof Error ? e.message : 'Preview failed' }));
1048
+ }
1049
+ });
1050
+ return;
1051
+ }
1052
+ // ==================== END RULES OPTIMIZER ENDPOINTS ====================
222
1053
  res.statusCode = 404;
223
1054
  res.end(JSON.stringify({ error: 'Not found' }));
224
1055
  }
@@ -368,6 +1199,8 @@ function serveStatic(res, filePath) {
368
1199
  res.end(readFileSync(fullPath));
369
1200
  }
370
1201
  export function startDashboard(port = 3333) {
1202
+ // Register core tools when dashboard starts
1203
+ registerCoreTools();
371
1204
  const server = createServer(async (req, res) => {
372
1205
  const url = new URL(req.url || '/', `http://localhost:${port}`);
373
1206
  const path = url.pathname;