@vfarcic/dot-ai 0.1.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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +51 -0
  6. package/dist/core/claude.d.ts +42 -0
  7. package/dist/core/claude.d.ts.map +1 -0
  8. package/dist/core/claude.js +229 -0
  9. package/dist/core/deploy-operation.d.ts +38 -0
  10. package/dist/core/deploy-operation.d.ts.map +1 -0
  11. package/dist/core/deploy-operation.js +101 -0
  12. package/dist/core/discovery.d.ts +162 -0
  13. package/dist/core/discovery.d.ts.map +1 -0
  14. package/dist/core/discovery.js +758 -0
  15. package/dist/core/error-handling.d.ts +167 -0
  16. package/dist/core/error-handling.d.ts.map +1 -0
  17. package/dist/core/error-handling.js +399 -0
  18. package/dist/core/index.d.ts +42 -0
  19. package/dist/core/index.d.ts.map +1 -0
  20. package/dist/core/index.js +123 -0
  21. package/dist/core/kubernetes-utils.d.ts +38 -0
  22. package/dist/core/kubernetes-utils.d.ts.map +1 -0
  23. package/dist/core/kubernetes-utils.js +177 -0
  24. package/dist/core/memory.d.ts +45 -0
  25. package/dist/core/memory.d.ts.map +1 -0
  26. package/dist/core/memory.js +113 -0
  27. package/dist/core/schema.d.ts +187 -0
  28. package/dist/core/schema.d.ts.map +1 -0
  29. package/dist/core/schema.js +655 -0
  30. package/dist/core/session-utils.d.ts +29 -0
  31. package/dist/core/session-utils.d.ts.map +1 -0
  32. package/dist/core/session-utils.js +121 -0
  33. package/dist/core/workflow.d.ts +70 -0
  34. package/dist/core/workflow.d.ts.map +1 -0
  35. package/dist/core/workflow.js +161 -0
  36. package/dist/index.d.ts +15 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +32 -0
  39. package/dist/interfaces/cli.d.ts +74 -0
  40. package/dist/interfaces/cli.d.ts.map +1 -0
  41. package/dist/interfaces/cli.js +769 -0
  42. package/dist/interfaces/mcp.d.ts +30 -0
  43. package/dist/interfaces/mcp.d.ts.map +1 -0
  44. package/dist/interfaces/mcp.js +105 -0
  45. package/dist/mcp/server.d.ts +9 -0
  46. package/dist/mcp/server.d.ts.map +1 -0
  47. package/dist/mcp/server.js +151 -0
  48. package/dist/tools/answer-question.d.ts +27 -0
  49. package/dist/tools/answer-question.d.ts.map +1 -0
  50. package/dist/tools/answer-question.js +696 -0
  51. package/dist/tools/choose-solution.d.ts +23 -0
  52. package/dist/tools/choose-solution.d.ts.map +1 -0
  53. package/dist/tools/choose-solution.js +171 -0
  54. package/dist/tools/deploy-manifests.d.ts +25 -0
  55. package/dist/tools/deploy-manifests.d.ts.map +1 -0
  56. package/dist/tools/deploy-manifests.js +74 -0
  57. package/dist/tools/generate-manifests.d.ts +23 -0
  58. package/dist/tools/generate-manifests.d.ts.map +1 -0
  59. package/dist/tools/generate-manifests.js +424 -0
  60. package/dist/tools/index.d.ts +11 -0
  61. package/dist/tools/index.d.ts.map +1 -0
  62. package/dist/tools/index.js +34 -0
  63. package/dist/tools/recommend.d.ts +23 -0
  64. package/dist/tools/recommend.d.ts.map +1 -0
  65. package/dist/tools/recommend.js +332 -0
  66. package/package.json +124 -0
  67. package/prompts/intent-validation.md +65 -0
  68. package/prompts/manifest-generation.md +79 -0
  69. package/prompts/question-generation.md +128 -0
  70. package/prompts/resource-analysis.md +127 -0
  71. package/prompts/resource-selection.md +55 -0
  72. package/prompts/resource-solution-ranking.md +77 -0
  73. package/prompts/solution-enhancement.md +129 -0
