cntx-ui 2.0.13 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +51 -339
  2. package/VISION.md +110 -0
  3. package/bin/cntx-ui-mcp.sh +3 -0
  4. package/bin/cntx-ui.js +138 -55
  5. package/lib/agent-runtime.js +301 -0
  6. package/lib/agent-tools.js +370 -0
  7. package/lib/api-router.js +1161 -0
  8. package/lib/bundle-manager.js +236 -0
  9. package/lib/configuration-manager.js +760 -0
  10. package/lib/database-manager.js +397 -0
  11. package/lib/file-system-manager.js +489 -0
  12. package/lib/heuristics-manager.js +527 -0
  13. package/lib/mcp-server.js +1125 -2
  14. package/lib/semantic-splitter.js +225 -491
  15. package/lib/simple-vector-store.js +98 -0
  16. package/lib/websocket-manager.js +470 -0
  17. package/package.json +19 -25
  18. package/server.js +742 -1935
  19. package/templates/TOOLS.md +41 -0
  20. package/templates/activities/README.md +67 -0
  21. package/templates/activities/activities/create-project-bundles/README.md +84 -0
  22. package/templates/activities/activities/create-project-bundles/notes.md +98 -0
  23. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  24. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  25. package/templates/activities/activities.json +219 -0
  26. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  27. package/templates/activities/lib/create-activity.mdc +63 -0
  28. package/templates/activities/lib/generate-tasks.mdc +64 -0
  29. package/templates/activities/lib/process-task-list.mdc +52 -0
  30. package/templates/agent-config.yaml +65 -0
  31. package/templates/agent-instructions.md +234 -0
  32. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  33. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  34. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  35. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  36. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  37. package/templates/agent-rules/core/response-formatting.md +120 -0
  38. package/templates/agent-rules/project-specific/architecture.md +145 -0
  39. package/templates/config.json +76 -0
  40. package/templates/hidden-files.json +14 -0
  41. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  42. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  43. package/web/dist/cntx-ui.svg +18 -0
  44. package/web/dist/index.html +25 -8
  45. package/lib/semantic-integration.js +0 -441
  46. package/mcp-config-example.json +0 -9
  47. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  48. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  49. package/web/dist/vite.svg +0 -21
package/lib/mcp-server.js CHANGED
@@ -1,5 +1,7 @@
1
- import { readFileSync } from 'fs';
2
- import { join, relative } from 'path';
1
+ import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync, copyFileSync } from 'fs';
2
+ import { join, relative, dirname } from 'path';
3
+ import fs from 'fs';
4
+ import AgentRuntime from './agent-runtime.js';
3
5
 
