@vfarcic/dot-ai 0.48.0 → 0.50.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 (47) hide show
  1. package/README.md +49 -2
  2. package/dist/core/base-vector-service.d.ts +86 -0
  3. package/dist/core/base-vector-service.d.ts.map +1 -0
  4. package/dist/core/base-vector-service.js +223 -0
  5. package/dist/core/capabilities.d.ts +71 -0
  6. package/dist/core/capabilities.d.ts.map +1 -0
  7. package/dist/core/capabilities.js +215 -0
  8. package/dist/core/capability-vector-service.d.ts +80 -0
  9. package/dist/core/capability-vector-service.d.ts.map +1 -0
  10. package/dist/core/capability-vector-service.js +142 -0
  11. package/dist/core/claude.d.ts +14 -1
  12. package/dist/core/claude.d.ts.map +1 -1
  13. package/dist/core/claude.js +109 -13
  14. package/dist/core/discovery.d.ts +6 -0
  15. package/dist/core/discovery.d.ts.map +1 -1
  16. package/dist/core/discovery.js +7 -1
  17. package/dist/core/embedding-service.d.ts +3 -3
  18. package/dist/core/embedding-service.d.ts.map +1 -1
  19. package/dist/core/embedding-service.js +6 -7
  20. package/dist/core/index.d.ts +2 -0
  21. package/dist/core/index.d.ts.map +1 -1
  22. package/dist/core/index.js +7 -4
  23. package/dist/core/pattern-vector-service.d.ts +11 -80
  24. package/dist/core/pattern-vector-service.d.ts.map +1 -1
  25. package/dist/core/pattern-vector-service.js +40 -277
  26. package/dist/core/schema.d.ts +11 -30
  27. package/dist/core/schema.d.ts.map +1 -1
  28. package/dist/core/schema.js +107 -126
  29. package/dist/core/vector-db-service.d.ts +6 -0
  30. package/dist/core/vector-db-service.d.ts.map +1 -1
  31. package/dist/core/vector-db-service.js +40 -10
  32. package/dist/tools/organizational-data.d.ts +18 -3
  33. package/dist/tools/organizational-data.d.ts.map +1 -1
  34. package/dist/tools/organizational-data.js +1750 -17
  35. package/dist/tools/recommend.d.ts.map +1 -1
  36. package/dist/tools/recommend.js +3 -7
  37. package/dist/tools/version.d.ts +9 -0
  38. package/dist/tools/version.d.ts.map +1 -1
  39. package/dist/tools/version.js +115 -5
  40. package/package.json +1 -1
  41. package/prompts/capability-inference.md +121 -0
  42. package/prompts/doc-testing-test-section.md +40 -2
  43. package/prompts/resource-selection.md +10 -3
  44. package/shared-prompts/prd-done.md +5 -4
  45. package/shared-prompts/prd-update-decisions.md +9 -0
  46. package/shared-prompts/prd-update-progress.md +33 -0
  47. package/prompts/concept-extraction.md +0 -95
@@ -49,23 +49,33 @@ const error_handling_1 = require("../core/error-handling");
49
49
  // Import only what we need - other imports removed as they're no longer used with Vector DB
50
50
  const pattern_creation_session_1 = require("../core/pattern-creation-session");
51
51
  const index_1 = require("../core/index");
52
+ const capabilities_1 = require("../core/capabilities");
52
53
  const session_utils_1 = require("../core/session-utils");
53
54
  const fs = __importStar(require("fs"));
54
55
  const path = __importStar(require("path"));
55
56
  // Tool metadata for MCP registration
56
57
  exports.ORGANIZATIONAL_DATA_TOOL_NAME = 'manageOrgData';
57
- exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = 'Manage organizational deployment patterns, templates, standards, and best practices for AI recommendations. Use this tool when user wants to save, create, add, or manage deployment patterns, templates, resource configurations, organizational standards, best practices, or reusable deployment guidelines. This tool uses a step-by-step workflow for creation. IMPORTANT: When user wants to create something, call this tool with operation=create (no other parameters). The tool will return a workflow step with a "prompt" field - you must execute that prompt immediately and wait for user response before calling again.';
58
- // Extensible schema - ready for future data types
58
+ exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = 'Unified tool for managing cluster data: organizational patterns and resource capabilities. For patterns: supports step-by-step creation workflow. For capabilities: supports scan, list, get, delete, deleteAll, and progress operations for cluster resource capability discovery and management. Use dataType parameter to specify what to manage: "pattern" for organizational patterns, "capabilities" for resource capabilities.';
59
+ // Extensible schema - supports patterns and capabilities
59
60
  exports.ORGANIZATIONAL_DATA_TOOL_INPUT_SCHEMA = {
60
- dataType: zod_1.z.enum(['pattern']).describe('Type of organizational data to manage (currently only "pattern" supported)'),
61
- operation: zod_1.z.enum(['create', 'list', 'get', 'delete']).describe('Operation to perform on the organizational data'),
61
+ dataType: zod_1.z.enum(['pattern', 'capabilities']).describe('Type of cluster data to manage: "pattern" for organizational patterns, "capabilities" for resource capabilities'),
62
+ operation: zod_1.z.enum(['create', 'list', 'get', 'delete', 'deleteAll', 'scan', 'analyze', 'progress', 'search']).describe('Operation to perform on the cluster data'),
62
63
  // Workflow fields for step-by-step pattern creation
63
- sessionId: zod_1.z.string().optional().describe('Pattern creation session ID (for continuing multi-step workflow)'),
64
+ sessionId: zod_1.z.string().optional().describe('Session ID (required for continuing workflow steps, optional for progress - uses latest session if omitted)'),
65
+ step: zod_1.z.string().optional().describe('Current workflow step (required when sessionId is provided)'),
64
66
  response: zod_1.z.string().optional().describe('User response to previous workflow step question'),
65
- // Generic fields for get/delete operations
66
- id: zod_1.z.string().optional().describe('Data item ID (required for get/delete operations)'),
67
+ // Generic fields for get/delete/search operations
68
+ id: zod_1.z.string().optional().describe('Data item ID (required for get/delete operations) or search query (required for search operations)'),
67
69
  // Generic fields for list operations
68
- limit: zod_1.z.number().optional().describe('Maximum number of items to return (default: 10)')
70
+ limit: zod_1.z.number().optional().describe('Maximum number of items to return (default: 10)'),
71
+ // Resource-specific fields (for capabilities operations)
72
+ resource: zod_1.z.object({
73
+ kind: zod_1.z.string(),
74
+ group: zod_1.z.string(),
75
+ apiVersion: zod_1.z.string()
76
+ }).optional().describe('Kubernetes resource reference (for capabilities operations)'),
77
+ // Resource list for specific resource scanning
78
+ resourceList: zod_1.z.string().optional().describe('Comma-separated list of resources to scan (format: Kind.group or Kind for core resources)')
69
79
  };
70
80
  /**
71
81
  * Get Vector DB-based pattern service with optional embedding support
@@ -120,6 +130,48 @@ async function validateVectorDBConnection(patternService, logger, requestId) {
120
130
  }
121
131
  return { success: true };
122
132
  }
133
+ /**
134
+ * Validate embedding service configuration and fail if unavailable
135
+ */
136
+ async function validateEmbeddingService(logger, requestId) {
137
+ const { EmbeddingService } = await Promise.resolve().then(() => __importStar(require('../core/embedding-service')));
138
+ const embeddingService = new EmbeddingService();
139
+ const status = embeddingService.getStatus();
140
+ if (!status.available) {
141
+ logger.warn('Embedding service required but not available', {
142
+ requestId,
143
+ reason: status.reason
144
+ });
145
+ return {
146
+ success: false,
147
+ error: {
148
+ message: 'OpenAI API key required for pattern management',
149
+ details: 'Pattern management requires OpenAI embeddings for semantic search and storage. The system cannot proceed without proper configuration.',
150
+ reason: status.reason,
151
+ setup: {
152
+ required: 'export OPENAI_API_KEY=your-openai-api-key',
153
+ optional: [
154
+ 'export OPENAI_MODEL=text-embedding-3-small (default)',
155
+ 'export OPENAI_DIMENSIONS=1536 (default)'
156
+ ],
157
+ docs: 'Get API key from https://platform.openai.com/api-keys'
158
+ },
159
+ currentConfig: {
160
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY ? 'set' : 'not set',
161
+ QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333',
162
+ status: 'embedding service unavailable'
163
+ }
164
+ }
165
+ };
166
+ }
167
+ logger.info('Embedding service available', {
168
+ requestId,
169
+ provider: status.provider,
170
+ model: status.model,
171
+ dimensions: status.dimensions
172
+ });
173
+ return { success: true };
174
+ }
123
175
  /**
124
176
  * Handle pattern operations with workflow support
125
177
  */
@@ -136,6 +188,17 @@ async function handlePatternOperation(operation, args, logger, requestId) {
136
188
  message: 'Vector DB connection required for pattern management'
137
189
  };
138
190
  }
191
+ // Validate embedding service and fail if unavailable
192
+ const embeddingCheck = await validateEmbeddingService(logger, requestId);
193
+ if (!embeddingCheck.success) {
194
+ return {
195
+ success: false,
196
+ operation,
197
+ dataType: 'pattern',
198
+ error: embeddingCheck.error,
199
+ message: 'OpenAI API key required for pattern management'
200
+ };
201
+ }
139
202
  const sessionManager = new pattern_creation_session_1.PatternCreationSessionManager();
140
203
  switch (operation) {
141
204
  case 'create': {
@@ -366,6 +429,1680 @@ async function handlePatternOperation(operation, args, logger, requestId) {
366
429
  });
367
430
  }
368
431
  }