@@ -0,0 +1,696 @@
1
+ "use strict";
2
+ /**
3
+ * Answer Question Tool - Process user answers and return remaining questions
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ANSWERQUESTION_TOOL_INPUT_SCHEMA = exports.ANSWERQUESTION_TOOL_DESCRIPTION = exports.ANSWERQUESTION_TOOL_NAME = void 0;
40
+ exports.handleAnswerQuestionTool = handleAnswerQuestionTool;
41
+ const zod_1 = require("zod");
42
+ const error_handling_1 = require("../core/error-handling");
43
+ const claude_1 = require("../core/claude");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const session_utils_1 = require("../core/session-utils");
47
+ // Tool metadata for direct MCP registration
48
+ exports.ANSWERQUESTION_TOOL_NAME = 'answerQuestion';
49
+ exports.ANSWERQUESTION_TOOL_DESCRIPTION = 'Process user answers and return remaining questions or completion status';
50
+ // Zod schema for MCP registration
51
+ exports.ANSWERQUESTION_TOOL_INPUT_SCHEMA = {
52
+ solutionId: zod_1.z.string().regex(/^sol_[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{6}_[a-f0-9]+$/).describe('The solution ID to update (e.g., sol_2025-07-01T154349_1e1e242592ff)'),
53
+ stage: zod_1.z.enum(['required', 'basic', 'advanced', 'open']).describe('The configuration stage being addressed'),
54
+ answers: zod_1.z.record(zod_1.z.any()).describe('User answers to configuration questions for the specified stage')
55
+ };
56
+ /**
57
+ * Load solution file by ID
58
+ */
59
+ function loadSolutionFile(solutionId, sessionDir) {
60
+ const solutionPath = path.join(sessionDir, `${solutionId}.json`);
61
+ if (!fs.existsSync(solutionPath)) {
62
+ throw new Error(`Solution file not found: ${solutionPath}. Available files: ${fs.readdirSync(sessionDir).filter(f => f.endsWith('.json')).join(', ')}`);
63
+ }
64
+ try {
65
+ const content = fs.readFileSync(solutionPath, 'utf8');
66
+ const solution = JSON.parse(content);
67
+ // Validate solution structure
68
+ if (!solution.solutionId || !solution.questions) {
69
+ throw new Error(`Invalid solution file structure: ${solutionId}. Missing required fields: solutionId or questions`);
70
+ }
71
+ return solution;
72
+ }
73
+ catch (error) {
74
+ if (error instanceof SyntaxError) {
75
+ throw new Error(`Invalid JSON in solution file: ${solutionId}`);
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ /**
81
+ * Save solution file with atomic operations
82
+ */
83
+ function saveSolutionFile(solution, solutionId, sessionDir) {
84
+ const solutionPath = path.join(sessionDir, `${solutionId}.json`);
85
+ const tempPath = solutionPath + '.tmp';
86
+ try {
87
+ // Write to temporary file first
88
+ fs.writeFileSync(tempPath, JSON.stringify(solution, null, 2), 'utf8');
89
+ // Atomically rename to final path
90
+ fs.renameSync(tempPath, solutionPath);
91
+ }
92
+ catch (error) {
93
+ // Clean up temporary file if it exists
94
+ if (fs.existsSync(tempPath)) {
95
+ try {
96
+ fs.unlinkSync(tempPath);
97
+ }
98
+ catch (cleanupError) {
99
+ // Ignore cleanup errors
100
+ }
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ /**
106
+ * Validate answer against question schema
107
+ */
108
+ function validateAnswer(answer, question) {
109
+ // Check required validation
110
+ if (question.validation?.required && (answer === undefined || answer === null || answer === '')) {
111
+ return question.validation.message || `${question.question} is required`;
112
+ }
113
+ // Skip validation if answer is empty and not required
114
+ if (answer === undefined || answer === null || answer === '') {
115
+ return null;
116
+ }
117
+ // Type validation
118
+ switch (question.type) {
119
+ case 'number': {
120
+ if (typeof answer !== 'number' && !(!isNaN(Number(answer)))) {
121
+ return `${question.question} must be a number`;
122
+ }
123
+ const numValue = typeof answer === 'number' ? answer : Number(answer);
124
+ if (question.validation?.min !== undefined && numValue < question.validation.min) {
125
+ return `${question.question} must be at least ${question.validation.min}`;
126
+ }
127
+ if (question.validation?.max !== undefined && numValue > question.validation.max) {
128
+ return `${question.question} must be at most ${question.validation.max}`;
129
+ }
130
+ break;
131
+ }
132
+ case 'text':
133
+ if (typeof answer !== 'string') {
134
+ return `${question.question} must be a string`;
135
+ }
136
+ if (question.validation?.pattern) {
137
+ const pattern = new RegExp(question.validation.pattern);
138
+ if (!pattern.test(answer)) {
139
+ return question.validation.message || `${question.question} format is invalid`;
140
+ }
141
+ }
142
+ break;
143
+ case 'boolean':
144
+ if (typeof answer !== 'boolean') {
145
+ return `${question.question} must be true or false`;
146
+ }
147
+ break;
148
+ case 'select':
149
+ if (question.options && !question.options.includes(answer)) {
150
+ return `${question.question} must be one of: ${question.options.join(', ')}`;
151
+ }
152
+ break;
153
+ }
154
+ return null;
155
+ }
156
+ /**
157
+ * Get anti-cascade agent instructions for stage responses
158
+ */
159
+ function getAgentInstructions(stage) {
160
+ switch (stage) {
161
+ case 'required':
162
+ return 'CRITICAL: Present these required questions to the user. All must be answered before proceeding.';
163
+ case 'basic':
164
+ return 'CRITICAL: Present these questions to the user and wait for their response. Do NOT skip this stage unless the user explicitly says to skip THIS specific stage.';
165
+ case 'advanced':
166
+ return 'CRITICAL: Present these questions to the user and wait for their response. Do NOT skip this stage unless the user explicitly says to skip THIS specific stage.';
167
+ case 'open':
168
+ return 'CRITICAL: This is the final configuration stage. Present these questions to the user. Use "N/A" only if user explicitly states no additional requirements.';
169
+ default:
170
+ return 'Present questions to the user and wait for their response.';
171
+ }
172
+ }
173
+ /**
174
+ * Determine current stage based on solution state
175
+ */
176
+ function getCurrentStage(solution) {
177
+ const hasRequired = solution.questions.required && solution.questions.required.length > 0;
178
+ const hasBasic = solution.questions.basic && solution.questions.basic.length > 0;
179
+ const hasAdvanced = solution.questions.advanced && solution.questions.advanced.length > 0;
180
+ const hasOpen = !!solution.questions.open;
181
+ // Check completion status
182
+ const requiredComplete = !hasRequired || solution.questions.required.every((q) => q.answer !== undefined);
183
+ const basicComplete = !hasBasic || solution.questions.basic.every((q) => q.answer !== undefined);
184
+ const advancedComplete = !hasAdvanced || solution.questions.advanced.every((q) => q.answer !== undefined);
185
+ const openComplete = !hasOpen || solution.questions.open.answer !== undefined;
186
+ // Determine current stage
187
+ if (!requiredComplete) {
188
+ return {
189
+ currentStage: 'required',
190
+ nextStage: hasBasic ? 'basic' : 'open',
191
+ hasQuestions: true,
192
+ isComplete: false
193
+ };
194
+ }
195
+ if (!basicComplete) {
196
+ return {
197
+ currentStage: 'basic',
198
+ nextStage: hasAdvanced ? 'advanced' : 'open',
199
+ hasQuestions: true,
200
+ isComplete: false
201
+ };
202
+ }
203
+ if (!advancedComplete) {
204
+ return {
205
+ currentStage: 'advanced',
206
+ nextStage: 'open',
207
+ hasQuestions: true,
208
+ isComplete: false
209
+ };
210
+ }
211
+ if (!openComplete) {
212
+ return {
213
+ currentStage: 'open',
214
+ nextStage: null,
215
+ hasQuestions: hasOpen,
216
+ isComplete: false
217
+ };
218
+ }
219
+ // All stages complete
220
+ return {
221
+ currentStage: 'open',
222
+ nextStage: null,
223
+ hasQuestions: false,
224
+ isComplete: true
225
+ };
226
+ }
227
+ /**
228
+ * Validate stage transition is allowed
229
+ */
230
+ function validateStageTransition(currentStage, requestedStage) {
231
+ const validTransitions = {
232
+ 'required': ['basic', 'open'],
233
+ 'basic': ['advanced', 'open'],
234
+ 'advanced': ['open'],
235
+ 'open': []
236
+ };
237
+ if (currentStage === requestedStage) {
238
+ return { valid: true }; // Same stage is always valid
239
+ }
240
+ const allowedNext = validTransitions[currentStage] || [];
241
+ if (!allowedNext.includes(requestedStage)) {
242
+ return {
243
+ valid: false,
244
+ error: `Cannot transition from '${currentStage}' to '${requestedStage}'. Valid options: ${allowedNext.join(', ')}`
245
+ };
246
+ }
247
+ return { valid: true };
248
+ }
249
+ /**
250
+ * Get questions for a specific stage
251
+ */
252
+ function getQuestionsForStage(solution, stage) {
253
+ switch (stage) {
254
+ case 'required':
255
+ return solution.questions.required || [];
256
+ case 'basic':
257
+ return solution.questions.basic || [];
258
+ case 'advanced':
259
+ return solution.questions.advanced || [];
260
+ case 'open':
261
+ return solution.questions.open ? [solution.questions.open] : [];
262
+ default:
263
+ return [];
264
+ }
265
+ }
266
+ /**
267
+ * Get stage-specific message
268
+ */
269
+ function getStageMessage(stage) {
270
+ switch (stage) {
271
+ case 'required':
272
+ return 'Please answer the required configuration questions.';
273
+ case 'basic':
274
+ return 'Would you like to configure basic settings?';
275
+ case 'advanced':
276
+ return 'Would you like to configure advanced features?';
277
+ case 'open':
278
+ return 'Any additional requirements or constraints?';
279
+ default:
280
+ return 'Configuration stage unknown.';
281
+ }
282
+ }
283
+ /**
284
+ * Get stage-specific guidance
285
+ */
286
+ function getStageGuidance(stage) {
287
+ switch (stage) {
288
+ case 'required':
289
+ return 'All required questions must be answered to proceed.';
290
+ case 'basic':
291
+ return 'Answer questions in this stage or skip to proceed to the advanced stage. Do NOT try to generate manifests yet.';
292
+ case 'advanced':
293
+ return 'Answer questions in this stage or skip to proceed to the open stage. Do NOT try to generate manifests yet.';
294
+ case 'open':
295
+ return 'Use "N/A" if you have no additional requirements. Complete this stage before generating manifests. IMPORTANT: This is the final configuration stage - do not skip.';
296
+ default:
297
+ return 'Please provide answers for this stage.';
298
+ }
299
+ }
300
+ /**
301
+ * Phase 1: Analyze what resources are needed for the user request
302
+ */
303
+ async function analyzeResourceNeeds(currentSolution, openResponse, context) {
304
+ const promptPath = path.join(process.cwd(), 'prompts', 'resource-analysis.md');
305
+ const template = fs.readFileSync(promptPath, 'utf8');
306
+ // Get available resources from solution or use defaults
307
+ const availableResources = currentSolution.availableResources || {
308
+ resources: [],
309
+ custom: []
310
+ };
311
+ // Extract resource types for analysis
312
+ const availableResourceTypes = [
313
+ ...(availableResources.resources || []),
314
+ ...(availableResources.custom || [])
315
+ ].map((r) => r.kind || r);
316
+ const analysisPrompt = template
317
+ .replace('{current_solution}', JSON.stringify(currentSolution, null, 2))
318
+ .replace('{user_request}', openResponse)
319
+ .replace('{available_resource_types}', JSON.stringify(availableResourceTypes, null, 2));
320
+ // Initialize Claude integration
321
+ const apiKey = process.env.ANTHROPIC_API_KEY || 'test-key';
322
+ const claudeIntegration = new claude_1.ClaudeIntegration(apiKey);
323
+ context.logger.info('Analyzing resource needs for open question', {
324
+ openResponse,
325
+ availableResourceCount: availableResourceTypes.length
326
+ });
327
+ const response = await claudeIntegration.sendMessage(analysisPrompt);
328
+ return parseEnhancementResponse(response.content);
329
+ }
330
+ /**
331
+ * Phase 2: Apply enhancements based on analysis result
332
+ */
333
+ async function applySolutionEnhancement(solution, openResponse, analysisResult, context) {
334
+ if (analysisResult.approach === 'capability_gap') {
335
+ throw new Error(`Enhancement capability gap: ${analysisResult.reasoning}. ${analysisResult.suggestedAction}`);
336
+ }
337
+ if (analysisResult.approach === 'complete_existing_questions') {
338
+ // Auto-populate existing questions based on user requirements
339
+ context.logger.info('Auto-populating existing questions based on requirements', {
340
+ approach: analysisResult.approach,
341
+ reasoning: analysisResult.reasoning
342
+ });
343
+ return autoPopulateQuestions(solution, openResponse, analysisResult);
344
+ }
345
+ if (analysisResult.approach === 'add_resources') {
346
+ // Add new resources and their questions
347
+ context.logger.info('Adding new resources to solution', {
348
+ approach: analysisResult.approach,
349
+ suggestedResources: analysisResult.suggestedResources
350
+ });
351
+ return addResourcesAndQuestions(solution, openResponse, analysisResult, context);
352
+ }
353
+ // Default: no changes needed
354
+ return solution;
355
+ }
356
+ /**
357
+ * Auto-populate existing questions based on user requirements
358
+ */
359
+ async function autoPopulateQuestions(solution, openResponse, analysisResult) {
360
+ const promptPath = path.join(process.cwd(), 'prompts', 'solution-enhancement.md');
361
+ const template = fs.readFileSync(promptPath, 'utf8');
362
+ const enhancementPrompt = template
363
+ .replace('{current_solution}', JSON.stringify(solution, null, 2))
364
+ .replace('{detailed_schemas}', JSON.stringify(solution.schemas || {}, null, 2))
365
+ .replace('{analysis_result}', JSON.stringify(analysisResult, null, 2))
366
+ .replace('{open_response}', openResponse);
367
+ const apiKey = process.env.ANTHROPIC_API_KEY || 'test-key';
368
+ const claudeIntegration = new claude_1.ClaudeIntegration(apiKey);
369
+ const response = await claudeIntegration.sendMessage(enhancementPrompt);
370
+ const enhancementData = parseEnhancementResponse(response.content);
371
+ if (enhancementData.enhancedSolution) {
372
+ return enhancementData.enhancedSolution;
373
+ }
374
+ return solution;
375
+ }
376
+ /**
377
+ * Add new resources and their questions to the solution
378
+ */
379
+ async function addResourcesAndQuestions(solution, openResponse, analysisResult, context) {
380
+ // For now, implement basic resource addition
381
+ // This would need more sophisticated question generation for new resources
382
+ context.logger.warn('Resource addition not fully implemented yet', {
383
+ suggestedResources: analysisResult.suggestedResources
384
+ });
385
+ // TODO: Implement full resource addition with question generation
386
+ return solution;
387
+ }
388
+ /**
389
+ * Parse AI response (handles both JSON and text responses)
390
+ */
391
+ function parseEnhancementResponse(content) {
392
+ try {
393
+ // Try to extract JSON from response
394
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
395
+ if (jsonMatch) {
396
+ return JSON.parse(jsonMatch[0]);
397
+ }
398
+ // If no JSON found, return error
399
+ throw new Error('No valid JSON found in AI response');
400
+ }
401
+ catch (error) {
402
+ throw new Error(`Failed to parse AI response: ${error}`);
403
+ }
404
+ }
405
+ /**
406
+ * Enhance solution with AI analysis of open question
407
+ */
408
+ async function enhanceSolutionWithOpenAnswer(solution, openAnswer, context) {
409
+ try {
410
+ context.logger.info('Starting AI enhancement of solution', {
411
+ solutionId: solution.solutionId,
412
+ openAnswer
413
+ });
414
+ // Phase 1: Analyze what resources are needed
415
+ const analysisResult = await analyzeResourceNeeds(solution, openAnswer, context);
416
+ // Phase 2: Apply enhancements based on analysis
417
+ const enhancedSolution = await applySolutionEnhancement(solution, openAnswer, analysisResult, context);
418
+ context.logger.info('AI enhancement completed', {
419
+ approach: analysisResult.approach,
420
+ changed: enhancedSolution !== solution
421
+ });
422
+ return enhancedSolution;
423
+ }
424
+ catch (error) {
425
+ context.logger.error('Solution enhancement failed', error);
426
+ throw error;
427
+ }
428
+ }
429
+ /**
430
+ * Direct MCP tool handler for answerQuestion functionality
431
+ */
432
+ async function handleAnswerQuestionTool(args, dotAI, logger, requestId) {
433
+ return await error_handling_1.ErrorHandler.withErrorHandling(async () => {
434
+ logger.debug('Handling answerQuestion request', {
435
+ requestId,
436
+ solutionId: args?.solutionId,
437
+ stage: args?.stage,
438
+ answerCount: Object.keys(args?.answers || {}).length
439
+ });
440
+ // Input validation is handled automatically by MCP SDK with Zod schema
441
+ // args are already validated and typed when we reach this point
442
+ // Get session directory from environment
443
+ let sessionDir;
444
+ try {
445
+ sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false); // requireWrite=false for reading
446
+ logger.debug('Session directory resolved and validated', { sessionDir });
447
+ }
448
+ catch (error) {
449
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, error instanceof Error ? error.message : 'Session directory validation failed', {
450
+ operation: 'session_directory_validation',
451
+ component: 'AnswerQuestionTool',
452
+ requestId,
453
+ suggestedActions: [
454
+ 'Ensure session directory exists and is writable',
455
+ 'Check directory permissions',
456
+ 'Verify the directory path is correct',
457
+ 'Verify DOT_AI_SESSION_DIR environment variable is correctly set'
458
+ ]
459
+ });
460
+ }
461
+ // Load solution file
462
+ let solution;
463
+ try {
464
+ solution = loadSolutionFile(args.solutionId, sessionDir);
465
+ logger.debug('Solution file loaded successfully', {
466
+ solutionId: args.solutionId,
467
+ hasQuestions: !!solution.questions
468
+ });
469
+ }
470
+ catch (error) {
471
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, error instanceof Error ? error.message : 'Failed to load solution file', {
472
+ operation: 'solution_file_loading',
473
+ component: 'AnswerQuestionTool',
474
+ requestId,
475
+ suggestedActions: [
476
+ 'Verify the solution ID exists in the session directory',
477
+ 'Check that the solution file is valid JSON',
478
+ 'Ensure the solution was selected by chooseSolution tool',
479
+ 'List available solution files in the session directory'
480
+ ]
481
+ });
482
+ }
483
+ // Stage-based validation and workflow
484
+ const stageState = getCurrentStage(solution);
485
+ // Validate stage transition
486
+ const transitionResult = validateStageTransition(stageState.currentStage, args.stage);
487
+ if (!transitionResult.valid) {
488
+ const response = {
489
+ status: 'stage_error',
490
+ solutionId: args.solutionId,
491
+ error: 'invalid_transition',
492
+ expected: stageState.currentStage,
493
+ received: args.stage,
494
+ message: transitionResult.error,
495
+ nextStage: stageState.nextStage,
496
+ timestamp: new Date().toISOString()
497
+ };
498
+ return {
499
+ content: [
500
+ {
501
+ type: 'text',
502
+ text: JSON.stringify(response, null, 2)
503
+ }
504
+ ]
505
+ };
506
+ }
507
+ // Validate answers against questions for the requested stage
508
+ const stageQuestions = getQuestionsForStage(solution, args.stage);
509
+ const validationErrors = [];
510
+ for (const [questionId, answer] of Object.entries(args.answers)) {
511
+ // For open stage, handle special case since open question doesn't have 'id' property
512
+ if (args.stage === 'open') {
513
+ // Only allow 'open' as the question ID for open stage
514
+ if (questionId !== 'open') {
515
+ validationErrors.push(`Unknown question ID '${questionId}' for stage '${args.stage}'. Open stage only accepts 'open' as question ID.`);
516
+ continue;
517
+ }
518
+ // Skip further validation for open stage as it doesn't follow Question interface
519
+ continue;
520
+ }
521
+ const question = stageQuestions.find(q => q.id === questionId);
522
+ if (!question) {
523
+ validationErrors.push(`Unknown question ID '${questionId}' for stage '${args.stage}'`);
524
+ continue;
525
+ }
526
+ const error = validateAnswer(answer, question);
527
+ if (error) {
528
+ validationErrors.push(error);
529
+ }
530
+ }
531
+ if (validationErrors.length > 0) {
532
+ const response = {
533
+ status: 'stage_error',
534
+ solutionId: args.solutionId,
535
+ error: 'validation_failed',
536
+ validationErrors,
537
+ currentStage: args.stage,
538
+ message: 'Answer validation failed for stage questions',
539
+ timestamp: new Date().toISOString()
540
+ };
541
+ return {
542
+ content: [
543
+ {
544
+ type: 'text',
545
+ text: JSON.stringify(response, null, 2)
546
+ }
547
+ ]
548
+ };
549
+ }
550
+ // Update solution with answers for the current stage
551
+ if (args.stage === 'open') {
552
+ // Handle open question
553
+ const openAnswer = args.answers.open;
554
+ if (openAnswer && solution.questions.open) {
555
+ solution.questions.open.answer = openAnswer;
556
+ }
557
+ }
558
+ else {
559
+ // Handle structured questions
560
+ for (const [questionId, answer] of Object.entries(args.answers)) {
561
+ const question = stageQuestions.find(q => q.id === questionId);
562
+ if (question) {
563
+ question.answer = answer;
564
+ }
565
+ }
566
+ // If empty answers provided for skippable stage, mark all questions as skipped
567
+ if (Object.keys(args.answers).length === 0 && (args.stage === 'basic' || args.stage === 'advanced')) {
568
+ for (const question of stageQuestions) {
569
+ if (question.answer === undefined) {
570
+ question.answer = null; // Mark as explicitly skipped
571
+ }
572
+ }
573
+ }
574
+ }
575
+ // Save solution with answers
576
+ try {
577
+ saveSolutionFile(solution, args.solutionId, sessionDir);
578
+ logger.info('Solution updated with stage answers', {
579
+ solutionId: args.solutionId,
580
+ stage: args.stage,
581
+ answerCount: Object.keys(args.answers).length
582
+ });
583
+ }
584
+ catch (error) {
585
+ throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.STORAGE, error_handling_1.ErrorSeverity.HIGH, 'Failed to save solution file', {
586
+ operation: 'solution_file_saving',
587
+ component: 'AnswerQuestionTool',
588
+ requestId,
589
+ suggestedActions: [
590
+ 'Check session directory write permissions',
591
+ 'Ensure adequate disk space',
592
+ 'Verify solution JSON is valid'
593
+ ]
594
+ });
595
+ }
596
+ // Handle open stage completion (triggers manifest generation)
597
+ if (args.stage === 'open') {
598
+ const openAnswer = args.answers.open;
599
+ // Enhance solution with AI analysis if open answer provided
600
+ if (openAnswer && openAnswer !== 'N/A') {
601
+ try {
602
+ logger.info('Starting AI enhancement based on open question', {
603
+ solutionId: args.solutionId,
604
+ openAnswer
605
+ });
606
+ solution = await enhanceSolutionWithOpenAnswer(solution, openAnswer, { requestId, logger, dotAI });
607
+ // Save enhanced solution
608
+ saveSolutionFile(solution, args.solutionId, sessionDir);
609
+ logger.info('Enhanced solution saved', {
610
+ solutionId: args.solutionId,
611
+ hasOpenAnswer: !!openAnswer
612
+ });
613
+ }
614
+ catch (error) {
615
+ const errorMessage = error instanceof Error ? error.message : String(error);
616
+ // Check if this is a capability gap error (should fail the entire operation)
617
+ if (errorMessage.includes('Enhancement capability gap') || errorMessage.includes('capability_gap')) {
618
+ logger.error('Capability gap detected, failing operation', error);
619
+ throw error; // Re-throw capability gap errors to fail the entire operation
620
+ }
621
+ // For other errors (AI service issues, parsing errors), continue with original solution
622
+ logger.error('AI enhancement failed due to service issue, continuing with original solution', error);
623
+ }
624
+ }
625
+ // Extract all user answers for handoff
626
+ const userAnswers = {};
627
+ // Extract from all question categories
628
+ const questionCategories = ['required', 'basic', 'advanced'];
629
+ for (const category of questionCategories) {
630
+ const questions = solution.questions[category] || [];
631
+ for (const question of questions) {
632
+ if (question.answer !== undefined && question.answer !== null) {
633
+ userAnswers[question.id] = question.answer;
634
+ }
635
+ }
636
+ }
637
+ // Include open answer if provided
638
+ if (openAnswer) {
639
+ userAnswers.open = openAnswer;
640
+ }
641
+ const response = {
642
+ status: 'ready_for_manifest_generation',
643
+ solutionId: args.solutionId,
644
+ message: `Configuration complete. Ready to generate deployment manifests.`,
645
+ solutionData: {
646
+ primaryResources: solution.resources || [],
647
+ type: solution.type || 'single',
648
+ description: solution.description || '',
649
+ userAnswers: userAnswers,
650
+ hasOpenRequirements: !!(openAnswer && openAnswer !== 'N/A')
651
+ },
652
+ nextAction: 'generateManifests',
653
+ guidance: 'Configuration complete. Ready to generate Kubernetes manifests for deployment.',
654
+ agentInstructions: 'All configuration stages are now complete. You may proceed to generate Kubernetes manifests using the generateManifests tool.',
655
+ timestamp: new Date().toISOString()
656
+ };
657
+ return {
658
+ content: [
659
+ {
660
+ type: 'text',
661
+ text: JSON.stringify(response, null, 2)
662
+ }
663
+ ]
664
+ };
665
+ }
666
+ // Regular stage flow: determine next stage and return questions
667
+ const newStageState = getCurrentStage(solution);
668
+ // If stage is complete, move to next stage
669
+ const nextStageQuestions = getQuestionsForStage(solution, newStageState.currentStage);
670
+ const response = {
671
+ status: 'stage_questions',
672
+ solutionId: args.solutionId,
673
+ currentStage: newStageState.currentStage,
674
+ questions: nextStageQuestions,
675
+ nextStage: newStageState.nextStage,
676
+ message: getStageMessage(newStageState.currentStage),
677
+ guidance: getStageGuidance(newStageState.currentStage),
678
+ agentInstructions: getAgentInstructions(newStageState.currentStage),
679
+ nextAction: 'answerQuestion',
680
+ timestamp: new Date().toISOString()
681
+ };
682
+ return {
683
+ content: [
684
+ {
685
+ type: 'text',
686
+ text: JSON.stringify(response, null, 2)
687
+ }
688
+ ]
689
+ };
690
+ }, {
691
+ operation: 'answer_question',
692
+ component: 'AnswerQuestionTool',
693
+ requestId,
694
+ input: args
695
+ });
696
+ }