cntx-ui 2.0.13 → 2.0.15

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 (44) hide show
  1. package/bin/cntx-ui.js +137 -55
  2. package/lib/agent-runtime.js +1480 -0
  3. package/lib/agent-tools.js +368 -0
  4. package/lib/api-router.js +978 -0
  5. package/lib/bundle-manager.js +471 -0
  6. package/lib/configuration-manager.js +725 -0
  7. package/lib/file-system-manager.js +472 -0
  8. package/lib/heuristics-manager.js +425 -0
  9. package/lib/mcp-server.js +1054 -1
  10. package/lib/semantic-splitter.js +7 -14
  11. package/lib/simple-vector-store.js +329 -0
  12. package/lib/websocket-manager.js +470 -0
  13. package/package.json +10 -3
  14. package/server.js +662 -1933
  15. package/templates/activities/README.md +67 -0
  16. package/templates/activities/activities/create-project-bundles/README.md +83 -0
  17. package/templates/activities/activities/create-project-bundles/notes.md +102 -0
  18. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  19. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  20. package/templates/activities/activities.json +219 -0
  21. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  22. package/templates/activities/lib/create-activity.mdc +63 -0
  23. package/templates/activities/lib/generate-tasks.mdc +64 -0
  24. package/templates/activities/lib/process-task-list.mdc +52 -0
  25. package/templates/agent-config.yaml +78 -0
  26. package/templates/agent-instructions.md +218 -0
  27. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  28. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  29. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  30. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  31. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  32. package/templates/agent-rules/core/response-formatting.md +120 -0
  33. package/templates/agent-rules/project-specific/architecture.md +145 -0
  34. package/templates/config.json +76 -0
  35. package/templates/hidden-files.json +14 -0
  36. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
  37. package/web/dist/assets/index-dF3qg-y_.js +2486 -0
  38. package/web/dist/assets/index-h5FGSg_P.css +1 -0
  39. package/web/dist/cntx-ui.svg +18 -0
  40. package/web/dist/index.html +25 -8
  41. package/lib/semantic-integration.js +0 -441
  42. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  43. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  44. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,978 @@
