@vfarcic/dot-ai 1.3.0 → 1.4.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 (64) hide show
  1. package/dist/core/artifacthub.d.ts.map +1 -1
  2. package/dist/core/artifacthub.js +16 -10
  3. package/dist/core/base-vector-service.d.ts.map +1 -1
  4. package/dist/core/base-vector-service.js +19 -10
  5. package/dist/core/capabilities.d.ts.map +1 -1
  6. package/dist/core/capabilities.js +17 -11
  7. package/dist/core/capability-operations.d.ts.map +1 -1
  8. package/dist/core/capability-operations.js +118 -98
  9. package/dist/core/crd-availability.d.ts.map +1 -1
  10. package/dist/core/crd-availability.js +2 -2
  11. package/dist/core/deploy-operation.d.ts.map +1 -1
  12. package/dist/core/deploy-operation.js +9 -6
  13. package/dist/core/discovery.d.ts.map +1 -1
  14. package/dist/core/discovery.js +157 -56
  15. package/dist/core/embedding-service.d.ts.map +1 -1
  16. package/dist/core/embedding-service.js +11 -7
  17. package/dist/core/packaging.d.ts.map +1 -1
  18. package/dist/core/packaging.js +11 -10
  19. package/dist/core/platform-utils.d.ts.map +1 -1
  20. package/dist/core/platform-utils.js +6 -2
  21. package/dist/core/plugin-manager.d.ts.map +1 -1
  22. package/dist/core/plugin-manager.js +26 -20
  23. package/dist/core/policy-operations.d.ts.map +1 -1
  24. package/dist/core/policy-operations.js +111 -65
  25. package/dist/core/providers/host-provider.d.ts.map +1 -1
  26. package/dist/core/providers/host-provider.js +12 -5
  27. package/dist/core/providers/vercel-provider.d.ts.map +1 -1
  28. package/dist/core/providers/vercel-provider.js +4 -2
  29. package/dist/core/schema.d.ts.map +1 -1
  30. package/dist/core/schema.js +173 -95
  31. package/dist/core/session-utils.d.ts.map +1 -1
  32. package/dist/core/session-utils.js +3 -3
  33. package/dist/core/user-prompts-loader.d.ts.map +1 -1
  34. package/dist/core/user-prompts-loader.js +2 -4
  35. package/dist/evaluation/datasets/loader.d.ts.map +1 -1
  36. package/dist/evaluation/datasets/loader.js +7 -3
  37. package/dist/interfaces/rest-api.d.ts +4 -0
  38. package/dist/interfaces/rest-api.d.ts.map +1 -1
  39. package/dist/interfaces/rest-api.js +35 -0
  40. package/dist/interfaces/rest-route-registry.js +3 -3
  41. package/dist/interfaces/routes/index.d.ts.map +1 -1
  42. package/dist/interfaces/routes/index.js +10 -0
  43. package/dist/interfaces/schemas/common.d.ts +1 -0
  44. package/dist/interfaces/schemas/common.d.ts.map +1 -1
  45. package/dist/interfaces/schemas/common.js +1 -0
  46. package/dist/interfaces/schemas/index.d.ts +1 -1
  47. package/dist/interfaces/schemas/index.d.ts.map +1 -1
  48. package/dist/interfaces/schemas/index.js +5 -2
  49. package/dist/interfaces/schemas/prompts.d.ts +37 -0
  50. package/dist/interfaces/schemas/prompts.d.ts.map +1 -1
  51. package/dist/interfaces/schemas/prompts.js +18 -1
  52. package/dist/tools/answer-question.d.ts.map +1 -1
  53. package/dist/tools/answer-question.js +109 -81
  54. package/dist/tools/generate-manifests.d.ts.map +1 -1
  55. package/dist/tools/generate-manifests.js +163 -103
  56. package/dist/tools/operate-analysis.d.ts.map +1 -1
  57. package/dist/tools/operate-analysis.js +27 -17
  58. package/dist/tools/operate.d.ts.map +1 -1
  59. package/dist/tools/operate.js +47 -17
  60. package/dist/tools/prompts.d.ts.map +1 -1
  61. package/dist/tools/prompts.js +1 -1
  62. package/dist/tools/remediate.d.ts.map +1 -1
  63. package/dist/tools/remediate.js +199 -115
  64. package/package.json +6 -6
@@ -59,7 +59,9 @@ function sanitizeChartInfo(chart) {
59
59
  repositoryName: sanitizeShellArg(chart.repositoryName, 'repository name'),
60
60
  repository: sanitizeShellArg(chart.repository, 'repository URL'),
61
61
  chartName: sanitizeShellArg(chart.chartName, 'chart name'),
62
- version: chart.version ? sanitizeShellArg(chart.version, 'version') : undefined
62
+ version: chart.version
63
+ ? sanitizeShellArg(chart.version, 'version')
64
+ : undefined,
63
65
  };