432
+ /**
433
+ * Create standardized capability scan completion response
434
+ */
435
+ function createCapabilityScanCompletionResponse(sessionId, totalProcessed, successful, failed, processingTime, mode, stopped = false) {
436
+ let message;
437
+ if (stopped) {
438
+ message = `⏹️ Capability scan stopped by user after processing ${successful} of ${totalProcessed} resources.`;
439
+ }
440
+ else if (failed > 0) {
441
+ message = `✅ Capability scan completed with ${failed} errors. ${successful}/${totalProcessed} resources processed successfully.`;
442
+ }
443
+ else {
444
+ message = `✅ Capability scan completed successfully! Processed ${totalProcessed} resources.`;
445
+ }
446
+ return {
447
+ success: true,
448
+ operation: 'scan',
449
+ dataType: 'capabilities',
450
+ mode: mode,
451
+ step: 'complete',
452
+ sessionId: sessionId,
453
+ summary: {
454
+ totalScanned: totalProcessed,
455
+ successful: successful,
456
+ failed: failed,
457
+ processingTime: processingTime,
458
+ ...(stopped && { stopped: true })
459
+ },
460
+ message: message,
461
+ availableOptions: {
462
+ viewResults: "Use 'list' operation to browse all discovered capabilities",
463
+ getDetails: "Use 'get' operation with capability ID to view specific capability details",
464
+ checkStatus: successful > 0 ? "Capabilities are now available for AI-powered recommendations" : "No capabilities were stored"
465
+ },
466
+ userNote: "The above options are available for you to choose from - the system will not execute them automatically."
467
+ };
468
+ }
469
+ /**
470
+ * Handle capabilities operations - PRD #48 Implementation
471
+ */
472
+ async function handleCapabilitiesOperation(operation, args, logger, requestId) {
473
+ logger.info('Capabilities operation requested', { requestId, operation });
474
+ switch (operation) {
475
+ case 'scan':
476
+ return await handleCapabilityScan(args, logger, requestId);
477
+ case 'progress':
478
+ return await handleCapabilityProgress(args, logger, requestId);
479
+ case 'list':
480
+ case 'get':
481
+ case 'search':
482
+ case 'delete':
483
+ case 'deleteAll': {
484
+ // Create and initialize capability service for list/get/delete operations
485
+ const capabilityService = new index_1.CapabilityVectorService();
486
+ try {
487
+ const vectorDBHealthy = await capabilityService.healthCheck();
488
+ if (!vectorDBHealthy) {
489
+ return {
490
+ success: false,
491
+ operation,
492
+ dataType: 'capabilities',
493
+ error: {
494
+ message: 'Vector DB (Qdrant) connection required',
495
+ details: 'Capability operations require a working Qdrant connection.',
496
+ setup: {
497
+ docker: 'docker run -p 6333:6333 qdrant/qdrant',
498
+ config: 'export QDRANT_URL=http://localhost:6333'
499
+ }
500
+ }
501
+ };
502
+ }
503
+ await capabilityService.initialize();
504
+ if (operation === 'list') {
505
+ return await handleCapabilityList(args, logger, requestId, capabilityService);
506
+ }
507
+ else if (operation === 'get') {
508
+ return await handleCapabilityGet(args, logger, requestId, capabilityService);
509
+ }
510
+ else if (operation === 'search') {
511
+ return await handleCapabilitySearch(args, logger, requestId, capabilityService);
512
+ }
513
+ else if (operation === 'delete') {
514
+ return await handleCapabilityDelete(args, logger, requestId, capabilityService);
515
+ }
516
+ else if (operation === 'deleteAll') {
517
+ return await handleCapabilityDeleteAll(args, logger, requestId, capabilityService);
518
+ }
519
+ else {
520
+ // This should never happen since we already check the operation in the switch case
521
+ throw new Error(`Unexpected operation: ${operation}`);
522
+ }
523
+ }
524
+ catch (error) {
525
+ logger.error(`Capability ${operation} operation failed`, error, { requestId });
526
+ return {
527
+ success: false,
528
+ operation,
529
+ dataType: 'capabilities',
530
+ error: {
531
+ message: `Capability ${operation} failed`,
532
+ details: error instanceof Error ? error.message : String(error)
533
+ }
534
+ };
535
+ }
536
+ }
537
+ default:
538
+ return {
539
+ success: false,
540
+ operation,
541
+ dataType: 'capabilities',
542
+ error: {
543
+ message: `Unsupported capabilities operation: ${operation}`,
544
+ supportedOperations: ['scan', 'list', 'get', 'delete', 'deleteAll']
545
+ }
546
+ };
547
+ }
548
+ }
549
+ /**
550
+ * Convert numeric response to option value
551
+ */
552
+ function parseNumericResponse(response, validOptions) {
553
+ // If response is a number, map it to the corresponding option
554
+ const num = parseInt(response, 10);
555
+ if (!isNaN(num) && num >= 1 && num <= validOptions.length) {
556
+ return validOptions[num - 1]; // Convert 1-based to 0-based index
557
+ }
558
+ // Otherwise return the original response (for backward compatibility)
559
+ return response;
560
+ }
561
+ /**
562
+ * Get session file path following established pattern
563
+ */
564
+ function getCapabilitySessionPath(sessionId, args) {
565
+ const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false);
566
+ const sessionSubDir = path.join(sessionDir, 'capability-sessions');
567
+ // Ensure capability-sessions subdirectory exists
568
+ if (!fs.existsSync(sessionSubDir)) {
569
+ fs.mkdirSync(sessionSubDir, { recursive: true });
570
+ }
571
+ return path.join(sessionSubDir, `${sessionId}.json`);
572
+ }
573
+ /**
574
+ * Load session from file system following established pattern
575
+ */
576
+ function loadCapabilitySession(sessionId, args) {
577
+ try {
578
+ const sessionPath = getCapabilitySessionPath(sessionId, args);
579
+ if (!fs.existsSync(sessionPath)) {
580
+ return null;
581
+ }
582
+ const sessionData = fs.readFileSync(sessionPath, 'utf8');
583
+ const session = JSON.parse(sessionData);
584
+ // Update last activity
585
+ session.lastActivity = new Date().toISOString();
586
+ saveCapabilitySession(session, args);
587
+ return session;
588
+ }
589
+ catch (error) {
590
+ // Log error but don't throw - return null to create new session
591
+ return null;
592
+ }
593
+ }
594
+ /**
595
+ * Save session to file system following established pattern
596
+ */
597
+ function saveCapabilitySession(session, args) {
598
+ try {
599
+ const sessionPath = getCapabilitySessionPath(session.sessionId, args);
600
+ fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2), 'utf8');
601
+ }
602
+ catch (error) {
603
+ // Log error but don't throw - workflow can continue
604
+ console.warn('Failed to save capability session:', error);
605
+ }
606
+ }
607
+ /**
608
+ * Get or create session with file-based persistence
609
+ */
610
+ function getOrCreateCapabilitySession(sessionId, args, logger, requestId) {
611
+ if (sessionId) {
612
+ const existing = loadCapabilitySession(sessionId, args);
613
+ if (existing) {
614
+ logger.info('Loaded existing capability session', {
615
+ requestId,
616
+ sessionId,
617
+ currentStep: existing.currentStep
618
+ });
619
+ return existing;
620
+ }
621
+ }
622
+ // Create new session
623
+ const newSessionId = sessionId || `cap-scan-${Date.now()}`;
624
+ const session = {
625
+ sessionId: newSessionId,
626
+ currentStep: 'resource-selection',
627
+ startedAt: new Date().toISOString(),
628
+ lastActivity: new Date().toISOString()
629
+ };
630
+ saveCapabilitySession(session, args);
631
+ logger.info('Created new capability session', {
632
+ requestId,
633
+ sessionId: newSessionId,
634
+ currentStep: session.currentStep
635
+ });
636
+ return session;
637
+ }
638
+ /**
639
+ * Validate client is on the correct step - step parameter is MANDATORY
640
+ */
641
+ function validateCapabilityStep(session, clientStep) {
642
+ if (!clientStep) {
643
+ return {
644
+ valid: false,
645
+ error: `Step parameter is required. You are currently on step '${session.currentStep}'. Please call with step='${session.currentStep}' and appropriate parameters.`
646
+ };
647
+ }
648
+ if (clientStep !== session.currentStep) {
649
+ return {
650
+ valid: false,
651
+ error: `Step mismatch: you're on step '${session.currentStep}', but called with step '${clientStep}'. Please call with step='${session.currentStep}'.`
652
+ };
653
+ }
654
+ return { valid: true };
655
+ }
656
+ /**
657
+ * Transition session to next step with proper state updates
658
+ */
659
+ function transitionCapabilitySession(session, nextStep, updates, args) {
660
+ session.currentStep = nextStep;
661
+ session.lastActivity = new Date().toISOString();
662
+ if (updates) {
663
+ Object.assign(session, updates);
664
+ }
665
+ saveCapabilitySession(session, args);
666
+ }
667
+ /**
668
+ * Clean up session file after successful completion
669
+ */
670
+ function cleanupCapabilitySession(session, args, logger, requestId) {
671
+ try {
672
+ const sessionPath = getCapabilitySessionPath(session.sessionId, args);
673
+ if (fs.existsSync(sessionPath)) {
674
+ fs.unlinkSync(sessionPath);
675
+ logger.info('Capability session cleaned up after completion', {
676
+ requestId,
677
+ sessionId: session.sessionId
678
+ });
679
+ }
680
+ }
681
+ catch (error) {
682
+ // Log cleanup failure but don't fail the operation
683
+ logger.warn('Failed to cleanup capability session file', {
684
+ requestId,
685
+ sessionId: session.sessionId,
686
+ error: error instanceof Error ? error.message : String(error)
687
+ });
688
+ }
689
+ }
690
+ /**
691
+ * Handle capability scanning workflow with step-based state management
692
+ */
693
+ async function handleCapabilityScan(args, logger, requestId) {
694
+ // Validate Vector DB and embedding service dependencies upfront
695
+ // This prevents users from going through the entire workflow only to fail at storage
696
+ const capabilityService = new index_1.CapabilityVectorService();
697
+ // Check Vector DB connection and initialize collection
698
+ try {
699
+ const vectorDBHealthy = await capabilityService.healthCheck();
700
+ if (!vectorDBHealthy) {
701
+ return {
702
+ success: false,
703
+ operation: 'scan',
704
+ dataType: 'capabilities',
705
+ error: {
706
+ message: 'Vector DB (Qdrant) connection required for capability management',
707
+ details: 'Capability scanning requires Qdrant for storing and searching capabilities. The system cannot proceed without a working Vector DB connection.',
708
+ setup: {
709
+ required: 'Qdrant server must be running',
710
+ default: 'http://localhost:6333',
711
+ docker: 'docker run -p 6333:6333 qdrant/qdrant',
712
+ config: 'export QDRANT_URL=http://localhost:6333'
713
+ },
714
+ currentConfig: {
715
+ QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333 (default)',
716
+ status: 'connection failed'
717
+ }
718
+ }
719
+ };
720
+ }
721
+ }
722
+ catch (error) {
723
+ logger.error('Vector DB connection check failed', error, { requestId });
724
+ return {
725
+ success: false,
726
+ operation: 'scan',
727
+ dataType: 'capabilities',
728
+ error: {
729
+ message: 'Vector DB (Qdrant) connection failed',
730
+ details: 'Cannot establish connection to Qdrant server. Please ensure Qdrant is running and accessible.',
731
+ technicalDetails: error instanceof Error ? error.message : String(error),
732
+ setup: {
733
+ required: 'Qdrant server must be running',
734
+ default: 'http://localhost:6333',
735
+ docker: 'docker run -p 6333:6333 qdrant/qdrant',
736
+ config: 'export QDRANT_URL=http://localhost:6333'
737
+ },
738
+ currentConfig: {
739
+ QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333 (default)',
740
+ status: 'connection error'
741
+ }
742
+ }
743
+ };
744
+ }
745
+ // Initialize the collection now that we know Qdrant is healthy
746
+ try {
747
+ await capabilityService.initialize();
748
+ }
749
+ catch (error) {
750
+ logger.error('Failed to initialize capabilities collection', error, { requestId });
751
+ return {
752
+ success: false,
753
+ operation: 'scan',
754
+ dataType: 'capabilities',
755
+ error: {
756
+ message: 'Vector DB collection initialization failed',
757
+ details: 'Could not create or access the capabilities collection in Qdrant.',
758
+ technicalDetails: error instanceof Error ? error.message : String(error),
759
+ setup: {
760
+ possibleCauses: [
761
+ 'Qdrant version compatibility issue',
762
+ 'Insufficient permissions',
763
+ 'Collection dimension mismatch',
764
+ 'Corrupted existing collection'
765
+ ],
766
+ recommendations: [
767
+ 'Check Qdrant logs for detailed error information',
768
+ 'Verify Qdrant version compatibility',
769
+ 'Consider removing existing capabilities collection if corrupted'
770
+ ]
771
+ }
772
+ }
773
+ };
774
+ }
775
+ // Check embedding service (OpenAI API) availability
776
+ const embeddingCheck = await validateEmbeddingService(logger, requestId);
777
+ if (!embeddingCheck.success) {
778
+ return {
779
+ success: false,
780
+ operation: 'scan',
781
+ dataType: 'capabilities',
782
+ error: {
783
+ message: 'OpenAI API required for capability semantic search',
784
+ details: 'Capability scanning requires OpenAI embeddings for semantic search functionality. The system cannot proceed without proper OpenAI API configuration.',
785
+ ...embeddingCheck.error,
786
+ recommendation: 'Set up OpenAI API key to enable full capability scanning with semantic search'
787
+ }
788
+ };
789
+ }
790
+ logger.info('Capability scanning dependencies validated', {
791
+ requestId,
792
+ vectorDB: 'healthy',
793
+ embeddings: 'available'
794
+ });
795
+ // Get or create session with step-based state management
796
+ const session = getOrCreateCapabilitySession(args.sessionId, args, logger, requestId);
797
+ // Validate client is on correct step - only for existing sessions
798
+ // New sessions (no sessionId provided) are allowed to start without step parameter
799
+ // If sessionId is provided, client must follow step-based protocol
800
+ const clientProvidedSessionId = !!args.sessionId;
801
+ if (clientProvidedSessionId) {
802
+ const stepValidation = validateCapabilityStep(session, args.step);
803
+ if (!stepValidation.valid) {
804
+ return {
805
+ success: false,
806
+ operation: 'scan',
807
+ dataType: 'capabilities',
808
+ error: {
809
+ message: 'Step validation failed',
810
+ details: stepValidation.error,
811
+ currentStep: session.currentStep,
812
+ expectedCall: `Call with step='${session.currentStep}' and appropriate parameters`
813
+ }
814
+ };
815
+ }
816
+ }
817
+ // Handle workflow based on current step
818
+ switch (session.currentStep) {
819
+ case 'resource-selection':
820
+ return await handleResourceSelection(session, args, logger, requestId);
821
+ case 'resource-specification':
822
+ return await handleResourceSpecification(session, args, logger, requestId);
823
+ case 'processing-mode':
824
+ return await handleProcessingMode(session, args, logger, requestId, capabilityService);
825
+ case 'scanning':
826
+ return await handleScanning(session, args, logger, requestId, capabilityService);
827
+ case 'complete':
828
+ return {
829
+ success: false,
830
+ operation: 'scan',
831
+ dataType: 'capabilities',
832
+ error: {
833
+ message: 'Workflow already complete',
834
+ details: `Session ${session.sessionId} has already completed capability scanning.`,
835
+ sessionId: session.sessionId
836
+ }
837
+ };
838
+ default:
839
+ return {
840
+ success: false,
841
+ operation: 'scan',
842
+ dataType: 'capabilities',
843
+ error: {
844
+ message: 'Invalid workflow state',
845
+ details: `Unknown step: ${session.currentStep}`,
846
+ currentStep: session.currentStep
847
+ }
848
+ };
849
+ }
850
+ }
851
+ /**
852
+ * Handle resource selection step
853
+ */
854
+ async function handleResourceSelection(session, args, _logger, _requestId) {
855
+ if (!args.response) {
856
+ // Show initial resource selection prompt
857
+ return {
858
+ success: true,
859
+ operation: 'scan',
860
+ dataType: 'capabilities',
861
+ // CRITICAL: Put required parameters at top level for maximum visibility
862
+ REQUIRED_NEXT_CALL: {
863
+ tool: 'dot-ai:manageOrgData',
864
+ parameters: {
865
+ dataType: 'capabilities',
866
+ operation: 'scan',
867
+ sessionId: session.sessionId,
868
+ step: 'resource-selection', // MANDATORY PARAMETER
869
+ response: 'user_choice_here' // Replace with actual user choice
870
+ },
871
+ note: 'The step parameter is MANDATORY when sessionId is provided'
872
+ },
873
+ workflow: {
874
+ step: 'resource-selection',
875
+ question: 'Scan all cluster resources or specify subset?',
876
+ options: [
877
+ { number: 1, value: 'all', display: '1. all - Scan all available cluster resources' },
878
+ { number: 2, value: 'specific', display: '2. specific - Specify particular resource types to scan' }
879
+ ],
880
+ sessionId: session.sessionId,
881
+ instruction: 'IMPORTANT: You MUST ask the user to make a choice. Do NOT automatically select an option.',
882
+ userPrompt: 'Would you like to scan all cluster resources or specify a subset?',
883
+ clientInstructions: {
884
+ behavior: 'interactive',
885
+ requirement: 'Ask user to choose between options',
886
+ prohibit: 'Do not auto-select options',
887
+ nextStep: `Call with step='resource-selection', sessionId='${session.sessionId}', and response parameter containing the semantic value (all or specific)`,
888
+ responseFormat: 'Convert user input to semantic values: 1→all, 2→specific, or pass through semantic words directly',
889
+ requiredParameters: {
890
+ step: 'resource-selection',
891
+ sessionId: session.sessionId,
892
+ response: 'user choice (all or specific)'
893
+ }
894
+ }
895
+ }
896
+ };
897
+ }
898
+ // Process user response
899
+ const normalizedResponse = parseNumericResponse(args.response, ['all', 'specific']);
900
+ if (normalizedResponse === 'all') {
901
+ // Transition to processing mode for all resources
902
+ transitionCapabilitySession(session, 'processing-mode', { selectedResources: 'all' }, args);
903
+ return {
904
+ success: true,
905
+ operation: 'scan',
906
+ dataType: 'capabilities',
907
+ // CRITICAL: Put required parameters at top level for maximum visibility
908
+ REQUIRED_NEXT_CALL: {
909
+ tool: 'dot-ai:manageOrgData',
910
+ parameters: {
911
+ dataType: 'capabilities',
912
+ operation: 'scan',
913
+ sessionId: session.sessionId,
914
+ step: 'processing-mode', // MANDATORY PARAMETER
915
+ response: 'user_choice_here' // Replace with actual user choice
916
+ },
917
+ note: 'The step parameter is MANDATORY when sessionId is provided'
918
+ },
919
+ workflow: {
920
+ step: 'processing-mode',
921
+ question: 'Processing mode: auto (batch process) or manual (review each)?',
922
+ options: [
923
+ { number: 1, value: 'auto', display: '1. auto - Batch process automatically' },
924
+ { number: 2, value: 'manual', display: '2. manual - Review each step' }
925
+ ],
926
+ sessionId: session.sessionId,
927
+ selectedResources: 'all',
928
+ instruction: 'IMPORTANT: You MUST ask the user to make a choice. Do NOT automatically select a processing mode.',
929
+ userPrompt: 'How would you like to process the resources?',
930
+ clientInstructions: {
931
+ behavior: 'interactive',
932
+ requirement: 'Ask user to choose processing mode',
933
+ prohibit: 'Do not auto-select processing mode',
934
+ nextStep: `Call with step='processing-mode', sessionId='${session.sessionId}', and response parameter containing the semantic value (auto or manual)`,
935
+ responseFormat: 'Convert user input to semantic values: 1→auto, 2→manual, or pass through semantic words directly',
936
+ requiredParameters: {
937
+ step: 'processing-mode',
938
+ sessionId: session.sessionId,
939
+ response: 'user choice (auto or manual)'
940
+ }
941
+ }
942
+ }
943
+ };
944
+ }
945
+ if (normalizedResponse === 'specific') {
946
+ // Transition to resource specification
947
+ transitionCapabilitySession(session, 'resource-specification', {}, args);
948
+ return {
949
+ success: true,
950
+ operation: 'scan',
951
+ dataType: 'capabilities',
952
+ // CRITICAL: Put required parameters at top level for maximum visibility
953
+ REQUIRED_NEXT_CALL: {
954
+ tool: 'dot-ai:manageOrgData',
955
+ parameters: {
956
+ dataType: 'capabilities',
957
+ operation: 'scan',
958
+ sessionId: session.sessionId,
959
+ step: 'resource-specification', // MANDATORY PARAMETER
960
+ resourceList: 'user_resource_list_here' // Replace with actual resource list
961
+ },
962
+ note: 'The step parameter is MANDATORY when sessionId is provided'
963
+ },
964
+ workflow: {
965
+ step: 'resource-specification',
966
+ question: 'Which resources would you like to scan?',
967
+ sessionId: session.sessionId,
968
+ instruction: 'IMPORTANT: You MUST ask the user to specify which resources to scan. Do NOT provide a default list.',
969
+ userPrompt: 'Please specify which resources you want to scan.',
970
+ resourceFormat: {
971
+ description: 'Specify resources using Kubernetes naming convention',
972
+ format: 'Kind.group for CRDs, Kind for core resources',
973
+ examples: {
974
+ crds: ['SQL.devopstoolkit.live', 'Server.dbforpostgresql.azure.upbound.io'],
975
+ core: ['Pod', 'Service', 'ConfigMap'],
976
+ apps: ['Deployment.apps', 'StatefulSet.apps']
977
+ },
978
+ input: 'Comma-separated list (e.g.: SQL.devopstoolkit.live, Deployment.apps, Pod)'
979
+ },
980
+ clientInstructions: {
981
+ behavior: 'interactive',
982
+ requirement: 'Ask user to provide specific resource list',
983
+ prohibit: 'Do not suggest or auto-select resources',
984
+ nextStep: `Call with step='resource-specification', sessionId='${session.sessionId}', and resourceList parameter`,
985
+ requiredParameters: {
986
+ step: 'resource-specification',
987
+ sessionId: session.sessionId,
988
+ resourceList: 'comma-separated list of resources'
989
+ }
990
+ }
991
+ }
992
+ };
993
+ }
994
+ return {
995
+ success: false,
996
+ operation: 'scan',
997
+ dataType: 'capabilities',
998
+ error: {
999
+ message: 'Invalid resource selection response',
1000
+ details: `Expected 'all' or 'specific', got: ${args.response}`,
1001
+ currentStep: session.currentStep
1002
+ }
1003
+ };
1004
+ }
1005
+ /**
1006
+ * Handle resource specification step
1007
+ */
1008
+ async function handleResourceSpecification(session, args, _logger, _requestId) {
1009
+ if (!args.resourceList) {
1010
+ return {
1011
+ success: false,
1012
+ operation: 'scan',
1013
+ dataType: 'capabilities',
1014
+ error: {
1015
+ message: 'Missing resource list',
1016
+ details: 'Expected resourceList parameter with comma-separated resource names',
1017
+ currentStep: session.currentStep,
1018
+ expectedCall: `Call with step='resource-specification' and resourceList parameter`
1019
+ }
1020
+ };
1021
+ }
1022
+ // Parse and validate resource list
1023
+ const resources = args.resourceList.split(',').map((r) => r.trim()).filter((r) => r.length > 0);
1024
+ if (resources.length === 0) {
1025
+ return {
1026
+ success: false,
1027
+ operation: 'scan',
1028
+ dataType: 'capabilities',
1029
+ error: {
1030
+ message: 'Empty resource list',
1031
+ details: 'Resource list cannot be empty',
1032
+ currentStep: session.currentStep
1033
+ }
1034
+ };
1035
+ }
1036
+ // Transition to processing mode for specific resources
1037
+ transitionCapabilitySession(session, 'processing-mode', {
1038
+ selectedResources: resources,
1039
+ resourceList: args.resourceList
1040
+ }, args);
1041
+ return {
1042
+ success: true,
1043
+ operation: 'scan',
1044
+ dataType: 'capabilities',
1045
+ // CRITICAL: Put required parameters at top level for maximum visibility
1046
+ REQUIRED_NEXT_CALL: {
1047
+ tool: 'dot-ai:manageOrgData',
1048
+ parameters: {
1049
+ dataType: 'capabilities',
1050
+ operation: 'scan',
1051
+ sessionId: session.sessionId,
1052
+ step: 'processing-mode', // MANDATORY PARAMETER
1053
+ response: 'user_choice_here' // Replace with actual user choice
1054
+ },
1055
+ note: 'The step parameter is MANDATORY when sessionId is provided'
1056
+ },
1057
+ workflow: {
1058
+ step: 'processing-mode',
1059
+ question: `Processing mode for ${resources.length} selected resources: auto (batch process) or manual (review each)?`,
1060
+ options: [
1061
+ { number: 1, value: 'auto', display: '1. auto - Batch process automatically' },
1062
+ { number: 2, value: 'manual', display: '2. manual - Review each step' }
1063
+ ],
1064
+ sessionId: session.sessionId,
1065
+ selectedResources: resources,
1066
+ instruction: 'IMPORTANT: You MUST ask the user to choose processing mode for the specified resources.',
1067
+ userPrompt: `How would you like to process these ${resources.length} resources?`,
1068
+ clientInstructions: {
1069
+ behavior: 'interactive',
1070
+ requirement: 'Ask user to choose processing mode for specific resources',
1071
+ context: `Processing ${resources.length} user-specified resources: ${resources.join(', ')}`,
1072
+ prohibit: 'Do not auto-select processing mode',
1073
+ nextStep: `Call with step='processing-mode', sessionId='${session.sessionId}', and response parameter containing the semantic value (auto or manual)`,
1074
+ responseFormat: 'Convert user input to semantic values: 1→auto, 2→manual, or pass through semantic words directly',
1075
+ requiredParameters: {
1076
+ step: 'processing-mode',
1077
+ sessionId: session.sessionId,
1078
+ response: 'user choice (auto or manual)'
1079
+ }
1080
+ }
1081
+ }
1082
+ };
1083
+ }
1084
+ /**
1085
+ * Handle processing mode step
1086
+ */
1087
+ async function handleProcessingMode(session, args, logger, requestId, capabilityService) {
1088
+ if (!args.response) {
1089
+ return {
1090
+ success: false,
1091
+ operation: 'scan',
1092
+ dataType: 'capabilities',
1093
+ error: {
1094
+ message: 'Missing processing mode response',
1095
+ details: 'Expected response parameter with processing mode choice',
1096
+ currentStep: session.currentStep,
1097
+ expectedCall: `Call with step='processing-mode' and response parameter (auto or manual)`
1098
+ }
1099
+ };
1100
+ }
1101
+ // Process user response
1102
+ const processingModeResponse = parseNumericResponse(args.response, ['auto', 'manual']);
1103
+ if (processingModeResponse !== 'auto' && processingModeResponse !== 'manual') {
1104
+ return {
1105
+ success: false,
1106
+ operation: 'scan',
1107
+ dataType: 'capabilities',
1108
+ error: {
1109
+ message: 'Invalid processing mode response',
1110
+ details: `Expected 'auto' or 'manual', got: ${args.response}`,
1111
+ currentStep: session.currentStep
1112
+ }
1113
+ };
1114
+ }
1115
+ // Transition to scanning with processing mode and initialize resource tracking
1116
+ transitionCapabilitySession(session, 'scanning', {
1117
+ processingMode: processingModeResponse,
1118
+ currentResourceIndex: 0 // Start with first resource
1119
+ }, args);
1120
+ // Begin actual capability scanning - clear response from previous step
1121
+ return await handleScanning(session, { ...args, response: undefined }, logger, requestId, capabilityService);
1122
+ }
1123
+ /**
1124
+ * Handle scanning step (actual capability analysis)
1125
+ */
1126
+ async function handleScanning(session, args, logger, requestId, capabilityService) {
1127
+ try {
1128
+ // If this is a response to a manual mode preview, handle it first
1129
+ if (session.processingMode === 'manual' && args.response) {
1130
+ const userResponse = parseNumericResponse(args.response, ['yes', 'no', 'stop']);
1131
+ if (userResponse === 'stop') {
1132
+ // User wants to stop scanning
1133
+ transitionCapabilitySession(session, 'complete', {}, args);
1134
+ cleanupCapabilitySession(session, args, logger, requestId);
1135
+ const currentIndex = session.currentResourceIndex || 0;
1136
+ const totalResources = Array.isArray(session.selectedResources) ? session.selectedResources.length : 1;
1137
+ return createCapabilityScanCompletionResponse(session.sessionId, totalResources, currentIndex, // Resources processed so far
1138
+ 0, 'stopped interactively', 'manual', true // stopped = true
1139
+ );
1140
+ }
1141
+ if (userResponse === 'yes' || userResponse === 'no') {
1142
+ // TODO: If 'yes', store the capability in Vector DB (Milestone 2)
1143
+ // For now, just log the decision
1144
+ logger.info(`User ${userResponse === 'yes' ? 'accepted' : 'skipped'} capability for resource`, {
1145
+ requestId,
1146
+ sessionId: session.sessionId,
1147
+ resourceIndex: session.currentResourceIndex,
1148
+ decision: userResponse
1149
+ });
1150
+ // Move to the next resource
1151
+ const nextIndex = (session.currentResourceIndex || 0) + 1;
1152
+ transitionCapabilitySession(session, 'scanning', { currentResourceIndex: nextIndex }, args);
1153
+ // Continue processing (will handle the next resource or complete if done)
1154
+ return await handleScanning(session, { ...args, response: undefined }, logger, requestId, capabilityService);
1155
+ }
1156
+ // Invalid response
1157
+ return {
1158
+ success: false,
1159
+ operation: 'scan',
1160
+ dataType: 'capabilities',
1161
+ error: {
1162
+ message: 'Invalid response to capability preview',
1163
+ details: `Expected 'yes', 'no', or 'stop', got: ${args.response}`,
1164
+ currentStep: session.currentStep
1165
+ }
1166
+ };
1167
+ }
1168
+ // Import capability engine
1169
+ const { CapabilityInferenceEngine } = await Promise.resolve().then(() => __importStar(require('../core/capabilities')));
1170
+ const { ClaudeIntegration } = await Promise.resolve().then(() => __importStar(require('../core/claude')));
1171
+ // Validate Claude API key
1172
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1173
+ if (!apiKey) {
1174
+ return {
1175
+ success: false,
1176
+ operation: 'scan',
1177
+ dataType: 'capabilities',
1178
+ error: {
1179
+ message: 'ANTHROPIC_API_KEY required for capability inference',
1180
+ details: 'Set ANTHROPIC_API_KEY environment variable to enable AI-powered capability analysis'
1181
+ }
1182
+ };
1183
+ }
1184
+ // Initialize capability engine
1185
+ const claudeIntegration = new ClaudeIntegration(apiKey);
1186
+ const engine = new CapabilityInferenceEngine(claudeIntegration, logger);
1187
+ // Get the resource to analyze
1188
+ let resourceName;
1189
+ let currentIndex;
1190
+ let totalResources;
1191
+ if (session.selectedResources === 'all') {
1192
+ // For 'all' mode, discover actual cluster resources first
1193
+ try {
1194
+ logger.info('Discovering all cluster resources for capability scanning', { requestId, sessionId: session.sessionId });
1195
+ // Import discovery engine
1196
+ const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
1197
+ const discovery = new KubernetesDiscovery();
1198
+ await discovery.connect();
1199
+ // Discover all available resources
1200
+ const resourceMap = await discovery.discoverResources();
1201
+ const allResources = [...resourceMap.resources, ...resourceMap.custom];
1202
+ // Extract resource names for capability analysis
1203
+ const discoveredResourceNames = allResources.map(resource => {
1204
+ // For CRDs (custom resources), prioritize full name format
1205
+ if (resource.name && resource.name.includes('.')) {
1206
+ return resource.name;
1207
+ }
1208
+ // For standard resources, use kind
1209
+ if (resource.kind) {
1210
+ return resource.kind;
1211
+ }
1212
+ // Fallback to name if no kind
1213
+ if (resource.name) {
1214
+ return resource.name;
1215
+ }
1216
+ return 'unknown-resource';
1217
+ }).filter(name => name !== 'unknown-resource');
1218
+ logger.info('Discovered cluster resources for capability scanning', {
1219
+ requestId,
1220
+ sessionId: session.sessionId,
1221
+ totalDiscovered: discoveredResourceNames.length,
1222
+ sampleResources: discoveredResourceNames.slice(0, 5)
1223
+ });
1224
+ if (discoveredResourceNames.length === 0) {
1225
+ return {
1226
+ success: false,
1227
+ operation: 'scan',
1228
+ dataType: 'capabilities',
1229
+ error: {
1230
+ message: 'No resources discovered in cluster',
1231
+ details: 'Cluster resource discovery returned empty results. Check cluster connectivity and permissions.',
1232
+ sessionId: session.sessionId
1233
+ }
1234
+ };
1235
+ }
1236
+ // Update session with discovered resources and start batch processing
1237
+ transitionCapabilitySession(session, 'scanning', {
1238
+ selectedResources: discoveredResourceNames,
1239
+ processingMode: session.processingMode,
1240
+ currentResourceIndex: 0
1241
+ }, args);
1242
+ // Continue to batch processing with discovered resources
1243
+ resourceName = discoveredResourceNames[0]; // First resource for manual mode
1244
+ currentIndex = 0;
1245
+ totalResources = discoveredResourceNames.length;
1246
+ }
1247
+ catch (error) {
1248
+ logger.error('Failed to discover cluster resources', error, {
1249
+ requestId,
1250
+ sessionId: session.sessionId
1251
+ });
1252
+ return {
1253
+ success: false,
1254
+ operation: 'scan',
1255
+ dataType: 'capabilities',
1256
+ error: {
1257
+ message: 'Cluster resource discovery failed',
1258
+ details: error instanceof Error ? error.message : String(error),
1259
+ sessionId: session.sessionId,
1260
+ suggestedActions: [
1261
+ 'Check cluster connectivity',
1262
+ 'Verify kubectl access permissions',
1263
+ 'Try specifying specific resources instead of "all"'
1264
+ ]
1265
+ }
1266
+ };
1267
+ }
1268
+ }
1269
+ else if (Array.isArray(session.selectedResources)) {
1270
+ // Get the current resource based on currentResourceIndex
1271
+ currentIndex = session.currentResourceIndex || 0;
1272
+ totalResources = session.selectedResources.length;
1273
+ if (currentIndex >= totalResources) {
1274
+ // All resources processed - mark as complete
1275
+ transitionCapabilitySession(session, 'complete', {}, args);
1276
+ cleanupCapabilitySession(session, args, logger, requestId);
1277
+ return createCapabilityScanCompletionResponse(session.sessionId, totalResources, totalResources, // Assume all successful for manual mode
1278
+ 0, 'completed interactively', 'manual');
1279
+ }
1280
+ resourceName = session.selectedResources[currentIndex];
1281
+ if (!resourceName) {
1282
+ throw new Error(`No resource found at index ${currentIndex}`);
1283
+ }
1284
+ }
1285
+ else {
1286
+ throw new Error('Invalid selectedResources in session state');
1287
+ }
1288
+ // Get actual CRD schema and metadata for comprehensive analysis
1289
+ let resourceSchema;
1290
+ let resourceMetadata;
1291
+ try {
1292
+ // Import and connect to discovery engine
1293
+ const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
1294
+ const discovery = new KubernetesDiscovery();
1295
+ await discovery.connect();
1296
+ // Get CRD definition with schema and metadata if it's a custom resource
1297
+ if (resourceName.includes('.')) {
1298
+ const crdList = await discovery.discoverCRDDetails();
1299
+ const matchingCRD = crdList.find((crd) => crd.name === resourceName);
1300
+ if (matchingCRD) {
1301
+ resourceSchema = JSON.stringify(matchingCRD.schema, null, 2);
1302
+ resourceMetadata = matchingCRD.metadata;
1303
+ logger.info('Found CRD schema and metadata for capability analysis', {
1304
+ requestId,
1305
+ sessionId: session.sessionId,
1306
+ resource: resourceName,
1307
+ hasSchema: !!resourceSchema,
1308
+ hasMetadata: !!resourceMetadata,
1309
+ metadataLabels: Object.keys(resourceMetadata?.labels || {}),
1310
+ categories: resourceMetadata?.categories?.length || 0
1311
+ });
1312
+ }
1313
+ }
1314
+ }
1315
+ catch (error) {
1316
+ logger.warn('Could not retrieve CRD schema and metadata, using name-based inference', {
1317
+ requestId,
1318
+ sessionId: session.sessionId,
1319
+ resource: resourceName,
1320
+ error: error instanceof Error ? error.message : String(error)
1321
+ });
1322
+ }
1323
+ logger.info('Analyzing resource for capability inference', {
1324
+ requestId,
1325
+ sessionId: session.sessionId,
1326
+ resource: resourceName,
1327
+ mode: session.processingMode
1328
+ });
1329
+ if (session.processingMode === 'manual') {
1330
+ // Manual mode: Show capability data for user review
1331
+ const capability = await engine.inferCapabilities(resourceName, resourceSchema, resourceMetadata);
1332
+ const capabilityId = CapabilityInferenceEngine.generateCapabilityId(resourceName);
1333
+ return {
1334
+ success: true,
1335
+ operation: 'scan',
1336
+ dataType: 'capabilities',
1337
+ mode: 'manual',
1338
+ step: 'scanning',
1339
+ sessionId: session.sessionId,
1340
+ preview: {
1341
+ resource: resourceName,
1342
+ resourceIndex: `${currentIndex + 1}/${totalResources}`,
1343
+ id: capabilityId,
1344
+ data: capability,
1345
+ question: 'Continue storing this capability?',
1346
+ options: [
1347
+ { number: 1, value: 'yes', display: '1. yes - Store this capability' },
1348
+ { number: 2, value: 'no', display: '2. no - Skip this resource' },
1349
+ { number: 3, value: 'stop', display: '3. stop - End scanning process' }
1350
+ ],
1351
+ instruction: 'Review the capability analysis results before storing',
1352
+ clientInstructions: {
1353
+ behavior: 'interactive',
1354
+ requirement: 'Ask user to review capability data and decide on storage',
1355
+ nextStep: `Call with step='scanning' and response parameter containing their choice (yes/no/stop)`,
1356
+ responseFormat: 'Convert user input to semantic values: 1→yes, 2→no, 3→stop'
1357
+ }
1358
+ }
1359
+ };
1360
+ }
1361
+ else {
1362
+ // Auto mode: Process ALL resources in batch without user interaction
1363
+ // At this point, selectedResources should always be an array (either discovered or specified)
1364
+ if (!Array.isArray(session.selectedResources)) {
1365
+ throw new Error(`Invalid selectedResources state: expected array, got ${typeof session.selectedResources}. This indicates a bug in resource discovery.`);
1366
+ }
1367
+ const resources = session.selectedResources;
1368
+ const totalResources = resources.length;
1369
+ const processedResults = [];
1370
+ const errors = [];
1371
+ logger.info('Starting auto batch processing', {
1372
+ requestId,
1373
+ sessionId: session.sessionId,
1374
+ totalResources,
1375
+ resources: resources
1376
+ });
1377
+ // Initialize progress tracking
1378
+ const startTime = Date.now();
1379
+ const updateProgress = (current, currentResource, successful, failed, recentErrors) => {
1380
+ const elapsed = Date.now() - startTime;
1381
+ const percentage = Math.round((current / totalResources) * 100);
1382
+ // Calculate estimated time remaining
1383
+ let estimatedTimeRemaining;
1384
+ if (current > 0) {
1385
+ const avgTimePerResource = elapsed / current;
1386
+ const remainingResources = totalResources - current;
1387
+ const estimatedRemainingMs = remainingResources * avgTimePerResource;
1388
+ const estimatedMinutes = Math.round(estimatedRemainingMs / 60000 * 10) / 10;
1389
+ estimatedTimeRemaining = estimatedMinutes > 1 ?
1390
+ `${estimatedMinutes} minutes` :
1391
+ `${Math.round(estimatedRemainingMs / 1000)} seconds`;
1392
+ }
1393
+ const progressData = {
1394
+ status: 'processing',
1395
+ current: current,
1396
+ total: totalResources,
1397
+ percentage: percentage,
1398
+ currentResource: currentResource,
1399
+ startedAt: new Date(startTime).toISOString(),
1400
+ lastUpdated: new Date().toISOString(),
1401
+ estimatedTimeRemaining,
1402
+ successfulResources: successful,
1403
+ failedResources: failed,
1404
+ errors: recentErrors.slice(-5) // Keep last 5 errors for troubleshooting
1405
+ };
1406
+ // Update session file with progress
1407
+ transitionCapabilitySession(session, 'scanning', {
1408
+ processingMode: session.processingMode,
1409
+ selectedResources: session.selectedResources,
1410
+ currentResourceIndex: current - 1,
1411
+ progress: progressData
1412
+ }, args);
1413
+ };
1414
+ // Get all CRD definitions once for efficiency
1415
+ let availableCRDs = [];
1416
+ try {
1417
+ const { KubernetesDiscovery } = await Promise.resolve().then(() => __importStar(require('../core/discovery')));
1418
+ const discovery = new KubernetesDiscovery();
1419
+ await discovery.connect();
1420
+ availableCRDs = await discovery.discoverCRDDetails();
1421
+ logger.info('Retrieved CRD definitions for batch processing', {
1422
+ requestId,
1423
+ sessionId: session.sessionId,
1424
+ totalCRDs: availableCRDs.length
1425
+ });
1426
+ }
1427
+ catch (error) {
1428
+ logger.warn('Could not retrieve CRDs for batch processing, falling back to name-based inference', {
1429
+ requestId,
1430
+ sessionId: session.sessionId,
1431
+ error: error instanceof Error ? error.message : String(error)
1432
+ });
1433
+ }
1434
+ // Process each resource in the batch with progress tracking
1435
+ for (let i = 0; i < resources.length; i++) {
1436
+ const currentResource = resources[i];
1437
+ // Get CRD schema and metadata for this resource
1438
+ let currentSchema;
1439
+ let currentMetadata;
1440
+ if (currentResource.includes('.')) {
1441
+ const matchingCRD = availableCRDs.find((crd) => crd.name === currentResource);
1442
+ if (matchingCRD) {
1443
+ currentSchema = JSON.stringify(matchingCRD.schema, null, 2);
1444
+ currentMetadata = matchingCRD.metadata;
1445
+ }
1446
+ }
1447
+ // Update progress before processing
1448
+ updateProgress(i + 1, currentResource, processedResults.length, errors.length, errors);
1449
+ try {
1450
+ logger.info(`Processing resource ${i + 1}/${totalResources}`, {
1451
+ requestId,
1452
+ sessionId: session.sessionId,
1453
+ resource: currentResource,
1454
+ percentage: Math.round(((i + 1) / totalResources) * 100)
1455
+ });
1456
+ const capability = await engine.inferCapabilities(currentResource, currentSchema, currentMetadata);
1457
+ const capabilityId = CapabilityInferenceEngine.generateCapabilityId(currentResource);
1458
+ // Store capability in Vector DB
1459
+ await capabilityService.storeCapability(capability);
1460
+ processedResults.push({
1461
+ resource: currentResource,
1462
+ id: capabilityId,
1463
+ capabilities: capability.capabilities,
1464
+ providers: capability.providers,
1465
+ complexity: capability.complexity,
1466
+ confidence: capability.confidence
1467
+ });
1468
+ logger.info(`Successfully processed resource ${i + 1}/${totalResources}`, {
1469
+ requestId,
1470
+ sessionId: session.sessionId,
1471
+ resource: currentResource,
1472
+ capabilitiesFound: capability.capabilities.length,
1473
+ percentage: Math.round(((i + 1) / totalResources) * 100)
1474
+ });
1475
+ }
1476
+ catch (error) {
1477
+ const errorMessage = error instanceof Error ? error.message : String(error);
1478
+ const errorDetail = {
1479
+ resource: currentResource,
1480
+ error: errorMessage,
1481
+ index: i + 1,
1482
+ timestamp: new Date().toISOString()
1483
+ };
1484
+ logger.error(`Failed to process resource ${i + 1}/${totalResources}`, error, {
1485
+ requestId,
1486
+ sessionId: session.sessionId,
1487
+ resource: currentResource,
1488
+ percentage: Math.round(((i + 1) / totalResources) * 100)
1489
+ });
1490
+ errors.push(errorDetail);
1491
+ }
1492
+ }
1493
+ // Final progress update - mark as completed
1494
+ const finalElapsed = Date.now() - startTime;
1495
+ const finalMinutes = Math.round(finalElapsed / 60000 * 10) / 10;
1496
+ const successful = processedResults.length;
1497
+ const failed = errors.length;
1498
+ const completionData = {
1499
+ status: 'completed',
1500
+ current: totalResources,
1501
+ total: totalResources,
1502
+ percentage: 100,
1503
+ currentResource: 'Processing complete',
1504
+ startedAt: new Date(startTime).toISOString(),
1505
+ lastUpdated: new Date().toISOString(),
1506
+ completedAt: new Date().toISOString(),
1507
+ totalProcessingTime: finalMinutes > 1 ? `${finalMinutes} minutes` : `${Math.round(finalElapsed / 1000)} seconds`,
1508
+ successfulResources: successful,
1509
+ failedResources: failed,
1510
+ errors: errors.slice(-5)
1511
+ };
1512
+ // Update session with completion status
1513
+ transitionCapabilitySession(session, 'complete', {
1514
+ progress: completionData
1515
+ }, args);
1516
+ logger.info('Auto batch processing completed', {
1517
+ requestId,
1518
+ sessionId: session.sessionId,
1519
+ processed: totalResources,
1520
+ successful,
1521
+ failed,
1522
+ processingTime: completionData.totalProcessingTime
1523
+ });
1524
+ // Clean up session file after a brief delay to allow progress viewing
1525
+ setTimeout(() => {
1526
+ cleanupCapabilitySession(session, args, logger, requestId);
1527
+ }, 30000); // Keep for 30 seconds after completion
1528
+ return createCapabilityScanCompletionResponse(session.sessionId, totalResources, successful, failed, completionData.totalProcessingTime || 'completed', 'auto');
1529
+ }
1530
+ }
1531
+ catch (error) {
1532
+ logger.error('Capability scanning failed', error, {
1533
+ requestId,
1534
+ sessionId: session.sessionId,
1535
+ resource: (error && typeof error === 'object' && 'resourceName' in error) ? error.resourceName : 'unknown',
1536
+ step: session.currentStep
1537
+ });
1538
+ // Clean up session on error
1539
+ cleanupCapabilitySession(session, args, logger, requestId);
1540
+ return {
1541
+ success: false,
1542
+ operation: 'scan',
1543
+ dataType: 'capabilities',
1544
+ error: {
1545
+ message: 'Capability scanning failed',
1546
+ details: error instanceof Error ? error.message : String(error)
1547
+ }
1548
+ };
1549
+ }
1550
+ }
1551
+ /**
1552
+ * Handle capability listing (placeholder for future implementation)
1553
+ */
1554
+ async function handleCapabilityList(args, logger, requestId, capabilityService) {
1555
+ try {
1556
+ // Get all capabilities with optional limit
1557
+ const limit = args.limit || 10;
1558
+ const capabilities = await capabilityService.getAllCapabilities(limit);
1559
+ const count = await capabilityService.getCapabilitiesCount();
1560
+ logger.info('Capabilities listed successfully', {
1561
+ requestId,
1562
+ count: capabilities.length,
1563
+ totalCount: count,
1564
+ limit
1565
+ });
1566
+ return {
1567
+ success: true,
1568
+ operation: 'list',
1569
+ dataType: 'capabilities',
1570
+ data: {
1571
+ capabilities: capabilities.map(cap => ({
1572
+ id: cap.id,
1573
+ resourceName: cap.resourceName,
1574
+ capabilities: cap.capabilities,
1575
+ description: cap.description.length > 100 ? cap.description.substring(0, 100) + '...' : cap.description,
1576
+ complexity: cap.complexity,
1577
+ confidence: cap.confidence,
1578
+ analyzedAt: cap.analyzedAt
1579
+ })),
1580
+ totalCount: count,
1581
+ returnedCount: capabilities.length,
1582
+ limit
1583
+ },
1584
+ message: `Retrieved ${capabilities.length} capabilities (${count} total)`,
1585
+ clientInstructions: {
1586
+ behavior: 'Display capability list with IDs prominently visible for user reference',
1587
+ requirement: 'Each capability must show: ID, resource name, main capabilities, and description',
1588
+ format: 'List format with ID clearly labeled (e.g., "ID: abc123") so users can reference specific capabilities',
1589
+ prohibit: 'Do not hide or omit capability IDs from the display - users need them for get operations'
1590
+ }
1591
+ };
1592
+ }
1593
+ catch (error) {
1594
+ logger.error('Failed to list capabilities', error, {
1595
+ requestId
1596
+ });
1597
+ return {
1598
+ success: false,
1599
+ operation: 'list',
1600
+ dataType: 'capabilities',
1601
+ error: {
1602
+ message: 'Failed to list capabilities',
1603
+ details: error instanceof Error ? error.message : String(error)
1604
+ }
1605
+ };
1606
+ }
1607
+ }
1608
+ /**
1609
+ * Handle capability retrieval (placeholder for future implementation)
1610
+ */
1611
+ async function handleCapabilityGet(args, logger, requestId, capabilityService) {
1612
+ try {
1613
+ // Validate required parameters
1614
+ if (!args.id) {
1615
+ return {
1616
+ success: false,
1617
+ operation: 'get',
1618
+ dataType: 'capabilities',
1619
+ error: {
1620
+ message: 'Missing required parameter: id',
1621
+ details: 'Specify id to retrieve capability data',
1622
+ example: { id: 'capability-id-example' }
1623
+ }
1624
+ };
1625
+ }
1626
+ // Get capability by ID
1627
+ const capability = await capabilityService.getCapability(args.id);
1628
+ if (!capability) {
1629
+ logger.warn('Capability not found', {
1630
+ requestId,
1631
+ capabilityId: args.id
1632
+ });
1633
+ return {
1634
+ success: false,
1635
+ operation: 'get',
1636
+ dataType: 'capabilities',
1637
+ error: {
1638
+ message: `Capability not found for ID: ${args.id}`,
1639
+ details: 'Resource capability may not have been scanned yet',
1640
+ suggestion: 'Use scan operation to analyze this resource first'
1641
+ }
1642
+ };
1643
+ }
1644
+ logger.info('Capability retrieved successfully', {
1645
+ requestId,
1646
+ capabilityId: args.id,
1647
+ resourceName: capability.resourceName,
1648
+ capabilitiesFound: capability.capabilities.length,
1649
+ confidence: capability.confidence
1650
+ });
1651
+ return {
1652
+ success: true,
1653
+ operation: 'get',
1654
+ dataType: 'capabilities',
1655
+ data: capability,
1656
+ message: `Retrieved capability data for ${capability.resourceName} (ID: ${args.id})`,
1657
+ clientInstructions: {
1658
+ behavior: 'Display comprehensive capability details in organized sections',
1659
+ requirement: 'Show resource name, capabilities, providers, complexity, use case, and confidence prominently',
1660
+ format: 'Structured display with clear sections: Resource Info, Capabilities, Technical Details, and Analysis Results',
1661
+ sections: {
1662
+ resourceInfo: 'Resource name and description with use case',
1663
+ capabilities: 'List all capabilities, providers, and abstractions clearly',
1664
+ technicalDetails: 'Complexity level and provider information',
1665
+ analysisResults: 'Confidence score, analysis timestamp, and ID for reference'
1666
+ }
1667
+ }
1668
+ };
1669
+ }
1670
+ catch (error) {
1671
+ logger.error('Failed to get capability', error, {
1672
+ requestId,
1673
+ capabilityId: args.id
1674
+ });
1675
+ return {
1676
+ success: false,
1677
+ operation: 'get',
1678
+ dataType: 'capabilities',
1679
+ error: {
1680
+ message: 'Failed to retrieve capability',
1681
+ details: error instanceof Error ? error.message : String(error)
1682
+ }
1683
+ };
1684
+ }
1685
+ }
1686
+ /**
1687
+ * Handle capability progress query (check progress of running scan)
1688
+ */
1689
+ async function handleCapabilityProgress(args, logger, requestId) {
1690
+ try {
1691
+ logger.info('Capability progress query requested', {
1692
+ requestId,
1693
+ sessionId: args.sessionId
1694
+ });
1695
+ // Get session directory first
1696
+ const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false);
1697
+ let sessionId = args.sessionId;
1698
+ let sessionFilePath;
1699
+ // If no sessionId provided, auto-discover the latest session
1700
+ if (!sessionId) {
1701
+ logger.info('No sessionId provided, searching for latest session file', { requestId });
1702
+ try {
1703
+ // Check if capability-sessions subdirectory exists
1704
+ const sessionSubDir = path.join(sessionDir, 'capability-sessions');
1705
+ if (!fs.existsSync(sessionSubDir)) {
1706
+ logger.info('No capability-sessions directory found', { requestId, sessionDir });
1707
+ return {
1708
+ success: false,
1709
+ operation: 'progress',
1710
+ dataType: 'capabilities',
1711
+ error: {
1712
+ message: 'No capability scan sessions found',
1713
+ details: 'No capability-sessions directory found in the session directory',
1714
+ help: 'Start a new capability scan with the scan operation first',
1715
+ sessionDirectory: sessionDir
1716
+ }
1717
+ };
1718
+ }
1719
+ // Find all capability scan session files in the subdirectory
1720
+ const sessionFiles = fs.readdirSync(sessionSubDir)
1721
+ .filter(file => file.endsWith('.json'))
1722
+ .map(file => {
1723
+ const filePath = path.join(sessionSubDir, file);
1724
+ const stats = fs.statSync(filePath);
1725
+ return {
1726
+ filename: file,
1727
+ path: filePath,
1728
+ mtime: stats.mtime,
1729
+ sessionId: file.replace('.json', '') // Remove .json extension to get sessionId
1730
+ };
1731
+ })
1732
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // Sort by newest first
1733
+ if (sessionFiles.length === 0) {
1734
+ logger.info('No capability scan session files found', { requestId, sessionDir });
1735
+ return {
1736
+ success: false,
1737
+ operation: 'progress',
1738
+ dataType: 'capabilities',
1739
+ error: {
1740
+ message: 'No capability scan sessions found',
1741
+ details: 'No active or recent capability scans found in the session directory',
1742
+ help: 'Start a new capability scan with the scan operation first',
1743
+ sessionDirectory: sessionDir
1744
+ }
1745
+ };
1746
+ }
1747
+ // Use the latest session (first after sorting)
1748
+ const latestSession = sessionFiles[0];
1749
+ sessionId = latestSession.sessionId;
1750
+ sessionFilePath = latestSession.path;
1751
+ logger.info('Using latest session file', {
1752
+ requestId,
1753
+ sessionId,
1754
+ totalSessions: sessionFiles.length,
1755
+ sessionFile: latestSession.filename
1756
+ });
1757
+ }
1758
+ catch (error) {
1759
+ logger.error('Failed to discover session files', error, { requestId, sessionDir });
1760
+ return {
1761
+ success: false,
1762
+ operation: 'progress',
1763
+ dataType: 'capabilities',
1764
+ error: {
1765
+ message: 'Failed to discover session files',
1766
+ details: error instanceof Error ? error.message : String(error),
1767
+ sessionDirectory: sessionDir
1768
+ }
1769
+ };
1770
+ }
1771
+ }
1772
+ else {
1773
+ // Use provided sessionId - look in capability-sessions subdirectory
1774
+ const sessionSubDir = path.join(sessionDir, 'capability-sessions');
1775
+ sessionFilePath = path.join(sessionSubDir, `${sessionId}.json`);
1776
+ if (!fs.existsSync(sessionFilePath)) {
1777
+ logger.warn('Session file not found for provided sessionId', {
1778
+ requestId,
1779
+ sessionId,
1780
+ filePath: sessionFilePath
1781
+ });
1782
+ return {
1783
+ success: false,
1784
+ operation: 'progress',
1785
+ dataType: 'capabilities',
1786
+ error: {
1787
+ message: 'Session not found',
1788
+ details: `No capability scan found for session: ${sessionId}`,
1789
+ help: 'Use the scan operation to start a new capability scan, or omit sessionId to use the latest session'
1790
+ }
1791
+ };
1792
+ }
1793
+ }
1794
+ // Read and parse session file
1795
+ const sessionData = JSON.parse(fs.readFileSync(sessionFilePath, 'utf8'));
1796
+ // Extract progress information
1797
+ const progress = sessionData.progress;
1798
+ if (!progress) {
1799
+ return {
1800
+ success: true,
1801
+ operation: 'progress',
1802
+ dataType: 'capabilities',
1803
+ sessionId: sessionId,
1804
+ status: 'no-progress-data',
1805
+ message: 'Session exists but no progress tracking is active',
1806
+ currentStep: sessionData.currentStep,
1807
+ startedAt: sessionData.startedAt,
1808
+ lastActivity: sessionData.lastActivity
1809
+ };
1810
+ }
1811
+ // Build comprehensive progress response
1812
+ const response = {
1813
+ success: true,
1814
+ operation: 'progress',
1815
+ dataType: 'capabilities',
1816
+ sessionId: sessionId,
1817
+ progress: {
1818
+ status: progress.status,
1819
+ current: progress.current,
1820
+ total: progress.total,
1821
+ percentage: progress.percentage,
1822
+ currentResource: progress.currentResource,
1823
+ startedAt: progress.startedAt,
1824
+ lastUpdated: progress.lastUpdated
1825
+ },
1826
+ sessionInfo: {
1827
+ currentStep: sessionData.currentStep,
1828
+ processingMode: sessionData.processingMode,
1829
+ resourceCount: Array.isArray(sessionData.selectedResources)
1830
+ ? sessionData.selectedResources.length
1831
+ : (sessionData.selectedResources === 'all' ? 'all resources' : 'unknown'),
1832
+ startedAt: sessionData.startedAt,
1833
+ lastActivity: sessionData.lastActivity
1834
+ }
1835
+ };
1836
+ // Add completion information if scan is done
1837
+ if (progress.status === 'completed') {
1838
+ response.progress.completedAt = progress.completedAt;
1839
+ response.progress.totalProcessingTime = progress.totalProcessingTime;
1840
+ response.message = 'Capability scan completed successfully';
1841
+ }
1842
+ else {
1843
+ response.progress.estimatedTimeRemaining = progress.estimatedTimeRemaining;
1844
+ response.message = `Capability scan in progress: ${progress.current}/${progress.total} resources processed`;
1845
+ }
1846
+ // Add user-friendly display information with client formatting instructions
1847
+ response.display = {
1848
+ summary: progress.status === 'completed'
1849
+ ? `✅ Scan complete: processed ${progress.total} resources in ${progress.totalProcessingTime}`
1850
+ : `⏳ Processing: ${progress.current}/${progress.total} (${progress.percentage}%) - ${progress.estimatedTimeRemaining} remaining`,
1851
+ currentResource: progress.currentResource,
1852
+ timeline: {
1853
+ started: progress.startedAt,
1854
+ lastUpdate: progress.lastUpdated,
1855
+ ...(progress.completedAt && { completed: progress.completedAt })
1856
+ }
1857
+ };
1858
+ // Add client instructions for readable formatting
1859
+ response.clientInstructions = {
1860
+ behavior: 'Display capability scan progress in a clean, readable format',
1861
+ requirement: 'Show progress information in separate lines, not as a single condensed line',
1862
+ format: progress.status === 'completed'
1863
+ ? 'Completion format: Status, total processed, processing time on separate lines'
1864
+ : 'Progress format: Status line, current resource line, time estimates line, timestamps line',
1865
+ example: progress.status === 'completed'
1866
+ ? '✅ Capability Scan Complete\\n📊 Processed: X resources\\n⏰ Processing time: X minutes'
1867
+ : '⏳ Progress: X/Y resources (Z%)\\n📊 Current resource: ResourceName\\n⏰ Est. remaining: X minutes\\n🕒 Started: timestamp\\n🔄 Last updated: timestamp',
1868
+ prohibit: 'Do not display all progress information on a single line'
1869
+ };
1870
+ logger.info('Progress query completed successfully', {
1871
+ requestId,
1872
+ sessionId: args.sessionId,
1873
+ status: progress.status,
1874
+ percentage: progress.percentage
1875
+ });
1876
+ return response;
1877
+ }
1878
+ catch (error) {
1879
+ logger.error('Failed to query capability progress', error, {
1880
+ requestId,
1881
+ sessionId: args.sessionId
1882
+ });
1883
+ return {
1884
+ success: false,
1885
+ operation: 'progress',
1886
+ dataType: 'capabilities',
1887
+ error: {
1888
+ message: 'Failed to query scan progress',
1889
+ details: error instanceof Error ? error.message : String(error)
1890
+ }
1891
+ };
1892
+ }
1893
+ }
1894
+ /**
1895
+ * Handle capability deletion (delete single capability by ID)
1896
+ */
1897
+ async function handleCapabilityDelete(args, logger, requestId, capabilityService) {
1898
+ try {
1899
+ // Validate required parameters
1900
+ if (!args.id) {
1901
+ return {
1902
+ success: false,
1903
+ operation: 'delete',
1904
+ dataType: 'capabilities',
1905
+ error: {
1906
+ message: 'Missing required parameter: id',
1907
+ details: 'Specify id to delete capability data',
1908
+ example: { id: 'capability-id-example' }
1909
+ }
1910
+ };
1911
+ }
1912
+ // Check if capability exists before deletion
1913
+ const capability = await capabilityService.getCapability(args.id);
1914
+ if (!capability) {
1915
+ logger.warn('Capability not found for deletion', {
1916
+ requestId,
1917
+ capabilityId: args.id
1918
+ });
1919
+ return {
1920
+ success: false,
1921
+ operation: 'delete',
1922
+ dataType: 'capabilities',
1923
+ error: {
1924
+ message: `Capability not found for ID: ${args.id}`,
1925
+ details: 'Cannot delete capability that does not exist'
1926
+ }
1927
+ };
1928
+ }
1929
+ // Delete capability by ID
1930
+ await capabilityService.deleteCapabilityById(args.id);
1931
+ logger.info('Capability deleted successfully', {
1932
+ requestId,
1933
+ capabilityId: args.id,
1934
+ resourceName: capability.resourceName
1935
+ });
1936
+ return {
1937
+ success: true,
1938
+ operation: 'delete',
1939
+ dataType: 'capabilities',
1940
+ deletedCapability: {
1941
+ id: args.id,
1942
+ resourceName: capability.resourceName
1943
+ },
1944
+ message: `Capability deleted: ${capability.resourceName}`
1945
+ };
1946
+ }
1947
+ catch (error) {
1948
+ logger.error('Failed to delete capability', error, {
1949
+ requestId,
1950
+ capabilityId: args.id
1951
+ });
1952
+ return {
1953
+ success: false,
1954
+ operation: 'delete',
1955
+ dataType: 'capabilities',
1956
+ error: {
1957
+ message: 'Failed to delete capability',
1958
+ details: error instanceof Error ? error.message : String(error)
1959
+ }
1960
+ };
1961
+ }
1962
+ }
1963
+ /**
1964
+ * Handle capability bulk deletion (delete all capabilities)
1965
+ */
1966
+ async function handleCapabilityDeleteAll(args, logger, requestId, capabilityService) {
1967
+ try {
1968
+ // Get count first to provide feedback (but don't retrieve all data)
1969
+ const totalCount = await capabilityService.getCapabilitiesCount();
1970
+ if (totalCount === 0) {
1971
+ logger.info('No capabilities found to delete', { requestId });
1972
+ return {
1973
+ success: true,
1974
+ operation: 'deleteAll',
1975
+ dataType: 'capabilities',
1976
+ deletedCount: 0,
1977
+ totalCount: 0,
1978
+ message: 'No capabilities found to delete'
1979
+ };
1980
+ }
1981
+ logger.info('Starting efficient bulk capability deletion', {
1982
+ requestId,
1983
+ totalCapabilities: totalCount,
1984
+ method: 'collection recreation'
1985
+ });
1986
+ // Efficiently delete all capabilities by recreating collection
1987
+ await capabilityService.deleteAllCapabilities();
1988
+ logger.info('Bulk capability deletion completed successfully', {
1989
+ requestId,
1990
+ deleted: totalCount,
1991
+ method: 'collection recreation'
1992
+ });
1993
+ return {
1994
+ success: true,
1995
+ operation: 'deleteAll',
1996
+ dataType: 'capabilities',
1997
+ deletedCount: totalCount,
1998
+ totalCount,
1999
+ errorCount: 0,
2000
+ message: `Successfully deleted all ${totalCount} capabilities`,
2001
+ confirmation: 'All capability data has been permanently removed from the Vector DB',
2002
+ method: 'Efficient collection recreation (no individual record retrieval)'
2003
+ };
2004
+ }
2005
+ catch (error) {
2006
+ logger.error('Failed to delete all capabilities', error, {
2007
+ requestId
2008
+ });
2009
+ return {
2010
+ success: false,
2011
+ operation: 'deleteAll',
2012
+ dataType: 'capabilities',
2013
+ error: {
2014
+ message: 'Failed to delete all capabilities',
2015
+ details: error instanceof Error ? error.message : String(error)
2016
+ }
2017
+ };
2018
+ }
2019
+ }
2020
+ /**
2021
+ * Handle capability search operation
2022
+ */
2023
+ async function handleCapabilitySearch(args, logger, requestId, capabilityService) {
2024
+ try {
2025
+ // Validate required search query (stored in id field)
2026
+ if (!args.id || typeof args.id !== 'string' || args.id.trim() === '') {
2027
+ return {
2028
+ success: false,
2029
+ operation: 'search',
2030
+ dataType: 'capabilities',
2031
+ error: {
2032
+ message: 'Search query required',
2033
+ details: 'The id field must contain a search query (e.g., "postgresql database in azure")'
2034
+ }
2035
+ };
2036
+ }
2037
+ const searchQuery = args.id.trim();
2038
+ const limit = args.limit || 25;
2039
+ logger.info('Searching capabilities', {
2040
+ requestId,
2041
+ query: searchQuery,
2042
+ limit
2043
+ });
2044
+ // Perform the search using existing service
2045
+ const searchResults = await capabilityService.searchCapabilities(searchQuery, { limit });
2046
+ // Format results for user display
2047
+ const formattedResults = searchResults.map((result, index) => ({
2048
+ rank: index + 1,
2049
+ score: Math.round(result.score * 100) / 100, // Round to 2 decimal places
2050
+ id: capabilities_1.CapabilityInferenceEngine.generateCapabilityId(result.data.resourceName),
2051
+ resourceName: result.data.resourceName,
2052
+ capabilities: result.data.capabilities,
2053
+ providers: result.data.providers,
2054
+ complexity: result.data.complexity,
2055
+ description: result.data.description,
2056
+ useCase: result.data.useCase,
2057
+ confidence: result.data.confidence,
2058
+ analyzedAt: result.data.analyzedAt
2059
+ }));
2060
+ logger.info('Capability search completed', {
2061
+ requestId,
2062
+ query: searchQuery,
2063
+ resultsFound: searchResults.length,
2064
+ limit
2065
+ });
2066
+ return {
2067
+ success: true,
2068
+ operation: 'search',
2069
+ dataType: 'capabilities',
2070
+ data: {
2071
+ query: searchQuery,
2072
+ results: formattedResults,
2073
+ resultCount: searchResults.length,
2074
+ limit
2075
+ },
2076
+ message: `Found ${searchResults.length} capabilities matching "${searchQuery}"`,
2077
+ clientInstructions: {
2078
+ behavior: 'Display search results with relevance scores and capability details',
2079
+ sections: {
2080
+ searchSummary: 'Show query and result count prominently',
2081
+ resultsList: 'Display each result with rank, score, resource name, and capabilities',
2082
+ capabilityDetails: 'Include providers, complexity, and description for context',
2083
+ actionGuidance: 'Show IDs for get operations and suggest refinement if needed'
2084
+ },
2085
+ format: 'Ranked list with scores (higher scores = better matches)',
2086
+ emphasize: 'Resource names and main capabilities for easy scanning'
2087
+ }
2088
+ };
2089
+ }
2090
+ catch (error) {
2091
+ logger.error('Failed to search capabilities', error, {
2092
+ requestId,
2093
+ query: args.id
2094
+ });
2095
+ return {
2096
+ success: false,
2097
+ operation: 'search',
2098
+ dataType: 'capabilities',
2099
+ error: {
2100
+ message: 'Capability search failed',
2101
+ details: error instanceof Error ? error.message : String(error)
2102
+ }
2103
+ };
2104
+ }
2105
+ }
369
2106
  /**
370
2107
  * Main tool handler - routes to appropriate data type handler
371
2108
  */