1
+ /**
2
+ * API Router for cntx-ui
3
+ * Handles all HTTP API endpoints and request routing
4
+ */
5
+
6
+ import { parse } from 'url';
7
+
8
+ export default class APIRouter {
9
+ constructor(configManager, bundleManager, fileSystemManager, semanticAnalysisManager, vectorStore, activityManager) {
10
+ this.configManager = configManager;
11
+ this.bundleManager = bundleManager;
12
+ this.fileSystemManager = fileSystemManager;
13
+ this.semanticAnalysisManager = semanticAnalysisManager;
14
+ this.vectorStore = vectorStore;
15
+ this.activityManager = activityManager;
16
+ }
17
+
18
+ async handleRequest(req, res, url) {
19
+ const method = req.method;
20
+ const pathname = url.pathname;
21
+
22
+ try {
23
+ // Route to appropriate handler
24
+ if (pathname === '/api/bundles' && method === 'GET') {
25
+ return await this.handleGetBundles(req, res, url);
26
+ }
27
+
28
+ if (pathname === '/api/bundles' && method === 'POST') {
29
+ return await this.handlePostBundles(req, res);
30
+ }
31
+
32
+ if (pathname.startsWith('/api/bundles/') && method === 'GET') {
33
+ const bundleName = pathname.split('/')[3];
34
+ return await this.handleGetBundle(req, res, bundleName);
35
+ }
36
+
37
+ if (pathname.startsWith('/api/regenerate/') && (method === 'GET' || method === 'POST')) {
38
+ const bundleName = pathname.split('/')[3];
39
+ return await this.handleRegenerateBundle(req, res, bundleName);
40
+ }
41
+
42
+ if (pathname === '/api/bundles-from-chunk' && method === 'POST') {
43
+ return await this.handleCreateBundlesFromChunk(req, res);
44
+ }
45
+
46
+ if (pathname === '/api/bundle-visibility-stats' && method === 'GET') {
47
+ return await this.handleBundleVisibilityStats(req, res);
48
+ }
49
+
50
+ if (pathname.startsWith('/api/bundle-categories/') && method === 'GET') {
51
+ const bundleName = pathname.split('/')[3];
52
+ return await this.handleBundleCategories(req, res, bundleName);
53
+ }
54
+
55
+ if (pathname === '/api/config' && method === 'GET') {
56
+ return await this.handleGetConfig(req, res);
57
+ }
58
+
59
+ if (pathname === '/api/config' && method === 'POST') {
60
+ return await this.handlePostConfig(req, res);
61
+ }
62
+
63
+ if (pathname === '/api/files' && method === 'GET') {
64
+ return await this.handleGetFiles(req, res);
65
+ }
66
+
67
+ if (pathname === '/api/cursor-rules' && method === 'GET') {
68
+ return await this.handleGetCursorRules(req, res);
69
+ }
70
+
71
+ if (pathname === '/api/cursor-rules' && method === 'POST') {
72
+ return await this.handlePostCursorRules(req, res);
73
+ }
74
+
75
+ if (pathname === '/api/cursor-rules/templates' && method === 'GET') {
76
+ return await this.handleGetCursorRulesTemplates(req, res);
77
+ }
78
+
79
+ if (pathname === '/api/claude-md' && method === 'GET') {
80
+ return await this.handleGetClaudeMd(req, res);
81
+ }
82
+
83
+ if (pathname === '/api/claude-md' && method === 'POST') {
84
+ return await this.handlePostClaudeMd(req, res);
85
+ }
86
+
87
+ if (pathname === '/api/heuristics/config' && method === 'GET') {
88
+ return await this.handleGetHeuristicsConfig(req, res);
89
+ }
90
+
91
+ if (pathname === '/api/heuristics/config' && method === 'PUT') {
92
+ return await this.handlePutHeuristicsConfig(req, res);
93
+ }
94
+
95
+ if (pathname === '/api/test-pattern' && method === 'POST') {
96
+ return await this.handleTestPattern(req, res);
97
+ }
98
+
99
+ if (pathname === '/api/hidden-files' && method === 'GET') {
100
+ return await this.handleGetHiddenFiles(req, res);
101
+ }
102
+
103
+ if (pathname === '/api/hidden-files' && method === 'POST') {
104
+ return await this.handlePostHiddenFiles(req, res);
105
+ }
106
+
107
+ if (pathname === '/api/files-with-visibility' && method === 'GET') {
108
+ return await this.handleGetFilesWithVisibility(req, res, url);
109
+ }
110
+
111
+ if (pathname === '/api/ignore-patterns' && method === 'GET') {
112
+ return await this.handleGetIgnorePatterns(req, res);
113
+ }
114
+
115
+ if (pathname === '/api/ignore-patterns' && method === 'POST') {
116
+ return await this.handlePostIgnorePatterns(req, res);
117
+ }
118
+
119
+ if (pathname === '/api/reset-hidden-files' && method === 'POST') {
120
+ return await this.handleResetHiddenFiles(req, res);
121
+ }
122
+
123
+ if (pathname === '/api/semantic-chunks' && method === 'GET') {
124
+ return await this.handleGetSemanticChunks(req, res, url);
125
+ }
126
+
127
+ if (pathname === '/api/semantic-chunks/export' && method === 'POST') {
128
+ return await this.handleExportSemanticChunk(req, res);
129
+ }
130
+
131
+ if (pathname === '/api/mcp-status' && method === 'GET') {
132
+ return await this.handleGetMcpStatus(req, res);
133
+ }
134
+
135
+ if (pathname === '/api/cntxignore' && method === 'GET') {
136
+ return await this.handleGetCntxignore(req, res);
137
+ }
138
+
139
+ if (pathname === '/api/cntxignore' && method === 'POST') {
140
+ return await this.handlePostCntxignore(req, res);
141
+ }
142
+
143
+ if (pathname === '/api/gitignore' && method === 'GET') {
144
+ return await this.handleGetGitignore(req, res);
145
+ }
146
+
147
+ if (pathname === '/api/gitignore' && method === 'POST') {
148
+ return await this.handlePostGitignore(req, res);
149
+ }
150
+
151
+ if (pathname === '/api/status' && method === 'GET') {
152
+ return await this.handleGetStatus(req, res);
153
+ }
154
+
155
+ if (pathname === '/api/vector-db/status' && method === 'GET') {
156
+ return await this.handleGetVectorDbStatus(req, res);
157
+ }
158
+
159
+ if (pathname === '/api/vector-db/rebuild' && method === 'POST') {
160
+ return await this.handlePostVectorDbRebuild(req, res);
161
+ }
162
+
163
+ if (pathname === '/api/vector-db/search' && method === 'POST') {
164
+ return await this.handlePostVectorDbSearch(req, res);
165
+ }
166
+
167
+ if (pathname === '/api/vector-db/search-by-type' && method === 'POST') {
168
+ return await this.handlePostVectorDbSearchByType(req, res);
169
+ }
170
+
171
+ if (pathname === '/api/vector-db/search-by-domain' && method === 'POST') {
172
+ return await this.handlePostVectorDbSearchByDomain(req, res);
173
+ }
174
+
175
+ if (pathname === '/api/activities' && method === 'GET') {
176
+ return await this.handleGetActivities(req, res);
177
+ }
178
+
179
+ if (pathname.startsWith('/api/activities/') && pathname.endsWith('/execute') && method === 'POST') {
180
+ const activityId = pathname.split('/')[3];
181
+ return await this.handlePostActivityExecute(req, res, activityId);
182
+ }
183
+
184
+ if (pathname.startsWith('/api/activities/') && pathname.endsWith('/stop') && method === 'POST') {
185
+ const activityId = pathname.split('/')[3];
186
+ return await this.handlePostActivityStop(req, res, activityId);
187
+ }
188
+
189
+ if (pathname === '/api/open-file' && method === 'POST') {
190
+ return await this.handleOpenFile(req, res);
191
+ }
192
+
193
+ // If no route matches, return 404
194
+ res.writeHead(404, { 'Content-Type': 'application/json' });
195
+ res.end(JSON.stringify({ error: 'API endpoint not found' }));
196
+
197
+ } catch (error) {
198
+ console.error('API Error:', error);
199
+ res.writeHead(500, { 'Content-Type': 'application/json' });
200
+ res.end(JSON.stringify({ error: error.message }));
201
+ }
202
+ }
203
+
204
+ // === Bundle Operations ===
205
+
206
+ async handleGetBundles(req, res, url) {
207
+ const bundles = this.configManager.getBundles();
208
+ const bundleData = Array.from(bundles.entries()).map(([name, bundle]) => ({
209
+ name,
210
+ changed: bundle.changed,
211
+ fileCount: bundle.files.length,
212
+ contentPreview: bundle.content.substring(0, 200) + (bundle.content.length > 200 ? '...' : ''),
213
+ files: bundle.files,
214
+ patterns: bundle.patterns,
215
+ size: bundle.size,
216
+ generated: bundle.generated
217
+ }));
218
+
219
+ res.writeHead(200, { 'Content-Type': 'application/json' });
220
+ res.end(JSON.stringify(bundleData));
221
+ }
222
+
223
+ async handlePostBundles(req, res) {
224
+ try {
225
+ const body = JSON.parse(await this.getRequestBody(req));
226
+ const { action, bundleName, fileName, fileNames } = body;
227
+
228
+ if (!action || !bundleName) {
229
+ return this.sendError(res, 400, 'Missing required fields: action and bundleName');
230
+ }
231
+
232
+ const bundles = this.configManager.getBundles();
233
+ const bundle = bundles.get(bundleName);
234
+
235
+ if (!bundle) {
236
+ return this.sendError(res, 404, `Bundle "${bundleName}" not found`);
237
+ }
238
+
239
+ switch (action) {
240
+ case 'add-file':
241
+ if (!fileName) {
242
+ return this.sendError(res, 400, 'Missing fileName for add-file action');
243
+ }
244
+ // Ensure we're working with relative paths
245
+ const relativeAddFileName = fileName.startsWith('/') ?
246
+ require('path').relative(this.configManager.CWD, fileName) : fileName;
247
+ if (!bundle.files.includes(relativeAddFileName)) {
248
+ bundle.files.push(relativeAddFileName);
249
+ bundle.changed = true;
250
+ this.configManager.saveBundleStates();
251
+ }
252
+ break;
253
+
254
+ case 'remove-file':
255
+ if (!fileName) {
256
+ return this.sendError(res, 400, 'Missing fileName for remove-file action');
257
+ }
258
+ // Ensure we're working with relative paths for both search and removal
259
+ const relativeRemoveFileName = fileName.startsWith('/') ?
260
+ require('path').relative(this.configManager.CWD, fileName) : fileName;
261
+ const removeIndex = bundle.files.indexOf(relativeRemoveFileName);
262
+ if (removeIndex > -1) {
263
+ bundle.files.splice(removeIndex, 1);
264
+ bundle.changed = true;
265
+ this.configManager.saveBundleStates();
266
+ }
267
+ break;
268
+
269
+ case 'bulk-add-files':
270
+ if (!fileNames || !Array.isArray(fileNames)) {
271
+ return this.sendError(res, 400, 'Missing fileNames array for bulk-add-files action');
272
+ }
273
+ fileNames.forEach(file => {
274
+ // Ensure we're working with relative paths
275
+ const relativeFile = file.startsWith('/') ?
276
+ require('path').relative(this.configManager.CWD, file) : file;
277
+ if (!bundle.files.includes(relativeFile)) {
278
+ bundle.files.push(relativeFile);
279
+ }
280
+ });
281
+ bundle.changed = true;
282
+ this.configManager.saveBundleStates();
283
+ break;
284
+
285
+ case 'bulk-remove-files':
286
+ if (!fileNames || !Array.isArray(fileNames)) {
287
+ return this.sendError(res, 400, 'Missing fileNames array for bulk-remove-files action');
288
+ }
289
+ fileNames.forEach(file => {
290
+ // Ensure we're working with relative paths
291
+ const relativeFile = file.startsWith('/') ?
292
+ require('path').relative(this.configManager.CWD, file) : file;
293
+ const index = bundle.files.indexOf(relativeFile);
294
+ if (index > -1) {
295
+ bundle.files.splice(index, 1);
296
+ }
297
+ });
298
+ bundle.changed = true;
299
+ this.configManager.saveBundleStates();
300
+ break;
301
+
302
+ default:
303
+ return this.sendError(res, 400, `Unknown action: ${action}`);
304
+ }
305
+
306
+ this.sendResponse(res, 200, { success: true, message: `Action ${action} completed successfully` });
307
+ } catch (error) {
308
+ console.error('handlePostBundles error:', error);
309
+ this.sendError(res, 500, error.message);
310
+ }
311
+ }
312
+
313
+ async handleGetBundle(req, res, bundleName) {
314
+ const content = this.bundleManager.getBundleContent(bundleName);
315
+ if (!content) {
316
+ res.writeHead(404, { 'Content-Type': 'application/json' });
317
+ res.end(JSON.stringify({ error: 'Bundle not found' }));
318
+ return;
319
+ }
320
+
321
+ res.writeHead(200, { 'Content-Type': 'application/xml' });
322
+ res.end(content);
323
+ }
324
+
325
+ async handleRegenerateBundle(req, res, bundleName) {
326
+ try {
327
+ await this.bundleManager.regenerateBundle(bundleName);
328
+ this.sendResponse(res, 200, { success: true, message: `Bundle ${bundleName} regenerated` });
329
+ } catch (error) {
330
+ this.sendError(res, 500, error.message);
331
+ }
332
+ }
333
+
334
+ async handleCreateBundlesFromChunk(req, res) {
335
+ const body = await this.getRequestBody(req);
336
+ const { chunkName, files } = JSON.parse(body);
337
+
338
+ const bundleName = this.configManager.createBundleFromChunk(chunkName, files);
339
+ await this.bundleManager.regenerateBundle(bundleName);
340
+
341
+ res.writeHead(200, { 'Content-Type': 'application/json' });
342
+ res.end(JSON.stringify({ success: true, bundleName }));
343
+ }
344
+
345
+ async handleBundleVisibilityStats(req, res) {
346
+ const bundles = this.configManager.getBundles();
347
+ const stats = {};
348
+
349
+ for (const [bundleName] of bundles) {
350
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
351
+ stats[bundleName] = {
352
+ total: files.length,
353
+ included: files.filter(f => f.included).length,
354
+ hidden: files.filter(f => f.hidden).length,
355
+ matching: files.filter(f => f.matchesPattern).length
356
+ };
357
+ }
358
+
359
+ res.writeHead(200, { 'Content-Type': 'application/json' });
360
+ res.end(JSON.stringify(stats));
361
+ }
362
+
363
+ async handleBundleCategories(req, res, bundleName) {
364
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
365
+ const includedFiles = files.filter(f => f.included).map(f => f.fullPath);
366
+ const categories = this.fileSystemManager.categorizeFiles(includedFiles);
367
+
368
+ res.writeHead(200, { 'Content-Type': 'application/json' });
369
+ res.end(JSON.stringify(categories));
370
+ }
371
+
372
+ // === Configuration Endpoints ===
373
+
374
+ async handleGetConfig(req, res) {
375
+ const bundles = this.configManager.getBundles();
376
+ const config = {
377
+ bundles: {},
378
+ editor: this.configManager.getEditor()
379
+ };
380
+
381
+ bundles.forEach((bundle, name) => {
382
+ config.bundles[name] = bundle.patterns;
383
+ });
384
+
385
+ res.writeHead(200, { 'Content-Type': 'application/json' });
386
+ res.end(JSON.stringify(config));
387
+ }
388
+
389
+ async handlePostConfig(req, res) {
390
+ const body = await this.getRequestBody(req);
391
+ const config = JSON.parse(body);
392
+
393
+ this.configManager.saveConfig(config);
394
+ this.configManager.loadConfig();
395
+ await this.bundleManager.generateAllBundles();
396
+
397
+ res.writeHead(200, { 'Content-Type': 'application/json' });
398
+ res.end(JSON.stringify({ success: true }));
399
+ }
400
+
401
+ async handleGetCursorRules(req, res) {
402
+ const content = this.configManager.loadCursorRules();
403
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
404
+ res.end(content);
405
+ }
406
+
407
+ async handlePostCursorRules(req, res) {
408
+ const body = await this.getRequestBody(req);
409
+ const { content } = JSON.parse(body);
410
+
411
+ this.configManager.saveCursorRules(content);
412
+ res.writeHead(200, { 'Content-Type': 'application/json' });
413
+ res.end(JSON.stringify({ success: true }));
414
+ }
415
+
416
+ async handleGetCursorRulesTemplates(req, res) {
417
+ const templates = {
418
+ react: this.configManager.generateCursorRulesTemplate({ projectType: 'react', name: 'React Project' }),
419
+ vue: this.configManager.generateCursorRulesTemplate({ projectType: 'vue', name: 'Vue Project' }),
420
+ angular: this.configManager.generateCursorRulesTemplate({ projectType: 'angular', name: 'Angular Project' }),
421
+ 'node-backend': this.configManager.generateCursorRulesTemplate({ projectType: 'node-backend', name: 'Node.js Backend' }),
422
+ javascript: this.configManager.generateCursorRulesTemplate({ projectType: 'javascript', name: 'JavaScript Project' })
423
+ };
424
+
425
+ res.writeHead(200, { 'Content-Type': 'application/json' });
426
+ res.end(JSON.stringify(templates));
427
+ }
428
+
429
+ async handleGetClaudeMd(req, res) {
430
+ const content = this.configManager.loadClaudeMd();
431
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
432
+ res.end(content);
433
+ }
434
+
435
+ async handlePostClaudeMd(req, res) {
436
+ const body = await this.getRequestBody(req);
437
+ const { content } = JSON.parse(body);
438
+
439
+ this.configManager.saveClaudeMd(content);
440
+ res.writeHead(200, { 'Content-Type': 'application/json' });
441
+ res.end(JSON.stringify({ success: true }));
442
+ }
443
+
444
+ async handleGetHeuristicsConfig(req, res) {
445
+ const config = this.configManager.loadHeuristicsConfig();
446
+ res.writeHead(200, { 'Content-Type': 'application/json' });
447
+ res.end(JSON.stringify(config));
448
+ }
449
+
450
+ async handlePutHeuristicsConfig(req, res) {
451
+ const body = await this.getRequestBody(req);
452
+ const config = JSON.parse(body);
453
+
454
+ this.configManager.saveHeuristicsConfig(config);
455
+ res.writeHead(200, { 'Content-Type': 'application/json' });
456
+ res.end(JSON.stringify({ success: true }));
457
+ }
458
+
459
+ // === File Operations ===
460
+
461
+ async handleGetFiles(req, res) {
462
+ const fileTree = this.fileSystemManager.getFileTree();
463
+ res.writeHead(200, { 'Content-Type': 'application/json' });
464
+ res.end(JSON.stringify(fileTree));
465
+ }
466
+
467
+ async handleGetFilesWithVisibility(req, res, url) {
468
+ const bundleName = url.searchParams.get('bundle');
469
+ const files = this.bundleManager.getFileListWithVisibility(bundleName);
470
+
471
+ res.writeHead(200, { 'Content-Type': 'application/json' });
472
+ res.end(JSON.stringify(files));
473
+ }
474
+
475
+ async handleGetHiddenFiles(req, res) {
476
+ const hiddenConfig = this.configManager.getHiddenFilesConfig();
477
+ const stats = {
478
+ globalHidden: hiddenConfig.globalHidden.length,
479
+ bundleSpecificTotal: Object.values(hiddenConfig.bundleSpecific).reduce((sum, arr) => sum + arr.length, 0),
480
+ userIgnorePatterns: hiddenConfig.userIgnorePatterns.length,
481
+ disabledSystemPatterns: hiddenConfig.disabledSystemPatterns.length,
482
+ config: hiddenConfig
483
+ };
484
+
485
+ res.writeHead(200, { 'Content-Type': 'application/json' });
486
+ res.end(JSON.stringify(stats));
487
+ }
488
+
489
+ async handlePostHiddenFiles(req, res) {
490
+ const body = await this.getRequestBody(req);
491
+ const data = JSON.parse(body);
492
+
493
+ if (data.action === 'toggle') {
494
+ this.configManager.toggleFileVisibility(data.filePath, data.bundleName, data.forceHide);
495
+ } else if (data.action === 'bulk-toggle') {
496
+ this.configManager.bulkToggleFileVisibility(data.filePaths, data.bundleName, data.forceHide);
497
+ }
498
+
499
+ res.writeHead(200, { 'Content-Type': 'application/json' });
500
+ res.end(JSON.stringify({ success: true }));
501
+ }
502
+
503
+ async handleGetIgnorePatterns(req, res) {
504
+ const patterns = {
505
+ system: this.fileSystemManager.ignorePatterns.filter(p => !this.configManager.getHiddenFilesConfig().userIgnorePatterns.some(up => up.pattern === p)),
506
+ user: this.configManager.getHiddenFilesConfig().userIgnorePatterns,
507
+ disabled: this.configManager.getHiddenFilesConfig().disabledSystemPatterns
508
+ };
509
+
510
+ res.writeHead(200, { 'Content-Type': 'application/json' });
511
+ res.end(JSON.stringify(patterns));
512
+ }
513
+
514
+ async handlePostIgnorePatterns(req, res) {
515
+ const body = await this.getRequestBody(req);
516
+ const data = JSON.parse(body);
517
+
518
+ if (data.action === 'add') {
519
+ this.configManager.addUserIgnorePattern(data.pattern);
520
+ } else if (data.action === 'remove') {
521
+ this.configManager.removeUserIgnorePattern(data.pattern);
522
+ } else if (data.action === 'toggle-system') {
523
+ this.configManager.toggleSystemIgnorePattern(data.pattern);
524
+ }
525
+
526
+ // Reload patterns and regenerate bundles
527
+ this.configManager.loadIgnorePatterns();
528
+ this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
529
+ await this.bundleManager.generateAllBundles();
530
+
531
+ res.writeHead(200, { 'Content-Type': 'application/json' });
532
+ res.end(JSON.stringify({ success: true }));
533
+ }
534
+
535
+ async handleResetHiddenFiles(req, res) {
536
+ const body = await this.getRequestBody(req);
537
+ const { scope, bundleName } = JSON.parse(body);
538
+
539
+ const hiddenConfig = this.configManager.getHiddenFilesConfig();
540
+
541
+ if (scope === 'global') {
542
+ hiddenConfig.globalHidden = [];
543
+ } else if (scope === 'bundle' && bundleName) {
544
+ delete hiddenConfig.bundleSpecific[bundleName];
545
+ } else if (scope === 'all') {
546
+ hiddenConfig.globalHidden = [];
547
+ hiddenConfig.bundleSpecific = {};
548
+ }
549
+
550
+ this.configManager.saveHiddenFilesConfig();
551
+ await this.bundleManager.generateAllBundles();
552
+
553
+ res.writeHead(200, { 'Content-Type': 'application/json' });
554
+ res.end(JSON.stringify({ success: true }));
555
+ }
556
+
557
+ async handleGetCntxignore(req, res) {
558
+ try {
559
+ const content = this.configManager.loadCntxignore();
560
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
561
+ res.end(content);
562
+ } catch (error) {
563
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
564
+ res.end('');
565
+ }
566
+ }
567
+
568
+ async handlePostCntxignore(req, res) {
569
+ const body = await this.getRequestBody(req);
570
+ const { content } = JSON.parse(body);
571
+
572
+ // Save content and reload patterns
573
+ this.configManager.saveIgnoreFile ? this.configManager.saveIgnoreFile(content) : null;
574
+ this.configManager.loadIgnorePatterns();
575
+ this.fileSystemManager.setIgnorePatterns(this.configManager.getIgnorePatterns());
576
+
577
+ res.writeHead(200, { 'Content-Type': 'application/json' });
578
+ res.end(JSON.stringify({ success: true }));
579
+ }
580
+
581
+ async handleGetGitignore(req, res) {
582
+ try {
583
+ const content = this.configManager.loadGitignore();
584
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
585
+ res.end(content);
586
+ } catch (error) {
587
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
588
+ res.end('');
589
+ }
590
+ }
591
+
592
+ async handlePostGitignore(req, res) {
593
+ const body = await this.getRequestBody(req);
594
+ const { content } = JSON.parse(body);
595
+
596
+ this.configManager.saveGitignore(content);
597
+
598
+ res.writeHead(200, { 'Content-Type': 'application/json' });
599
+ res.end(JSON.stringify({ success: true }));
600
+ }
601
+
602
+ // === Semantic Analysis ===
603
+
604
+ async handleGetSemanticChunks(req, res, url) {
605
+ const forceRefresh = url.searchParams.get('refresh') === 'true';
606
+
607
+ try {
608
+ const analysis = forceRefresh
609
+ ? await this.semanticAnalysisManager.refreshSemanticAnalysis()
610
+ : await this.semanticAnalysisManager.getSemanticAnalysis();
611
+
612
+ const rawChunks = analysis?.chunks || [];
613
+ // console.log('📊 Raw chunks sample:', rawChunks[0] ? {
614
+ // name: rawChunks[0].name,
615
+ // type: rawChunks[0].type,
616
+ // subtype: rawChunks[0].subtype,
617
+ // purpose: rawChunks[0].purpose,
618
+ // hasCode: !!rawChunks[0].code
619
+ // } : 'No chunks');
620
+
621
+ // Transform chunks to match VectorVisualization expectations
622
+ const chunks = rawChunks.map(chunk => ({
623
+ id: chunk.name || chunk.id || `chunk-${Math.random()}`,
624
+ name: chunk.name,
625
+ code: chunk.code,
626
+ semanticType: chunk.subtype || chunk.type || 'unknown',
627
+ businessDomain: chunk.tags || [],
628
+ technicalPatterns: chunk.tags || [],
629
+ purpose: chunk.purpose || '',
630
+ filePath: chunk.filePath,
631
+ files: chunk.filePath ? [chunk.filePath] : [],
632
+ size: chunk.size || 0,
633
+ complexity: chunk.complexity || 0,
634
+ startLine: chunk.startLine,
635
+ isExported: chunk.isExported,
636
+ isAsync: chunk.isAsync,
637
+ bundles: chunk.bundles || [],
638
+ embedding: chunk.embedding,
639
+ // Also include nested metadata format that VectorVisualization expects
640
+ metadata: {
641
+ content: chunk.code || '',
642
+ semanticType: chunk.subtype || chunk.type || 'unknown',
643
+ businessDomain: chunk.tags || [],
644
+ technicalPatterns: chunk.tags || [],
645
+ purpose: chunk.purpose || '',
646
+ files: chunk.filePath ? [chunk.filePath] : [],
647
+ size: chunk.size || 0,
648
+ complexity: chunk.complexity || 0
649
+ }
650
+ }));
651
+
652
+ // console.log('📊 Transformed chunks sample:', chunks[0] ? {
653
+ // id: chunks[0].id,
654
+ // semanticType: chunks[0].semanticType,
655
+ // hasMetadata: !!chunks[0].metadata
656
+ // } : 'No chunks');
657
+
658
+ this.sendResponse(res, 200, {
659
+ summary: {
660
+ totalFiles: analysis?.summary?.totalFiles || analysis?.fileCount || 0,
661
+ totalFunctions: rawChunks.filter(c => c.type === 'function_chunk').length,
662
+ totalChunks: rawChunks.length,
663
+ averageChunkSize: rawChunks.length > 0 ? Math.round(rawChunks.reduce((sum, c) => sum + (c.size || 0), 0) / rawChunks.length) : 0
664
+ },
665
+ chunks: chunks,
666
+ lastSemanticAnalysis: this.semanticAnalysisManager.lastSemanticAnalysis
667
+ });
668
+ } catch (error) {
669
+ this.sendError(res, 500, error.message);
670
+ }
671
+ }
672
+
673
+ async handleExportSemanticChunk(req, res) {
674
+ const body = await this.getRequestBody(req);
675
+ const { chunkName } = JSON.parse(body);
676
+
677
+ try {
678
+ const content = await this.semanticAnalysisManager.exportSemanticChunk(chunkName);
679
+ res.writeHead(200, { 'Content-Type': 'application/xml' });
680
+ res.end(content);
681
+ } catch (error) {
682
+ res.writeHead(500, { 'Content-Type': 'application/json' });
683
+ res.end(JSON.stringify({ error: error.message }));
684
+ }
685
+ }
686
+
687
+ // === Utilities ===
688
+
689
+ async handleTestPattern(req, res) {
690
+ const body = await this.getRequestBody(req);
691
+ const { pattern } = JSON.parse(body);
692
+
693
+ const allFiles = this.fileSystemManager.getAllFiles();
694
+ const matchingFiles = allFiles.filter(file =>
695
+ this.fileSystemManager.matchesPattern(file, pattern)
696
+ ).map(file => this.fileSystemManager.relativePath(file));
697
+
698
+ res.writeHead(200, { 'Content-Type': 'application/json' });
699
+ res.end(JSON.stringify({
700
+ pattern,
701
+ matchCount: matchingFiles.length,
702
+ matches: matchingFiles.slice(0, 100) // Limit to first 100 matches
703
+ }));
704
+ }
705
+
706
+ async handleGetMcpStatus(req, res) {
707
+ const status = {
708
+ enabled: this.mcpServerStarted || false,
709
+ available: true,
710
+ message: 'MCP server integration available'
711
+ };
712
+
713
+ res.writeHead(200, { 'Content-Type': 'application/json' });
714
+ res.end(JSON.stringify(status));
715
+ }
716
+
717
+ async handleGetStatus(req, res) {
718
+ const bundles = this.configManager.getBundles();
719
+ const bundleStats = Array.from(bundles.entries()).map(([name, bundle]) => ({
720
+ name,
721
+ fileCount: bundle.files.length,
722
+ size: bundle.size,
723
+ changed: bundle.changed,
724
+ generated: bundle.generated
725
+ }));
726
+
727
+ const status = {
728
+ uptime: process.uptime(),
729
+ memory: process.memoryUsage(),
730
+ bundles: bundleStats,
731
+ scanning: this.bundleManager._isScanning || false,
732
+ totalFiles: this.fileSystemManager.getAllFiles().length,
733
+ mcp: {
734
+ enabled: this.mcpServerStarted || false,
735
+ available: true
736
+ }
737
+ };
738
+
739
+ res.writeHead(200, { 'Content-Type': 'application/json' });
740
+ res.end(JSON.stringify(status));
741
+ }
742
+
743
+ // === Vector Database Operations ===
744
+
745
+ async handleGetVectorDbStatus(req, res) {
746
+ try {
747
+ // Initialize vector store if needed
748
+ if (!this.vectorStore.embedder) {
749
+ await this.vectorStore.init();
750
+ }
751
+
752
+ const stats = await this.vectorStore.getStats();
753
+ this.sendResponse(res, 200, { stats });
754
+ } catch (error) {
755
+ this.sendError(res, 500, error.message);
756
+ }
757
+ }
758
+
759
+ async handlePostVectorDbRebuild(req, res) {
760
+ try {
761
+ await this.vectorStore.clear();
762
+ const analysis = await this.semanticAnalysisManager.getSemanticAnalysis();
763
+
764
+ if (analysis && analysis.chunks) {
765
+ await this.vectorStore.storePrecomputedChunks(analysis.chunks);
766
+ }
767
+
768
+ const stats = await this.vectorStore.getStats();
769
+ res.writeHead(200, { 'Content-Type': 'application/json' });
770
+ res.end(JSON.stringify({ success: true, stats }));
771
+ } catch (error) {
772
+ res.writeHead(500, { 'Content-Type': 'application/json' });
773
+ res.end(JSON.stringify({ error: error.message }));
774
+ }
775
+ }
776
+
777
+ async handlePostVectorDbSearch(req, res) {
778
+ try {
779
+ const body = await this.getRequestBody(req);
780
+ const { query, limit = 10 } = JSON.parse(body);
781
+
782
+ // Initialize vector store if needed
783
+ if (!this.vectorStore.embedder) {
784
+ await this.vectorStore.init();
785
+ }
786
+
787
+ const results = await this.vectorStore.findSimilar(query, { limit });
788
+ this.sendResponse(res, 200, results);
789
+ } catch (error) {
790
+ this.sendError(res, 500, error.message);
791
+ }
792
+ }
793
+
794
+ async handlePostVectorDbSearchByType(req, res) {
795
+ try {
796
+ const body = await this.getRequestBody(req);
797
+ const { type, limit = 10 } = JSON.parse(body);
798
+
799
+ // Initialize vector store if needed
800
+ if (!this.vectorStore.embedder) {
801
+ await this.vectorStore.init();
802
+ }
803
+
804
+ const results = await this.vectorStore.findByType(type, limit);
805
+ this.sendResponse(res, 200, results);
806
+ } catch (error) {
807
+ this.sendError(res, 500, error.message);
808
+ }
809
+ }
810
+
811
+ async handlePostVectorDbSearchByDomain(req, res) {
812
+ try {
813
+ const body = await this.getRequestBody(req);
814
+ const { domain, limit = 10 } = JSON.parse(body);
815
+
816
+ // Initialize vector store if needed
817
+ if (!this.vectorStore.embedder) {
818
+ await this.vectorStore.init();
819
+ }
820
+
821
+ const results = await this.vectorStore.findByDomain(domain, limit);
822
+ this.sendResponse(res, 200, results);
823
+ } catch (error) {
824
+ this.sendError(res, 500, error.message);
825
+ }
826
+ }
827
+
828
+ // === Activities ===
829
+
830
+ async handleGetActivities(req, res) {
831
+ try {
832
+ console.log('API: /api/activities called');
833
+ const activities = await this.activityManager.loadActivities();
834
+ console.log('API: Loaded activities:', activities.length);
835
+ res.writeHead(200, { 'Content-Type': 'application/json' });
836
+ res.end(JSON.stringify(activities));
837
+ } catch (error) {
838
+ console.log('API: Error loading activities:', error.message);
839
+ res.writeHead(500, { 'Content-Type': 'application/json' });
840
+ res.end(JSON.stringify({ error: error.message }));
841
+ }
842
+ }
843
+
844
+ async handlePostActivityExecute(req, res, activityId) {
845
+ try {
846
+ const result = await this.activityManager.executeActivity(activityId);
847
+ res.writeHead(200, { 'Content-Type': 'application/json' });
848
+ res.end(JSON.stringify(result));
849
+ } catch (error) {
850
+ res.writeHead(500, { 'Content-Type': 'application/json' });
851
+ res.end(JSON.stringify({ error: error.message }));
852
+ }
853
+ }
854
+
855
+ async handlePostActivityStop(req, res, activityId) {
856
+ try {
857
+ const result = await this.activityManager.stopActivity(activityId);
858
+ res.writeHead(200, { 'Content-Type': 'application/json' });
859
+ res.end(JSON.stringify(result));
860
+ } catch (error) {
861
+ res.writeHead(500, { 'Content-Type': 'application/json' });
862
+ res.end(JSON.stringify({ error: error.message }));
863
+ }
864
+ }
865
+
866
+ async handleOpenFile(req, res) {
867
+ try {
868
+ const body = await this.getRequestBody(req);
869
+ const { filePath, line, column } = JSON.parse(body);
870
+
871
+ if (!filePath) {
872
+ return this.sendError(res, 400, 'Missing filePath parameter');
873
+ }
874
+
875
+ const { spawn } = await import('child_process');
876
+ const path = await import('path');
877
+ const fs = await import('fs');
878
+
879
+ const fullPath = path.resolve(this.configManager.CWD, filePath);
880
+
881
+ if (!fs.existsSync(fullPath)) {
882
+ return this.sendError(res, 404, 'File not found');
883
+ }
884
+
885
+ let editorCommand = this.configManager.getEditor();
886
+ let command, args;
887
+
888
+ // Helper to add line/column if supported
889
+ const addLineColumn = (file, line, column) => {
890
+ if (line && column) return `${file}:${line}:${column}`;
891
+ if (line) return `${file}:${line}`;
892
+ return file;
893
+ };
894
+
895
+ if (editorCommand === 'system') {
896
+ switch (process.platform) {
897
+ case 'darwin':
898
+ command = 'open';
899
+ args = [fullPath];
900
+ break;
901
+ case 'win32':
902
+ command = 'start';
903
+ args = [fullPath];
904
+ break;
905
+ default:
906
+ command = 'xdg-open';
907
+ args = [fullPath];
908
+ break;
909
+ }
910
+ } else if (editorCommand.startsWith('code')) {
911
+ // VS Code: try direct file:line format
912
+ command = 'code';
913
+ if (line) {
914
+ args = [`${fullPath}:${line}`];
915
+ } else {
916
+ args = [fullPath];
917
+ }
918
+ } else if (editorCommand.startsWith('subl')) {
919
+ // Sublime Text supports file:line[:column]
920
+ command = 'subl';
921
+ args = [addLineColumn(fullPath, line, column)];
922
+ } else {
923
+ // Custom editor: just append file path, ignore line/column
924
+ command = editorCommand.split(' ')[0];
925
+ args = [...editorCommand.split(' ').slice(1), fullPath];
926
+ }
927
+
928
+ const child = spawn(command, args, {
929
+ detached: true,
930
+ stdio: 'ignore',
931
+ shell: false
932
+ });
933
+
934
+ child.unref();
935
+
936
+ res.writeHead(200, { 'Content-Type': 'application/json' });
937
+ res.end(JSON.stringify({ success: true, message: `File opened with ${command}` }));
938
+
939
+ } catch (error) {
940
+ console.error('Failed to open file:', error);
941
+ res.writeHead(500, { 'Content-Type': 'application/json' });
942
+ res.end(JSON.stringify({
943
+ error: 'Failed to open file in editor. Please check your editor configuration.',
944
+ details: error.message
945
+ }));
946
+ }
947
+ }
948
+
949
+ // === Utility Methods ===
950
+
951
+ sendResponse(res, statusCode, data, contentType = 'application/json') {
952
+ res.setHeader('Content-Type', contentType);
953
+ res.statusCode = statusCode;
954
+
955
+ if (typeof data === 'string') {
956
+ res.end(data);
957
+ } else {
958
+ res.end(JSON.stringify(data));
959
+ }
960
+ }
961
+
962
+ sendError(res, statusCode, message) {
963
+ this.sendResponse(res, statusCode, { error: message });
964
+ }
965
+
966
+ async getRequestBody(req) {
967
+ return new Promise((resolve, reject) => {
968
+ let body = '';
969
+ req.on('data', chunk => {
970
+ body += chunk.toString();
971
+ });
972
+ req.on('end', () => {
973
+ resolve(body);
974
+ });
975
+ req.on('error', reject);
976
+ });
977
+ }
978
+ }