64
66
  }
65
67
  /**
@@ -73,7 +75,7 @@ exports.OUTPUT_FORMAT_QUESTION = {
73
75
  options: ['raw', 'helm', 'kustomize'],
74
76
  placeholder: 'Select output format',
75
77
  suggestedAnswer: 'kustomize',
76
- validation: { required: true }
78
+ validation: { required: true },
77
79
  };
78
80
  exports.OUTPUT_PATH_QUESTION = {
79
81
  id: 'outputPath',
@@ -81,7 +83,7 @@ exports.OUTPUT_PATH_QUESTION = {
81
83
  type: 'text',
82
84
  placeholder: 'e.g., ./manifests or ./my-app',
83
85
  suggestedAnswer: './manifests',
84
- validation: { required: true }
86
+ validation: { required: true },
85
87
  };
86
88
  /**
87
89
  * Inject packaging questions into a QuestionGroup for capability-based solutions
@@ -99,7 +101,7 @@ function injectPackagingQuestions(questions) {
99
101
  }
100
102
  return {
101
103
  ...questions,
102
- required: [...questions.required, ...packagingQuestions]
104
+ required: [...questions.required, ...packagingQuestions],
103
105
  };
104
106
  }
105
107
  /**
@@ -131,7 +133,7 @@ class SchemaParser {
131
133
  description: field.description,
132
134
  required: field.required,
133
135
  constraints: this.parseFieldConstraints(field.type, field.description),
134
- nested: new Map()
136
+ nested: new Map(),
135
137
  });
136
138
  }
137
139
  // Handle nested fields
@@ -147,7 +149,7 @@ class SchemaParser {
147
149
  description: explanation.description,
148
150
  properties,
149
151
  required,
150
- namespace: true // Default to namespaced, could be enhanced with discovery data
152
+ namespace: true, // Default to namespaced, could be enhanced with discovery data
151
153
  };
152
154
  }
153
155
  /**
@@ -162,7 +164,7 @@ class SchemaParser {
162
164
  description: field.description,
163
165
  required: field.required,
164
166
  constraints: this.parseFieldConstraints(field.type, field.description),
165
- nested: new Map()
167
+ nested: new Map(),
166
168
  });
167
169
  }
168
170
  // Continue recursively if there are more field parts
@@ -177,17 +179,17 @@ class SchemaParser {
177
179
  const lowerType = type.toLowerCase();
178
180
  // Map kubectl types to standard types
179
181
  const typeMap = {
180
- 'object': 'object',
181
- 'string': 'string',
182
- 'integer': 'integer',
183
- 'int32': 'integer',
184
- 'int64': 'integer',
185
- 'boolean': 'boolean',
186
- 'array': 'array',
182
+ object: 'object',
183
+ string: 'string',
184
+ integer: 'integer',
185
+ int32: 'integer',
186
+ int64: 'integer',
187
+ boolean: 'boolean',
188
+ array: 'array',
187
189
  '[]string': 'array',
188
190
  '[]object': 'array',
189
191
  'map[string]string': 'object',
190
- 'map[string]object': 'object'
192
+ 'map[string]object': 'object',
191
193
  };
192
194
  return typeMap[lowerType] || 'string';
193
195
  }
@@ -310,7 +312,7 @@ class ManifestValidator {
310
312
  // Use kubectl_apply_dryrun tool which accepts manifest content
311
313
  const response = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_apply_dryrun', {
312
314
  manifest: manifestContent,
313
- dryRunMode: dryRunMode
315
+ dryRunMode: dryRunMode,
314
316
  });
315
317
  if (!response.success) {
316
318
  throw new Error(response.error?.message || 'kubectl dry-run validation failed');
@@ -319,7 +321,9 @@ class ManifestValidator {
319
321
  if (typeof response.result === 'object' && response.result !== null) {
320
322
  const result = response.result;
321
323
  if (result.success === false) {
322
- throw new Error(result.error || result.message || 'kubectl dry-run validation failed');
324
+ throw new Error(result.error ||
325
+ result.message ||
326
+ 'kubectl dry-run validation failed');
323
327
  }
324
328
  }
325
329
  // If we get here, validation passed
@@ -334,7 +338,7 @@ class ManifestValidator {
334
338
  return {
335
339
  valid: true,
336
340
  errors,
337
- warnings
341
+ warnings,
338
342
  };
339
343
  }
340
344
  catch (error) {
@@ -355,7 +359,7 @@ class ManifestValidator {
355
359
  return {
356
360
  valid: false,
357
361
  errors,
358
- warnings
362
+ warnings,
359
363
  };
360
364
  }
361
365
  }
@@ -385,12 +389,14 @@ class ResourceRecommender {
385
389
  policyService;
386
390
  constructor(aiProvider) {
387
391
  // Use provided AI provider or create from environment
388
- this.aiProvider = aiProvider || (() => {
389
- // Lazy import to avoid circular dependencies
390
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- Dynamic require to avoid circular dependency
391
- const { createAIProvider } = require('./ai-provider-factory');
392
- return createAIProvider();
393
- })();
392
+ this.aiProvider =
393
+ aiProvider ||
394
+ (() => {
395
+ // Lazy import to avoid circular dependencies
396
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- Dynamic require to avoid circular dependency
397
+ const { createAIProvider } = require('./ai-provider-factory');
398
+ return createAIProvider();
399
+ })();
394
400
  // Initialize capability service - fail gracefully if plugin unavailable
395
401
  try {
396
402
  // Use environment variable for collection name (allows using test data collection)
@@ -472,13 +478,16 @@ class ResourceRecommender {
472
478
  let relevantCapabilities = [];
473
479
  if (this.capabilityService) {
474
480
  try {
475
- relevantCapabilities = await this.capabilityService.searchCapabilities(intent, { limit: 50 });
481
+ relevantCapabilities =
482
+ await this.capabilityService.searchCapabilities(intent, {
483
+ limit: 50,
484
+ });
476
485
  }
477
486
  catch (error) {
478
487
  // Capability search failed - fail fast with clear guidance
479
488
  throw new Error(`Capability search failed for intent "${intent}". Please scan your cluster first:\n` +
480
489
  `Run: manageOrgData({ dataType: "capabilities", operation: "scan" })\n` +
481
- `Error: ${error}`);
490
+ `Error: ${error}`, { cause: error });
482
491
  }
483
492
  }
484
493
  else {
@@ -493,11 +502,12 @@ class ResourceRecommender {
493
502
  // Create normalized resource objects from capability matches
494
503
  const capabilityFilteredResources = relevantCapabilities.map(cap => ({
495
504
  kind: this.extractKindFromResourceName(cap.data.resourceName),
496
- group: cap.data.group || this.extractGroupFromResourceName(cap.data.resourceName),
505
+ group: cap.data.group ||
506
+ this.extractGroupFromResourceName(cap.data.resourceName),
497
507
  apiVersion: cap.data.apiVersion, // Use stored apiVersion from capability scan
498
508
  version: cap.data.version, // Just the version part (e.g., "v1beta1")
499
509
  resourceName: cap.data.resourceName,
500
- capabilities: cap.data // Include capability data for AI decision-making (includes namespaced, etc.)
510
+ capabilities: cap.data, // Include capability data for AI decision-making (includes namespaced, etc.)
501
511
  }));
502
512
  // Phase 1: Add missing pattern-suggested resources to available resources list
503
513
  const enhancedResources = await this.addMissingPatternResources(capabilityFilteredResources, relevantPatterns);
@@ -515,7 +525,7 @@ class ResourceRecommender {
515
525
  return solutionResult;
516
526
  }
517
527
  catch (error) {
518
- throw new Error(`AI-powered resource solution analysis failed: ${error}`);
528
+ throw new Error(`AI-powered resource solution analysis failed: ${error}`, { cause: error });
519
529
  }
520
530
  }
521
531
  /**
@@ -524,8 +534,10 @@ class ResourceRecommender {
524
534
  async assembleAndRankSolutions(intent, availableResources, patterns, interaction_id) {
525
535
  const prompt = await this.loadSolutionAssemblyPrompt(intent, availableResources, patterns);
526
536
  const response = await this.aiProvider.sendMessage(prompt, 'recommend-solution-assembly', {
527
- user_intent: intent ? `Kubernetes solution assembly for: ${intent}` : 'Kubernetes solution assembly',
528
- interaction_id: interaction_id || 'recommend_solution_assembly'
537
+ user_intent: intent
538
+ ? `Kubernetes solution assembly for: ${intent}`
539
+ : 'Kubernetes solution assembly',
540
+ interaction_id: interaction_id || 'recommend_solution_assembly',
529
541
  });
530
542
  return this.parseSimpleSolutionResponse(response.content);
531
543
  }
@@ -539,10 +551,11 @@ class ResourceRecommender {
539
551
  // Handle Helm recommendation case (presence of helmRecommendation means Helm is needed)
540
552
  const helmRecommendation = parsed.helmRecommendation || null;
541
553
  // If Helm is recommended (empty solutions + helmRecommendation present), return early
542
- if (helmRecommendation && (!parsed.solutions || parsed.solutions.length === 0)) {
554
+ if (helmRecommendation &&
555
+ (!parsed.solutions || parsed.solutions.length === 0)) {
543
556
  return {
544
557
  solutions: [],
545
- helmRecommendation
558
+ helmRecommendation,
546
559
  };
547
560
  }
548
561
  const solutions = (parsed.solutions || []).map((solution) => {
@@ -551,14 +564,14 @@ class ResourceRecommender {
551
564
  console.debug('DEBUG: solution object:', JSON.stringify(solution, null, 2));
552
565
  }
553
566
  // Convert resource references to ResourceSchema format for compatibility
554
- const resources = (solution.resources || []).map((resource) => ({
567
+ const resources = (solution.resources || []).map(resource => ({
555
568
  kind: resource.kind,
556
569
  apiVersion: resource.apiVersion,
557
570
  group: resource.group || '',
558
571
  resourceName: resource.resourceName, // Preserve resourceName from AI response
559
572
  description: `${resource.kind} resource from ${resource.group || 'core'} group`,
560
573
  properties: new Map(),
561
- namespace: true // Default assumption for new architecture
574
+ namespace: true, // Default assumption for new architecture
562
575
  }));
563
576
  return {
564
577
  type: solution.type,
@@ -566,22 +579,27 @@ class ResourceRecommender {
566
579
  score: solution.score,
567
580
  description: solution.description,
568
581
  reasons: solution.reasons || [],
569
- questions: { required: [], basic: [], advanced: [], open: { question: '', placeholder: '' } },
570
- appliedPatterns: solution.appliedPatterns || []
582
+ questions: {
583
+ required: [],
584
+ basic: [],
585
+ advanced: [],
586
+ open: { question: '', placeholder: '' },
587
+ },
588
+ appliedPatterns: solution.appliedPatterns || [],
571
589
  };
572
590
  });
573
591
  // Sort by score descending
574
592
  const sortedSolutions = solutions.sort((a, b) => b.score - a.score);
575
593
  return {
576
594
  solutions: sortedSolutions,
577
- helmRecommendation
595
+ helmRecommendation,
578
596
  };
579
597
  }
580
598
  catch (error) {
581
599
  // Enhanced error message with more context
582
600
  const errorMsg = `Failed to parse AI solution response: ${error.message}`;
583
601
  const contextMsg = `\nAI Response (first 500 chars): "${aiResponse.substring(0, 500)}..."`;
584
- throw new Error(errorMsg + contextMsg);
602
+ throw new Error(errorMsg + contextMsg, { cause: error });
585
603
  }
586
604
  }
587
605
  /**
@@ -589,7 +607,8 @@ class ResourceRecommender {
589
607
  */
