cntx-ui 2.0.13 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/bin/cntx-ui.js +137 -55
  2. package/lib/agent-runtime.js +1480 -0
  3. package/lib/agent-tools.js +368 -0
  4. package/lib/api-router.js +978 -0
  5. package/lib/bundle-manager.js +471 -0
  6. package/lib/configuration-manager.js +725 -0
  7. package/lib/file-system-manager.js +472 -0
  8. package/lib/heuristics-manager.js +425 -0
  9. package/lib/mcp-server.js +1054 -1
  10. package/lib/semantic-splitter.js +7 -14
  11. package/lib/simple-vector-store.js +329 -0
  12. package/lib/websocket-manager.js +470 -0
  13. package/package.json +10 -3
  14. package/server.js +662 -1933
  15. package/templates/activities/README.md +67 -0
  16. package/templates/activities/activities/create-project-bundles/README.md +83 -0
  17. package/templates/activities/activities/create-project-bundles/notes.md +102 -0
  18. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  19. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  20. package/templates/activities/activities.json +219 -0
  21. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  22. package/templates/activities/lib/create-activity.mdc +63 -0
  23. package/templates/activities/lib/generate-tasks.mdc +64 -0
  24. package/templates/activities/lib/process-task-list.mdc +52 -0
  25. package/templates/agent-config.yaml +78 -0
  26. package/templates/agent-instructions.md +218 -0
  27. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  28. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  29. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  30. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  31. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  32. package/templates/agent-rules/core/response-formatting.md +120 -0
  33. package/templates/agent-rules/project-specific/architecture.md +145 -0
  34. package/templates/config.json +76 -0
  35. package/templates/hidden-files.json +14 -0
  36. package/web/dist/assets/heuristics-manager-browser-DfonOP5I.js +1 -0
  37. package/web/dist/assets/index-dF3qg-y_.js +2486 -0
  38. package/web/dist/assets/index-h5FGSg_P.css +1 -0
  39. package/web/dist/cntx-ui.svg +18 -0
  40. package/web/dist/index.html +25 -8
  41. package/lib/semantic-integration.js +0 -441
  42. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  43. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  44. package/web/dist/vite.svg +0 -21
package/lib/mcp-server.js CHANGED
@@ -1,5 +1,6 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
2
  import { join, relative } from 'path';
3
+ import AgentRuntime from './agent-runtime.js';
3
4
 