@@ -399,19 +2136,15 @@ async function handleOrganizationalDataTool(args, _dotAI, logger, requestId) {
399
2136
  case 'pattern':
400
2137
  result = await handlePatternOperation(args.operation, args, logger, requestId);
401
2138
  break;
402
- // Future data types will be added here:
403
- // case 'policy':
404
- // result = await handlePolicyOperation(args.operation, args, logger, requestId);
405
- // break;
406
- // case 'memory':
407
- // result = await handleMemoryOperation(args.operation, args, logger, requestId);
408
- // break;
2139
+ case 'capabilities':
2140
+ result = await handleCapabilitiesOperation(args.operation, args, logger, requestId);
2141
+ break;
409
2142
  default:
410
- throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported data type: ${args.dataType}. Currently supported: pattern`, {
2143
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported data type: ${args.dataType}. Currently supported: pattern, capabilities`, {
411
2144
  operation: 'data_type_validation',
412
2145
  component: 'OrganizationalDataTool',
413
2146
  requestId,
414
- input: { dataType: args.dataType, supportedTypes: ['pattern'] }
2147
+ input: { dataType: args.dataType, supportedTypes: ['pattern', 'capabilities'] }
415
2148
  });
416
2149
  }
417
2150
  logger.info('Organizational-data tool request completed successfully', {