@vfarcic/dot-ai 0.33.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.
@@ -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;AAMhD,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,CAqVxD"}
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;AAQhD,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAC/D,eAAO,MAAM,kCAAkC,+IAA+I,CAAC;AAG/L,eAAO,MAAM,mCAAmC;;CAE/C,CAAC;AAiSF;;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,CAqLxD"}
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
- // Generate manifests with AI
343
- const manifests = await generateManifestsWithAI(solution, dotAI, logger, lastError);
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
- // If this is the last attempt, throw the error
401
- if (attempt === maxAttempts) {
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: error instanceof Error ? error.message : 'Unknown error',
474
+ kubectlOutput: errorMessage,
410
475
  exitCode: -1,
411
- stderr: error instanceof Error ? error.message : 'Unknown error',
476
+ stderr: errorMessage,
412
477
  stdout: ''
413
478
  };
414
479
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vfarcic/dot-ai",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Universal Kubernetes application deployment agent with CLI and MCP interfaces",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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. **Cross-Resource Field Mapping**:
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
- 4. **Process Open Requirements**:
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,7 +58,7 @@ 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
- 5. **Generate Appropriate Manifests**:
61
+ 6. **Generate Appropriate Manifests**:
53
62
  - **CRITICAL**: Analyze resource relationships before generating separate manifests
54
63
  - When multiple resources can be integrated (e.g., one resource has fields that reference another), prefer integration over separate resources
55
64
  - Generate separate manifests only when resources must be standalone or when integration is not supported by the schema