4
6
  export class MCPServer {
5
7
  constructor(cntxServer) {
@@ -9,6 +11,7 @@ export class MCPServer {
9
11
  name: 'cntx-ui',
10
12
  version: '2.0.8'
11
13
  };
14
+ this.agentRuntime = new AgentRuntime(cntxServer);
12
15
  }
13
16
 
14
17
  // JSON-RPC 2.0 message handler
@@ -52,6 +55,12 @@ export class MCPServer {
52
55
  case 'tools/call':
53
56
  return this.handleCallTool(params, id);
54
57
 
58
+ case 'prompts/list':
59
+ return this.handleListPrompts(id);
60
+
61
+ case 'prompts/get':
62
+ return this.handleGetPrompt(params, id);
63
+
55
64
  case 'prompts/list':
56
65
  return this.createErrorResponse(id, -32601, 'Method not found');
57
66
 
@@ -157,6 +166,62 @@ export class MCPServer {
157
166
  }
158
167
  }
159
168
 
169
+ // List available prompts
170
+ handleListPrompts(id) {
171
+ const prompts = [
172
+ {
173
+ name: 'onboard-me',
174
+ description: 'Guided walkthrough of the current codebase architecture.',
175
+ arguments: []
176
+ },
177
+ {
178
+ name: 'refactor-planner',
179
+ description: 'Plan a refactor by analyzing semantic dependencies.',
180
+ arguments: [
181
+ { name: 'target', description: 'The function or file to refactor', required: true }
182
+ ]
183
+ }
184
+ ];
185
+ return this.createSuccessResponse(id, { prompts });
186
+ }
187
+
188
+ // Get a specific prompt
189
+ handleGetPrompt(params, id) {
190
+ const { name, arguments: args } = params;
191
+
192
+ if (name === 'onboard-me') {
193
+ return this.createSuccessResponse(id, {
194
+ description: 'Codebase Onboarding',
195
+ messages: [
196
+ {
197
+ role: 'user',
198
+ content: {
199
+ type: 'text',
200
+ 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.'
201
+ }
202
+ }
203
+ ]
204
+ });
205
+ }
206
+
207
+ if (name === 'refactor-planner') {
208
+ return this.createSuccessResponse(id, {
209
+ description: 'Refactor Planning',
210
+ messages: [
211
+ {
212
+ role: 'user',
213
+ content: {
214
+ type: 'text',
215
+ 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.`
216
+ }
217
+ }
218
+ ]
219
+ });
220
+ }
221
+
222
+ return this.createErrorResponse(id, -32602, 'Prompt not found');
223
+ }
224
+
160
225
  // List available tools
161
226
  handleListTools(id) {
162
227
  const tools = [
@@ -214,6 +279,322 @@ export class MCPServer {
214
279
  properties: {},
215
280
  required: []
216
281
  }
282
+ },
283
+ {
284
+ name: 'get_semantic_chunks',
285
+ description: 'Get function-level semantic chunks from the codebase',
286
+ inputSchema: {
287
+ type: 'object',
288
+ properties: {},
289
+ required: []
290
+ }
291
+ },
292
+ {
293
+ name: 'get_semantic_chunks_filtered',
294
+ description: 'Get semantic chunks filtered by purpose, type, complexity, or bundle',
295
+ inputSchema: {
296
+ type: 'object',
297
+ properties: {
298
+ purpose: {
299
+ type: 'string',
300
+ description: 'Filter by function purpose (e.g., "API handler", "React component", "Data retrieval")'
301
+ },
302
+ type: {
303
+ type: 'string',
304
+ description: 'Filter by function type (e.g., "arrow_function", "react_component", "method")'
305
+ },
306
+ complexity: {
307
+ type: 'string',
308
+ description: 'Filter by complexity level ("low", "medium", "high")'
309
+ },
310
+ bundle: {
311
+ type: 'string',
312
+ description: 'Filter by bundle membership'
313
+ },
314
+ exported: {
315
+ type: 'boolean',
316
+ description: 'Filter by export status'
317
+ },
318
+ async: {
319
+ type: 'boolean',
320
+ description: 'Filter by async functions'
321
+ }
322
+ },
323
+ required: []
324
+ }
325
+ },
326
+ {
327
+ name: 'analyze_bundle_suggestions',
328
+ description: 'Analyze codebase and suggest optimal bundle organization based on semantic chunks',
329
+ inputSchema: {
330
+ type: 'object',
331
+ properties: {
332
+ max_suggestions: {
333
+ type: 'number',
334
+ description: 'Maximum number of bundle suggestions to return (default: 5)'
335
+ }
336
+ },
337
+ required: []
338
+ }
339
+ },
340
+ {
341
+ name: 'create_bundle',
342
+ description: 'Create a new bundle with specified patterns',
343
+ inputSchema: {
344
+ type: 'object',
345
+ properties: {
346
+ name: {
347
+ type: 'string',
348
+ description: 'Name of the new bundle'
349
+ },
350
+ patterns: {
351
+ type: 'array',
352
+ items: { type: 'string' },
353
+ description: 'Array of glob patterns for the bundle (e.g., ["src/api/**", "src/services/**"])'
354
+ },
355
+ description: {
356
+ type: 'string',
357
+ description: 'Optional description of the bundle purpose'
358
+ }
359
+ },
360
+ required: ['name', 'patterns']
361
+ }
362
+ },
363
+ {
364
+ name: 'update_bundle',
365
+ description: 'Update an existing bundle\'s patterns',
366
+ inputSchema: {
367
+ type: 'object',
368
+ properties: {
369
+ name: {
370
+ type: 'string',
371
+ description: 'Name of the bundle to update'
372
+ },
373
+ patterns: {
374
+ type: 'array',
375
+ items: { type: 'string' },
376
+ description: 'New array of glob patterns for the bundle'
377
+ },
378
+ description: {
379
+ type: 'string',
380
+ description: 'Optional updated description'
381
+ }
382
+ },
383
+ required: ['name', 'patterns']
384
+ }
385
+ },
386
+ {
387
+ name: 'delete_bundle',
388
+ description: 'Delete an existing bundle',
389
+ inputSchema: {
390
+ type: 'object',
391
+ properties: {
392
+ name: {
393
+ type: 'string',
394
+ description: 'Name of the bundle to delete'
395
+ }
396
+ },
397
+ required: ['name']
398
+ }
399
+ },
400
+ {
401
+ name: 'update_cntxignore',
402
+ description: 'Update the .cntxignore file with new ignore patterns',
403
+ inputSchema: {
404
+ type: 'object',
405
+ properties: {
406
+ content: {
407
+ type: 'string',
408
+ description: 'Full content for the .cntxignore file (newline-separated patterns)'
409
+ }
410
+ },
411
+ required: ['content']
412
+ }
413
+ },
414
+ {
415
+ name: 'agent_discover',
416
+ description: 'Agent Discovery Mode: Get comprehensive codebase overview including bundles, architecture, and patterns',
417
+ inputSchema: {
418
+ type: 'object',
419
+ properties: {
420
+ scope: {
421
+ type: 'string',
422
+ description: 'Scope of discovery: "all" for full codebase or specific bundle name (default: "all")'
423
+ },
424
+ includeDetails: {
425
+ type: 'boolean',
426
+ description: 'Include detailed semantic analysis and complexity metrics (default: true)'
427
+ }
428
+ },
429
+ required: []
430
+ }
431
+ },
432
+ {
433
+ name: 'agent_query',
434
+ description: 'Agent Query Mode: Answer specific questions about the codebase using semantic search and analysis',
435
+ inputSchema: {
436
+ type: 'object',
437
+ properties: {
438
+ question: {
439
+ type: 'string',
440
+ description: 'The question to answer about the codebase (e.g., "Where is user authentication handled?")'
441
+ },
442
+ scope: {
443
+ type: 'string',
444
+ description: 'Optional bundle to limit search scope'
445
+ },
446
+ maxResults: {
447
+ type: 'number',
448
+ description: 'Maximum number of results to return (default: 10)'
449
+ },
450
+ includeCode: {
451
+ type: 'boolean',
452
+ description: 'Include code snippets in the response (default: false)'
453
+ }
454
+ },
455
+ required: ['question']
456
+ }
457
+ },
458
+ {
459
+ name: 'agent_investigate',
460
+ description: 'Agent Investigation Mode: Investigate existing implementations for a feature and find integration points',
461
+ inputSchema: {
462
+ type: 'object',
463
+ properties: {
464
+ featureDescription: {
465
+ type: 'string',
466
+ description: 'Description of the feature to investigate (e.g., "dark mode", "user authentication", "form validation")'
467
+ },
468
+ includeRecommendations: {
469
+ type: 'boolean',
470
+ description: 'Include implementation recommendations and approach suggestions (default: true)'
471
+ }
472
+ },
473
+ required: ['featureDescription']
474
+ }
475
+ },
476
+ {
477
+ name: 'agent_discuss',
478
+ description: 'Agent Passive Mode: Engage in discussion about codebase architecture, design decisions, and planning',
479
+ inputSchema: {
480
+ type: 'object',
481
+ properties: {
482
+ userInput: {
483
+ type: 'string',
484
+ description: 'The topic or question for discussion (e.g., "Let\'s discuss the architecture before I make changes")'
485
+ },
486
+ context: {
487
+ type: 'object',
488
+ description: 'Additional context for the discussion',
489
+ properties: {
490
+ scope: {
491
+ type: 'string',
492
+ description: 'Specific area of focus (e.g., "frontend", "api", "database")'
493
+ }
494
+ }
495
+ }
496
+ },
497
+ required: ['userInput']
498
+ }
499
+ },
500
+ {
501
+ name: 'agent_organize',
502
+ description: 'Agent Project Organizer Mode: Setup and maintenance of project organization - adapts to project maturity',
503
+ inputSchema: {
504
+ type: 'object',
505
+ properties: {
506
+ activity: {
507
+ type: 'string',
508
+ enum: ['detect', 'analyze', 'bundle', 'create', 'optimize', 'audit', 'cleanup', 'validate'],
509
+ description: 'Activity to perform: detect project state, analyze semantics, suggest bundles, create bundles, optimize organization, audit health, cleanup issues, or validate structure'
510
+ },
511
+ autoDetect: {
512
+ type: 'boolean',
513
+ description: 'Automatically detect appropriate activity based on project state (default: true)',
514
+ default: true
515
+ },
516
+ force: {
517
+ type: 'boolean',
518
+ description: 'Force execution even if preconditions are not met (default: false)',
519
+ default: false
520
+ }
521
+ },
522
+ required: []
523
+ }
524
+ },
525
+ {
526
+ name: 'read_file',
527
+ description: 'Read contents of a specific file with bundle context and metadata',
528
+ inputSchema: {
529
+ type: 'object',
530
+ properties: {
531
+ path: {
532
+ type: 'string',
533
+ description: 'File path relative to project root'
534
+ },
535
+ includeMetadata: {
536
+ type: 'boolean',
537
+ description: 'Include file metadata (size, bundles, etc.) - default: true'
538
+ }
539
+ },
540
+ required: ['path']
541
+ }
542
+ },
543
+ {
544
+ name: 'write_file',
545
+ description: 'Write content to a file with validation and safety checks',
546
+ inputSchema: {
547
+ type: 'object',
548
+ properties: {
549
+ path: {
550
+ type: 'string',
551
+ description: 'File path relative to project root'
552
+ },
553
+ content: {
554
+ type: 'string',
555
+ description: 'Content to write to the file'
556
+ },
557
+ backup: {
558
+ type: 'boolean',
559
+ description: 'Create backup before writing - default: true'
560
+ },
561
+ createDirs: {
562
+ type: 'boolean',
563
+ description: 'Create parent directories if they don\'t exist - default: true'
564
+ }
565
+ },
566
+ required: ['path', 'content']
567
+ }
568
+ },
569
+ {
570
+ name: 'manage_activities',
571
+ description: 'CRUD operations for project activities',
572
+ inputSchema: {
573
+ type: 'object',
574
+ properties: {
575
+ action: {
576
+ type: 'string',
577
+ enum: ['list', 'get', 'create', 'update', 'delete'],
578
+ description: 'Action to perform on activities'
579
+ },
580
+ activityId: {
581
+ type: 'string',
582
+ description: 'Activity ID (required for get, update, delete)'
583
+ },
584
+ activity: {
585
+ type: 'object',
586
+ description: 'Activity data (required for create, update)',
587
+ properties: {
588
+ title: { type: 'string' },
589
+ description: { type: 'string' },
590
+ status: { type: 'string', enum: ['todo', 'in_progress', 'completed', 'blocked'] },
591
+ tags: { type: 'array', items: { type: 'string' } },
592
+ tasks: { type: 'array', items: { type: 'object' } }
593
+ }
594
+ }
595
+ },
596
+ required: ['action']
597
+ }
217
598
  }
218
599
  ];
219
600
 
@@ -241,6 +622,51 @@ export class MCPServer {
241
622
  case 'get_project_status':
242
623
  return this.toolGetProjectStatus(id);
243
624
 
625
+ case 'get_semantic_chunks':
626
+ return this.toolGetSemanticChunks(id);
627
+
628
+ case 'get_semantic_chunks_filtered':
629
+ return this.toolGetSemanticChunksFiltered(args, id);
630
+
631
+ case 'analyze_bundle_suggestions':
632
+ return this.toolAnalyzeBundleSuggestions(args, id);
633
+
634
+ case 'create_bundle':
635
+ return this.toolCreateBundle(args, id);
636
+
637
+ case 'update_bundle':
638
+ return this.toolUpdateBundle(args, id);
639
+
640
+ case 'delete_bundle':
641
+ return this.toolDeleteBundle(args, id);
642
+
643
+ case 'update_cntxignore':
644
+ return this.toolUpdateCntxignore(args, id);
645
+
646
+ case 'agent_discover':
647
+ return this.toolAgentDiscover(args, id);
648
+
649
+ case 'agent_query':
650
+ return this.toolAgentQuery(args, id);
651
+
652
+ case 'agent_investigate':
653
+ return this.toolAgentInvestigate(args, id);
654
+
655
+ case 'agent_discuss':
656
+ return this.toolAgentDiscuss(args, id);
657
+
658
+ case 'agent_organize':
659
+ return this.toolAgentOrganize(args, id);
660
+
661
+ case 'read_file':
662
+ return this.toolReadFile(args, id);
663
+
664
+ case 'write_file':
665
+ return this.toolWriteFile(args, id);
666
+
667
+ case 'manage_activities':
668
+ return this.toolManageActivities(args, id);
669
+
244
670
  default:
245
671
  return this.createErrorResponse(id, -32602, 'Unknown tool');
246
672
  }
@@ -347,6 +773,703 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
347
773
  });
348
774
  }
349
775
 
776
+ // New semantic chunks tools
777
+ async toolGetSemanticChunks(id) {
778
+ try {
779
+ const analysis = await this.cntxServer.getSemanticAnalysis();
780
+
781
+ // Clean the analysis data to prevent JSON issues
782
+ const cleanAnalysis = {
783
+ ...analysis,
784
+ chunks: analysis.chunks?.map(chunk => ({
785
+ ...chunk,
786
+ code: chunk.code ? chunk.code.substring(0, 500) + (chunk.code.length > 500 ? '...' : '') : '',
787
+ bundles: chunk.bundles || [],
788
+ includes: {
789
+ imports: chunk.includes?.imports || [],
790
+ types: chunk.includes?.types || []
791
+ }
792
+ })) || []
793
+ };
794
+
795
+ return this.createSuccessResponse(id, {
796
+ content: [{
797
+ type: 'text',
798
+ text: JSON.stringify(cleanAnalysis, null, 2)
799
+ }]
800
+ });
801
+ } catch (error) {
802
+ return this.createErrorResponse(id, -32603, 'Failed to get semantic chunks', error.message);
803
+ }
804
+ }
805
+
806
+ async toolGetSemanticChunksFiltered(args, id) {
807
+ try {
808
+ const analysis = await this.cntxServer.getSemanticAnalysis();
809
+ let chunks = analysis.chunks || [];
810
+
811
+ // Apply filters
812
+ if (args.purpose) {
813
+ chunks = chunks.filter(chunk =>
814
+ chunk.purpose && chunk.purpose.toLowerCase().includes(args.purpose.toLowerCase())
815
+ );
816
+ }
817
+
818
+ if (args.type) {
819
+ chunks = chunks.filter(chunk => chunk.subtype === args.type);
820
+ }
821
+
822
+ if (args.complexity) {
823
+ chunks = chunks.filter(chunk => chunk.complexity?.level === args.complexity);
824
+ }
825
+
826
+ if (args.bundle) {
827
+ chunks = chunks.filter(chunk =>
828
+ chunk.bundles && chunk.bundles.includes(args.bundle)
829
+ );
830
+ }
831
+
832
+ if (args.exported !== undefined) {
833
+ chunks = chunks.filter(chunk => chunk.isExported === args.exported);
834
+ }
835
+
836
+ if (args.async !== undefined) {
837
+ chunks = chunks.filter(chunk => chunk.isAsync === args.async);
838
+ }
839
+
840
+ // Clean chunks for JSON safety
841
+ const cleanChunks = chunks.map(chunk => ({
842
+ ...chunk,
843
+ code: chunk.code ? chunk.code.substring(0, 300) + (chunk.code.length > 300 ? '...' : '') : '',
844
+ bundles: chunk.bundles || [],
845
+ includes: {
846
+ imports: chunk.includes?.imports || [],
847
+ types: chunk.includes?.types || []
848
+ }
849
+ }));
850
+
851
+ const filteredAnalysis = {
852
+ ...analysis,
853
+ chunks: cleanChunks,
854
+ summary: {
855
+ ...analysis.summary,
856
+ totalChunks: cleanChunks.length,
857
+ filteredCount: cleanChunks.length,
858
+ originalCount: analysis.chunks?.length || 0
859
+ }
860
+ };
861
+
862
+ return this.createSuccessResponse(id, {
863
+ content: [{
864
+ type: 'text',
865
+ text: JSON.stringify(filteredAnalysis, null, 2)
866
+ }]
867
+ });
868
+ } catch (error) {
869
+ return this.createErrorResponse(id, -32603, 'Failed to filter semantic chunks', error.message);
870
+ }
871
+ }
872
+
873
+ async toolAnalyzeBundleSuggestions(args, id) {
874
+ try {
875
+ const analysis = await this.cntxServer.getSemanticAnalysis();
876
+ const chunks = analysis.chunks || [];
877
+ const maxSuggestions = args.max_suggestions || 5;
878
+
879
+ // Group chunks by purpose and file location
880
+ const purposeGroups = {};
881
+ const locationGroups = {};
882
+
883
+ chunks.forEach(chunk => {
884
+ // Group by purpose
885
+ if (!purposeGroups[chunk.purpose]) {
886
+ purposeGroups[chunk.purpose] = [];
887
+ }
888
+ purposeGroups[chunk.purpose].push(chunk);
889
+
890
+ // Group by file location patterns
891
+ const pathParts = chunk.filePath.split('/');
892
+ if (pathParts.length > 1) {
893
+ const dirPattern = pathParts.slice(0, -1).join('/') + '/**';
894
+ if (!locationGroups[dirPattern]) {
895
+ locationGroups[dirPattern] = [];
896
+ }
897
+ locationGroups[dirPattern].push(chunk);
898
+ }
899
+ });
900
+
901
+ const suggestions = [];
902
+
903
+ // Suggest bundles by purpose
904
+ Object.entries(purposeGroups).forEach(([purpose, chunks]) => {
905
+ if (chunks.length >= 3) { // Only suggest if enough functions
906
+ const bundleName = purpose.toLowerCase().replace(/\s+/g, '-');
907
+ const patterns = [...new Set(chunks.map(c => {
908
+ const dir = c.filePath.split('/').slice(0, -1).join('/');
909
+ return dir ? `${dir}/**` : c.filePath;
910
+ }))];
911
+
912
+ suggestions.push({
913
+ name: bundleName,
914
+ reason: `Groups ${chunks.length} functions with purpose: ${purpose}`,
915
+ patterns,
916
+ chunkCount: chunks.length,
917
+ files: [...new Set(chunks.map(c => c.filePath))]
918
+ });
919
+ }
920
+ });
921
+
922
+ // Suggest bundles by common directory patterns
923
+ Object.entries(locationGroups).forEach(([pattern, chunks]) => {
924
+ if (chunks.length >= 5) { // Only suggest if enough functions in same location
925
+ const dirName = pattern.split('/').pop().replace('/**', '');
926
+ const bundleName = dirName === '*' ? 'utils' : dirName;
927
+
928
+ suggestions.push({
929
+ name: bundleName,
930
+ reason: `Groups ${chunks.length} functions from ${pattern}`,
931
+ patterns: [pattern],
932
+ chunkCount: chunks.length,
933
+ files: [...new Set(chunks.map(c => c.filePath))]
934
+ });
935
+ }
936
+ });
937
+
938
+ // Sort by chunk count and take top suggestions
939
+ const topSuggestions = suggestions
940
+ .sort((a, b) => b.chunkCount - a.chunkCount)
941
+ .slice(0, maxSuggestions);
942
+
943
+ const result = {
944
+ totalSuggestions: suggestions.length,
945
+ suggestions: topSuggestions,
946
+ analysis: {
947
+ totalChunks: chunks.length,
948
+ purposeGroups: Object.keys(purposeGroups).length,
949
+ locationGroups: Object.keys(locationGroups).length
950
+ }
951
+ };
952
+
953
+ return this.createSuccessResponse(id, {
954
+ content: [{
955
+ type: 'text',
956
+ text: JSON.stringify(result, null, 2)
957
+ }]
958
+ });
959
+ } catch (error) {
960
+ return this.createErrorResponse(id, -32603, 'Failed to analyze bundle suggestions', error.message);
961
+ }
962
+ }
963
+
964
+ // Bundle management tools
965
+ async toolCreateBundle(args, id) {
966
+ try {
967
+ const { name, patterns, description } = args;
968
+
969
+ if (!name || !patterns || !Array.isArray(patterns)) {
970
+ return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
971
+ }
972
+
973
+ // Prevent overwriting existing bundles
974
+ if (this.cntxServer.bundles.has(name)) {
975
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' already exists`);
976
+ }
977
+
978
+ // Create bundle in bundle-states.json (single source of truth)
979
+ this.cntxServer.configManager.bundleStates.set(name, {
980
+ patterns: patterns,
981
+ files: [],
982
+ content: '',
983
+ changed: false,
984
+ size: 0,
985
+ generated: null
986
+ });
987
+
988
+ // Save bundle states
989
+ this.cntxServer.configManager.saveBundleStates();
990
+
991
+ // Regenerate bundles
992
+ this.cntxServer.generateAllBundles();
993
+
994
+ const result = {
995
+ success: true,
996
+ bundle: {
997
+ name,
998
+ patterns,
999
+ description,
1000
+ created: new Date().toISOString()
1001
+ }
1002
+ };
1003
+
1004
+ return this.createSuccessResponse(id, {
1005
+ content: [{
1006
+ type: 'text',
1007
+ text: JSON.stringify(result, null, 2)
1008
+ }]
1009
+ });
1010
+ } catch (error) {
1011
+ return this.createErrorResponse(id, -32603, 'Failed to create bundle', error.message);
1012
+ }
1013
+ }
1014
+
1015
+ async toolUpdateBundle(args, id) {
1016
+ try {
1017
+ const { name, patterns, description } = args;
1018
+
1019
+ if (!name || !patterns || !Array.isArray(patterns)) {
1020
+ return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
1021
+ }
1022
+
1023
+ // Check if bundle exists
1024
+ if (!this.cntxServer.bundles.has(name)) {
1025
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
1026
+ }
1027
+
1028
+ // Prevent updating master bundle
1029
+ if (name === 'master') {
1030
+ return this.createErrorResponse(id, -32602, 'Cannot update master bundle');
1031
+ }
1032
+
1033
+ // Update bundle in bundle-states.json (single source of truth)
1034
+ const bundle = this.cntxServer.configManager.bundleStates.get(name);
1035
+ bundle.patterns = patterns;
1036
+ bundle.changed = true;
1037
+
1038
+ // Save bundle states
1039
+ this.cntxServer.configManager.saveBundleStates();
1040
+
1041
+ // Regenerate bundles
1042
+ this.cntxServer.generateAllBundles();
1043
+
1044
+ const result = {
1045
+ success: true,
1046
+ bundle: {
1047
+ name,
1048
+ patterns,
1049
+ description,
1050
+ updated: new Date().toISOString()
1051
+ }
1052
+ };
1053
+
1054
+ return this.createSuccessResponse(id, {
1055
+ content: [{
1056
+ type: 'text',
1057
+ text: JSON.stringify(result, null, 2)
1058
+ }]
1059
+ });
1060
+ } catch (error) {
1061
+ return this.createErrorResponse(id, -32603, 'Failed to update bundle', error.message);
1062
+ }
1063
+ }
1064
+
1065
+ async toolDeleteBundle(args, id) {
1066
+ try {
1067
+ const { name } = args;
1068
+
1069
+ if (!name) {
1070
+ return this.createErrorResponse(id, -32602, 'Bundle name required');
1071
+ }
1072
+
1073
+ // Check if bundle exists
1074
+ if (!this.cntxServer.bundles.has(name)) {
1075
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
1076
+ }
1077
+
1078
+ // Prevent deleting master bundle
1079
+ if (name === 'master') {
1080
+ return this.createErrorResponse(id, -32602, 'Cannot delete master bundle');
1081
+ }
1082
+
1083
+ // Remove bundle from bundle-states.json (single source of truth)
1084
+ this.cntxServer.configManager.bundleStates.delete(name);
1085
+
1086
+ // Save bundle states
1087
+ this.cntxServer.configManager.saveBundleStates();
1088
+
1089
+ // Regenerate bundles
1090
+ this.cntxServer.generateAllBundles();
1091
+
1092
+ const result = {
1093
+ success: true,
1094
+ deleted: name,
1095
+ timestamp: new Date().toISOString()
1096
+ };
1097
+
1098
+ return this.createSuccessResponse(id, {
1099
+ content: [{
1100
+ type: 'text',
1101
+ text: JSON.stringify(result, null, 2)
1102
+ }]
1103
+ });
1104
+ } catch (error) {
1105
+ return this.createErrorResponse(id, -32603, 'Failed to delete bundle', error.message);
1106
+ }
1107
+ }
1108
+
1109
+ async toolUpdateCntxignore(args, id) {
1110
+ try {
1111
+ const { content } = args;
1112
+
1113
+ if (content === undefined) {
1114
+ return this.createErrorResponse(id, -32602, 'Content required');
1115
+ }
1116
+
1117
+ const ignorePath = join(this.cntxServer.CWD, '.cntxignore');
1118
+
1119
+ // Write the .cntxignore file
1120
+ writeFileSync(ignorePath, content);
1121
+
1122
+ // Reload ignore patterns
1123
+ this.cntxServer.loadIgnorePatterns();
1124
+ this.cntxServer.generateAllBundles();
1125
+
1126
+ const result = {
1127
+ success: true,
1128
+ file: '.cntxignore',
1129
+ lines: content.split('\n').length,
1130
+ patterns: content.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')).length,
1131
+ updated: new Date().toISOString()
1132
+ };
1133
+
1134
+ return this.createSuccessResponse(id, {
1135
+ content: [{
1136
+ type: 'text',
1137
+ text: JSON.stringify(result, null, 2)
1138
+ }]
1139
+ });
1140
+ } catch (error) {
1141
+ return this.createErrorResponse(id, -32603, 'Failed to update .cntxignore', error.message);
1142
+ }
1143
+ }
1144
+
1145
+ // Agent Tools Implementation
1146
+ async toolAgentDiscover(args, id) {
1147
+ try {
1148
+ const { scope = 'all', includeDetails = true } = args;
1149
+ const result = await this.agentRuntime.discoverCodebase({ scope, includeDetails });
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 discovery failed', error.message);
1159
+ }
1160
+ }
1161
+
1162
+ async toolAgentQuery(args, id) {
1163
+ try {
1164
+ const { question, scope, maxResults = 10, includeCode = false } = args;
1165
+
1166
+ if (!question) {
1167
+ return this.createErrorResponse(id, -32602, 'Question is required');
1168
+ }
1169
+
1170
+ const result = await this.agentRuntime.answerQuery(question, { scope, maxResults, includeCode });
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 query failed', error.message);
1180
+ }
1181
+ }
1182
+
1183
+ async toolAgentInvestigate(args, id) {
1184
+ try {
1185
+ const { featureDescription, includeRecommendations = true } = args;
1186
+
1187
+ if (!featureDescription) {
1188
+ return this.createErrorResponse(id, -32602, 'Feature description is required');
1189
+ }
1190
+
1191
+ const result = await this.agentRuntime.investigateFeature(featureDescription, { includeRecommendations });
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 investigation failed', error.message);
1201
+ }
1202
+ }
1203
+
1204
+ async toolAgentDiscuss(args, id) {
1205
+ try {
1206
+ const { userInput, context = {} } = args;
1207
+
1208
+ if (!userInput) {
1209
+ return this.createErrorResponse(id, -32602, 'User input is required');
1210
+ }
1211
+
1212
+ const result = await this.agentRuntime.discussAndPlan(userInput, context);
1213
+
1214
+ return this.createSuccessResponse(id, {
1215
+ content: [{
1216
+ type: 'text',
1217
+ text: JSON.stringify(result, null, 2)
1218
+ }]
1219
+ });
1220
+ } catch (error) {
1221
+ return this.createErrorResponse(id, -32603, 'Agent discussion failed', error.message);
1222
+ }
1223
+ }
1224
+
1225
+ async toolAgentOrganize(args, id) {
1226
+ try {
1227
+ const { activity = 'detect', autoDetect = true, force = false } = args;
1228
+
1229
+ const result = await this.agentRuntime.organizeProject({ activity, autoDetect, force });
1230
+
1231
+ return this.createSuccessResponse(id, {
1232
+ content: [{
1233
+ type: 'text',
1234
+ text: JSON.stringify(result, null, 2)
1235
+ }]
1236
+ });
1237
+ } catch (error) {
1238
+ return this.createErrorResponse(id, -32603, 'Agent organization failed', error.message);
1239
+ }
1240
+ }
1241
+
1242
+ // New tool implementations
1243
+ async toolReadFile(args, id) {
1244
+ const { path: filePath, includeMetadata = true } = args;
1245
+
1246
+ if (!filePath) {
1247
+ return this.createErrorResponse(id, -32602, 'Path is required');
1248
+ }
1249
+
1250
+ try {
1251
+ const fullPath = join(this.cntxServer.CWD, filePath);
1252
+
1253
+ if (!existsSync(fullPath)) {
1254
+ return this.createErrorResponse(id, -32602, `File not found: ${filePath}`);
1255
+ }
1256
+
1257
+ const content = readFileSync(fullPath, 'utf8');
1258
+
1259
+ // Fetch semantic context for this file
1260
+ const chunks = this.cntxServer.databaseManager.getChunksByFile(filePath);
1261
+ const totalComplexity = chunks.reduce((sum, c) => sum + (c.complexity?.score || 0), 0);
1262
+ const avgComplexity = chunks.length > 0 ? Math.round(totalComplexity / chunks.length) : 0;
1263
+
1264
+ let semanticHeader = `--- SEMANTIC CONTEXT ---\n`;
1265
+ semanticHeader += `File importance: ${chunks.length} semantic chunks found.\n`;
1266
+ semanticHeader += `Aggregate Complexity: ${totalComplexity} (Avg: ${avgComplexity})\n`;
1267
+ if (chunks.length > 0) {
1268
+ semanticHeader += `Primary purposes: ${[...new Set(chunks.map(c => c.purpose))].join(', ')}\n`;
1269
+ }
1270
+ semanticHeader += `------------------------\n\n`;
1271
+
1272
+ const result = {
1273
+ path: filePath,
1274
+ content: semanticHeader + content
1275
+ };
1276
+
1277
+ if (includeMetadata) {
1278
+ const stats = fs.statSync(fullPath);
1279
+ const bundles = [];
1280
+
1281
+ // Find which bundles include this file
1282
+ this.cntxServer.bundles.forEach((bundle, name) => {
1283
+ if (bundle.files && bundle.files.includes(filePath)) {
1284
+ bundles.push(name);
1285
+ }
1286
+ });
1287
+
1288
+ result.metadata = {
1289
+ size: stats.size,
1290
+ mimeType: this.getMimeType(filePath),
1291
+ modified: stats.mtime.toISOString(),
1292
+ lines: content.split('\n').length,
1293
+ bundles: bundles
1294
+ };
1295
+ }
1296
+
1297
+ return this.createSuccessResponse(id, {
1298
+ content: [{
1299
+ type: 'text',
1300
+ text: JSON.stringify(result, null, 2)
1301
+ }]
1302
+ });
1303
+ } catch (error) {
1304
+ return this.createErrorResponse(id, -32603, 'Failed to read file', error.message);
1305
+ }
1306
+ }
1307
+
1308
+ async toolWriteFile(args, id) {
1309
+ const { path: filePath, content, backup = true, createDirs = true } = args;
1310
+
1311
+ if (!filePath || content === undefined) {
1312
+ return this.createErrorResponse(id, -32602, 'Path and content are required');
1313
+ }
1314
+
1315
+ try {
1316
+ const fullPath = join(this.cntxServer.CWD, filePath);
1317
+ const parentDir = dirname(fullPath);
1318
+
1319
+ // Create parent directories if needed
1320
+ if (createDirs && !existsSync(parentDir)) {
1321
+ fs.mkdirSync(parentDir, { recursive: true });
1322
+ }
1323
+
1324
+ // Create backup if file exists
1325
+ if (backup && existsSync(fullPath)) {
1326
+ const backupPath = `${fullPath}.backup.${Date.now()}`;
1327
+ fs.copyFileSync(fullPath, backupPath);
1328
+ }
1329
+
1330
+ // Write the file
1331
+ writeFileSync(fullPath, content, 'utf8');
1332
+
1333
+ // Mark relevant bundles as changed
1334
+ this.cntxServer.bundles.forEach((bundle, name) => {
1335
+ if (bundle.patterns && bundle.patterns.some(pattern =>
1336
+ this.cntxServer.fileSystemManager.matchesPattern(fullPath, pattern)
1337
+ )) {
1338
+ bundle.changed = true;
1339
+ }
1340
+ });
1341
+
1342
+ const stats = fs.statSync(fullPath);
1343
+
1344
+ return this.createSuccessResponse(id, {
1345
+ content: [{
1346
+ type: 'text',
1347
+ text: JSON.stringify({
1348
+ path: filePath,
1349
+ written: true,
1350
+ size: stats.size,
1351
+ modified: stats.mtime.toISOString()
1352
+ }, null, 2)
1353
+ }]
1354
+ });
1355
+ } catch (error) {
1356
+ return this.createErrorResponse(id, -32603, 'Failed to write file', error.message);
1357
+ }
1358
+ }
1359
+
1360
+ async toolManageActivities(args, id) {
1361
+ const { action, activityId, activity } = args;
1362
+
1363
+ if (!action) {
1364
+ return this.createErrorResponse(id, -32602, 'Action is required');
1365
+ }
1366
+
1367
+ try {
1368
+ const activitiesPath = join(this.cntxServer.CWD, '.cntx', 'activities');
1369
+ const activitiesJsonPath = join(activitiesPath, 'activities.json');
1370
+
1371
+ let activities = [];
1372
+ if (existsSync(activitiesJsonPath)) {
1373
+ activities = JSON.parse(readFileSync(activitiesJsonPath, 'utf8'));
1374
+ }
1375
+
1376
+ let result;
1377
+
1378
+ switch (action) {
1379
+ case 'list':
1380
+ result = { activities: activities.map(a => ({
1381
+ id: a.title.toLowerCase().replace(/[^a-z0-9]/g, '-'),
1382
+ title: a.title,
1383
+ description: a.description,
1384
+ status: a.status,
1385
+ tags: a.tags
1386
+ })) };
1387
+ break;
1388
+
1389
+ case 'get':
1390
+ if (!activityId) {
1391
+ return this.createErrorResponse(id, -32602, 'Activity ID is required for get action');
1392
+ }
1393
+ const found = activities.find(a =>
1394
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1395
+ );
1396
+ if (!found) {
1397
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1398
+ }
1399
+
1400
+ // Load markdown files
1401
+ const activityDir = join(activitiesPath, 'activities', activityId);
1402
+ const files = {};
1403
+ ['README.md', 'progress.md', 'tasks.md', 'notes.md'].forEach(file => {
1404
+ const filePath = join(activityDir, file);
1405
+ files[file.replace('.md', '')] = existsSync(filePath)
1406
+ ? readFileSync(filePath, 'utf8')
1407
+ : 'No content available';
1408
+ });
1409
+
1410
+ result = { ...found, files };
1411
+ break;
1412
+
1413
+ case 'create':
1414
+ if (!activity || !activity.title) {
1415
+ return this.createErrorResponse(id, -32602, 'Activity with title is required for create action');
1416
+ }
1417
+ activities.push({
1418
+ title: activity.title,
1419
+ description: activity.description || '',
1420
+ status: activity.status || 'todo',
1421
+ tags: activity.tags || ['general'],
1422
+ tasks: activity.tasks || []
1423
+ });
1424
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1425
+ result = { created: true, activityId: activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-') };
1426
+ break;
1427
+
1428
+ case 'update':
1429
+ if (!activityId || !activity) {
1430
+ return this.createErrorResponse(id, -32602, 'Activity ID and activity data are required for update action');
1431
+ }
1432
+ const updateIndex = activities.findIndex(a =>
1433
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1434
+ );
1435
+ if (updateIndex === -1) {
1436
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1437
+ }
1438
+ activities[updateIndex] = { ...activities[updateIndex], ...activity };
1439
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1440
+ result = { updated: true };
1441
+ break;
1442
+
1443
+ case 'delete':
1444
+ if (!activityId) {
1445
+ return this.createErrorResponse(id, -32602, 'Activity ID is required for delete action');
1446
+ }
1447
+ const deleteIndex = activities.findIndex(a =>
1448
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1449
+ );
1450
+ if (deleteIndex === -1) {
1451
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1452
+ }
1453
+ activities.splice(deleteIndex, 1);
1454
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1455
+ result = { deleted: true };
1456
+ break;
1457
+
1458
+ default:
1459
+ return this.createErrorResponse(id, -32602, `Unknown action: ${action}`);
1460
+ }
1461
+
1462
+ return this.createSuccessResponse(id, {
1463
+ content: [{
1464
+ type: 'text',
1465
+ text: JSON.stringify(result, null, 2)
1466
+ }]
1467
+ });
1468
+ } catch (error) {
1469
+ return this.createErrorResponse(id, -32603, 'Failed to manage activities', error.message);
1470
+ }
1471
+ }
1472
+
350
1473
  // Helper methods
351
1474
  getMimeType(filePath) {
352
1475
  const ext = filePath.split('.').pop()?.toLowerCase();