@vfarcic/dot-ai 1.23.1 → 1.24.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 (63) hide show
  1. package/dist/core/index.d.ts +0 -5
  2. package/dist/core/index.d.ts.map +1 -1
  3. package/dist/core/index.js +2 -11
  4. package/dist/core/knowledge-migration.d.ts +30 -0
  5. package/dist/core/knowledge-migration.d.ts.map +1 -0
  6. package/dist/core/knowledge-migration.js +289 -0
  7. package/dist/core/knowledge-service.d.ts +46 -0
  8. package/dist/core/knowledge-service.d.ts.map +1 -0
  9. package/dist/core/knowledge-service.js +185 -0
  10. package/dist/core/knowledge-types.d.ts +14 -0
  11. package/dist/core/knowledge-types.d.ts.map +1 -1
  12. package/dist/core/schema.d.ts +13 -12
  13. package/dist/core/schema.d.ts.map +1 -1
  14. package/dist/core/schema.js +105 -170
  15. package/dist/core/user-prompts-loader.d.ts.map +1 -1
  16. package/dist/core/user-prompts-loader.js +13 -6
  17. package/dist/interfaces/rest-api.d.ts.map +1 -1
  18. package/dist/interfaces/rest-api.js +7 -6
  19. package/dist/mcp/server.js +12 -0
  20. package/dist/tools/manage-knowledge.d.ts +0 -22
  21. package/dist/tools/manage-knowledge.d.ts.map +1 -1
  22. package/dist/tools/manage-knowledge.js +71 -131
  23. package/dist/tools/operate-analysis.d.ts.map +1 -1
  24. package/dist/tools/operate-analysis.js +7 -6
  25. package/dist/tools/operate.d.ts +23 -11
  26. package/dist/tools/operate.d.ts.map +1 -1
  27. package/dist/tools/operate.js +40 -58
  28. package/dist/tools/organizational-data.d.ts +5 -8
  29. package/dist/tools/organizational-data.d.ts.map +1 -1
  30. package/dist/tools/organizational-data.js +19 -74
  31. package/dist/tools/query.d.ts +17 -0
  32. package/dist/tools/query.d.ts.map +1 -1
  33. package/dist/tools/query.js +36 -1
  34. package/dist/tools/version.d.ts +1 -12
  35. package/dist/tools/version.d.ts.map +1 -1
  36. package/dist/tools/version.js +10 -19
  37. package/package.json +2 -2
  38. package/prompts/knowledge-classification.md +24 -0
  39. package/prompts/operate-user.md +7 -8
  40. package/dist/core/organizational-types.d.ts +0 -43
  41. package/dist/core/organizational-types.d.ts.map +0 -1
  42. package/dist/core/organizational-types.js +0 -8
  43. package/dist/core/pattern-operations.d.ts +0 -50
  44. package/dist/core/pattern-operations.d.ts.map +0 -1
  45. package/dist/core/pattern-operations.js +0 -445
  46. package/dist/core/pattern-types.d.ts +0 -8
  47. package/dist/core/pattern-types.d.ts.map +0 -1
  48. package/dist/core/pattern-types.js +0 -8
  49. package/dist/core/pattern-vector-service.d.ts +0 -27
  50. package/dist/core/pattern-vector-service.d.ts.map +0 -1
  51. package/dist/core/pattern-vector-service.js +0 -65
  52. package/dist/core/policy-operations.d.ts +0 -136
  53. package/dist/core/policy-operations.d.ts.map +0 -1
  54. package/dist/core/policy-operations.js +0 -623
  55. package/dist/core/policy-vector-service.d.ts +0 -27
  56. package/dist/core/policy-vector-service.d.ts.map +0 -1
  57. package/dist/core/policy-vector-service.js +0 -64
  58. package/dist/core/unified-creation-session.d.ts +0 -88
  59. package/dist/core/unified-creation-session.d.ts.map +0 -1
  60. package/dist/core/unified-creation-session.js +0 -948
  61. package/dist/core/unified-creation-types.d.ts +0 -68
  62. package/dist/core/unified-creation-types.d.ts.map +0 -1
  63. package/dist/core/unified-creation-types.js +0 -61
