@vfarcic/dot-ai 0.32.0 → 0.34.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/solution-utils.d.ts +20 -0
- package/dist/core/solution-utils.d.ts.map +1 -0
- package/dist/core/solution-utils.js +65 -0
- package/dist/tools/answer-question.d.ts.map +1 -1
- package/dist/tools/answer-question.js +2 -15
- package/dist/tools/generate-manifests.d.ts.map +1 -1
- package/dist/tools/generate-manifests.js +73 -8
- package/package.json +1 -1
- package/prompts/manifest-generation.md +16 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solution utilities for working with solution data structures
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extract all user answers from a solution's questions
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractUserAnswers(solution: any): Record<string, any>;
|
|
8
|
+
/**
|
|
9
|
+
* Sanitize intent string for use as Kubernetes label (63 char limit, alphanumeric + hyphens)
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeIntentForLabel(intent: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate standard dot-ai labels for Kubernetes resources
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateDotAiLabels(userAnswers: Record<string, any>, solution: any): Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Add dot-ai labels to existing labels object
|
|
18
|
+
*/
|
|
19
|
+
export declare function addDotAiLabels(existingLabels: Record<string, string> | undefined, userAnswers: Record<string, any>, solution: any): Record<string, string>;
|
|
20
|
+
//# sourceMappingURL=solution-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solution-utils.d.ts","sourceRoot":"","sources":["../../src/core/solution-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAoBrE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAiB3G;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChC,QAAQ,EAAE,GAAG,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGxB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Solution utilities for working with solution data structures
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractUserAnswers = extractUserAnswers;
|
|
7
|
+
exports.sanitizeIntentForLabel = sanitizeIntentForLabel;
|
|
8
|
+
exports.generateDotAiLabels = generateDotAiLabels;
|
|
9
|
+
exports.addDotAiLabels = addDotAiLabels;
|
|
10
|
+
/**
|
|
11
|
+
* Extract all user answers from a solution's questions
|
|
12
|
+
*/
|
|
13
|
+
function extractUserAnswers(solution) {
|
|
14
|
+
const userAnswers = {};
|
|
15
|
+
// Extract from all question categories
|
|
16
|
+
const questionCategories = ['required', 'basic', 'advanced'];
|
|
17
|
+
for (const category of questionCategories) {
|
|
18
|
+
const questions = solution.questions[category] || [];
|
|
19
|
+
for (const question of questions) {
|
|
20
|
+
if (question.answer !== undefined && question.answer !== null) {
|
|
21
|
+
userAnswers[question.id] = question.answer;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Include open answer if provided
|
|
26
|
+
if (solution.questions.open?.answer) {
|
|
27
|
+
userAnswers.open = solution.questions.open.answer;
|
|
28
|
+
}
|
|
29
|
+
return userAnswers;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize intent string for use as Kubernetes label (63 char limit, alphanumeric + hyphens)
|
|
33
|
+
*/
|
|
34
|
+
function sanitizeIntentForLabel(intent) {
|
|
35
|
+
return intent
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
38
|
+
.substring(0, 63)
|
|
39
|
+
.replace(/^-+|-+$/g, '');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate standard dot-ai labels for Kubernetes resources
|
|
43
|
+
*/
|
|
44
|
+
function generateDotAiLabels(userAnswers, solution) {
|
|
45
|
+
const appName = userAnswers.name;
|
|
46
|
+
const originalIntent = solution.intent;
|
|
47
|
+
if (!appName) {
|
|
48
|
+
throw new Error('Application name is required for dot-ai labels. This indicates a bug in the MCP workflow.');
|
|
49
|
+
}
|
|
50
|
+
if (!originalIntent) {
|
|
51
|
+
throw new Error('Application intent is required for dot-ai labels. This indicates a bug in the solution data.');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
'dot-ai.io/managed': 'true',
|
|
55
|
+
'dot-ai.io/app-name': appName,
|
|
56
|
+
'dot-ai.io/intent': sanitizeIntentForLabel(originalIntent)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add dot-ai labels to existing labels object
|
|
61
|
+
*/
|
|
62
|
+
function addDotAiLabels(existingLabels, userAnswers, solution) {
|
|
63
|
+
const dotAiLabels = generateDotAiLabels(userAnswers, solution);
|
|
64
|
+
return { ...existingLabels, ...dotAiLabels };
|
|
65
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer-question.d.ts","sourceRoot":"","sources":["../../src/tools/answer-question.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"answer-question.d.ts","sourceRoot":"","sources":["../../src/tools/answer-question.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAOhD,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AACzD,eAAO,MAAM,+BAA+B,8HAA4H,CAAC;AAGzK,eAAO,MAAM,gCAAgC;;;;CAI5C,CAAC;AAkiBF;;GAEG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAAE,EAC7G,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAqUxD"}
|
|
@@ -44,6 +44,7 @@ const claude_1 = require("../core/claude");
|
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const session_utils_1 = require("../core/session-utils");
|
|
47
|
+
const solution_utils_1 = require("../core/solution-utils");
|
|
47
48
|
// Tool metadata for direct MCP registration
|
|
48
49
|
exports.ANSWERQUESTION_TOOL_NAME = 'answerQuestion';
|
|
49
50
|
exports.ANSWERQUESTION_TOOL_DESCRIPTION = 'Process user answers and return remaining questions or completion status. For open stage, use "open" as the answer key.';
|
|
@@ -724,21 +725,7 @@ async function handleAnswerQuestionTool(args, dotAI, logger, requestId) {
|
|
|
724
725
|
}
|
|
725
726
|
}
|
|
726
727
|
// Extract all user answers for handoff
|
|
727
|
-
const userAnswers =
|
|
728
|
-
// Extract from all question categories
|
|
729
|
-
const questionCategories = ['required', 'basic', 'advanced'];
|
|
730
|
-
for (const category of questionCategories) {
|
|
731
|
-
const questions = solution.questions[category] || [];
|
|
732
|
-
for (const question of questions) {
|
|
733
|
-
if (question.answer !== undefined && question.answer !== null) {
|
|
734
|
-
userAnswers[question.id] = question.answer;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
// Include open answer if provided
|
|
739
|
-
if (openAnswer) {
|
|
740
|
-
userAnswers.open = openAnswer;
|
|
741
|
-
}
|
|
728
|
+
const userAnswers = (0, solution_utils_1.extractUserAnswers)(solution);
|
|
742
729
|
const response = {
|
|
743
730
|
status: 'ready_for_manifest_generation',
|
|
744
731
|
solutionId: args.solutionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-manifests.d.ts","sourceRoot":"","sources":["../../src/tools/generate-manifests.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-manifests.d.ts","sourceRoot":"","sources":["../../src/tools/generate-manifests.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAShD,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAC/D,eAAO,MAAM,kCAAkC,+IAA+I,CAAC;AAG/L,eAAO,MAAM,mCAAmC;;CAE/C,CAAC;AA2VF;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAC5B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAE,CAAC,CAqMxD"}
|
|
@@ -46,6 +46,7 @@ const path = __importStar(require("path"));
|
|
|
46
46
|
const yaml = __importStar(require("js-yaml"));
|
|
47
47
|
const child_process_1 = require("child_process");
|
|
48
48
|
const session_utils_1 = require("../core/session-utils");
|
|
49
|
+
const solution_utils_1 = require("../core/solution-utils");
|
|
49
50
|
// Tool metadata for direct MCP registration
|
|
50
51
|
exports.GENERATEMANIFESTS_TOOL_NAME = 'generateManifests';
|
|
51
52
|
exports.GENERATEMANIFESTS_TOOL_DESCRIPTION = 'Generate final Kubernetes manifests from fully configured solution (ONLY after completing ALL stages: required, basic, advanced, and open)';
|
|
@@ -216,7 +217,7 @@ async function validateManifests(yamlPath) {
|
|
|
216
217
|
/**
|
|
217
218
|
* Generate manifests using AI with Claude integration
|
|
218
219
|
*/
|
|
219
|
-
async function generateManifestsWithAI(solution, dotAI, logger, errorContext) {
|
|
220
|
+
async function generateManifestsWithAI(solution, dotAI, logger, errorContext, dotAiLabels) {
|
|
220
221
|
// Load prompt template
|
|
221
222
|
const promptPath = path.join(__dirname, '..', '..', 'prompts', 'manifest-generation.md');
|
|
222
223
|
const template = fs.readFileSync(promptPath, 'utf8');
|
|
@@ -239,11 +240,13 @@ ${errorContext.previousManifests}
|
|
|
239
240
|
` : 'None - this is the first attempt.';
|
|
240
241
|
// Replace template variables
|
|
241
242
|
const schemasData = JSON.stringify(resourceSchemas, null, 2);
|
|
243
|
+
const labelsData = dotAiLabels ? JSON.stringify(dotAiLabels, null, 2) : '{}';
|
|
242
244
|
const aiPrompt = template
|
|
243
245
|
.replace('{solution}', solutionData)
|
|
244
246
|
.replace('{schemas}', schemasData)
|
|
245
247
|
.replace('{previous_attempt}', previousAttempt)
|
|
246
|
-
.replace('{error_details}', errorDetails)
|
|
248
|
+
.replace('{error_details}', errorDetails)
|
|
249
|
+
.replace('{labels}', labelsData);
|
|
247
250
|
const isRetry = !!errorContext;
|
|
248
251
|
logger.info('Generating manifests with AI', {
|
|
249
252
|
isRetry,
|
|
@@ -272,6 +275,57 @@ ${errorContext.previousManifests}
|
|
|
272
275
|
});
|
|
273
276
|
return manifestContent;
|
|
274
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Generate dot-ai application metadata ConfigMap
|
|
280
|
+
*/
|
|
281
|
+
function generateMetadataConfigMap(solution, userAnswers, logger) {
|
|
282
|
+
const appName = userAnswers.name;
|
|
283
|
+
const namespace = userAnswers.namespace || 'default';
|
|
284
|
+
const solutionId = solution.solutionId;
|
|
285
|
+
const originalIntent = solution.intent;
|
|
286
|
+
// Validate required fields (will throw if missing)
|
|
287
|
+
const dotAiLabels = (0, solution_utils_1.addDotAiLabels)(undefined, userAnswers, solution);
|
|
288
|
+
// Extract resource references from solution
|
|
289
|
+
const resources = (solution.resources || []).map((resource) => ({
|
|
290
|
+
apiVersion: resource.apiVersion,
|
|
291
|
+
kind: resource.kind,
|
|
292
|
+
name: resource.name || appName, // Use app name as fallback
|
|
293
|
+
namespace: resource.namespace || namespace
|
|
294
|
+
}));
|
|
295
|
+
// Create ConfigMap object
|
|
296
|
+
const configMap = {
|
|
297
|
+
apiVersion: 'v1',
|
|
298
|
+
kind: 'ConfigMap',
|
|
299
|
+
metadata: {
|
|
300
|
+
name: `dot-ai-app-${appName}-${solutionId}`,
|
|
301
|
+
namespace: namespace,
|
|
302
|
+
labels: dotAiLabels,
|
|
303
|
+
annotations: {
|
|
304
|
+
'dot-ai.io/original-intent': originalIntent
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
data: {
|
|
308
|
+
'deployment-info.yaml': yaml.dump({
|
|
309
|
+
appName,
|
|
310
|
+
deployedAt: new Date().toISOString(),
|
|
311
|
+
originalIntent,
|
|
312
|
+
resources
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
try {
|
|
317
|
+
return yaml.dump(configMap);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
logger.error('Failed to generate YAML for ConfigMap', error, {
|
|
321
|
+
configMap,
|
|
322
|
+
appName,
|
|
323
|
+
solutionId,
|
|
324
|
+
namespace
|
|
325
|
+
});
|
|
326
|
+
throw new Error(`ConfigMap YAML generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
275
329
|
/**
|
|
276
330
|
* Direct MCP tool handler for generateManifests functionality
|
|
277
331
|
*/
|
|
@@ -339,8 +393,15 @@ async function handleGenerateManifestsTool(args, dotAI, logger, requestId) {
|
|
|
339
393
|
requestId
|
|
340
394
|
});
|
|
341
395
|
try {
|
|
342
|
-
//
|
|
343
|
-
const
|
|
396
|
+
// Extract user answers and generate required labels
|
|
397
|
+
const userAnswers = (0, solution_utils_1.extractUserAnswers)(solution);
|
|
398
|
+
const dotAiLabels = (0, solution_utils_1.addDotAiLabels)(undefined, userAnswers, solution);
|
|
399
|
+
// Generate manifests with AI (including labels)
|
|
400
|
+
const aiManifests = await generateManifestsWithAI(solution, dotAI, logger, lastError, dotAiLabels);
|
|
401
|
+
// Generate metadata ConfigMap
|
|
402
|
+
const metadataConfigMap = generateMetadataConfigMap(solution, userAnswers, logger);
|
|
403
|
+
// Combine ConfigMap with AI-generated manifests
|
|
404
|
+
const manifests = metadataConfigMap + '---\n' + aiManifests;
|
|
344
405
|
// Save manifests to file
|
|
345
406
|
fs.writeFileSync(yamlPath, manifests, 'utf8');
|
|
346
407
|
logger.info('Manifests saved to file', { yamlPath, attempt, requestId });
|
|
@@ -397,8 +458,12 @@ async function handleGenerateManifestsTool(args, dotAI, logger, requestId) {
|
|
|
397
458
|
}
|
|
398
459
|
catch (error) {
|
|
399
460
|
logger.error('Error during manifest generation attempt', error);
|
|
400
|
-
//
|
|
401
|
-
|
|
461
|
+
// Check if this is a validation error that should not be retried
|
|
462
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
463
|
+
const isValidationError = errorMessage.includes('Application name is required') ||
|
|
464
|
+
errorMessage.includes('Application intent is required');
|
|
465
|
+
// If this is a validation error or the last attempt, throw the error immediately
|
|
466
|
+
if (isValidationError || attempt === maxAttempts) {
|
|
402
467
|
throw error;
|
|
403
468
|
}
|
|
404
469
|
// Prepare error context for retry
|
|
@@ -406,9 +471,9 @@ async function handleGenerateManifestsTool(args, dotAI, logger, requestId) {
|
|
|
406
471
|
attempt,
|
|
407
472
|
previousManifests: lastError?.previousManifests || '',
|
|
408
473
|
yamlSyntaxValid: false,
|
|
409
|
-
kubectlOutput:
|
|
474
|
+
kubectlOutput: errorMessage,
|
|
410
475
|
exitCode: -1,
|
|
411
|
-
stderr:
|
|
476
|
+
stderr: errorMessage,
|
|
412
477
|
stdout: ''
|
|
413
478
|
};
|
|
414
479
|
}
|
package/package.json
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
The following schemas are available for the resources selected in the solution:
|
|
8
8
|
{schemas}
|
|
9
9
|
|
|
10
|
+
## Required Labels
|
|
11
|
+
Add these labels to the metadata.labels section of EVERY resource you generate:
|
|
12
|
+
{labels}
|
|
13
|
+
|
|
10
14
|
## Previous Attempt (if retry)
|
|
11
15
|
{previous_attempt}
|
|
12
16
|
|
|
@@ -30,13 +34,18 @@ Generate production-ready Kubernetes YAML manifests from the complete solution c
|
|
|
30
34
|
- Do not invent or guess field names - refer to the schema section above
|
|
31
35
|
- Apply configuration values appropriately for the specific resource type
|
|
32
36
|
|
|
33
|
-
3. **
|
|
37
|
+
3. **Apply Required Labels**:
|
|
38
|
+
- **CRITICAL**: Add the required labels shown above to EVERY resource's metadata.labels section
|
|
39
|
+
- Merge required labels with any existing labels the resource needs
|
|
40
|
+
- These labels are essential for application tracking and management
|
|
41
|
+
|
|
42
|
+
4. **Cross-Resource Field Mapping**:
|
|
34
43
|
- Many user answers apply to multiple resources in the solution
|
|
35
44
|
- Use the same values consistently across related resources where appropriate
|
|
36
45
|
- Ensure proper relationships between resources through consistent naming and labeling
|
|
37
46
|
- Apply configuration values to all relevant fields across different resource types
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
5. **Process Open Requirements**:
|
|
40
49
|
- If user provided open requirements, analyze their specific needs
|
|
41
50
|
- Use available cluster resources to fulfill those requirements intelligently
|
|
42
51
|
- Make enhancement decisions based on actual cluster capabilities
|
|
@@ -49,8 +58,11 @@ Generate production-ready Kubernetes YAML manifests from the complete solution c
|
|
|
49
58
|
* **Network policies** → Add NetworkPolicy resources
|
|
50
59
|
* **Resource limits** → Add ResourceQuota or LimitRange resources
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
-
|
|
61
|
+
6. **Generate Appropriate Manifests**:
|
|
62
|
+
- **CRITICAL**: Analyze resource relationships before generating separate manifests
|
|
63
|
+
- When multiple resources can be integrated (e.g., one resource has fields that reference another), prefer integration over separate resources
|
|
64
|
+
- Generate separate manifests only when resources must be standalone or when integration is not supported by the schema
|
|
65
|
+
- Use resource schemas to determine integration possibilities and required field relationships
|
|
54
66
|
- **IMPORTANT**: Include any additional supporting resources needed to fulfill open requirements
|
|
55
67
|
- Use correct API versions and schemas from the solution data
|
|
56
68
|
- Ensure all resources work together to meet user's complete requirements
|