cntx-ui 3.0.7 → 3.0.9

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