4
5
  export class MCPServer {
5
6
  constructor(cntxServer) {
@@ -9,6 +10,7 @@ export class MCPServer {
9
10
  name: 'cntx-ui',
10
11
  version: '2.0.8'
11
12
  };
13
+ this.agentRuntime = new AgentRuntime(cntxServer);
12
14
  }
13
15
 
14
16
  // JSON-RPC 2.0 message handler
@@ -214,6 +216,322 @@ export class MCPServer {
214
216
  properties: {},
215
217
  required: []
216
218
  }
219
+ },
220
+ {
221
+ name: 'get_semantic_chunks',
222
+ description: 'Get function-level semantic chunks from the codebase',
223
+ inputSchema: {
224
+ type: 'object',
225
+ properties: {},
226
+ required: []
227
+ }
228
+ },
229
+ {
230
+ name: 'get_semantic_chunks_filtered',
231
+ description: 'Get semantic chunks filtered by purpose, type, complexity, or bundle',
232
+ inputSchema: {
233
+ type: 'object',
234
+ properties: {
235
+ purpose: {
236
+ type: 'string',
237
+ description: 'Filter by function purpose (e.g., "API handler", "React component", "Data retrieval")'
238
+ },
239
+ type: {
240
+ type: 'string',
241
+ description: 'Filter by function type (e.g., "arrow_function", "react_component", "method")'
242
+ },
243
+ complexity: {
244
+ type: 'string',
245
+ description: 'Filter by complexity level ("low", "medium", "high")'
246
+ },
247
+ bundle: {
248
+ type: 'string',
249
+ description: 'Filter by bundle membership'
250
+ },
251
+ exported: {
252
+ type: 'boolean',
253
+ description: 'Filter by export status'
254
+ },
255
+ async: {
256
+ type: 'boolean',
257
+ description: 'Filter by async functions'
258
+ }
259
+ },
260
+ required: []
261
+ }
262
+ },
263
+ {
264
+ name: 'analyze_bundle_suggestions',
265
+ description: 'Analyze codebase and suggest optimal bundle organization based on semantic chunks',
266
+ inputSchema: {
267
+ type: 'object',
268
+ properties: {
269
+ max_suggestions: {
270
+ type: 'number',
271
+ description: 'Maximum number of bundle suggestions to return (default: 5)'
272
+ }
273
+ },
274
+ required: []
275
+ }
276
+ },
277
+ {
278
+ name: 'create_bundle',
279
+ description: 'Create a new bundle with specified patterns',
280
+ inputSchema: {
281
+ type: 'object',
282
+ properties: {
283
+ name: {
284
+ type: 'string',
285
+ description: 'Name of the new bundle'
286
+ },
287
+ patterns: {
288
+ type: 'array',
289
+ items: { type: 'string' },
290
+ description: 'Array of glob patterns for the bundle (e.g., ["src/api/**", "src/services/**"])'
291
+ },
292
+ description: {
293
+ type: 'string',
294
+ description: 'Optional description of the bundle purpose'
295
+ }
296
+ },
297
+ required: ['name', 'patterns']
298
+ }
299
+ },
300
+ {
301
+ name: 'update_bundle',
302
+ description: 'Update an existing bundle\'s patterns',
303
+ inputSchema: {
304
+ type: 'object',
305
+ properties: {
306
+ name: {
307
+ type: 'string',
308
+ description: 'Name of the bundle to update'
309
+ },
310
+ patterns: {
311
+ type: 'array',
312
+ items: { type: 'string' },
313
+ description: 'New array of glob patterns for the bundle'
314
+ },
315
+ description: {
316
+ type: 'string',
317
+ description: 'Optional updated description'
318
+ }
319
+ },
320
+ required: ['name', 'patterns']
321
+ }
322
+ },
323
+ {
324
+ name: 'delete_bundle',
325
+ description: 'Delete an existing bundle',
326
+ inputSchema: {
327
+ type: 'object',
328
+ properties: {
329
+ name: {
330
+ type: 'string',
331
+ description: 'Name of the bundle to delete'
332
+ }
333
+ },
334
+ required: ['name']
335
+ }
336
+ },
337
+ {
338
+ name: 'update_cntxignore',
339
+ description: 'Update the .cntxignore file with new ignore patterns',
340
+ inputSchema: {
341
+ type: 'object',
342
+ properties: {
343
+ content: {
344
+ type: 'string',
345
+ description: 'Full content for the .cntxignore file (newline-separated patterns)'
346
+ }
347
+ },
348
+ required: ['content']
349
+ }
350
+ },
351
+ {
352
+ name: 'agent_discover',
353
+ description: 'Agent Discovery Mode: Get comprehensive codebase overview including bundles, architecture, and patterns',
354
+ inputSchema: {
355
+ type: 'object',
356
+ properties: {
357
+ scope: {
358
+ type: 'string',
359
+ description: 'Scope of discovery: "all" for full codebase or specific bundle name (default: "all")'
360
+ },
361
+ includeDetails: {
362
+ type: 'boolean',
363
+ description: 'Include detailed semantic analysis and complexity metrics (default: true)'
364
+ }
365
+ },
366
+ required: []
367
+ }
368
+ },
369
+ {
370
+ name: 'agent_query',
371
+ description: 'Agent Query Mode: Answer specific questions about the codebase using semantic search and analysis',
372
+ inputSchema: {
373
+ type: 'object',
374
+ properties: {
375
+ question: {
376
+ type: 'string',
377
+ description: 'The question to answer about the codebase (e.g., "Where is user authentication handled?")'
378
+ },
379
+ scope: {
380
+ type: 'string',
381
+ description: 'Optional bundle to limit search scope'
382
+ },
383
+ maxResults: {
384
+ type: 'number',
385
+ description: 'Maximum number of results to return (default: 10)'
386
+ },
387
+ includeCode: {
388
+ type: 'boolean',
389
+ description: 'Include code snippets in the response (default: false)'
390
+ }
391
+ },
392
+ required: ['question']
393
+ }
394
+ },
395
+ {
396
+ name: 'agent_investigate',
397
+ description: 'Agent Investigation Mode: Investigate existing implementations for a feature and find integration points',
398
+ inputSchema: {
399
+ type: 'object',
400
+ properties: {
401
+ featureDescription: {
402
+ type: 'string',
403
+ description: 'Description of the feature to investigate (e.g., "dark mode", "user authentication", "form validation")'
404
+ },
405
+ includeRecommendations: {
406
+ type: 'boolean',
407
+ description: 'Include implementation recommendations and approach suggestions (default: true)'
408
+ }
409
+ },
410
+ required: ['featureDescription']
411
+ }
412
+ },
413
+ {
414
+ name: 'agent_discuss',
415
+ description: 'Agent Passive Mode: Engage in discussion about codebase architecture, design decisions, and planning',
416
+ inputSchema: {
417
+ type: 'object',
418
+ properties: {
419
+ userInput: {
420
+ type: 'string',
421
+ description: 'The topic or question for discussion (e.g., "Let\'s discuss the architecture before I make changes")'
422
+ },
423
+ context: {
424
+ type: 'object',
425
+ description: 'Additional context for the discussion',
426
+ properties: {
427
+ scope: {
428
+ type: 'string',
429
+ description: 'Specific area of focus (e.g., "frontend", "api", "database")'
430
+ }
431
+ }
432
+ }
433
+ },
434
+ required: ['userInput']
435
+ }
436
+ },
437
+ {
438
+ name: 'agent_organize',
439
+ description: 'Agent Project Organizer Mode: Setup and maintenance of project organization - adapts to project maturity',
440
+ inputSchema: {
441
+ type: 'object',
442
+ properties: {
443
+ activity: {
444
+ type: 'string',
445
+ enum: ['detect', 'analyze', 'bundle', 'create', 'optimize', 'audit', 'cleanup', 'validate'],
446
+ description: 'Activity to perform: detect project state, analyze semantics, suggest bundles, create bundles, optimize organization, audit health, cleanup issues, or validate structure'
447
+ },
448
+ autoDetect: {
449
+ type: 'boolean',
450
+ description: 'Automatically detect appropriate activity based on project state (default: true)',
451
+ default: true
452
+ },
453
+ force: {
454
+ type: 'boolean',
455
+ description: 'Force execution even if preconditions are not met (default: false)',
456
+ default: false
457
+ }
458
+ },
459
+ required: []
460
+ }
461
+ },
462
+ {
463
+ name: 'read_file',
464
+ description: 'Read contents of a specific file with bundle context and metadata',
465
+ inputSchema: {
466
+ type: 'object',
467
+ properties: {
468
+ path: {
469
+ type: 'string',
470
+ description: 'File path relative to project root'
471
+ },
472
+ includeMetadata: {
473
+ type: 'boolean',
474
+ description: 'Include file metadata (size, bundles, etc.) - default: true'
475
+ }
476
+ },
477
+ required: ['path']
478
+ }
479
+ },
480
+ {
481
+ name: 'write_file',
482
+ description: 'Write content to a file with validation and safety checks',
483
+ inputSchema: {
484
+ type: 'object',
485
+ properties: {
486
+ path: {
487
+ type: 'string',
488
+ description: 'File path relative to project root'
489
+ },
490
+ content: {
491
+ type: 'string',
492
+ description: 'Content to write to the file'
493
+ },
494
+ backup: {
495
+ type: 'boolean',
496
+ description: 'Create backup before writing - default: true'
497
+ },
498
+ createDirs: {
499
+ type: 'boolean',
500
+ description: 'Create parent directories if they don\'t exist - default: true'
501
+ }
502
+ },
503
+ required: ['path', 'content']
504
+ }
505
+ },
506
+ {
507
+ name: 'manage_activities',
508
+ description: 'CRUD operations for project activities',
509
+ inputSchema: {
510
+ type: 'object',
511
+ properties: {
512
+ action: {
513
+ type: 'string',
514
+ enum: ['list', 'get', 'create', 'update', 'delete'],
515
+ description: 'Action to perform on activities'
516
+ },
517
+ activityId: {
518
+ type: 'string',
519
+ description: 'Activity ID (required for get, update, delete)'
520
+ },
521
+ activity: {
522
+ type: 'object',
523
+ description: 'Activity data (required for create, update)',
524
+ properties: {
525
+ title: { type: 'string' },
526
+ description: { type: 'string' },
527
+ status: { type: 'string', enum: ['todo', 'in_progress', 'completed', 'blocked'] },
528
+ tags: { type: 'array', items: { type: 'string' } },
529
+ tasks: { type: 'array', items: { type: 'object' } }
530
+ }
531
+ }
532
+ },
533
+ required: ['action']
534
+ }
217
535
  }
