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/mcp-server.js DELETED
@@ -1,1380 +0,0 @@
1
- import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync, copyFileSync } from 'fs';
2
- import { join, relative, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
- import fs from 'fs';
5
- import AgentRuntime from './agent-runtime.js';
6
-
7
- export class MCPServer {
8
- constructor(cntxServer) {
9
- this.cntxServer = cntxServer;
10
- this.clientCapabilities = null;
11
- this.serverInfo = {
12
- name: 'cntx-ui',
13
- version: this.getServerVersion()
14
- };
15
- this.agentRuntime = new AgentRuntime(cntxServer);
16
- }
17
-
18
- getServerVersion() {
19
- try {
20
- const currentDir = dirname(fileURLToPath(import.meta.url));
21
- const packagePath = join(currentDir, '..', 'package.json');
22
- const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
23
- return packageJson.version || '0.0.0';
24
- } catch (error) {
25
- return '0.0.0';
26
- }
27
- }
28
-
29
- // JSON-RPC 2.0 message handler
30
- async handleMessage(message) {
31
- try {
32
- const request = typeof message === 'string' ? JSON.parse(message) : message;
33
-
34
- // Handle JSON-RPC 2.0 format
35
- if (!request.jsonrpc || request.jsonrpc !== '2.0') {
36
- return this.createErrorResponse(null, -32600, 'Invalid Request');
37
- }
38
-
39
- const response = await this.routeRequest(request);
40
- return response;
41
- } catch (error) {
42
- return this.createErrorResponse(null, -32700, 'Parse error');
43
- }
44
- }
45
-
46
- async routeRequest(request) {
47
- const { method, params, id } = request;
48
-
49
- try {
50
- switch (method) {
51
- case 'initialize':
52
- return this.handleInitialize(params, id);
53
-
54
- case 'initialized':
55
- case 'notifications/initialized':
56
- return null; // No response needed for notification
57
-
58
- case 'resources/list':
59
- return this.handleListResources(id);
60
-
61
- case 'resources/read':
62
- return this.handleReadResource(params, id);
63
-
64
- case 'tools/list':
65
- return this.handleListTools(id);
66
-
67
- case 'tools/call':
68
- return this.handleCallTool(params, id);
69
-
70
- case 'prompts/list':
71
- return this.handleListPrompts(id);
72
-
73
- case 'prompts/get':
74
- return this.handleGetPrompt(params, id);
75
-
76
- case 'prompts/list':
77
- return this.createErrorResponse(id, -32601, 'Method not found');
78
-
79
- default:
80
- return this.createErrorResponse(id, -32601, 'Method not found');
81
- }
82
- } catch (error) {
83
- return this.createErrorResponse(id, -32603, 'Internal error', error.message);
84
- }
85
- }
86
-
87
- // Initialize MCP session
88
- handleInitialize(params, id) {
89
- this.clientCapabilities = params?.capabilities || {};
90
-
91
- return this.createSuccessResponse(id, {
92
- protocolVersion: '2024-11-05',
93
- capabilities: {
94
- resources: {
95
- subscribe: true,
96
- listChanged: true
97
- },
98
- tools: {}
99
- },
100
- serverInfo: this.serverInfo
101
- });
102
- }
103
-
104
- // List available resources (bundles)
105
- handleListResources(id) {
106
- const resources = [];
107
-
108
- this.cntxServer.bundles.forEach((bundle, name) => {
109
- resources.push({
110
- uri: `cntx://bundle/${name}`,
111
- name: `Bundle: ${name}`,
112
- description: `File bundle containing ${bundle.files.length} files`,
113
- mimeType: 'application/xml'
114
- });
115
- });
116
-
117
- // Add individual file resources
118
- const allFiles = this.cntxServer.getAllFiles();
119
- allFiles.slice(0, 100).forEach((filePath) => { // Limit to first 100 files
120
- resources.push({
121
- uri: `cntx://file/${filePath}`,
122
- name: `File: ${filePath}`,
123
- description: `Individual file: ${filePath}`,
124
- mimeType: this.getMimeType(filePath)
125
- });
126
- });
127
-
128
- return this.createSuccessResponse(id, {
129
- resources
130
- });
131
- }
132
-
133
- // Read a specific resource
134
- handleReadResource(params, id) {
135
- const { uri } = params;
136
-
137
- if (!uri || !uri.startsWith('cntx://')) {
138
- return this.createErrorResponse(id, -32602, 'Invalid URI');
139
- }
140
-
141
- try {
142
- if (uri.startsWith('cntx://bundle/')) {
143
- const bundleName = uri.replace('cntx://bundle/', '');
144
- const bundle = this.cntxServer.bundles.get(bundleName);
145
-
146
- if (!bundle) {
147
- return this.createErrorResponse(id, -32602, 'Bundle not found');
148
- }
149
-
150
- return this.createSuccessResponse(id, {
151
- contents: [{
152
- uri,
153
- mimeType: 'application/xml',
154
- text: bundle.content
155
- }]
156
- });
157
- } else if (uri.startsWith('cntx://file/')) {
158
- const filePath = uri.replace('cntx://file/', '');
159
- const fullPath = join(this.cntxServer.CWD, filePath);
160
-
161
- try {
162
- const content = readFileSync(fullPath, 'utf8');
163
- return this.createSuccessResponse(id, {
164
- contents: [{
165
- uri,
166
- mimeType: this.getMimeType(filePath),
167
- text: content
168
- }]
169
- });
170
- } catch (error) {
171
- return this.createErrorResponse(id, -32602, 'File not found');
172
- }
173
- }
174
-
175
- return this.createErrorResponse(id, -32602, 'Invalid resource URI');
176
- } catch (error) {
177
- return this.createErrorResponse(id, -32603, 'Internal error reading resource');
178
- }
179
- }
180
-
181
- // List available prompts
182
- handleListPrompts(id) {
183
- const prompts = [
184
- {
185
- name: 'onboard-me',
186
- description: 'Guided walkthrough of the current codebase architecture.',
187
- arguments: []
188
- },
189
- {
190
- name: 'refactor-planner',
191
- description: 'Plan a refactor by analyzing semantic dependencies.',
192
- arguments: [
193
- { name: 'target', description: 'The function or file to refactor', required: true }
194
- ]
195
- }
196
- ];
197
- return this.createSuccessResponse(id, { prompts });
198
- }
199
-
200
- // Get a specific prompt
201
- handleGetPrompt(params, id) {
202
- const { name, arguments: args } = params;
203
-
204
- if (name === 'onboard-me') {
205
- return this.createSuccessResponse(id, {
206
- description: 'Codebase Onboarding',
207
- messages: [
208
- {
209
- role: 'user',
210
- content: {
211
- type: 'text',
212
- text: 'Please perform a comprehensive discovery of this codebase using the `agent/discover` tool. Focus on identifying the primary entry points and the core architectural patterns. Use the results to explain how the project is organized.'
213
- }
214
- }
215
- ]
216
- });
217
- }
218
-
219
- if (name === 'refactor-planner') {
220
- return this.createSuccessResponse(id, {
221
- description: 'Refactor Planning',
222
- messages: [
223
- {
224
- role: 'user',
225
- content: {
226
- type: 'text',
227
- text: `I want to refactor ${args.target}. Use the \`agent/query\` tool to find all semantic chunks related to this target and the \`agent/investigate\` tool to identify potential impact on other modules. Present a step-by-step refactoring plan.`
228
- }
229
- }
230
- ]
231
- });
232
- }
233
-
234
- return this.createErrorResponse(id, -32602, 'Prompt not found');
235
- }
236
-
237
- // List available tools
238
- handleListTools(id) {
239
- const tools = [
240
- {
241
- name: 'list_bundles',
242
- description: 'List all available file bundles',
243
- inputSchema: {
244
- type: 'object',
245
- properties: {},
246
- required: []
247
- }
248
- },
249
- {
250
- name: 'get_bundle',
251
- description: 'Get the content of a specific bundle',
252
- inputSchema: {
253
- type: 'object',
254
- properties: {
255
- name: {
256
- type: 'string',
257
- description: 'Name of the bundle to retrieve'
258
- }
259
- },
260
- required: ['name']
261
- }
262
- },
263
- {
264
- name: 'generate_bundle',
265
- description: 'Regenerate a specific bundle',
266
- inputSchema: {
267
- type: 'object',
268
- properties: {
269
- name: {
270
- type: 'string',
271
- description: 'Name of the bundle to regenerate'
272
- }
273
- },
274
- required: ['name']
275
- }
276
- },
277
- {
278
- name: 'get_file_tree',
279
- description: 'Get the project file tree',
280
- inputSchema: {
281
- type: 'object',
282
- properties: {},
283
- required: []
284
- }
285
- },
286
- {
287
- name: 'get_project_status',
288
- description: 'Get current project status and bundle information',
289
- inputSchema: {
290
- type: 'object',
291
- properties: {},
292
- required: []
293
- }
294
- },
295
- {
296
- name: 'get_semantic_chunks',
297
- description: 'Get function-level semantic chunks from the codebase',
298
- inputSchema: {
299
- type: 'object',
300
- properties: {},
301
- required: []
302
- }
303
- },
304
- {
305
- name: 'get_semantic_chunks_filtered',
306
- description: 'Get semantic chunks filtered by purpose, type, complexity, or bundle',
307
- inputSchema: {
308
- type: 'object',
309
- properties: {
310
- purpose: {
311
- type: 'string',
312
- description: 'Filter by function purpose (e.g., "API handler", "React component", "Data retrieval")'
313
- },
314
- type: {
315
- type: 'string',
316
- description: 'Filter by function type (e.g., "arrow_function", "react_component", "method")'
317
- },
318
- complexity: {
319
- type: 'string',
320
- description: 'Filter by complexity level ("low", "medium", "high")'
321
- },
322
- bundle: {
323
- type: 'string',
324
- description: 'Filter by bundle membership'
325
- },
326
- exported: {
327
- type: 'boolean',
328
- description: 'Filter by export status'
329
- },
330
- async: {
331
- type: 'boolean',
332
- description: 'Filter by async functions'
333
- }
334
- },
335
- required: []
336
- }
337
- },
338
- {
339
- name: 'analyze_bundle_suggestions',
340
- description: 'Analyze codebase and suggest optimal bundle organization based on semantic chunks',
341
- inputSchema: {
342
- type: 'object',
343
- properties: {
344
- max_suggestions: {
345
- type: 'number',
346
- description: 'Maximum number of bundle suggestions to return (default: 5)'
347
- }
348
- },
349
- required: []
350
- }
351
- },
352
- {
353
- name: 'create_bundle',
354
- description: 'Create a new bundle with specified patterns',
355
- inputSchema: {
356
- type: 'object',
357
- properties: {
358
- name: {
359
- type: 'string',
360
- description: 'Name of the new bundle'
361
- },
362
- patterns: {
363
- type: 'array',
364
- items: { type: 'string' },
365
- description: 'Array of glob patterns for the bundle (e.g., ["src/api/**", "src/services/**"])'
366
- },
367
- description: {
368
- type: 'string',
369
- description: 'Optional description of the bundle purpose'
370
- }
371
- },
372
- required: ['name', 'patterns']
373
- }
374
- },
375
- {
376
- name: 'update_bundle',
377
- description: 'Update an existing bundle\'s patterns',
378
- inputSchema: {
379
- type: 'object',
380
- properties: {
381
- name: {
382
- type: 'string',
383
- description: 'Name of the bundle to update'
384
- },
385
- patterns: {
386
- type: 'array',
387
- items: { type: 'string' },
388
- description: 'New array of glob patterns for the bundle'
389
- },
390
- description: {
391
- type: 'string',
392
- description: 'Optional updated description'
393
- }
394
- },
395
- required: ['name', 'patterns']
396
- }
397
- },
398
- {
399
- name: 'delete_bundle',
400
- description: 'Delete an existing bundle',
401
- inputSchema: {
402
- type: 'object',
403
- properties: {
404
- name: {
405
- type: 'string',
406
- description: 'Name of the bundle to delete'
407
- }
408
- },
409
- required: ['name']
410
- }
411
- },
412
- {
413
- name: 'update_cntxignore',
414
- description: 'Update the .cntxignore file with new ignore patterns',
415
- inputSchema: {
416
- type: 'object',
417
- properties: {
418
- content: {
419
- type: 'string',
420
- description: 'Full content for the .cntxignore file (newline-separated patterns)'
421
- }
422
- },
423
- required: ['content']
424
- }
425
- },
426
- {
427
- name: 'agent_discover',
428
- description: 'Agent Discovery Mode: Get comprehensive codebase overview including bundles, architecture, and patterns',
429
- inputSchema: {
430
- type: 'object',
431
- properties: {
432
- scope: {
433
- type: 'string',
434
- description: 'Scope of discovery: "all" for full codebase or specific bundle name (default: "all")'
435
- },
436
- includeDetails: {
437
- type: 'boolean',
438
- description: 'Include detailed semantic analysis and complexity metrics (default: true)'
439
- }
440
- },
441
- required: []
442
- }
443
- },
444
- {
445
- name: 'agent_query',
446
- description: 'Agent Query Mode: Answer specific questions about the codebase using semantic search and analysis',
447
- inputSchema: {
448
- type: 'object',
449
- properties: {
450
- question: {
451
- type: 'string',
452
- description: 'The question to answer about the codebase (e.g., "Where is user authentication handled?")'
453
- },
454
- scope: {
455
- type: 'string',
456
- description: 'Optional bundle to limit search scope'
457
- },
458
- maxResults: {
459
- type: 'number',
460
- description: 'Maximum number of results to return (default: 10)'
461
- },
462
- includeCode: {
463
- type: 'boolean',
464
- description: 'Include code snippets in the response (default: false)'
465
- }
466
- },
467
- required: ['question']
468
- }
469
- },
470
- {
471
- name: 'agent_investigate',
472
- description: 'Agent Investigation Mode: Investigate existing implementations for a feature and find integration points',
473
- inputSchema: {
474
- type: 'object',
475
- properties: {
476
- featureDescription: {
477
- type: 'string',
478
- description: 'Description of the feature to investigate (e.g., "dark mode", "user authentication", "form validation")'
479
- },
480
- includeRecommendations: {
481
- type: 'boolean',
482
- description: 'Include implementation recommendations and approach suggestions (default: true)'
483
- }
484
- },
485
- required: ['featureDescription']
486
- }
487
- },
488
- {
489
- name: 'agent_discuss',
490
- description: 'Agent Passive Mode: Engage in discussion about codebase architecture, design decisions, and planning',
491
- inputSchema: {
492
- type: 'object',
493
- properties: {
494
- userInput: {
495
- type: 'string',
496
- description: 'The topic or question for discussion (e.g., "Let\'s discuss the architecture before I make changes")'
497
- },
498
- context: {
499
- type: 'object',
500
- description: 'Additional context for the discussion',
501
- properties: {
502
- scope: {
503
- type: 'string',
504
- description: 'Specific area of focus (e.g., "frontend", "api", "database")'
505
- }
506
- }
507
- }
508
- },
509
- required: ['userInput']
510
- }
511
- },
512
- {
513
- name: 'agent_organize',
514
- description: 'Agent Project Organizer Mode: Setup and maintenance of project organization - adapts to project maturity',
515
- inputSchema: {
516
- type: 'object',
517
- properties: {
518
- activity: {
519
- type: 'string',
520
- enum: ['detect', 'analyze', 'bundle', 'create', 'optimize', 'audit', 'cleanup', 'validate'],
521
- description: 'Activity to perform: detect project state, analyze semantics, suggest bundles, create bundles, optimize organization, audit health, cleanup issues, or validate structure'
522
- },
523
- autoDetect: {
524
- type: 'boolean',
525
- description: 'Automatically detect appropriate activity based on project state (default: true)',
526
- default: true
527
- },
528
- force: {
529
- type: 'boolean',
530
- description: 'Force execution even if preconditions are not met (default: false)',
531
- default: false
532
- }
533
- },
534
- required: []
535
- }
536
- },
537
- {
538
- name: 'read_file',
539
- description: 'Read contents of a specific file with bundle context and metadata',
540
- inputSchema: {
541
- type: 'object',
542
- properties: {
543
- path: {
544
- type: 'string',
545
- description: 'File path relative to project root'
546
- },
547
- includeMetadata: {
548
- type: 'boolean',
549
- description: 'Include file metadata (size, bundles, etc.) - default: true'
550
- }
551
- },
552
- required: ['path']
553
- }
554
- },
555
- {
556
- name: 'write_file',
557
- description: 'Write content to a file with validation and safety checks',
558
- inputSchema: {
559
- type: 'object',
560
- properties: {
561
- path: {
562
- type: 'string',
563
- description: 'File path relative to project root'
564
- },
565
- content: {
566
- type: 'string',
567
- description: 'Content to write to the file'
568
- },
569
- backup: {
570
- type: 'boolean',
571
- description: 'Create backup before writing - default: true'
572
- },
573
- createDirs: {
574
- type: 'boolean',
575
- description: 'Create parent directories if they don\'t exist - default: true'
576
- }
577
- },
578
- required: ['path', 'content']
579
- }
580
- },
581
- ];
582
-
583
- return this.createSuccessResponse(id, { tools });
584
- }
585
-
586
- // Handle tool execution
587
- async handleCallTool(params, id) {
588
- const { name, arguments: args = {} } = params;
589
-
590
- try {
591
- switch (name) {
592
- case 'list_bundles':
593
- return this.toolListBundles(id);
594
-
595
- case 'get_bundle':
596
- return this.toolGetBundle(args, id);
597
-
598
- case 'generate_bundle':
599
- return this.toolGenerateBundle(args, id);
600
-
601
- case 'get_file_tree':
602
- return this.toolGetFileTree(id);
603
-
604
- case 'get_project_status':
605
- return this.toolGetProjectStatus(id);
606
-
607
- case 'get_semantic_chunks':
608
- return this.toolGetSemanticChunks(id);
609
-
610
- case 'get_semantic_chunks_filtered':
611
- return this.toolGetSemanticChunksFiltered(args, id);
612
-
613
- case 'analyze_bundle_suggestions':
614
- return this.toolAnalyzeBundleSuggestions(args, id);
615
-
616
- case 'create_bundle':
617
- return this.toolCreateBundle(args, id);
618
-
619
- case 'update_bundle':
620
- return this.toolUpdateBundle(args, id);
621
-
622
- case 'delete_bundle':
623
- return this.toolDeleteBundle(args, id);
624
-
625
- case 'update_cntxignore':
626
- return this.toolUpdateCntxignore(args, id);
627
-
628
- case 'agent_discover':
629
- return this.toolAgentDiscover(args, id);
630
-
631
- case 'agent_query':
632
- return this.toolAgentQuery(args, id);
633
-
634
- case 'agent_investigate':
635
- return this.toolAgentInvestigate(args, id);
636
-
637
- case 'agent_discuss':
638
- return this.toolAgentDiscuss(args, id);
639
-
640
- case 'agent_organize':
641
- return this.toolAgentOrganize(args, id);
642
-
643
- case 'read_file':
644
- return this.toolReadFile(args, id);
645
-
646
- case 'write_file':
647
- return this.toolWriteFile(args, id);
648
-
649
- default:
650
- return this.createErrorResponse(id, -32602, 'Unknown tool');
651
- }
652
- } catch (error) {
653
- return this.createErrorResponse(id, -32603, 'Tool execution failed', error.message);
654
- }
655
- }
656
-
657
- // Tool implementations
658
- toolListBundles(id) {
659
- const bundles = [];
660
- this.cntxServer.bundles.forEach((bundle, name) => {
661
- bundles.push({
662
- name,
663
- fileCount: bundle.files.length,
664
- size: bundle.size,
665
- lastGenerated: bundle.lastGenerated,
666
- changed: bundle.changed,
667
- patterns: bundle.patterns
668
- });
669
- });
670
-
671
- return this.createSuccessResponse(id, {
672
- content: [{
673
- type: 'text',
674
- text: `Available bundles:\n${bundles.map(b =>
675
- `• ${b.name}: ${b.fileCount} files (${(b.size / 1024).toFixed(1)}KB) ${b.changed ? '[CHANGED]' : '[SYNCED]'}`
676
- ).join('\n')}`
677
- }]
678
- });
679
- }
680
-
681
- toolGetBundle(args, id) {
682
- const { name } = args;
683
- const bundle = this.cntxServer.bundles.get(name);
684
-
685
- if (!bundle) {
686
- return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
687
- }
688
-
689
- return this.createSuccessResponse(id, {
690
- content: [{
691
- type: 'text',
692
- text: bundle.content
693
- }]
694
- });
695
- }
696
-
697
- toolGenerateBundle(args, id) {
698
- const { name } = args;
699
-
700
- if (!this.cntxServer.bundles.has(name)) {
701
- return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
702
- }
703
-
704
- this.cntxServer.generateBundle(name);
705
- this.cntxServer.saveBundleStates();
706
-
707
- const bundle = this.cntxServer.bundles.get(name);
708
-
709
- return this.createSuccessResponse(id, {
710
- content: [{
711
- type: 'text',
712
- text: `Bundle '${name}' regenerated successfully. Contains ${bundle.files.length} files (${(bundle.size / 1024).toFixed(1)}KB).`
713
- }]
714
- });
715
- }
716
-
717
- toolGetFileTree(id) {
718
- const fileTree = this.cntxServer.getFileTree();
719
- const treeText = fileTree.map(file =>
720
- `${file.path} (${(file.size / 1024).toFixed(1)}KB)`
721
- ).join('\n');
722
-
723
- return this.createSuccessResponse(id, {
724
- content: [{
725
- type: 'text',
726
- text: `Project file tree:\n${treeText}`
727
- }]
728
- });
729
- }
730
-
731
- toolGetProjectStatus(id) {
732
- const bundleCount = this.cntxServer.bundles.size;
733
- const changedBundles = Array.from(this.cntxServer.bundles.entries())
734
- .filter(([_, bundle]) => bundle.changed)
735
- .map(([name, _]) => name);
736
-
737
- const statusText = `Project Status:
738
- Working Directory: ${relative(process.cwd(), this.cntxServer.CWD)}
739
- Total Bundles: ${bundleCount}
740
- Changed Bundles: ${changedBundles.length > 0 ? changedBundles.join(', ') : 'None'}
741
-
742
- Bundle Details:
743
- ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
744
- `• ${name}: ${bundle.files.length} files, ${(bundle.size / 1024).toFixed(1)}KB ${bundle.changed ? '[CHANGED]' : '[SYNCED]'}`
745
- ).join('\n')}`;
746
-
747
- return this.createSuccessResponse(id, {
748
- content: [{
749
- type: 'text',
750
- text: statusText
751
- }]
752
- });
753
- }
754
-
755
- // New semantic chunks tools
756
- async toolGetSemanticChunks(id) {
757
- try {
758
- const analysis = await this.cntxServer.getSemanticAnalysis();
759
-
760
- // Clean the analysis data to prevent JSON issues
761
- const cleanAnalysis = {
762
- ...analysis,
763
- chunks: analysis.chunks?.map(chunk => ({
764
- ...chunk,
765
- code: chunk.code ? chunk.code.substring(0, 500) + (chunk.code.length > 500 ? '...' : '') : '',
766
- bundles: chunk.bundles || [],
767
- includes: {
768
- imports: chunk.includes?.imports || [],
769
- types: chunk.includes?.types || []
770
- }
771
- })) || []
772
- };
773
-
774
- return this.createSuccessResponse(id, {
775
- content: [{
776
- type: 'text',
777
- text: JSON.stringify(cleanAnalysis, null, 2)
778
- }]
779
- });
780
- } catch (error) {
781
- return this.createErrorResponse(id, -32603, 'Failed to get semantic chunks', error.message);
782
- }
783
- }
784
-
785
- async toolGetSemanticChunksFiltered(args, id) {
786
- try {
787
- const analysis = await this.cntxServer.getSemanticAnalysis();
788
- let chunks = analysis.chunks || [];
789
-
790
- // Apply filters
791
- if (args.purpose) {
792
- chunks = chunks.filter(chunk =>
793
- chunk.purpose && chunk.purpose.toLowerCase().includes(args.purpose.toLowerCase())
794
- );
795
- }
796
-
797
- if (args.type) {
798
- chunks = chunks.filter(chunk => chunk.subtype === args.type);
799
- }
800
-
801
- if (args.complexity) {
802
- chunks = chunks.filter(chunk => chunk.complexity?.level === args.complexity);
803
- }
804
-
805
- if (args.bundle) {
806
- chunks = chunks.filter(chunk =>
807
- chunk.bundles && chunk.bundles.includes(args.bundle)
808
- );
809
- }
810
-
811
- if (args.exported !== undefined) {
812
- chunks = chunks.filter(chunk => chunk.isExported === args.exported);
813
- }
814
-
815
- if (args.async !== undefined) {
816
- chunks = chunks.filter(chunk => chunk.isAsync === args.async);
817
- }
818
-
819
- // Clean chunks for JSON safety
820
- const cleanChunks = chunks.map(chunk => ({
821
- ...chunk,
822
- code: chunk.code ? chunk.code.substring(0, 300) + (chunk.code.length > 300 ? '...' : '') : '',
823
- bundles: chunk.bundles || [],
824
- includes: {
825
- imports: chunk.includes?.imports || [],
826
- types: chunk.includes?.types || []
827
- }
828
- }));
829
-
830
- const filteredAnalysis = {
831
- ...analysis,
832
- chunks: cleanChunks,
833
- summary: {
834
- ...analysis.summary,
835
- totalChunks: cleanChunks.length,
836
- filteredCount: cleanChunks.length,
837
- originalCount: analysis.chunks?.length || 0
838
- }
839
- };
840
-
841
- return this.createSuccessResponse(id, {
842
- content: [{
843
- type: 'text',
844
- text: JSON.stringify(filteredAnalysis, null, 2)
845
- }]
846
- });
847
- } catch (error) {
848
- return this.createErrorResponse(id, -32603, 'Failed to filter semantic chunks', error.message);
849
- }
850
- }
851
-
852
- async toolAnalyzeBundleSuggestions(args, id) {
853
- try {
854
- const analysis = await this.cntxServer.getSemanticAnalysis();
855
- const chunks = analysis.chunks || [];
856
- const maxSuggestions = args.max_suggestions || 5;
857
-
858
- // Group chunks by purpose and file location
859
- const purposeGroups = {};
860
- const locationGroups = {};
861
-
862
- chunks.forEach(chunk => {
863
- // Group by purpose
864
- if (!purposeGroups[chunk.purpose]) {
865
- purposeGroups[chunk.purpose] = [];
866
- }
867
- purposeGroups[chunk.purpose].push(chunk);
868
-
869
- // Group by file location patterns
870
- const pathParts = chunk.filePath.split('/');
871
- if (pathParts.length > 1) {
872
- const dirPattern = pathParts.slice(0, -1).join('/') + '/**';
873
- if (!locationGroups[dirPattern]) {
874
- locationGroups[dirPattern] = [];
875
- }
876
- locationGroups[dirPattern].push(chunk);
877
- }
878
- });
879
-
880
- const suggestions = [];
881
-
882
- // Suggest bundles by purpose
883
- Object.entries(purposeGroups).forEach(([purpose, chunks]) => {
884
- if (chunks.length >= 3) { // Only suggest if enough functions
885
- const bundleName = purpose.toLowerCase().replace(/\s+/g, '-');
886
- const patterns = [...new Set(chunks.map(c => {
887
- const dir = c.filePath.split('/').slice(0, -1).join('/');
888
- return dir ? `${dir}/**` : c.filePath;
889
- }))];
890
-
891
- suggestions.push({
892
- name: bundleName,
893
- reason: `Groups ${chunks.length} functions with purpose: ${purpose}`,
894
- patterns,
895
- chunkCount: chunks.length,
896
- files: [...new Set(chunks.map(c => c.filePath))]
897
- });
898
- }
899
- });
900
-
901
- // Suggest bundles by common directory patterns
902
- Object.entries(locationGroups).forEach(([pattern, chunks]) => {
903
- if (chunks.length >= 5) { // Only suggest if enough functions in same location
904
- const dirName = pattern.split('/').pop().replace('/**', '');
905
- const bundleName = dirName === '*' ? 'utils' : dirName;
906
-
907
- suggestions.push({
908
- name: bundleName,
909
- reason: `Groups ${chunks.length} functions from ${pattern}`,
910
- patterns: [pattern],
911
- chunkCount: chunks.length,
912
- files: [...new Set(chunks.map(c => c.filePath))]
913
- });
914
- }
915
- });
916
-
917
- // Sort by chunk count and take top suggestions
918
- const topSuggestions = suggestions
919
- .sort((a, b) => b.chunkCount - a.chunkCount)
920
- .slice(0, maxSuggestions);
921
-
922
- const result = {
923
- totalSuggestions: suggestions.length,
924
- suggestions: topSuggestions,
925
- analysis: {
926
- totalChunks: chunks.length,
927
- purposeGroups: Object.keys(purposeGroups).length,
928
- locationGroups: Object.keys(locationGroups).length
929
- }
930
- };
931
-
932
- return this.createSuccessResponse(id, {
933
- content: [{
934
- type: 'text',
935
- text: JSON.stringify(result, null, 2)
936
- }]
937
- });
938
- } catch (error) {
939
- return this.createErrorResponse(id, -32603, 'Failed to analyze bundle suggestions', error.message);
940
- }
941
- }
942
-
943
- // Bundle management tools
944
- async toolCreateBundle(args, id) {
945
- try {
946
- const { name, patterns, description } = args;
947
-
948
- if (!name || !patterns || !Array.isArray(patterns)) {
949
- return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
950
- }
951
-
952
- // Prevent overwriting existing bundles
953
- if (this.cntxServer.bundles.has(name)) {
954
- return this.createErrorResponse(id, -32602, `Bundle '${name}' already exists`);
955
- }
956
-
957
- // Create bundle in bundle-states.json (single source of truth)
958
- this.cntxServer.configManager.bundleStates.set(name, {
959
- patterns: patterns,
960
- files: [],
961
- content: '',
962
- changed: false,
963
- size: 0,
964
- generated: null
965
- });
966
-
967
- // Save bundle states
968
- this.cntxServer.configManager.saveBundleStates();
969
-
970
- // Regenerate bundles
971
- this.cntxServer.generateAllBundles();
972
-
973
- const result = {
974
- success: true,
975
- bundle: {
976
- name,
977
- patterns,
978
- description,
979
- created: new Date().toISOString()
980
- }
981
- };
982
-
983
- return this.createSuccessResponse(id, {
984
- content: [{
985
- type: 'text',
986
- text: JSON.stringify(result, null, 2)
987
- }]
988
- });
989
- } catch (error) {
990
- return this.createErrorResponse(id, -32603, 'Failed to create bundle', error.message);
991
- }
992
- }
993
-
994
- async toolUpdateBundle(args, id) {
995
- try {
996
- const { name, patterns, description } = args;
997
-
998
- if (!name || !patterns || !Array.isArray(patterns)) {
999
- return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
1000
- }
1001
-
1002
- // Check if bundle exists
1003
- if (!this.cntxServer.bundles.has(name)) {
1004
- return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
1005
- }
1006
-
1007
- // Prevent updating master bundle
1008
- if (name === 'master') {
1009
- return this.createErrorResponse(id, -32602, 'Cannot update master bundle');
1010
- }
1011
-
1012
- // Update bundle in bundle-states.json (single source of truth)
1013
- const bundle = this.cntxServer.configManager.bundleStates.get(name);
1014
- bundle.patterns = patterns;
1015
- bundle.changed = true;
1016
-
1017
- // Save bundle states
1018
- this.cntxServer.configManager.saveBundleStates();
1019
-
1020
- // Regenerate bundles
1021
- this.cntxServer.generateAllBundles();
1022
-
1023
- const result = {
1024
- success: true,
1025
- bundle: {
1026
- name,
1027
- patterns,
1028
- description,
1029
- updated: new Date().toISOString()
1030
- }
1031
- };
1032
-
1033
- return this.createSuccessResponse(id, {
1034
- content: [{
1035
- type: 'text',
1036
- text: JSON.stringify(result, null, 2)
1037
- }]
1038
- });
1039
- } catch (error) {
1040
- return this.createErrorResponse(id, -32603, 'Failed to update bundle', error.message);
1041
- }
1042
- }
1043
-
1044
- async toolDeleteBundle(args, id) {
1045
- try {
1046
- const { name } = args;
1047
-
1048
- if (!name) {
1049
- return this.createErrorResponse(id, -32602, 'Bundle name required');
1050
- }
1051
-
1052
- // Check if bundle exists
1053
- if (!this.cntxServer.bundles.has(name)) {
1054
- return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
1055
- }
1056
-
1057
- // Prevent deleting master bundle
1058
- if (name === 'master') {
1059
- return this.createErrorResponse(id, -32602, 'Cannot delete master bundle');
1060
- }
1061
-
1062
- // Remove bundle from bundle-states.json (single source of truth)
1063
- this.cntxServer.configManager.bundleStates.delete(name);
1064
-
1065
- // Save bundle states
1066
- this.cntxServer.configManager.saveBundleStates();
1067
-
1068
- // Regenerate bundles
1069
- this.cntxServer.generateAllBundles();
1070
-
1071
- const result = {
1072
- success: true,
1073
- deleted: name,
1074
- timestamp: new Date().toISOString()
1075
- };
1076
-
1077
- return this.createSuccessResponse(id, {
1078
- content: [{
1079
- type: 'text',
1080
- text: JSON.stringify(result, null, 2)
1081
- }]
1082
- });
1083
- } catch (error) {
1084
- return this.createErrorResponse(id, -32603, 'Failed to delete bundle', error.message);
1085
- }
1086
- }
1087
-
1088
- async toolUpdateCntxignore(args, id) {
1089
- try {
1090
- const { content } = args;
1091
-
1092
- if (content === undefined) {
1093
- return this.createErrorResponse(id, -32602, 'Content required');
1094
- }
1095
-
1096
- const ignorePath = join(this.cntxServer.CWD, '.cntxignore');
1097
-
1098
- // Write the .cntxignore file
1099
- writeFileSync(ignorePath, content);
1100
-
1101
- // Reload ignore patterns
1102
- this.cntxServer.loadIgnorePatterns();
1103
- this.cntxServer.generateAllBundles();
1104
-
1105
- const result = {
1106
- success: true,
1107
- file: '.cntxignore',
1108
- lines: content.split('\n').length,
1109
- patterns: content.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')).length,
1110
- updated: new Date().toISOString()
1111
- };
1112
-
1113
- return this.createSuccessResponse(id, {
1114
- content: [{
1115
- type: 'text',
1116
- text: JSON.stringify(result, null, 2)
1117
- }]
1118
- });
1119
- } catch (error) {
1120
- return this.createErrorResponse(id, -32603, 'Failed to update .cntxignore', error.message);
1121
- }
1122
- }
1123
-
1124
- // Agent Tools Implementation
1125
- async toolAgentDiscover(args, id) {
1126
- try {
1127
- const { scope = 'all', includeDetails = true } = args;
1128
- const result = await this.agentRuntime.discoverCodebase({ scope, includeDetails });
1129
-
1130
- return this.createSuccessResponse(id, {
1131
- content: [{
1132
- type: 'text',
1133
- text: JSON.stringify(result, null, 2)
1134
- }]
1135
- });
1136
- } catch (error) {
1137
- return this.createErrorResponse(id, -32603, 'Agent discovery failed', error.message);
1138
- }
1139
- }
1140
-
1141
- async toolAgentQuery(args, id) {
1142
- try {
1143
- const { question, scope, maxResults = 10, includeCode = false } = args;
1144
-
1145
- if (!question) {
1146
- return this.createErrorResponse(id, -32602, 'Question is required');
1147
- }
1148
-
1149
- const result = await this.agentRuntime.answerQuery(question, { scope, maxResults, includeCode });
1150
-
1151
- return this.createSuccessResponse(id, {
1152
- content: [{
1153
- type: 'text',
1154
- text: JSON.stringify(result, null, 2)
1155
- }]
1156
- });
1157
- } catch (error) {
1158
- return this.createErrorResponse(id, -32603, 'Agent query failed', error.message);
1159
- }
1160
- }
1161
-
1162
- async toolAgentInvestigate(args, id) {
1163
- try {
1164
- const { featureDescription, includeRecommendations = true } = args;
1165
-
1166
- if (!featureDescription) {
1167
- return this.createErrorResponse(id, -32602, 'Feature description is required');
1168
- }
1169
-
1170
- const result = await this.agentRuntime.investigateFeature(featureDescription, { includeRecommendations });
1171
-
1172
- return this.createSuccessResponse(id, {
1173
- content: [{
1174
- type: 'text',
1175
- text: JSON.stringify(result, null, 2)
1176
- }]
1177
- });
1178
- } catch (error) {
1179
- return this.createErrorResponse(id, -32603, 'Agent investigation failed', error.message);
1180
- }
1181
- }
1182
-
1183
- async toolAgentDiscuss(args, id) {
1184
- try {
1185
- const { userInput, context = {} } = args;
1186
-
1187
- if (!userInput) {
1188
- return this.createErrorResponse(id, -32602, 'User input is required');
1189
- }
1190
-
1191
- const result = await this.agentRuntime.discussAndPlan(userInput, context);
1192
-
1193
- return this.createSuccessResponse(id, {
1194
- content: [{
1195
- type: 'text',
1196
- text: JSON.stringify(result, null, 2)
1197
- }]
1198
- });
1199
- } catch (error) {
1200
- return this.createErrorResponse(id, -32603, 'Agent discussion failed', error.message);
1201
- }
1202
- }
1203
-
1204
- async toolAgentOrganize(args, id) {
1205
- try {
1206
- const { activity = 'detect', autoDetect = true, force = false } = args;
1207
-
1208
- const result = await this.agentRuntime.organizeProject({ activity, autoDetect, force });
1209
-
1210
- return this.createSuccessResponse(id, {
1211
- content: [{
1212
- type: 'text',
1213
- text: JSON.stringify(result, null, 2)
1214
- }]
1215
- });
1216
- } catch (error) {
1217
- return this.createErrorResponse(id, -32603, 'Agent organization failed', error.message);
1218
- }
1219
- }
1220
-
1221
- // New tool implementations
1222
- async toolReadFile(args, id) {
1223
- const { path: filePath, includeMetadata = true } = args;
1224
-
1225
- if (!filePath) {
1226
- return this.createErrorResponse(id, -32602, 'Path is required');
1227
- }
1228
-
1229
- try {
1230
- const fullPath = join(this.cntxServer.CWD, filePath);
1231
-
1232
- if (!existsSync(fullPath)) {
1233
- return this.createErrorResponse(id, -32602, `File not found: ${filePath}`);
1234
- }
1235
-
1236
- const content = readFileSync(fullPath, 'utf8');
1237
-
1238
- // Fetch semantic context for this file
1239
- const chunks = this.cntxServer.databaseManager.getChunksByFile(filePath);
1240
- const totalComplexity = chunks.reduce((sum, c) => sum + (c.complexity?.score || 0), 0);
1241
- const avgComplexity = chunks.length > 0 ? Math.round(totalComplexity / chunks.length) : 0;
1242
-
1243
- let semanticHeader = `--- SEMANTIC CONTEXT ---\n`;
1244
- semanticHeader += `File importance: ${chunks.length} semantic chunks found.\n`;
1245
- semanticHeader += `Aggregate Complexity: ${totalComplexity} (Avg: ${avgComplexity})\n`;
1246
- if (chunks.length > 0) {
1247
- semanticHeader += `Primary purposes: ${[...new Set(chunks.map(c => c.purpose))].join(', ')}\n`;
1248
- }
1249
- semanticHeader += `------------------------\n\n`;
1250
-
1251
- const result = {
1252
- path: filePath,
1253
- content: semanticHeader + content
1254
- };
1255
-
1256
- if (includeMetadata) {
1257
- const stats = fs.statSync(fullPath);
1258
- const bundles = [];
1259
-
1260
- // Find which bundles include this file
1261
- this.cntxServer.bundles.forEach((bundle, name) => {
1262
- if (bundle.files && bundle.files.includes(filePath)) {
1263
- bundles.push(name);
1264
- }
1265
- });
1266
-
1267
- result.metadata = {
1268
- size: stats.size,
1269
- mimeType: this.getMimeType(filePath),
1270
- modified: stats.mtime.toISOString(),
1271
- lines: content.split('\n').length,
1272
- bundles: bundles
1273
- };
1274
- }
1275
-
1276
- return this.createSuccessResponse(id, {
1277
- content: [{
1278
- type: 'text',
1279
- text: JSON.stringify(result, null, 2)
1280
- }]
1281
- });
1282
- } catch (error) {
1283
- return this.createErrorResponse(id, -32603, 'Failed to read file', error.message);
1284
- }
1285
- }
1286
-
1287
- async toolWriteFile(args, id) {
1288
- const { path: filePath, content, backup = true, createDirs = true } = args;
1289
-
1290
- if (!filePath || content === undefined) {
1291
- return this.createErrorResponse(id, -32602, 'Path and content are required');
1292
- }
1293
-
1294
- try {
1295
- const fullPath = join(this.cntxServer.CWD, filePath);
1296
- const parentDir = dirname(fullPath);
1297
-
1298
- // Create parent directories if needed
1299
- if (createDirs && !existsSync(parentDir)) {
1300
- fs.mkdirSync(parentDir, { recursive: true });
1301
- }
1302
-
1303
- // Create backup if file exists
1304
- if (backup && existsSync(fullPath)) {
1305
- const backupPath = `${fullPath}.backup.${Date.now()}`;
1306
- fs.copyFileSync(fullPath, backupPath);
1307
- }
1308
-
1309
- // Write the file
1310
- writeFileSync(fullPath, content, 'utf8');
1311
-
1312
- // Mark relevant bundles as changed
1313
- this.cntxServer.bundles.forEach((bundle, name) => {
1314
- if (bundle.patterns && bundle.patterns.some(pattern =>
1315
- this.cntxServer.fileSystemManager.matchesPattern(fullPath, pattern)
1316
- )) {
1317
- bundle.changed = true;
1318
- }
1319
- });
1320
-
1321
- const stats = fs.statSync(fullPath);
1322
-
1323
- return this.createSuccessResponse(id, {
1324
- content: [{
1325
- type: 'text',
1326
- text: JSON.stringify({
1327
- path: filePath,
1328
- written: true,
1329
- size: stats.size,
1330
- modified: stats.mtime.toISOString()
1331
- }, null, 2)
1332
- }]
1333
- });
1334
- } catch (error) {
1335
- return this.createErrorResponse(id, -32603, 'Failed to write file', error.message);
1336
- }
1337
- }
1338
-
1339
- // Helper methods
1340
- getMimeType(filePath) {
1341
- const ext = filePath.split('.').pop()?.toLowerCase();
1342
- const mimeTypes = {
1343
- 'js': 'application/javascript',
1344
- 'jsx': 'application/javascript',
1345
- 'ts': 'application/typescript',
1346
- 'tsx': 'application/typescript',
1347
- 'json': 'application/json',
1348
- 'xml': 'application/xml',
1349
- 'html': 'text/html',
1350
- 'css': 'text/css',
1351
- 'md': 'text/markdown',
1352
- 'txt': 'text/plain',
1353
- 'py': 'text/x-python',
1354
- 'java': 'text/x-java',
1355
- 'c': 'text/x-c',
1356
- 'cpp': 'text/x-c++',
1357
- 'php': 'text/x-php'
1358
- };
1359
- return mimeTypes[ext] || 'text/plain';
1360
- }
1361
-
1362
- createSuccessResponse(id, result) {
1363
- return {
1364
- jsonrpc: '2.0',
1365
- id,
1366
- result
1367
- };
1368
- }
1369
-
1370
- createErrorResponse(id, code, message, data = null) {
1371
- const error = { code, message };
1372
- if (data) error.data = data;
1373
-
1374
- return {
1375
- jsonrpc: '2.0',
1376
- id,
1377
- error
1378
- };
1379
- }
1380
- }