590
608
  async loadSolutionAssemblyPrompt(intent, resources, patterns) {
591
609
  // Format resources for the prompt with capability information
592
- const resourcesText = resources.map((resource, index) => {
610
+ const resourcesText = resources
611
+ .map((resource, index) => {
593
612
  return `${index}: ${resource.kind.toUpperCase()}
594
613
  Group: ${resource.group || 'core'}
595
614
  API Version: ${resource.apiVersion || 'unknown'}
@@ -600,19 +619,22 @@ class ResourceRecommender {
600
619
  Use Case: ${resource.capabilities.useCase || resource.capabilities.description || 'General purpose'}
601
620
  Description: ${resource.capabilities.description || 'Kubernetes resource'}
602
621
  Confidence: ${resource.capabilities.confidence || 1.0}`;
603
- }).join('\n\n');
622
+ })
623
+ .join('\n\n');
604
624
  // Format organizational patterns for AI context
605
625
  const patternsContext = patterns.length > 0
606
- ? patterns.map(pattern => `- ID: ${pattern.id}
626
+ ? patterns
627
+ .map(pattern => `- ID: ${pattern.id}
607
628
  Description: ${pattern.description}
608
629
  Suggested Resources: ${pattern.suggestedResources?.join(', ') || 'Not specified'}
609
630
  Rationale: ${pattern.rationale}
610
- Triggers: ${pattern.triggers?.join(', ') || 'None'}`).join('\n')
631
+ Triggers: ${pattern.triggers?.join(', ') || 'None'}`)
632
+ .join('\n')
611
633
  : 'No organizational patterns found for this request.';
612
634
  return (0, shared_prompt_loader_1.loadPrompt)('resource-selection', {
613
635
  intent,
614
636
  resources: resourcesText,
615
- patterns: patternsContext
637
+ patterns: patternsContext,
616
638
  });
617
639
  }