218
536
  ];
219
537
 
@@ -241,6 +559,51 @@ export class MCPServer {
241
559
  case 'get_project_status':
242
560
  return this.toolGetProjectStatus(id);
243
561
 
562
+ case 'get_semantic_chunks':
563
+ return this.toolGetSemanticChunks(id);
564
+
565
+ case 'get_semantic_chunks_filtered':
566
+ return this.toolGetSemanticChunksFiltered(args, id);
567
+
568
+ case 'analyze_bundle_suggestions':
569
+ return this.toolAnalyzeBundleSuggestions(args, id);
570
+
571
+ case 'create_bundle':
572
+ return this.toolCreateBundle(args, id);
573
+
574
+ case 'update_bundle':
575
+ return this.toolUpdateBundle(args, id);
576
+
577
+ case 'delete_bundle':
578
+ return this.toolDeleteBundle(args, id);
579
+
580
+ case 'update_cntxignore':
581
+ return this.toolUpdateCntxignore(args, id);
582
+
583
+ case 'agent_discover':
584
+ return this.toolAgentDiscover(args, id);
585
+
586
+ case 'agent_query':
587
+ return this.toolAgentQuery(args, id);
588
+
589
+ case 'agent_investigate':
590
+ return this.toolAgentInvestigate(args, id);
591
+
592
+ case 'agent_discuss':
593
+ return this.toolAgentDiscuss(args, id);
594
+
595
+ case 'agent_organize':
596
+ return this.toolAgentOrganize(args, id);
597
+
598
+ case 'read_file':
599
+ return this.toolReadFile(args, id);
600
+
601
+ case 'write_file':
602
+ return this.toolWriteFile(args, id);
603
+
604
+ case 'manage_activities':
605
+ return this.toolManageActivities(args, id);
606
+
244
607
  default:
245
608
  return this.createErrorResponse(id, -32602, 'Unknown tool');
246
609
  }