@@ -1,948 +0,0 @@
1
- "use strict";
2
- /**
3
- * Unified Creation Session Manager
4
- *
5
- * Handles step-by-step creation workflow for both patterns and policies
6
- * with context-aware questions and AI-powered trigger expansion.
7
- * Loads prompts from markdown files following CLAUDE.md guidelines.
8
- */
9
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- var desc = Object.getOwnPropertyDescriptor(m, k);
12
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
- desc = { enumerable: true, get: function() { return m[k]; } };
14
- }
15
- Object.defineProperty(o, k2, desc);
16
- }) : (function(o, m, k, k2) {
17
- if (k2 === undefined) k2 = k;
18
- o[k2] = m[k];
19
- }));
20
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
- Object.defineProperty(o, "default", { enumerable: true, value: v });
22
- }) : function(o, v) {
23
- o["default"] = v;
24
- });
25
- var __importStar = (this && this.__importStar) || (function () {
26
- var ownKeys = function(o) {
27
- ownKeys = Object.getOwnPropertyNames || function (o) {
28
- var ar = [];
29
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
- return ar;
31
- };
32
- return ownKeys(o);
33
- };
34
- return function (mod) {
35
- if (mod && mod.__esModule) return mod;
36
- var result = {};
37
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
- __setModuleDefault(result, mod);
39
- return result;
40
- };
41
- })();
42
- Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.UnifiedCreationSessionManager = void 0;
44
- const fs = __importStar(require("fs"));
45
- const path = __importStar(require("path"));
46
- const crypto_1 = require("crypto");
47
- const generic_session_manager_1 = require("./generic-session-manager");
48
- const session_utils_1 = require("./session-utils");
49
- const shared_prompt_loader_1 = require("./shared-prompt-loader");
50
- const capability_vector_service_1 = require("./capability-vector-service");
51
- const discovery_1 = require("./discovery");
52
- const schema_1 = require("./schema");
53
- const version_1 = require("../tools/version");
54
- const platform_utils_1 = require("./platform-utils");
55
- const plugin_registry_1 = require("./plugin-registry");
56
- const yaml = __importStar(require("js-yaml"));
57
- const unified_creation_types_1 = require("./unified-creation-types");
58
- const pattern_operations_1 = require("./pattern-operations");
59
- class UnifiedCreationSessionManager {
60
- config;
61
- discovery;
62
- sessionManager;
63
- /**
64
- * PRD #359: Uses unified plugin registry for kubectl operations
65
- */
66
- constructor(entityType, discovery) {
67
- if (!(0, plugin_registry_1.isPluginInitialized)()) {
68
- throw new Error('Plugin system not available. UnifiedCreationSessionManager requires agentic-tools plugin for kubectl operations.');
69
- }
70
- this.config = unified_creation_types_1.WORKFLOW_CONFIGS[entityType];
71
- this.discovery = discovery || new discovery_1.KubernetesDiscovery();
72
- this.sessionManager = new generic_session_manager_1.GenericSessionManager(entityType);
73
- }
74
- /**
75
- * Create a new creation session
76
- */
77
- createSession(args) {
78
- const sessionData = {
79
- entityType: this.config.entityType,
80
- currentStep: this.config.steps[0], // Start with first step
81
- // Store capabilities collection if provided (for policy testing with pre-populated data)
82
- capabilitiesCollection: args.collection
83
- };
84
- return this.sessionManager.createSession(sessionData);
85
- }
86
- /**
87
- * Load existing session
88
- */
89
- loadSession(sessionId) {
90
- return this.sessionManager.getSession(sessionId);
91
- }
92
- /**
93
- * Process user response and advance session
94
- */
95
- processResponse(sessionId, response) {
96
- const session = this.loadSession(sessionId);
97
- if (!session) {
98
- throw new Error(`${this.config.displayName} session ${sessionId} not found`);
99
- }
100
- // Process response based on current step
101
- switch (session.data.currentStep) {
102
- case 'description':
103
- session.data.description = response.trim();
104
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('description', this.config);
105
- break;
106
- case 'triggers':
107
- session.data.initialTriggers = response.split(',').map(t => t.trim()).filter(t => t.length > 0);
108
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('triggers', this.config);
109
- break;
110
- case 'trigger-expansion':
111
- // Parse JSON response for confirmed triggers
112
- try {
113
- const confirmed = JSON.parse(response);
114
- session.data.expandedTriggers = confirmed;
115
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('trigger-expansion', this.config);
116
- }
117
- catch {
118
- // If not JSON, treat as comma-separated list
119
- session.data.expandedTriggers = response.split(',').map(t => t.trim()).filter(t => t.length > 0);
120
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('trigger-expansion', this.config);
121
- }
122
- break;
123
- case 'resources':
124
- if (this.config.entityType === 'pattern') {
125
- session.data.suggestedResources = response.split(',').map(r => r.trim()).filter(r => r.length > 0);
126
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('resources', this.config);
127
- }
128
- break;
129
- case 'rationale':
130
- session.data.rationale = response.trim();
131
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('rationale', this.config);
132
- break;
133
- case 'created-by':
134
- session.data.createdBy = response.trim();
135
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('created-by', this.config);
136
- break;
137
- case 'namespace-scope': {
138
- // Parse user's namespace selection
139
- const scopeChoice = response.trim().toLowerCase();
140
- if (scopeChoice === 'all' || scopeChoice === '1') {
141
- session.data.namespaceScope = { type: 'all' };
142
- }
143
- else if (scopeChoice.startsWith('include:')) {
144
- const namespaces = scopeChoice.replace('include:', '').split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
145
- session.data.namespaceScope = { type: 'include', namespaces };
146
- }
147
- else if (scopeChoice.startsWith('exclude:')) {
148
- const namespaces = scopeChoice.replace('exclude:', '').split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
149
- session.data.namespaceScope = { type: 'exclude', namespaces };
150
- }
151
- else {
152
- // Treat any other input as namespace names to include (e.g., "policy-test" or "ns1, ns2")
153
- const namespaces = scopeChoice.split(',').map(ns => ns.trim()).filter(ns => ns.length > 0);
154
- if (namespaces.length > 0) {
155
- session.data.namespaceScope = { type: 'include', namespaces };
156
- }
157
- }
158
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('namespace-scope', this.config);
159
- break;
160
- }
161
- case 'kyverno-generation':
162
- // Kyverno generation completed, store result in session
163
- if (response.startsWith('ERROR:')) {
164
- session.data.kyvernoGenerationError = response;
165
- session.data.generatedKyvernoPolicy = undefined;
166
- }
167
- else {
168
- session.data.generatedKyvernoPolicy = response;
169
- session.data.kyvernoGenerationError = undefined;
170
- }
171
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('kyverno-generation', this.config);
172
- break;
173
- case 'review':
174
- // Handle review step based on entity type
175
- if (this.config.entityType === 'policy') {
176
- // For policies, user provided deployment choice during review (semantic response)
177
- const deploymentChoice = response.trim();
178
- if (deploymentChoice === 'apply-to-cluster') {
179
- session.data.deploymentChoice = 'apply';
180
- }
181
- else if (deploymentChoice === 'store-intent-only') {
182
- session.data.deploymentChoice = 'policy-only';
183
- }
184
- else {
185
- // Handle cancel or any other response as discard
186
- session.data.deploymentChoice = 'discard';
187
- }
188
- session.data.currentStep = 'complete';
189
- }
190
- else {
191
- // For patterns, user confirmed review
192
- session.data.currentStep = 'complete';
193
- }
194
- break;
195
- default:
196
- throw new Error(`Unknown step: ${session.data.currentStep}`);
197
- }
198
- session.updatedAt = new Date().toISOString();
199
- this.sessionManager.replaceSession(session.sessionId, session.data);
200
- return session;
201
- }
202
- /**
203
- * Generate next workflow step
204
- */
205
- async getNextWorkflowStep(session, args) {
206
- const sessionId = session.sessionId;
207
- switch (session.data.currentStep) {
208
- case 'description':
209
- return {
210
- sessionId,
211
- entityType: this.config.entityType,
212
- prompt: (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-description`),
213
- instruction: `Wait for the user to provide a description of the ${this.config.displayName.toLowerCase()}. Once received, call this tool again with their response.`,
214
- nextStep: (0, unified_creation_types_1.getNextStep)('description', this.config) || undefined
215
- };
216
- case 'triggers':
217
- return {
218
- sessionId,
219
- entityType: this.config.entityType,
220
- prompt: (0, shared_prompt_loader_1.loadPrompt)('infrastructure-triggers'),
221
- instruction: 'Wait for the user to provide infrastructure type keywords separated by commas. Once received, call this tool again with their response.',
222
- nextStep: (0, unified_creation_types_1.getNextStep)('triggers', this.config) || undefined
223
- };
224
- case 'trigger-expansion':
225
- return await this.generateTriggerExpansionStep(session, args?.interaction_id);
226
- case 'resources':
227
- if (this.config.entityType === 'pattern') {
228
- return {
229
- sessionId,
230
- entityType: this.config.entityType,
231
- prompt: (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-resources`, { description: session.data.description || '' }),
232
- instruction: 'Wait for the user to provide Kubernetes resource types. Once received, call this tool again with their comma-separated response.',
233
- nextStep: (0, unified_creation_types_1.getNextStep)('resources', this.config) || undefined
234
- };
235
- }
236
- // If not pattern, skip to next step
237
- return this.getNextWorkflowStep({ ...session, data: { ...session.data, currentStep: (0, unified_creation_types_1.getNextStep)('resources', this.config) } }, args);
238
- case 'rationale':
239
- return {
240
- sessionId,
241
- entityType: this.config.entityType,
242
- prompt: (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-rationale`, { description: session.data.description || '' }),
243
- instruction: `Wait for the user to provide the rationale. Once received, call this tool again with their response.`,
244
- nextStep: (0, unified_creation_types_1.getNextStep)('rationale', this.config) || undefined
245
- };
246
- case 'created-by':
247
- return {
248
- sessionId,
249
- entityType: this.config.entityType,
250
- prompt: (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-created-by`),
251
- instruction: 'Wait for the user to provide creator information. Once received, call this tool again with their response.',
252
- nextStep: (0, unified_creation_types_1.getNextStep)('created-by', this.config) || undefined
253
- };
254
- case 'namespace-scope': {
255
- // Check if Kyverno is installed - only show namespace options if it is
256
- const kyvernoStatus = await (0, version_1.getKyvernoStatusViaPlugin)();
257
- if (!kyvernoStatus.installed) {
258
- // Skip namespace-scope if Kyverno not installed, go to next step
259
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('namespace-scope', this.config);
260
- return this.getNextWorkflowStep(session, args);
261
- }
262
- // Get actual namespaces from cluster via plugin
263
- const namespaces = await this.discovery.getNamespaces();
264
- const prompt = (0, shared_prompt_loader_1.loadPrompt)('policy-namespace-scope', {
265
- namespaces: namespaces.join(', ')
266
- });
267
- return {
268
- sessionId,
269
- entityType: this.config.entityType,
270
- prompt,
271
- instruction: 'Ask user to select namespace scope. Options: "all" for cluster-wide, "include: ns1,ns2" for specific namespaces, "exclude: ns1,ns2" to exclude namespaces.',
272
- nextStep: (0, unified_creation_types_1.getNextStep)('namespace-scope', this.config) || undefined,
273
- data: { availableNamespaces: namespaces }
274
- };
275
- }
276
- case 'kyverno-generation':
277
- return await this.generateKyvernoStep(session, args);
278
- case 'review':
279
- return this.generateReviewStep(session);
280
- case 'complete':
281
- return await this.completeWorkflow(session);
282
- default:
283
- throw new Error(`Unknown step: ${session.data.currentStep}`);
284
- }
285
- }
286
- /**
287
- * Generate trigger expansion step with AI suggestions
288
- */
289
- async generateTriggerExpansionStep(session, interaction_id) {
290
- const description = session.data.description || '';
291
- const initialTriggers = session.data.initialTriggers || [];
292
- try {
293
- // Generate expanded triggers internally using AI
294
- const expandedTriggers = await this.generateInternalTriggerExpansion(initialTriggers, description, interaction_id);
295
- // Combine initial and expanded triggers into full list
296
- const fullTriggerList = [...initialTriggers, ...expandedTriggers];
297
- if (expandedTriggers.length === 0) {
298
- // No expansions found, skip to next step with original triggers
299
- return {
300
- sessionId: session.sessionId,
301
- entityType: this.config.entityType,
302
- instruction: `No additional infrastructure types were found. Continue to the next step by calling this tool again with the original triggers: "${initialTriggers.join(', ')}"`,
303
- nextStep: (0, unified_creation_types_1.getNextStep)('trigger-expansion', this.config) || undefined,
304
- data: { fullTriggerList: initialTriggers }
305
- };
306
- }
307
- // Present full trigger list to user for selection
308
- return {
309
- sessionId: session.sessionId,
310
- entityType: this.config.entityType,
311
- instruction: `Present this complete list of infrastructure types: "${fullTriggerList.join(', ')}". Ask the user to select which ones they want to keep (they can choose any combination or add their own custom triggers). Return their final selection as a comma-separated list.`,
312
- nextStep: (0, unified_creation_types_1.getNextStep)('trigger-expansion', this.config) || undefined,
313
- data: { fullTriggerList }
314
- };
315
- }
316
- catch (error) {
317
- console.warn('Failed to generate trigger expansion, using original triggers:', error);
318
- // Fallback: continue with original triggers only
319
- return {
320
- sessionId: session.sessionId,
321
- entityType: this.config.entityType,
322
- instruction: `Unable to generate additional infrastructure type suggestions. Continue to the next step by calling this tool again with the original triggers: "${initialTriggers.join(', ')}"`,
323
- nextStep: (0, unified_creation_types_1.getNextStep)('trigger-expansion', this.config) || undefined,
324
- data: { fullTriggerList: initialTriggers }
325
- };
326
- }
327
- }
328
- /**
329
- * Generate trigger expansion using internal AI
330
- */
331
- async generateInternalTriggerExpansion(initialTriggers, description, interaction_id) {
332
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- Dynamic require to avoid circular dependency
333
- const { createAIProvider } = require('./ai-provider-factory');
334
- const aiProvider = createAIProvider();
335
- if (!aiProvider.isInitialized()) {
336
- console.warn('AI provider not available for trigger expansion');
337
- return [];
338
- }
339
- const prompt = (0, shared_prompt_loader_1.loadPrompt)('infrastructure-trigger-expansion', {
340
- initialTriggers: initialTriggers.join(', '),
341
- description
342
- });
343
- try {
344
- const response = await aiProvider.sendMessage(prompt, `${this.config.entityType}-trigger-expansion`, {
345
- user_intent: `Create organizational ${this.config.entityType} for ${initialTriggers.join(', ') || 'unspecified domain'}`,
346
- interaction_id: interaction_id || 'trigger_expansion'
347
- });
348
- const expandedText = response.content.trim();
349
- if (!expandedText || expandedText.toLowerCase().includes('no relevant') || expandedText.toLowerCase().includes('no additional')) {
350
- return [];
351
- }
352
- // Parse comma-separated response and clean up
353
- const expanded = expandedText
354
- .split(',')
355
- .map((trigger) => trigger.trim())
356
- .filter((trigger) => trigger.length > 0)
357
- .filter((trigger) => !initialTriggers.some((initial) => initial.toLowerCase() === trigger.toLowerCase()));
358
- return expanded;
359
- }
360
- catch (error) {
361
- console.warn('Error in trigger expansion AI call:', error);
362
- return [];
363
- }
364
- }
365
- /**
366
- * Generate review step showing all collected data
367
- */
368
- generateReviewStep(session) {
369
- const data = session.data;
370
- const finalTriggers = data.expandedTriggers || data.initialTriggers || [];
371
- const templateData = {
372
- description: data.description,
373
- triggers: finalTriggers.join(', '),
374
- rationale: data.rationale,
375
- createdBy: data.createdBy
376
- };
377
- if (this.config.entityType === 'pattern') {
378
- templateData.resources = data.suggestedResources?.join(', ') || 'None specified';
379
- const prompt = (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-review`, templateData);
380
- return {
381
- sessionId: session.sessionId,
382
- entityType: this.config.entityType,
383
- prompt,
384
- instruction: `Present the ${this.config.displayName.toLowerCase()} information for user review. Wait for their confirmation. If they say 'yes' or 'looks good' or similar, call this tool again with 'confirmed'. If they want to make changes, handle the corrections and then call this tool again with 'confirmed' when ready.`,
385
- nextStep: 'complete',
386
- data: session.data
387
- };
388
- }
389
- else {
390
- // Policy review - build prompt conditionally based on Kyverno status
391
- let prompt;
392
- let instruction;
393
- const baseReview = `Please review your policy intent:
394
-
395
- **Description**: ${data.description}
396
- **Triggers**: ${finalTriggers.join(', ')}
397
- **Rationale**: ${data.rationale}
398
- **Created By**: ${data.createdBy}
399
-
400
- `;
401
- if (data.kyvernoGenerationSkipped) {
402
- prompt = baseReview + `**Kyverno Generation**: Skipped (${data.kyvernoSkipReason})
403
-
404
- **Choose what to do:**
405
-
406
- 1. **Store policy intent only** - Save for AI guidance without cluster enforcement
407
- 2. **Cancel** - Do nothing`;
408
- instruction = `Present the policy intent with explanation that Kyverno generation was skipped (${data.kyvernoSkipReason}). Show these numbered options: "1. Store policy intent only (for AI guidance)", "2. Cancel (do nothing)". If user chooses option 1, call this tool again with "store-intent-only". If user chooses option 2 (cancel), do not call this tool again.`;
409
- }
410
- else {
411
- prompt = baseReview + `I've also generated a Kyverno ClusterPolicy that enforces this requirement:
412
-
413
- **Generated Kyverno Policy**:
414
- \`\`\`yaml
415
- ${data.generatedKyvernoPolicy || ''}
416
- \`\`\`
417
-
418
- **Choose what to do:**
419
-
420
- 1. **Apply Kyverno policy to cluster** - Store policy intent AND deploy enforcement to cluster
421
- 2. **Store policy intent only** - Save for AI guidance without cluster enforcement
422
- 3. **Cancel** - Do nothing
423
-
424
- ⚠️ **Warning**: Option 1 will deploy active policy enforcement to your cluster.`;
425
- instruction = `Present the policy intent and display the complete generated Kyverno policy YAML manifest for user review. Show these numbered options: "1. Apply Kyverno policy to cluster", "2. Store policy intent only (don't apply)", "3. Cancel (do nothing)". If user chooses option 1, call this tool again with "apply-to-cluster". If user chooses option 2, call this tool again with "store-intent-only". If user chooses option 3 (cancel), do not call this tool again.`;
426
- }
427
- return {
428
- sessionId: session.sessionId,
429
- entityType: this.config.entityType,
430
- prompt,
431
- instruction,
432
- nextStep: (0, unified_creation_types_1.getNextStep)('review', this.config) || undefined,
433
- data: session.data
434
- };
435
- }
436
- }
437
- /**
438
- * Complete the workflow and create the entity
439
- */
440
- async completeWorkflow(session) {
441
- try {
442
- const finalTriggers = session.data.expandedTriggers || session.data.initialTriggers || [];
443
- if (this.config.entityType === 'pattern') {
444
- // Create pattern internally in MCP
445
- const request = {
446
- description: session.data.description,
447
- triggers: finalTriggers,
448
- suggestedResources: session.data.suggestedResources,
449
- rationale: session.data.rationale,
450
- createdBy: session.data.createdBy
451
- };
452
- const pattern = (0, pattern_operations_1.createPattern)(request);
453
- // Return success message to client (not prompt to process)
454
- return {
455
- sessionId: session.sessionId,
456
- entityType: this.config.entityType,
457
- instruction: `**Pattern Created Successfully!**
458
-
459
- **Pattern ID**: ${pattern.id}
460
- **Description**: ${pattern.description}
461
- **Triggers**: ${pattern.triggers.join(', ')}
462
- **Resources**: ${pattern.suggestedResources.join(', ')}
463
-
464
- The pattern is now ready to enhance AI recommendations. When users ask for deployments matching your triggers, this pattern will suggest the specified Kubernetes resources.`,
465
- data: { pattern }
466
- };
467
- }
468
- else {
469
- // Policy creation with deployment choice handling
470
- const deploymentChoice = session.data.deploymentChoice || 'policy-only';
471
- // Handle discard choice early
472
- if (deploymentChoice === 'discard') {
473
- const prompt = (0, shared_prompt_loader_1.loadPrompt)('policy-complete-discard', {
474
- description: session.data.description || 'Unknown policy'
475
- });
476
- return {
477
- sessionId: session.sessionId,
478
- entityType: this.config.entityType,
479
- instruction: prompt,
480
- data: { discarded: true }
481
- };
482
- }
483
- // Create policy intent using the consistent ID generated during kyverno-generation step
484
- if (!session.data.policyId) {
485
- // In test environment, generate ID if missing (for mocked workflows)
486
- const isTestEnv = process.env.NODE_ENV === 'test';
487
- if (isTestEnv) {
488
- session.data.policyId = (0, crypto_1.randomUUID)();
489
- }
490
- else {
491
- throw new Error('Policy ID missing from session - this indicates a workflow error');
492
- }
493
- }
494
- const policy = {
495
- id: session.data.policyId,
496
- description: session.data.description,
497
- triggers: finalTriggers,
498
- rationale: session.data.rationale,
499
- createdAt: new Date().toISOString(),
500
- createdBy: session.data.createdBy,
501
- deployedPolicies: []
502
- };
503
- // Handle different deployment choices
504
- return await this.handlePolicyDeploymentChoice(session, policy, deploymentChoice);
505
- }
506
- }
507
- catch (error) {
508
- const errorPrompt = (0, shared_prompt_loader_1.loadPrompt)(`${this.config.entityType}-complete-error`, {
509
- error: error instanceof Error ? error.message : String(error)
510
- });
511
- return {
512
- sessionId: session.sessionId,
513
- entityType: this.config.entityType,
514
- instruction: errorPrompt,
515
- data: { error: error instanceof Error ? error.message : String(error) }
516
- };
517
- }
518
- }
519
- /**
520
- * Handle policy deployment choice (apply to cluster or store intent only)
521
- */
522
- async handlePolicyDeploymentChoice(session, policy, deploymentChoice) {
523
- const generatedKyvernoPolicy = session.data.generatedKyvernoPolicy;
524
- const kyvernoSkipped = session.data.kyvernoGenerationSkipped;
525
- // If Kyverno generation was skipped, only allow intent-only storage
526
- if (kyvernoSkipped && !generatedKyvernoPolicy) {
527
- if (deploymentChoice.trim() === '1' || deploymentChoice.toLowerCase() === 'apply-to-cluster') {
528
- throw new Error('Cannot apply to cluster: Kyverno generation was skipped because Kyverno is not available');
529
- }
530
- // Force intent-only storage when Kyverno is not available
531
- deploymentChoice = 'store-intent-only';
532
- }
533
- else if (!generatedKyvernoPolicy) {
534
- throw new Error('No Kyverno policy generated');
535
- }
536
- if (deploymentChoice.trim() === '1' || deploymentChoice.toLowerCase() === 'apply') {
537
- // Save Kyverno YAML to file for debugging and apply using existing DeployOperation
538
- const fs = await Promise.resolve().then(() => __importStar(require('fs')));
539
- const path = await Promise.resolve().then(() => __importStar(require('path')));
540
- const sessionDir = path.join(process.cwd(), 'tmp', 'sessions', 'policy-sessions');
541
- const kyvernoFileName = `${policy.id}-kyverno.yaml`;
542
- const kyvernoFilePath = path.join(sessionDir, kyvernoFileName);
543
- // Ensure directory exists
544
- fs.mkdirSync(sessionDir, { recursive: true });
545
- // Save Kyverno policy to file
546
- fs.writeFileSync(kyvernoFilePath, generatedKyvernoPolicy, 'utf8');
547
- // Apply to cluster using existing DeployOperation
548
- // PRD #359: Uses unified plugin registry for kubectl operations
549
- try {
550
- const { DeployOperation } = await Promise.resolve().then(() => __importStar(require('./deploy-operation')));
551
- const deployOp = new DeployOperation();
552
- const deployResult = await deployOp.deploy({
553
- solutionId: `${policy.id}-kyverno`,
554
- sessionDir,
555
- timeout: 30
556
- });
557
- // Track successful deployment
558
- policy.deployedPolicies = [{
559
- name: `policy-${policy.id}`,
560
- appliedAt: new Date().toISOString()
561
- }];
562
- return {
563
- sessionId: session.sessionId,
564
- entityType: this.config.entityType,
565
- instruction: `**Policy Applied to Cluster Successfully!**
566
-
567
- **Policy ID**: ${policy.id}
568
- **Description**: ${policy.description}
569
- **Triggers**: ${policy.triggers.join(', ')}
570
- **Rationale**: ${policy.rationale}
571
- **Created By**: ${policy.createdBy}
572
- **Deployed Policy**: ${policy.deployedPolicies[0].name}
573
- **Kyverno File**: ${kyvernoFilePath}
574
-
575
- The policy intent has been stored in the database and the Kyverno policy has been applied to your cluster.
576
-
577
- ${deployResult.kubectlOutput}`,
578
- data: { policy, kyvernoPolicy: generatedKyvernoPolicy, applied: true, kyvernoFile: kyvernoFilePath }
579
- };
580
- }
581
- catch (deployError) {
582
- const errorMessage = deployError instanceof Error ? deployError.message : String(deployError);
583
- return {
584
- sessionId: session.sessionId,
585
- entityType: this.config.entityType,
586
- instruction: `**Policy Application Failed!**
587
-
588
- **Policy ID**: ${policy.id}
589
- **Description**: ${policy.description}
590
- **Error**: ${errorMessage}
591
- **Kyverno File**: ${kyvernoFilePath}
592
-
593
- The policy intent has been stored in the database, but the Kyverno policy could not be applied to the cluster. You can manually apply it using:
594
- \`kubectl apply -f ${kyvernoFilePath}\``,
595
- data: { policy, kyvernoPolicy: generatedKyvernoPolicy, applied: false, error: errorMessage, kyvernoFile: kyvernoFilePath }
596
- };
597
- }
598
- }
599
- else {
600
- // Store only the policy intent, no Kyverno deployment
601
- const skipReason = session.data.kyvernoSkipReason;
602
- const instruction = kyvernoSkipped ?
603
- `**Policy Intent Stored Successfully!**
604
-
605
- **Policy ID**: ${policy.id}
606
- **Description**: ${policy.description}
607
- **Triggers**: ${policy.triggers.join(', ')}
608
- **Rationale**: ${policy.rationale}
609
- **Created By**: ${policy.createdBy}
610
-
611
- The policy intent has been stored in the database for AI guidance.
612
- **Note**: ${skipReason}` :
613
- `**Policy Intent Stored Successfully!**
614
-
615
- **Policy ID**: ${policy.id}
616
- **Description**: ${policy.description}
617
- **Triggers**: ${policy.triggers.join(', ')}
618
- **Rationale**: ${policy.rationale}
619
- **Created By**: ${policy.createdBy}
620
-
621
- The policy intent has been stored in the database. The Kyverno policy was not applied to the cluster.`;
622
- return {
623
- sessionId: session.sessionId,
624
- entityType: this.config.entityType,
625
- instruction,
626
- data: { policy, kyvernoPolicy: generatedKyvernoPolicy, applied: false, kyvernoSkipped }
627
- };
628
- }
629
- }
630
- /**
631
- * Validate YAML syntax
632
- */
633
- validateYamlSyntax(yamlContent) {
634
- try {
635
- yaml.loadAll(yamlContent);
636
- return { valid: true };
637
- }
638
- catch (error) {
639
- return {
640
- valid: false,
641
- error: error instanceof Error ? error.message : 'Unknown YAML syntax error'
642
- };
643
- }
644
- }
645
- /**
646
- * Validate Kyverno policy using multi-layer approach
647
- */
648
- async validateKyvernoPolicy(yamlPath) {
649
- // First check if file exists
650
- if (!fs.existsSync(yamlPath)) {
651
- return {
652
- valid: false,
653
- errors: [`Kyverno policy file not found: ${yamlPath}`],
654
- warnings: []
655
- };
656
- }
657
- // Read YAML content for syntax validation
658
- const yamlContent = fs.readFileSync(yamlPath, 'utf8');
659
- // 1. YAML syntax validation
660
- const syntaxCheck = this.validateYamlSyntax(yamlContent);
661
- if (!syntaxCheck.valid) {
662
- return {
663
- valid: false,
664
- errors: [`YAML syntax error: ${syntaxCheck.error}`],
665
- warnings: []
666
- };
667
- }
668
- // 2. kubectl dry-run validation using ManifestValidator
669
- // PRD #359: Uses unified plugin registry for kubectl operations
670
- try {
671
- const validator = new schema_1.ManifestValidator();
672
- const result = await validator.validateManifest(yamlPath, { dryRunMode: 'server' });
673
- return result;
674
- }
675
- catch (error) {
676
- return {
677
- valid: false,
678
- errors: [`Validation error: ${error instanceof Error ? error.message : String(error)}`],
679
- warnings: []
680
- };
681
- }
682
- }
683
- /**
684
- * Generate Kyverno policy step - automatically generates policy from intent data with validation loop
685
- */
686
- async generateKyvernoStep(session, args) {
687
- // Check if Kyverno is available before attempting policy generation
688
- const kyvernoStatus = await (0, version_1.getKyvernoStatusViaPlugin)();
689
- if (!kyvernoStatus.policyGenerationReady) {
690
- // Skip Kyverno generation and go directly to review with intent-only option
691
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('kyverno-generation', this.config);
692
- session.data.kyvernoGenerationSkipped = true;
693
- session.data.kyvernoSkipReason = kyvernoStatus.reason || 'Kyverno not available for policy generation';
694
- // Generate policy ID since we skipped the normal generation step
695
- session.data.policyId = (0, crypto_1.randomUUID)();
696
- // Save session and proceed to review
697
- if (args) {
698
- this.sessionManager.replaceSession(session.sessionId, session.data);
699
- }
700
- return this.generateReviewStep(session);
701
- }
702
- const data = session.data;
703
- const finalTriggers = data.expandedTriggers || data.initialTriggers || [];
704
- const maxAttempts = 5;
705
- let lastError;
706
- try {
707
- // Retrieve actual resource schemas using semantic search and discovery via plugin
708
- // Use capabilities collection from session data if provided (for testing with pre-populated data)
709
- const resourceSchemas = await this.retrieveRelevantSchemas(data.description || '', finalTriggers, data.capabilitiesCollection);
710
- // Prepare session directory for YAML saving
711
- const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(true);
712
- const policySessionDir = path.join(sessionDir, 'policy-sessions');
713
- if (!fs.existsSync(policySessionDir)) {
714
- fs.mkdirSync(policySessionDir, { recursive: true });
715
- }
716
- // Generate policy ID once and store in session for consistency
717
- if (!session.data.policyId) {
718
- session.data.policyId = (0, crypto_1.randomUUID)();
719
- }
720
- // AI generation and validation loop (like generate-manifests tool)
721
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
722
- try {
723
- // Prepare template data using consistent policy ID
724
- const templateData = {
725
- policy_description: data.description || '',
726
- policy_rationale: data.rationale || '',
727
- policy_triggers: finalTriggers.join(', '),
728
- policy_id: session.data.policyId,
729
- resource_schemas: this.formatSchemasForPrompt(resourceSchemas),
730
- namespace_scope: this.formatNamespaceScope(session.data.namespaceScope),
731
- previous_attempt: lastError ? `\n### Previous Generated Policy:\n\`\`\`yaml\n${lastError.previousPolicy}\n\`\`\`` : 'None - this is the first attempt.',
732
- error_details: lastError ? `\n**Attempt**: ${lastError.attempt}\n**Validation Errors**: ${lastError.validationResult.errors.join(', ')}\n**Validation Warnings**: ${lastError.validationResult.warnings.join(', ')}` : 'None - this is the first attempt.'
733
- };
734
- const prompt = (0, shared_prompt_loader_1.loadPrompt)('kyverno-generation', templateData);
735
- // Call AI provider internally to generate Kyverno policy
736
- // eslint-disable-next-line @typescript-eslint/no-require-imports -- Dynamic require to avoid circular dependency
737
- const { createAIProvider } = require('./ai-provider-factory');
738
- const aiProvider = createAIProvider();
739
- const response = await aiProvider.sendMessage(prompt, 'policy-kyverno-generation', {
740
- user_intent: data.description ? `Create Kyverno policy: ${data.description}` : `Generate Kyverno policy for ${finalTriggers.join(', ')}`,
741
- interaction_id: args?.interaction_id || 'kyverno_generation'
742
- });
743
- // Response should be clean YAML with analysis comments
744
- // Extract YAML from code blocks if wrapped (using shared utility)
745
- const kyvernoPolicy = (0, platform_utils_1.extractContentFromMarkdownCodeBlocks)(response.content, 'yaml');
746
- // Save policy to file immediately after generation
747
- const yamlPath = path.join(policySessionDir, `${session.sessionId}-kyverno.yaml`);
748
- fs.writeFileSync(yamlPath, kyvernoPolicy, 'utf8');
749
- // Save a copy of this attempt for debugging
750
- const attemptPath = yamlPath.replace('.yaml', `_attempt_${attempt.toString().padStart(2, '0')}.yaml`);
751
- fs.writeFileSync(attemptPath, kyvernoPolicy, 'utf8');
752
- // Validate policy using kubectl dry-run
753
- const validation = await this.validateKyvernoPolicy(yamlPath);
754
- if (validation.valid) {
755
- // Success! Store the validated policy in session data
756
- session.data.generatedKyvernoPolicy = kyvernoPolicy;
757
- // DEBUG_DOT_AI logging
758
- if (process.env.DEBUG_DOT_AI === 'true') {
759
- console.debug('Generated and validated Kyverno policy', {
760
- attempt,
761
- promptLength: prompt.length,
762
- schemaCount: Object.keys(resourceSchemas).length,
763
- policyLength: kyvernoPolicy.length,
764
- yamlPath
765
- });
766
- }
767
- // Skip display step and go directly to review
768
- session.data.currentStep = (0, unified_creation_types_1.getNextStep)('kyverno-generation', this.config);
769
- // Save session immediately after generating Kyverno policy AND updating the step
770
- if (args) {
771
- this.sessionManager.replaceSession(session.sessionId, session.data);
772
- }
773
- return this.getNextWorkflowStep(session, args);
774
- }
775
- // Validation failed, prepare error context for next attempt
776
- lastError = {
777
- attempt,
778
- previousPolicy: kyvernoPolicy,
779
- validationResult: validation
780
- };
781
- console.warn(`Kyverno policy validation failed on attempt ${attempt}/${maxAttempts}:`, {
782
- errors: validation.errors,
783
- warnings: validation.warnings
784
- });
785
- }
786
- catch (error) {
787
- console.error(`Error during Kyverno policy generation attempt ${attempt}:`, error);
788
- // If this is the last attempt, throw the error
789
- if (attempt === maxAttempts) {
790
- throw error;
791
- }
792
- // Prepare error context for retry
793
- lastError = {
794
- attempt,
795
- previousPolicy: lastError?.previousPolicy || '',
796
- validationResult: {
797
- valid: false,
798
- errors: [error instanceof Error ? error.message : String(error)],
799
- warnings: []
800
- }
801
- };
802
- }
803
- }
804
- // If we reach here, all attempts failed
805
- const errorMessage = `Failed to generate valid Kyverno policy after ${maxAttempts} attempts. Last errors: ${lastError?.validationResult.errors.join(', ')}`;
806
- session.data.kyvernoGenerationError = errorMessage;
807
- return {
808
- sessionId: session.sessionId,
809
- entityType: this.config.entityType,
810
- prompt: `**Error Generating Kyverno Policy**
811
-
812
- ${errorMessage}
813
-
814
- Please try again or modify your policy description.`,
815
- instruction: 'Kyverno policy generation failed after multiple attempts. You can ask for modifications to the policy intent or try again.',
816
- nextStep: (0, unified_creation_types_1.getNextStep)('kyverno-generation', this.config) || undefined,
817
- data: { kyvernoGenerationError: errorMessage }
818
- };
819
- }
820
- catch (error) {
821
- // Store error for later handling
822
- session.data.kyvernoGenerationError = error instanceof Error ? error.message : String(error);
823
- return {
824
- sessionId: session.sessionId,
825
- entityType: this.config.entityType,
826
- prompt: `**Error Generating Kyverno Policy**
827
-
828
- Failed to generate Kyverno policy: ${session.data.kyvernoGenerationError}
829
-
830
- Please try again or modify your policy description.`,
831
- instruction: 'Kyverno policy generation failed. You can ask for modifications to the policy intent or try again.',
832
- nextStep: (0, unified_creation_types_1.getNextStep)('kyverno-generation', this.config) || undefined,
833
- data: { kyvernoGenerationError: session.data.kyvernoGenerationError }
834
- };
835
- }
836
- }
837
- /**
838
- * Retrieve relevant schemas for Kyverno generation using semantic search
839
- */
840
- async retrieveRelevantSchemas(policyDescription, triggers, collection) {
841
- // Combine policy description with triggers for enhanced search
842
- const searchQuery = [policyDescription, ...triggers].join(' ');
843
- console.info('Performing semantic search for relevant capabilities', {
844
- searchQuery,
845
- triggerCount: triggers.length,
846
- collection: collection || 'capabilities'
847
- });
848
- const capabilityService = new capability_vector_service_1.CapabilityVectorService(collection);
849
- // Use existing searchCapabilities function - no fallback, let it throw if it fails
850
- const searchResults = await capabilityService.searchCapabilities(searchQuery, {
851
- limit: 40 // Haiku max: 180K tokens (HTML escaping fix reduced prompt size by ~38%)
852
- });
853
- if (searchResults.length === 0) {
854
- throw new Error(`No relevant capabilities found for policy description: "${policyDescription}"`);
855
- }
856
- // Filter out Kyverno CRDs - we don't need Kyverno's own CRD schemas to generate policies
857
- // Kyverno CRDs are massive (Policy = 52KB) and irrelevant for policy generation
858
- const filteredResults = searchResults.filter(result => {
859
- const resourceName = result.data.resourceName;
860
- const group = result.data.group || '';
861
- // Check the API group for kyverno.io (handles all Kyverno CRDs)
862
- const isKyvernoCRD = group.includes('kyverno.io') || resourceName.includes('.kyverno.io');
863
- if (isKyvernoCRD) {
864
- console.debug('Filtering out Kyverno CRD from schema retrieval', { resourceName, group });
865
- }
866
- return !isKyvernoCRD;
867
- });
868
- console.info('Filtered Kyverno CRDs from results', {
869
- originalCount: searchResults.length,
870
- filteredCount: filteredResults.length,
871
- kyvernoCRDsRemoved: searchResults.length - filteredResults.length
872
- });
873
- console.info('Semantic search completed', {
874
- resultsCount: searchResults.length,
875
- topScore: searchResults[0]?.score
876
- });
877
- // Retrieve schemas for relevant resources
878
- console.info('Retrieving schemas for relevant resources', {
879
- resourceCount: filteredResults.length,
880
- resources: filteredResults.map(r => r.data.resourceName)
881
- });
882
- const schemas = {};
883
- // Retrieve schema for each relevant resource using existing pattern from generate-manifests.ts
884
- for (const result of filteredResults) {
885
- const resourceName = result.data.resourceName;
886
- try {
887
- console.debug('Retrieving schema for resource', {
888
- resourceName,
889
- score: result.score
890
- });
891
- // Use discovery engine to explain the resource - no fallback, let it throw if it fails
892
- const explanation = await this.discovery.explainResource(resourceName);
893
- schemas[resourceName] = {
894
- resourceName,
895
- score: result.score,
896
- capabilities: result.data.capabilities,
897
- schema: explanation,
898
- timestamp: new Date().toISOString()
899
- };
900
- console.debug('Schema retrieved successfully', {
901
- resourceName,
902
- schemaLength: explanation.length
903
- });
904
- }
905
- catch (error) {
906
- console.warn('Skipping resource schema (not available in cluster)', error, {
907
- resourceName,
908
- error: error instanceof Error ? error.message : String(error)
909
- });
910
- // Skip resources that don't exist in the cluster instead of failing
911
- // This allows policy generation to work across different Kubernetes versions
912
- continue;
913
- }
914
- }
915
- console.info('All resource schemas retrieved successfully', {
916
- schemaCount: Object.keys(schemas).length
917
- });
918
- return schemas;
919
- }
920
- /**
921
- * Format namespace scope for inclusion in the Kyverno generation prompt
922
- */
923
- formatNamespaceScope(scope) {
924
- if (!scope || scope.type === 'all') {
925
- return 'Apply to all namespaces (no restrictions)';
926
- }
927
- else if (scope.type === 'include') {
928
- return `Apply ONLY to these namespaces: ${scope.namespaces?.join(', ')}`;
929
- }
930
- else {
931
- return `Apply to all namespaces EXCEPT: ${scope.namespaces?.join(', ')}`;
932
- }
933
- }
934
- /**
935
- * Format schemas for inclusion in the Kyverno generation prompt
936
- */
937
- formatSchemasForPrompt(resourceSchemas) {
938
- return Object.entries(resourceSchemas)
939
- .map(([resourceName, schemaData]) => {
940
- return `${resourceName} (Score: ${schemaData.score?.toFixed(2) || 'N/A'}):
941
- ${schemaData.schema}
942
-
943
- `;
944
- })
945
- .join('\n');
946
- }
947
- }
948
- exports.UnifiedCreationSessionManager = UnifiedCreationSessionManager;