618
640
  /**
@@ -634,7 +656,9 @@ class ResourceRecommender {
634
656
  continue;
635
657
  }
636
658
  // Convert pattern resource format to resource name (e.g., "resourcegroups.azure.upbound.io" -> resourceName)
637
- const resourceName = suggestedResource.includes('.') ? suggestedResource : `${suggestedResource}.core`;
659
+ const resourceName = suggestedResource.includes('.')
660
+ ? suggestedResource
661
+ : `${suggestedResource}.core`;
638
662
  // Only add if not already present in capability results
639
663
  if (!existingResourceNames.has(resourceName)) {
640
664
  try {
@@ -649,14 +673,17 @@ class ResourceRecommender {
649
673
  capabilities: {
650
674
  resourceName,
651
675
  description: `Resource suggested by organizational pattern: ${pattern.description}`,
652
- capabilities: [`organizational pattern`, pattern.description.toLowerCase()],
676
+ capabilities: [
677
+ `organizational pattern`,
678
+ pattern.description.toLowerCase(),
679
+ ],
653
680
  providers: this.inferProvidersFromResourceName(suggestedResource),
654
681
  complexity: 'medium',
655
682
  useCase: `Pattern-suggested resource for: ${pattern.rationale}`,
656
683
  confidence: 1.0, // High confidence since it's from organizational pattern
657
684
  source: 'organizational-pattern',
658
- patternId: pattern.id
659
- }
685
+ patternId: pattern.id,
686
+ },
660
687
  });
661
688
  existingResourceNames.add(resourceName);
662
689
  }
@@ -719,7 +746,9 @@ class ResourceRecommender {
719
746
  }
720
747
  try {
721
748
  // Search patterns directly with user intent (vector search handles semantic concepts)
722
- const patternResults = await this.patternService.searchPatterns(intent, { limit: 5 });
749
+ const patternResults = await this.patternService.searchPatterns(intent, {
750
+ limit: 5,
751
+ });
723
752
  return patternResults.map(result => result.data);
724
753
  }
725
754
  catch (error) {
@@ -745,8 +774,12 @@ class ResourceRecommender {
745
774
  const kindLine = lines.find((line) => line.startsWith('KIND:'));
746
775
  const versionLine = lines.find((line) => line.startsWith('VERSION:'));
747
776
  const group = groupLine ? groupLine.replace('GROUP:', '').trim() : '';
748
- const kind = kindLine ? kindLine.replace('KIND:', '').trim() : resource.kind;
749
- const version = versionLine ? versionLine.replace('VERSION:', '').trim() : 'v1';
777
+ const kind = kindLine
778
+ ? kindLine.replace('KIND:', '').trim()
779
+ : resource.kind;
780
+ const version = versionLine
781
+ ? versionLine.replace('VERSION:', '').trim()
782
+ : 'v1';
750
783
  // Build apiVersion from group and version
751
784
  const apiVersion = group ? `${group}/${version}` : version;
752
785
  // Create a simple schema with raw explanation for AI processing
@@ -754,9 +787,13 @@ class ResourceRecommender {
754
787
  kind: kind,
755
788
  apiVersion: apiVersion,
756
789
  group: group,
757
- description: explanation.split('\n').find((line) => line.startsWith('DESCRIPTION:'))?.replace('DESCRIPTION:', '').trim() || '',
790
+ description: explanation
791
+ .split('\n')
792
+ .find((line) => line.startsWith('DESCRIPTION:'))
793
+ ?.replace('DESCRIPTION:', '')
794
+ .trim() || '',
758
795
  properties: new Map(),
759
- rawExplanation: explanation // Include raw explanation for AI
796
+ rawExplanation: explanation, // Include raw explanation for AI
760
797
  };
761
798
  schemas.push(schema);
762
799
  }
@@ -780,16 +817,26 @@ class ResourceRecommender {
780
817
  async discoverClusterOptions() {
781
818
  try {
782
819
  // Discover namespaces via plugin
783
- const namespacesResult = await this.executeKubectlViaPlugin(['get', 'namespaces', '-o', 'jsonpath={.items[*].metadata.name}']);
820
+ const namespacesResult = await this.executeKubectlViaPlugin([
821
+ 'get',
822
+ 'namespaces',
823
+ '-o',
824
+ 'jsonpath={.items[*].metadata.name}',
825
+ ]);
784
826
  const namespaces = namespacesResult.split(/\s+/).filter(Boolean);
785
827
  // Discover storage classes with default marking
786
828
  let storageClasses = [];
787
829
  try {
788
- const storageResult = await this.executeKubectlViaPlugin(['get', 'storageclass', '-o', 'json']);
830
+ const storageResult = await this.executeKubectlViaPlugin([
831
+ 'get',
832
+ 'storageclass',
833
+ '-o',
834
+ 'json',
835
+ ]);
789
836
  const storageData = JSON.parse(storageResult);
790
837
  storageClasses = (storageData.items || []).map((item) => ({
791
838
  name: item.metadata?.name || '',
792
- isDefault: item.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true'
839
+ isDefault: item.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true',
793
840
  }));
794
841
  }
795
842
  catch {
@@ -799,11 +846,16 @@ class ResourceRecommender {
799
846
  // Discover ingress classes with default marking
800
847
  let ingressClasses = [];
801
848
  try {
802
- const ingressResult = await this.executeKubectlViaPlugin(['get', 'ingressclass', '-o', 'json']);
849
+ const ingressResult = await this.executeKubectlViaPlugin([
850
+ 'get',
851
+ 'ingressclass',
852
+ '-o',
853
+ 'json',
854
+ ]);
803
855
  const ingressData = JSON.parse(ingressResult);
804
856
  ingressClasses = (ingressData.items || []).map((item) => ({
805
857
  name: item.metadata?.name || '',
806
- isDefault: item.metadata?.annotations?.['ingressclass.kubernetes.io/is-default-class'] === 'true'
858
+ isDefault: item.metadata?.annotations?.['ingressclass.kubernetes.io/is-default-class'] === 'true',
807
859
  }));
808
860
  }
809
861
  catch {
@@ -813,12 +865,18 @@ class ResourceRecommender {
813
865
  // Get common node labels
814
866
  let nodeLabels = [];
815
867
  try {
816
- const nodesResult = await this.executeKubectlViaPlugin(['get', 'nodes', '-o', 'json']);
868
+ const nodesResult = await this.executeKubectlViaPlugin([
869
+ 'get',
870
+ 'nodes',
871
+ '-o',
872
+ 'json',
873
+ ]);
817
874
  const nodes = JSON.parse(nodesResult);
818
875
  const labelSet = new Set();
819
876
  nodes.items?.forEach((node) => {
820
877
  Object.keys(node.metadata?.labels || {}).forEach(label => {
821
- if (!label.startsWith('kubernetes.io/') && !label.startsWith('node.kubernetes.io/')) {
878
+ if (!label.startsWith('kubernetes.io/') &&
879
+ !label.startsWith('node.kubernetes.io/')) {
822
880
  labelSet.add(label);
823
881
  }
824
882
  });
@@ -832,7 +890,7 @@ class ResourceRecommender {
832
890
  namespaces,
833
891
  storageClasses,
834
892
  ingressClasses,
835
- nodeLabels
893
+ nodeLabels,
836
894
  };
837
895
  }
838
896
  catch (error) {
@@ -841,7 +899,7 @@ class ResourceRecommender {
841
899
  namespaces: ['default'],
842
900
  storageClasses: [],
843
901
  ingressClasses: [],
844
- nodeLabels: []
902
+ nodeLabels: [],
845
903
  };
846
904
  }
847
905
  }
@@ -852,7 +910,9 @@ class ResourceRecommender {
852
910
  const formatResourceList = (items) => {
853
911
  if (items.length === 0)
854
912
  return 'None discovered';
855
- return items.map(item => item.isDefault ? `${item.name} (default)` : item.name).join(', ');
913
+ return items
914
+ .map(item => (item.isDefault ? `${item.name} (default)` : item.name))
915
+ .join(', ');
856
916
  };
857
917
  return `Available Namespaces: ${clusterOptions.namespaces.join(', ')}
858
918
  Available Storage Classes: ${formatResourceList(clusterOptions.storageClasses)}
@@ -870,12 +930,14 @@ Available Node Labels: ${clusterOptions.nodeLabels.length > 0 ? clusterOptions.n
870
930
  let relevantPolicyResults = [];
871
931
  if (this.policyService) {
872
932
  try {
873
- const resourceContext = solution.resources.map(r => `${r.kind} ${r.description}`).join(' ');
933
+ const resourceContext = solution.resources
934
+ .map(r => `${r.kind} ${r.description}`)
935
+ .join(' ');
874
936
  const policyResults = await this.policyService.searchPolicyIntents(`${intent} ${resourceContext}`, { limit: 50 });
875
937
  relevantPolicyResults = policyResults.map(result => ({
876
938
  policy: result.data,
877
939
  score: result.score,
878
- matchType: result.matchType
940
+ matchType: result.matchType,
879
941
  }));
880
942
  console.log(`🛡️ Found ${relevantPolicyResults.length} relevant policy intents for question generation`);
881
943
  }
@@ -897,7 +959,7 @@ Available Node Labels: ${clusterOptions.nodeLabels.length > 0 ? clusterOptions.n
897
959
  const schemaExplanation = await _explainResource(resource.resourceName);
898
960
  return {
899
961
  ...resource,
900
- rawExplanation: schemaExplanation
962
+ rawExplanation: schemaExplanation,
901
963
  };
902
964
  }
903
965
  catch (error) {
@@ -906,7 +968,8 @@ Available Node Labels: ${clusterOptions.nodeLabels.length > 0 ? clusterOptions.n
906
968
  }
907
969
  }));
908
970
  // Format resource details for the prompt using raw explanation when available
909
- const resourceDetails = resourcesWithSchemas.map(resource => {
971
+ const resourceDetails = resourcesWithSchemas
972
+ .map(resource => {
910
973
  if (resource.rawExplanation) {
911
974
  // Use raw kubectl explain output for comprehensive field information
912
975
  return `${resource.kind} (${resource.apiVersion}):
@@ -917,26 +980,33 @@ ${resource.rawExplanation}`;
917
980
  }
918
981
  else {
919
982
  // Fallback to properties map if raw explanation is not available
920
- const properties = Array.from(resource.properties.entries()).map(([key, field]) => {
921
- const nestedFields = Array.from(field.nested.entries()).map(([nestedKey, nestedField]) => ` ${nestedKey}: ${nestedField.type} - ${nestedField.description}`).join('\n');
983
+ const properties = Array.from(resource.properties.entries())
984
+ .map(([key, field]) => {
985
+ const nestedFields = Array.from(field.nested.entries())
986
+ .map(([nestedKey, nestedField]) => ` ${nestedKey}: ${nestedField.type} - ${nestedField.description}`)
987
+ .join('\n');
922
988
  return ` ${key}: ${field.type} - ${field.description}${field.required ? ' (required)' : ''}${nestedFields ? '\n' + nestedFields : ''}`;
923
- }).join('\n');
989
+ })
990
+ .join('\n');
924
991
  return `${resource.kind} (${resource.apiVersion}):
925
992
  Description: ${resource.description}
926
993
  Required fields: ${resource.required?.join(', ') || 'none specified'}
927
994
  Properties:
928
995
  ${properties}`;
929
996
  }
930
- }).join('\n\n');
997
+ })
998
+ .join('\n\n');
931
999
  // Format cluster options for the prompt
932
1000
  const clusterOptionsText = this.formatClusterOptionsText(clusterOptions);
933
1001
  // Format organizational policies for AI context with relevance scores
934
1002
  const policyContextText = relevantPolicyResults.length > 0
935
- ? relevantPolicyResults.map(result => `- ID: ${result.policy.id}
1003
+ ? relevantPolicyResults
1004
+ .map(result => `- ID: ${result.policy.id}
936
1005
  Description: ${result.policy.description}
937
1006
  Rationale: ${result.policy.rationale}
938
1007
  Triggers: ${result.policy.triggers?.join(', ') || 'None'}
939
- Score: ${result.score.toFixed(3)} (${result.matchType})`).join('\n')
1008
+ Score: ${result.score.toFixed(3)} (${result.matchType})`)
1009
+ .join('\n')
940
1010
  : 'No organizational policies found for this request.';
941
1011
  // Build source_material for capabilities (Kubernetes resource-based solutions)
942
1012
  const sourceMaterial = `## Source Material
@@ -950,16 +1020,19 @@ ${resourceDetails}`;
950
1020
  solution_description: solution.description,
951
1021
  source_material: sourceMaterial,
952
1022
  cluster_options: clusterOptionsText,
953
- policy_context: policyContextText
1023
+ policy_context: policyContextText,
954
1024
  });
955
1025
  const response = await this.aiProvider.sendMessage(questionPrompt, 'recommend-question-generation', {
956
1026
  user_intent: `Generate deployment questions for: ${intent}`,
957
- interaction_id: interaction_id || 'recommend_question_generation'
1027
+ interaction_id: interaction_id || 'recommend_question_generation',
958
1028
  });
959
1029
  // Use robust JSON extraction
960
1030
  const questions = (0, platform_utils_1.extractJsonFromAIResponse)(response.content);
961
1031
  // Validate the response structure
962
- if (!questions.required || !questions.basic || !questions.advanced || !questions.open) {
1032
+ if (!questions.required ||
1033
+ !questions.basic ||
1034
+ !questions.advanced ||
1035
+ !questions.open) {
963
1036
  throw new Error('Invalid question structure from AI');
964
1037
  }
965
1038
  // Inject packaging questions for capability-based solutions
@@ -967,7 +1040,8 @@ ${resourceDetails}`;
967
1040
  }
968
1041
  catch (error) {
969
1042
  // Re-throw errors about missing resourceName - these are bugs, not generation failures
970
- if (error instanceof Error && error.message.includes('missing resourceName field')) {
1043
+ if (error instanceof Error &&
1044
+ error.message.includes('missing resourceName field')) {
971
1045
  throw error;
972
1046
  }
973
1047
  console.warn(`Failed to generate AI questions for solution: ${error}`);
@@ -977,9 +1051,9 @@ ${resourceDetails}`;
977
1051
  basic: [],
978
1052
  advanced: [],
979
1053
  open: {
980
- question: "Is there anything else about your requirements or constraints that would help us provide better recommendations?",
981
- placeholder: "e.g., specific security requirements, performance needs, existing infrastructure constraints..."
982
- }
1054
+ question: 'Is there anything else about your requirements or constraints that would help us provide better recommendations?',
1055
+ placeholder: 'e.g., specific security requirements, performance needs, existing infrastructure constraints...',
1056
+ },
983
1057
  });
984
1058
  }
985
1059
  }
@@ -1001,7 +1075,7 @@ ${resourceDetails}`;
1001
1075
  relevantPolicyResults = policyResults.map(result => ({
1002
1076
  policy: result.data,
1003
1077
  score: result.score,
1004
- matchType: result.matchType
1078
+ matchType: result.matchType,
1005
1079
  }));
1006
1080
  console.log(`🛡️ Found ${relevantPolicyResults.length} relevant policy intents for Helm question generation`);
1007
1081
  }
@@ -1027,11 +1101,13 @@ ${valuesYaml || '# No values.yaml available'}
1027
1101
  ${readme || 'No README available'}`;
1028
1102
  // Format organizational policies
1029
1103
  const policyContextText = relevantPolicyResults.length > 0
1030
- ? relevantPolicyResults.map(result => `- ID: ${result.policy.id}
1104
+ ? relevantPolicyResults
1105
+ .map(result => `- ID: ${result.policy.id}
1031
1106
  Description: ${result.policy.description}
1032
1107
  Rationale: ${result.policy.rationale}
1033
1108
  Triggers: ${result.policy.triggers?.join(', ') || 'None'}
1034
- Score: ${result.score.toFixed(3)} (${result.matchType})`).join('\n')
1109
+ Score: ${result.score.toFixed(3)} (${result.matchType})`)
1110
+ .join('\n')
1035
1111
  : 'No organizational policies found for this request.';
1036
1112
  // Format cluster options for the prompt
1037
1113
  const clusterOptionsText = this.formatClusterOptionsText(clusterOptions);
@@ -1041,11 +1117,11 @@ ${readme || 'No README available'}`;
1041
1117
  solution_description: description,
1042
1118
  source_material: sourceMaterial,
1043
1119
  cluster_options: clusterOptionsText,
1044
- policy_context: policyContextText
1120
+ policy_context: policyContextText,
1045
1121
  });
1046
1122
  const response = await this.aiProvider.sendMessage(questionPrompt, 'helm-question-generation', {
1047
1123
  user_intent: `Generate Helm installation questions for: ${intent}`,
1048
- interaction_id: interaction_id || 'helm_question_generation'
1124
+ interaction_id: interaction_id || 'helm_question_generation',
1049
1125
  });
1050
1126
  const questions = (0, platform_utils_1.extractJsonFromAIResponse)(response.content);
1051
1127
  if (!questions.required || !questions.basic || !questions.advanced) {
@@ -1053,8 +1129,8 @@ ${readme || 'No README available'}`;
1053
1129
  }
1054
1130
  if (!questions.open) {
1055
1131
  questions.open = {
1056
- question: "Any additional configuration requirements?",
1057
- placeholder: "e.g., custom values, specific settings..."
1132
+ question: 'Any additional configuration requirements?',
1133
+ placeholder: 'e.g., custom values, specific settings...',
1058
1134
  };
1059
1135
  }
1060
1136
  console.log(`✅ Generated ${questions.required.length} required, ${questions.basic.length} basic, ${questions.advanced.length} advanced questions`);
@@ -1069,21 +1145,21 @@ ${readme || 'No README available'}`;
1069
1145
  id: 'name',
1070
1146
  question: 'What name should be used for this Helm release?',
1071
1147
  type: 'text',
1072
- suggestedAnswer: chart.chartName
1148
+ suggestedAnswer: chart.chartName,
1073
1149
  },
1074
1150
  {
1075
1151
  id: 'namespace',
1076
1152
  question: 'Which namespace should this be installed in?',
1077
1153
  type: 'text',
1078
- suggestedAnswer: 'default'
1079
- }
1154
+ suggestedAnswer: 'default',
1155
+ },
1080
1156
  ],
1081
1157
  basic: [],
1082
1158
  advanced: [],
1083
1159
  open: {
1084
- question: "Any additional configuration requirements?",
1085
- placeholder: "e.g., custom values, specific settings..."
1086
- }
1160
+ question: 'Any additional configuration requirements?',
1161
+ placeholder: 'e.g., custom values, specific settings...',
1162
+ },
1087
1163
  };
1088
1164
  }
1089
1165
  }
@@ -1095,7 +1171,9 @@ ${readme || 'No README available'}`;
1095
1171
  let readme = '';
1096
1172
  // Sanitize chart info to prevent command injection
1097
1173
  const safeChart = sanitizeChartInfo(chart);
1098
- const versionFlag = safeChart.version ? `--version ${safeChart.version}` : '';
1174
+ const versionFlag = safeChart.version
1175
+ ? `--version ${safeChart.version}`
1176
+ : '';
1099
1177
  try {
1100
1178
  // Add repo and update
1101
1179
  await (0, platform_utils_1.execAsync)(`helm repo add ${safeChart.repositoryName} ${safeChart.repository} 2>/dev/null || true`);