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