@@ -347,6 +710,696 @@ ${Array.from(this.cntxServer.bundles.entries()).map(([name, bundle]) =>
347
710
  });
348
711
  }
349
712
 
713
+ // New semantic chunks tools
714
+ async toolGetSemanticChunks(id) {
715
+ try {
716
+ const analysis = await this.cntxServer.getSemanticAnalysis();
717
+
718
+ // Clean the analysis data to prevent JSON issues
719
+ const cleanAnalysis = {
720
+ ...analysis,
721
+ chunks: analysis.chunks?.map(chunk => ({
722
+ ...chunk,
723
+ code: chunk.code ? chunk.code.substring(0, 500) + (chunk.code.length > 500 ? '...' : '') : '',
724
+ bundles: chunk.bundles || [],
725
+ includes: {
726
+ imports: chunk.includes?.imports || [],
727
+ types: chunk.includes?.types || []
728
+ }
729
+ })) || []
730
+ };
731
+
732
+ return this.createSuccessResponse(id, {
733
+ content: [{
734
+ type: 'text',
735
+ text: JSON.stringify(cleanAnalysis, null, 2)
736
+ }]
737
+ });
738
+ } catch (error) {
739
+ return this.createErrorResponse(id, -32603, 'Failed to get semantic chunks', error.message);
740
+ }
741
+ }
742
+
743
+ async toolGetSemanticChunksFiltered(args, id) {
744
+ try {
745
+ const analysis = await this.cntxServer.getSemanticAnalysis();
746
+ let chunks = analysis.chunks || [];
747
+
748
+ // Apply filters
749
+ if (args.purpose) {
750
+ chunks = chunks.filter(chunk =>
751
+ chunk.purpose && chunk.purpose.toLowerCase().includes(args.purpose.toLowerCase())
752
+ );
753
+ }
754
+
755
+ if (args.type) {
756
+ chunks = chunks.filter(chunk => chunk.subtype === args.type);
757
+ }
758
+
759
+ if (args.complexity) {
760
+ chunks = chunks.filter(chunk => chunk.complexity?.level === args.complexity);
761
+ }
762
+
763
+ if (args.bundle) {
764
+ chunks = chunks.filter(chunk =>
765
+ chunk.bundles && chunk.bundles.includes(args.bundle)
766
+ );
767
+ }
768
+
769
+ if (args.exported !== undefined) {
770
+ chunks = chunks.filter(chunk => chunk.isExported === args.exported);
771
+ }
772
+
773
+ if (args.async !== undefined) {
774
+ chunks = chunks.filter(chunk => chunk.isAsync === args.async);
775
+ }
776
+
777
+ // Clean chunks for JSON safety
778
+ const cleanChunks = chunks.map(chunk => ({
779
+ ...chunk,
780
+ code: chunk.code ? chunk.code.substring(0, 300) + (chunk.code.length > 300 ? '...' : '') : '',
781
+ bundles: chunk.bundles || [],
782
+ includes: {
783
+ imports: chunk.includes?.imports || [],
784
+ types: chunk.includes?.types || []
785
+ }
786
+ }));
787
+
788
+ const filteredAnalysis = {
789
+ ...analysis,
790
+ chunks: cleanChunks,
791
+ summary: {
792
+ ...analysis.summary,
793
+ totalChunks: cleanChunks.length,
794
+ filteredCount: cleanChunks.length,
795
+ originalCount: analysis.chunks?.length || 0
796
+ }
797
+ };
798
+
799
+ return this.createSuccessResponse(id, {
800
+ content: [{
801
+ type: 'text',
802
+ text: JSON.stringify(filteredAnalysis, null, 2)
803
+ }]
804
+ });
805
+ } catch (error) {
806
+ return this.createErrorResponse(id, -32603, 'Failed to filter semantic chunks', error.message);
807
+ }
808
+ }
809
+
810
+ async toolAnalyzeBundleSuggestions(args, id) {
811
+ try {
812
+ const analysis = await this.cntxServer.getSemanticAnalysis();
813
+ const chunks = analysis.chunks || [];
814
+ const maxSuggestions = args.max_suggestions || 5;
815
+
816
+ // Group chunks by purpose and file location
817
+ const purposeGroups = {};
818
+ const locationGroups = {};
819
+
820
+ chunks.forEach(chunk => {
821
+ // Group by purpose
822
+ if (!purposeGroups[chunk.purpose]) {
823
+ purposeGroups[chunk.purpose] = [];
824
+ }
825
+ purposeGroups[chunk.purpose].push(chunk);
826
+
827
+ // Group by file location patterns
828
+ const pathParts = chunk.filePath.split('/');
829
+ if (pathParts.length > 1) {
830
+ const dirPattern = pathParts.slice(0, -1).join('/') + '/**';
831
+ if (!locationGroups[dirPattern]) {
832
+ locationGroups[dirPattern] = [];
833
+ }
834
+ locationGroups[dirPattern].push(chunk);
835
+ }
836
+ });
837
+
838
+ const suggestions = [];
839
+
840
+ // Suggest bundles by purpose
841
+ Object.entries(purposeGroups).forEach(([purpose, chunks]) => {
842
+ if (chunks.length >= 3) { // Only suggest if enough functions
843
+ const bundleName = purpose.toLowerCase().replace(/\s+/g, '-');
844
+ const patterns = [...new Set(chunks.map(c => {
845
+ const dir = c.filePath.split('/').slice(0, -1).join('/');
846
+ return dir ? `${dir}/**` : c.filePath;
847
+ }))];
848
+
849
+ suggestions.push({
850
+ name: bundleName,
851
+ reason: `Groups ${chunks.length} functions with purpose: ${purpose}`,
852
+ patterns,
853
+ chunkCount: chunks.length,
854
+ files: [...new Set(chunks.map(c => c.filePath))]
855
+ });
856
+ }
857
+ });
858
+
859
+ // Suggest bundles by common directory patterns
860
+ Object.entries(locationGroups).forEach(([pattern, chunks]) => {
861
+ if (chunks.length >= 5) { // Only suggest if enough functions in same location
862
+ const dirName = pattern.split('/').pop().replace('/**', '');
863
+ const bundleName = dirName === '*' ? 'utils' : dirName;
864
+
865
+ suggestions.push({
866
+ name: bundleName,
867
+ reason: `Groups ${chunks.length} functions from ${pattern}`,
868
+ patterns: [pattern],
869
+ chunkCount: chunks.length,
870
+ files: [...new Set(chunks.map(c => c.filePath))]
871
+ });
872
+ }
873
+ });
874
+
875
+ // Sort by chunk count and take top suggestions
876
+ const topSuggestions = suggestions
877
+ .sort((a, b) => b.chunkCount - a.chunkCount)
878
+ .slice(0, maxSuggestions);
879
+
880
+ const result = {
881
+ totalSuggestions: suggestions.length,
882
+ suggestions: topSuggestions,
883
+ analysis: {
884
+ totalChunks: chunks.length,
885
+ purposeGroups: Object.keys(purposeGroups).length,
886
+ locationGroups: Object.keys(locationGroups).length
887
+ }
888
+ };
889
+
890
+ return this.createSuccessResponse(id, {
891
+ content: [{
892
+ type: 'text',
893
+ text: JSON.stringify(result, null, 2)
894
+ }]
895
+ });
896
+ } catch (error) {
897
+ return this.createErrorResponse(id, -32603, 'Failed to analyze bundle suggestions', error.message);
898
+ }
899
+ }
900
+
901
+ // Bundle management tools
902
+ async toolCreateBundle(args, id) {
903
+ try {
904
+ const { name, patterns, description } = args;
905
+
906
+ if (!name || !patterns || !Array.isArray(patterns)) {
907
+ return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
908
+ }
909
+
910
+ // Prevent overwriting existing bundles
911
+ if (this.cntxServer.bundles.has(name)) {
912
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' already exists`);
913
+ }
914
+
915
+ // Load current config
916
+ const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
917
+ let config = { bundles: {} };
918
+
919
+ if (existsSync(configPath)) {
920
+ config = JSON.parse(readFileSync(configPath, 'utf8'));
921
+ }
922
+
923
+ // Add new bundle
924
+ config.bundles[name] = patterns;
925
+
926
+ // Save config
927
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
928
+
929
+ // Reload server config
930
+ this.cntxServer.loadConfig();
931
+ this.cntxServer.generateAllBundles();
932
+
933
+ const result = {
934
+ success: true,
935
+ bundle: {
936
+ name,
937
+ patterns,
938
+ description,
939
+ created: new Date().toISOString()
940
+ }
941
+ };
942
+
943
+ return this.createSuccessResponse(id, {
944
+ content: [{
945
+ type: 'text',
946
+ text: JSON.stringify(result, null, 2)
947
+ }]
948
+ });
949
+ } catch (error) {
950
+ return this.createErrorResponse(id, -32603, 'Failed to create bundle', error.message);
951
+ }
952
+ }
953
+
954
+ async toolUpdateBundle(args, id) {
955
+ try {
956
+ const { name, patterns, description } = args;
957
+
958
+ if (!name || !patterns || !Array.isArray(patterns)) {
959
+ return this.createErrorResponse(id, -32602, 'Invalid arguments: name and patterns array required');
960
+ }
961
+
962
+ // Check if bundle exists
963
+ if (!this.cntxServer.bundles.has(name)) {
964
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
965
+ }
966
+
967
+ // Prevent updating master bundle
968
+ if (name === 'master') {
969
+ return this.createErrorResponse(id, -32602, 'Cannot update master bundle');
970
+ }
971
+
972
+ // Load current config
973
+ const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
974
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
975
+
976
+ // Update bundle patterns
977
+ config.bundles[name] = patterns;
978
+
979
+ // Save config
980
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
981
+
982
+ // Reload server config
983
+ this.cntxServer.loadConfig();
984
+ this.cntxServer.generateAllBundles();
985
+
986
+ const result = {
987
+ success: true,
988
+ bundle: {
989
+ name,
990
+ patterns,
991
+ description,
992
+ updated: new Date().toISOString()
993
+ }
994
+ };
995
+
996
+ return this.createSuccessResponse(id, {
997
+ content: [{
998
+ type: 'text',
999
+ text: JSON.stringify(result, null, 2)
1000
+ }]
1001
+ });
1002
+ } catch (error) {
1003
+ return this.createErrorResponse(id, -32603, 'Failed to update bundle', error.message);
1004
+ }
1005
+ }
1006
+
1007
+ async toolDeleteBundle(args, id) {
1008
+ try {
1009
+ const { name } = args;
1010
+
1011
+ if (!name) {
1012
+ return this.createErrorResponse(id, -32602, 'Bundle name required');
1013
+ }
1014
+
1015
+ // Check if bundle exists
1016
+ if (!this.cntxServer.bundles.has(name)) {
1017
+ return this.createErrorResponse(id, -32602, `Bundle '${name}' not found`);
1018
+ }
1019
+
1020
+ // Prevent deleting master bundle
1021
+ if (name === 'master') {
1022
+ return this.createErrorResponse(id, -32602, 'Cannot delete master bundle');
1023
+ }
1024
+
1025
+ // Load current config
1026
+ const configPath = join(this.cntxServer.CNTX_DIR, 'config.json');
1027
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
1028
+
1029
+ // Remove bundle
1030
+ delete config.bundles[name];
1031
+
1032
+ // Save config
1033
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
1034
+
1035
+ // Reload server config
1036
+ this.cntxServer.loadConfig();
1037
+ this.cntxServer.generateAllBundles();
1038
+
1039
+ const result = {
1040
+ success: true,
1041
+ deleted: name,
1042
+ timestamp: new Date().toISOString()
1043
+ };
1044
+
1045
+ return this.createSuccessResponse(id, {
1046
+ content: [{
1047
+ type: 'text',
1048
+ text: JSON.stringify(result, null, 2)
1049
+ }]
1050
+ });
1051
+ } catch (error) {
1052
+ return this.createErrorResponse(id, -32603, 'Failed to delete bundle', error.message);
1053
+ }
1054
+ }
1055
+
1056
+ async toolUpdateCntxignore(args, id) {
1057
+ try {
1058
+ const { content } = args;
1059
+
1060
+ if (content === undefined) {
1061
+ return this.createErrorResponse(id, -32602, 'Content required');
1062
+ }
1063
+
1064
+ const ignorePath = join(this.cntxServer.CWD, '.cntxignore');
1065
+
1066
+ // Write the .cntxignore file
1067
+ writeFileSync(ignorePath, content);
1068
+
1069
+ // Reload ignore patterns
1070
+ this.cntxServer.loadIgnorePatterns();
1071
+ this.cntxServer.generateAllBundles();
1072
+
1073
+ const result = {
1074
+ success: true,
1075
+ file: '.cntxignore',
1076
+ lines: content.split('\n').length,
1077
+ patterns: content.split('\n').filter(line => line.trim() && !line.trim().startsWith('#')).length,
1078
+ updated: new Date().toISOString()
1079
+ };
1080
+
1081
+ return this.createSuccessResponse(id, {
1082
+ content: [{
1083
+ type: 'text',
1084
+ text: JSON.stringify(result, null, 2)
1085
+ }]
1086
+ });
1087
+ } catch (error) {
1088
+ return this.createErrorResponse(id, -32603, 'Failed to update .cntxignore', error.message);
1089
+ }
1090
+ }
1091
+
1092
+ // Agent Tools Implementation
1093
+ async toolAgentDiscover(args, id) {
1094
+ try {
1095
+ const { scope = 'all', includeDetails = true } = args;
1096
+ const result = await this.agentRuntime.discoverCodebase({ scope, includeDetails });
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, 'Agent discovery failed', error.message);
1106
+ }
1107
+ }
1108
+
1109
+ async toolAgentQuery(args, id) {
1110
+ try {
1111
+ const { question, scope, maxResults = 10, includeCode = false } = args;
1112
+
1113
+ if (!question) {
1114
+ return this.createErrorResponse(id, -32602, 'Question is required');
1115
+ }
1116
+
1117
+ const result = await this.agentRuntime.answerQuery(question, { scope, maxResults, includeCode });
1118
+
1119
+ return this.createSuccessResponse(id, {
1120
+ content: [{
1121
+ type: 'text',
1122
+ text: JSON.stringify(result, null, 2)
1123
+ }]
1124
+ });
1125
+ } catch (error) {
1126
+ return this.createErrorResponse(id, -32603, 'Agent query failed', error.message);
1127
+ }
1128
+ }
1129
+
1130
+ async toolAgentInvestigate(args, id) {
1131
+ try {
1132
+ const { featureDescription, includeRecommendations = true } = args;
1133
+
1134
+ if (!featureDescription) {
1135
+ return this.createErrorResponse(id, -32602, 'Feature description is required');
1136
+ }
1137
+
1138
+ const result = await this.agentRuntime.investigateFeature(featureDescription, { includeRecommendations });
1139
+
1140
+ return this.createSuccessResponse(id, {
1141
+ content: [{
1142
+ type: 'text',
1143
+ text: JSON.stringify(result, null, 2)
1144
+ }]
1145
+ });
1146
+ } catch (error) {
1147
+ return this.createErrorResponse(id, -32603, 'Agent investigation failed', error.message);
1148
+ }
1149
+ }
1150
+
1151
+ async toolAgentDiscuss(args, id) {
1152
+ try {
1153
+ const { userInput, context = {} } = args;
1154
+
1155
+ if (!userInput) {
1156
+ return this.createErrorResponse(id, -32602, 'User input is required');
1157
+ }
1158
+
1159
+ const result = await this.agentRuntime.discussAndPlan(userInput, context);
1160
+
1161
+ return this.createSuccessResponse(id, {
1162
+ content: [{
1163
+ type: 'text',
1164
+ text: JSON.stringify(result, null, 2)
1165
+ }]
1166
+ });
1167
+ } catch (error) {
1168
+ return this.createErrorResponse(id, -32603, 'Agent discussion failed', error.message);
1169
+ }
1170
+ }
1171
+
1172
+ async toolAgentOrganize(args, id) {
1173
+ try {
1174
+ const { activity = 'detect', autoDetect = true, force = false } = args;
1175
+
1176
+ const result = await this.agentRuntime.organizeProject({ activity, autoDetect, force });
1177
+
1178
+ return this.createSuccessResponse(id, {
1179
+ content: [{
1180
+ type: 'text',
1181
+ text: JSON.stringify(result, null, 2)
1182
+ }]
1183
+ });
1184
+ } catch (error) {
1185
+ return this.createErrorResponse(id, -32603, 'Agent organization failed', error.message);
1186
+ }
1187
+ }
1188
+
1189
+ // New tool implementations
1190
+ async toolReadFile(args, id) {
1191
+ const { path, includeMetadata = true } = args;
1192
+
1193
+ if (!path) {
1194
+ return this.createErrorResponse(id, -32602, 'Path is required');
1195
+ }
1196
+
1197
+ try {
1198
+ const fullPath = join(this.cntxServer.CWD, path);
1199
+
1200
+ if (!existsSync(fullPath)) {
1201
+ return this.createErrorResponse(id, -32602, `File not found: ${path}`);
1202
+ }
1203
+
1204
+ const content = readFileSync(fullPath, 'utf8');
1205
+ const result = { path, content };
1206
+
1207
+ if (includeMetadata) {
1208
+ const stats = require('fs').statSync(fullPath);
1209
+ const bundles = [];
1210
+
1211
+ // Find which bundles include this file
1212
+ this.cntxServer.bundles.forEach((bundle, name) => {
1213
+ if (bundle.files && bundle.files.includes(fullPath)) {
1214
+ bundles.push(name);
1215
+ }
1216
+ });
1217
+
1218
+ result.metadata = {
1219
+ size: stats.size,
1220
+ mimeType: this.getMimeType(path),
1221
+ modified: stats.mtime.toISOString(),
1222
+ lines: content.split('\n').length,
1223
+ bundles: bundles
1224
+ };
1225
+ }
1226
+
1227
+ return this.createSuccessResponse(id, {
1228
+ contents: [{
1229
+ type: 'text',
1230
+ text: JSON.stringify(result, null, 2)
1231
+ }]
1232
+ });
1233
+ } catch (error) {
1234
+ return this.createErrorResponse(id, -32603, 'Failed to read file', error.message);
1235
+ }
1236
+ }
1237
+
1238
+ async toolWriteFile(args, id) {
1239
+ const { path, content, backup = true, createDirs = true } = args;
1240
+
1241
+ if (!path || content === undefined) {
1242
+ return this.createErrorResponse(id, -32602, 'Path and content are required');
1243
+ }
1244
+
1245
+ try {
1246
+ const fullPath = join(this.cntxServer.CWD, path);
1247
+ const parentDir = require('path').dirname(fullPath);
1248
+
1249
+ // Create parent directories if needed
1250
+ if (createDirs && !existsSync(parentDir)) {
1251
+ require('fs').mkdirSync(parentDir, { recursive: true });
1252
+ }
1253
+
1254
+ // Create backup if file exists
1255
+ if (backup && existsSync(fullPath)) {
1256
+ const backupPath = `${fullPath}.backup.${Date.now()}`;
1257
+ require('fs').copyFileSync(fullPath, backupPath);
1258
+ }
1259
+
1260
+ // Write the file
1261
+ writeFileSync(fullPath, content, 'utf8');
1262
+
1263
+ // Mark relevant bundles as changed
1264
+ this.cntxServer.bundles.forEach((bundle, name) => {
1265
+ if (bundle.patterns && bundle.patterns.some(pattern =>
1266
+ this.cntxServer.fileSystemManager.matchesPattern(fullPath, pattern)
1267
+ )) {
1268
+ bundle.changed = true;
1269
+ }
1270
+ });
1271
+
1272
+ const stats = require('fs').statSync(fullPath);
1273
+
1274
+ return this.createSuccessResponse(id, {
1275
+ contents: [{
1276
+ type: 'text',
1277
+ text: JSON.stringify({
1278
+ path,
1279
+ written: true,
1280
+ size: stats.size,
1281
+ modified: stats.mtime.toISOString()
1282
+ }, null, 2)
1283
+ }]
1284
+ });
1285
+ } catch (error) {
1286
+ return this.createErrorResponse(id, -32603, 'Failed to write file', error.message);
1287
+ }
1288
+ }
1289
+
1290
+ async toolManageActivities(args, id) {
1291
+ const { action, activityId, activity } = args;
1292
+
1293
+ if (!action) {
1294
+ return this.createErrorResponse(id, -32602, 'Action is required');
1295
+ }
1296
+
1297
+ try {
1298
+ const activitiesPath = join(this.cntxServer.CWD, '.cntx', 'activities');
1299
+ const activitiesJsonPath = join(activitiesPath, 'activities.json');
1300
+
1301
+ let activities = [];
1302
+ if (existsSync(activitiesJsonPath)) {
1303
+ activities = JSON.parse(readFileSync(activitiesJsonPath, 'utf8'));
1304
+ }
1305
+
1306
+ let result;
1307
+
1308
+ switch (action) {
1309
+ case 'list':
1310
+ result = { activities: activities.map(a => ({
1311
+ id: a.title.toLowerCase().replace(/[^a-z0-9]/g, '-'),
1312
+ title: a.title,
1313
+ description: a.description,
1314
+ status: a.status,
1315
+ tags: a.tags
1316
+ })) };
1317
+ break;
1318
+
1319
+ case 'get':
1320
+ if (!activityId) {
1321
+ return this.createErrorResponse(id, -32602, 'Activity ID is required for get action');
1322
+ }
1323
+ const found = activities.find(a =>
1324
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1325
+ );
1326
+ if (!found) {
1327
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1328
+ }
1329
+
1330
+ // Load markdown files
1331
+ const activityDir = join(activitiesPath, 'activities', activityId);
1332
+ const files = {};
1333
+ ['README.md', 'progress.md', 'tasks.md', 'notes.md'].forEach(file => {
1334
+ const filePath = join(activityDir, file);
1335
+ files[file.replace('.md', '')] = existsSync(filePath)
1336
+ ? readFileSync(filePath, 'utf8')
1337
+ : 'No content available';
1338
+ });
1339
+
1340
+ result = { ...found, files };
1341
+ break;
1342
+
1343
+ case 'create':
1344
+ if (!activity || !activity.title) {
1345
+ return this.createErrorResponse(id, -32602, 'Activity with title is required for create action');
1346
+ }
1347
+ activities.push({
1348
+ title: activity.title,
1349
+ description: activity.description || '',
1350
+ status: activity.status || 'todo',
1351
+ tags: activity.tags || ['general'],
1352
+ tasks: activity.tasks || []
1353
+ });
1354
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1355
+ result = { created: true, activityId: activity.title.toLowerCase().replace(/[^a-z0-9]/g, '-') };
1356
+ break;
1357
+
1358
+ case 'update':
1359
+ if (!activityId || !activity) {
1360
+ return this.createErrorResponse(id, -32602, 'Activity ID and activity data are required for update action');
1361
+ }
1362
+ const updateIndex = activities.findIndex(a =>
1363
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1364
+ );
1365
+ if (updateIndex === -1) {
1366
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1367
+ }
1368
+ activities[updateIndex] = { ...activities[updateIndex], ...activity };
1369
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1370
+ result = { updated: true };
1371
+ break;
1372
+
1373
+ case 'delete':
1374
+ if (!activityId) {
1375
+ return this.createErrorResponse(id, -32602, 'Activity ID is required for delete action');
1376
+ }
1377
+ const deleteIndex = activities.findIndex(a =>
1378
+ a.title.toLowerCase().replace(/[^a-z0-9]/g, '-') === activityId
1379
+ );
1380
+ if (deleteIndex === -1) {
1381
+ return this.createErrorResponse(id, -32602, `Activity not found: ${activityId}`);
1382
+ }
1383
+ activities.splice(deleteIndex, 1);
1384
+ writeFileSync(activitiesJsonPath, JSON.stringify(activities, null, 2));
1385
+ result = { deleted: true };
1386
+ break;
1387
+
1388
+ default:
1389
+ return this.createErrorResponse(id, -32602, `Unknown action: ${action}`);
1390
+ }
1391
+
1392
+ return this.createSuccessResponse(id, {
1393
+ contents: [{
1394
+ type: 'text',
1395
+ text: JSON.stringify(result, null, 2)
1396
+ }]
1397
+ });
1398
+ } catch (error) {
1399
+ return this.createErrorResponse(id, -32603, 'Failed to manage activities', error.message);
1400
+ }
1401
+ }
1402
+
350
1403
  // Helper methods
351
1404
  getMimeType(filePath) {
352
1405
  const ext = filePath.split('.').pop()?.toLowerCase();