@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.
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +51 -0
- package/dist/core/claude.d.ts +42 -0
- package/dist/core/claude.d.ts.map +1 -0
- package/dist/core/claude.js +229 -0
- package/dist/core/deploy-operation.d.ts +38 -0
- package/dist/core/deploy-operation.d.ts.map +1 -0
- package/dist/core/deploy-operation.js +101 -0
- package/dist/core/discovery.d.ts +162 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +758 -0
- package/dist/core/error-handling.d.ts +167 -0
- package/dist/core/error-handling.d.ts.map +1 -0
- package/dist/core/error-handling.js +399 -0
- package/dist/core/index.d.ts +42 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +123 -0
- package/dist/core/kubernetes-utils.d.ts +38 -0
- package/dist/core/kubernetes-utils.d.ts.map +1 -0
- package/dist/core/kubernetes-utils.js +177 -0
- package/dist/core/memory.d.ts +45 -0
- package/dist/core/memory.d.ts.map +1 -0
- package/dist/core/memory.js +113 -0
- package/dist/core/schema.d.ts +187 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +655 -0
- package/dist/core/session-utils.d.ts +29 -0
- package/dist/core/session-utils.d.ts.map +1 -0
- package/dist/core/session-utils.js +121 -0
- package/dist/core/workflow.d.ts +70 -0
- package/dist/core/workflow.d.ts.map +1 -0
- package/dist/core/workflow.js +161 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/interfaces/cli.d.ts +74 -0
- package/dist/interfaces/cli.d.ts.map +1 -0
- package/dist/interfaces/cli.js +769 -0
- package/dist/interfaces/mcp.d.ts +30 -0
- package/dist/interfaces/mcp.d.ts.map +1 -0
- package/dist/interfaces/mcp.js +105 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +151 -0
- package/dist/tools/answer-question.d.ts +27 -0
- package/dist/tools/answer-question.d.ts.map +1 -0
- package/dist/tools/answer-question.js +696 -0
- package/dist/tools/choose-solution.d.ts +23 -0
- package/dist/tools/choose-solution.d.ts.map +1 -0
- package/dist/tools/choose-solution.js +171 -0
- package/dist/tools/deploy-manifests.d.ts +25 -0
- package/dist/tools/deploy-manifests.d.ts.map +1 -0
- package/dist/tools/deploy-manifests.js +74 -0
- package/dist/tools/generate-manifests.d.ts +23 -0
- package/dist/tools/generate-manifests.d.ts.map +1 -0
- package/dist/tools/generate-manifests.js +424 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/recommend.d.ts +23 -0
- package/dist/tools/recommend.d.ts.map +1 -0
- package/dist/tools/recommend.js +332 -0
- package/package.json +124 -0
- package/prompts/intent-validation.md +65 -0
- package/prompts/manifest-generation.md +79 -0
- package/prompts/question-generation.md +128 -0
- package/prompts/resource-analysis.md +127 -0
- package/prompts/resource-selection.md +55 -0
- package/prompts/resource-solution-ranking.md +77 -0
- 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
|
+
}
|