@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.
- package/dist/core/artifacthub.d.ts.map +1 -1
- package/dist/core/artifacthub.js +16 -10
- package/dist/core/base-vector-service.d.ts.map +1 -1
- package/dist/core/base-vector-service.js +19 -10
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js +17 -11
- package/dist/core/capability-operations.d.ts.map +1 -1
- package/dist/core/capability-operations.js +118 -98
- package/dist/core/crd-availability.d.ts.map +1 -1
- package/dist/core/crd-availability.js +2 -2
- package/dist/core/deploy-operation.d.ts.map +1 -1
- package/dist/core/deploy-operation.js +9 -6
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +157 -56
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +11 -7
- package/dist/core/packaging.d.ts.map +1 -1
- package/dist/core/packaging.js +11 -10
- package/dist/core/platform-utils.d.ts.map +1 -1
- package/dist/core/platform-utils.js +6 -2
- package/dist/core/plugin-manager.d.ts.map +1 -1
- package/dist/core/plugin-manager.js +26 -20
- package/dist/core/policy-operations.d.ts.map +1 -1
- package/dist/core/policy-operations.js +111 -65
- package/dist/core/providers/host-provider.d.ts.map +1 -1
- package/dist/core/providers/host-provider.js +12 -5
- package/dist/core/providers/vercel-provider.d.ts.map +1 -1
- package/dist/core/providers/vercel-provider.js +4 -2
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +173 -95
- package/dist/core/session-utils.d.ts.map +1 -1
- package/dist/core/session-utils.js +3 -3
- package/dist/core/user-prompts-loader.d.ts.map +1 -1
- package/dist/core/user-prompts-loader.js +2 -4
- package/dist/evaluation/datasets/loader.d.ts.map +1 -1
- package/dist/evaluation/datasets/loader.js +7 -3
- package/dist/interfaces/rest-api.d.ts +4 -0
- package/dist/interfaces/rest-api.d.ts.map +1 -1
- package/dist/interfaces/rest-api.js +35 -0
- package/dist/interfaces/rest-route-registry.js +3 -3
- package/dist/interfaces/routes/index.d.ts.map +1 -1
- package/dist/interfaces/routes/index.js +10 -0
- package/dist/interfaces/schemas/common.d.ts +1 -0
- package/dist/interfaces/schemas/common.d.ts.map +1 -1
- package/dist/interfaces/schemas/common.js +1 -0
- package/dist/interfaces/schemas/index.d.ts +1 -1
- package/dist/interfaces/schemas/index.d.ts.map +1 -1
- package/dist/interfaces/schemas/index.js +5 -2
- package/dist/interfaces/schemas/prompts.d.ts +37 -0
- package/dist/interfaces/schemas/prompts.d.ts.map +1 -1
- package/dist/interfaces/schemas/prompts.js +18 -1
- package/dist/tools/answer-question.d.ts.map +1 -1
- package/dist/tools/answer-question.js +109 -81
- package/dist/tools/generate-manifests.d.ts.map +1 -1
- package/dist/tools/generate-manifests.js +163 -103
- package/dist/tools/operate-analysis.d.ts.map +1 -1
- package/dist/tools/operate-analysis.js +27 -17
- package/dist/tools/operate.d.ts.map +1 -1
- package/dist/tools/operate.js +47 -17
- package/dist/tools/prompts.d.ts.map +1 -1
- package/dist/tools/prompts.js +1 -1
- package/dist/tools/remediate.d.ts.map +1 -1
- package/dist/tools/remediate.js +199 -115
- package/package.json +6 -6
package/dist/core/schema.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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 ||
|
|
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 =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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 =
|
|
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 ||
|
|
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
|
|
528
|
-
|
|
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 &&
|
|
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(
|
|
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: {
|
|
570
|
-
|
|
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
|
|
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
|
-
})
|
|
622
|
+
})
|
|
623
|
+
.join('\n\n');
|
|
604
624
|
// Format organizational patterns for AI context
|
|
605
625
|
const patternsContext = patterns.length > 0
|
|
606
|
-
? patterns
|
|
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'}`)
|
|
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('.')
|
|
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: [
|
|
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, {
|
|
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
|
|
749
|
-
|
|
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
|
|
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([
|
|
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([
|
|
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([
|
|
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([
|
|
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/') &&
|
|
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
|
|
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
|
|
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
|
|
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())
|
|
921
|
-
|
|
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
|
-
})
|
|
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
|
-
})
|
|
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
|
|
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})`)
|
|
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 ||
|
|
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 &&
|
|
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:
|
|
981
|
-
placeholder:
|
|
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
|
|
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})`)
|
|
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:
|
|
1057
|
-
placeholder:
|
|
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:
|
|
1085
|
-
placeholder:
|
|
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
|
|
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`);
|