@build-astron-co/nimbus 0.2.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,720 @@
1
+ import { logger } from '../utils';
2
+ import { LLMRouter } from '../llm/router';
3
+
4
+ // ==========================================
5
+ // Inline Types (from core-engine-service/src/types/agent.ts)
6
+ // ==========================================
7
+
8
+ export interface AgentTask {
9
+ id: string;
10
+ type: 'generate' | 'deploy' | 'verify' | 'rollback' | 'analyze';
11
+ status: 'pending' | 'planning' | 'executing' | 'verifying' | 'completed' | 'failed' | 'cancelled';
12
+ priority: 'low' | 'medium' | 'high' | 'critical';
13
+ user_id: string;
14
+ created_at: Date;
15
+ updated_at: Date;
16
+ completed_at?: Date;
17
+ context: {
18
+ provider: 'aws' | 'gcp' | 'azure';
19
+ environment: string;
20
+ region?: string;
21
+ components: string[];
22
+ requirements?: Record<string, unknown>;
23
+ };
24
+ execution: {
25
+ plan_id?: string;
26
+ execution_id?: string;
27
+ verification_id?: string;
28
+ };
29
+ result?: {
30
+ success: boolean;
31
+ outputs?: Record<string, unknown>;
32
+ artifacts?: string[];
33
+ errors?: string[];
34
+ };
35
+ metadata?: Record<string, unknown>;
36
+ }
37
+
38
+ export interface AgentPlan {
39
+ id: string;
40
+ task_id: string;
41
+ status: 'draft' | 'approved' | 'rejected' | 'executing' | 'completed';
42
+ created_at: Date;
43
+ updated_at: Date;
44
+ steps: PlanStep[];
45
+ dependencies: PlanDependency[];
46
+ estimated_duration?: number;
47
+ estimated_cost?: number;
48
+ risks: Risk[];
49
+ risk_level: 'low' | 'medium' | 'high' | 'critical';
50
+ requires_approval: boolean;
51
+ approved_by?: string;
52
+ approved_at?: Date;
53
+ }
54
+
55
+ export interface PlanStep {
56
+ id: string;
57
+ order: number;
58
+ type: 'generate' | 'validate' | 'deploy' | 'configure' | 'verify';
59
+ description: string;
60
+ component?: string;
61
+ action: string;
62
+ parameters: Record<string, unknown>;
63
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
64
+ started_at?: Date;
65
+ completed_at?: Date;
66
+ duration?: number;
67
+ depends_on?: string[];
68
+ rollback_action?: string;
69
+ rollback_parameters?: Record<string, unknown>;
70
+ }
71
+
72
+ export interface PlanDependency {
73
+ step_id: string;
74
+ depends_on: string[];
75
+ type: 'sequential' | 'parallel';
76
+ }
77
+
78
+ export interface Risk {
79
+ id: string;
80
+ severity: 'low' | 'medium' | 'high' | 'critical';
81
+ category: 'security' | 'cost' | 'availability' | 'performance' | 'compliance';
82
+ description: string;
83
+ mitigation?: string;
84
+ probability: number;
85
+ impact: number;
86
+ }
87
+
88
+ // ==========================================
89
+ // Constants
90
+ // ==========================================
91
+
92
+ /** System prompt instructing the LLM to generate execution steps as JSON. */
93
+ const PLANNING_PROMPT =
94
+ 'You are an infrastructure planning agent. Given the task context, generate an ordered array of execution steps as JSON. ' +
95
+ 'Each step has fields: id (string like step_1), name (string), description (string), type (one of: generate, validate, deploy, configure, verify), ' +
96
+ 'order (number), estimatedDuration (number in seconds). Return ONLY the JSON array, no markdown.';
97
+
98
+ /** System prompt instructing the LLM to assess risks as JSON. */
99
+ const RISK_ASSESSMENT_PROMPT =
100
+ 'You are an infrastructure risk assessor. Given the task and execution steps, identify risks. ' +
101
+ 'Return a JSON array of risks with fields: id (string), severity (low|medium|high|critical), ' +
102
+ 'category (security|cost|availability|performance|compliance), description (string), mitigation (string), ' +
103
+ 'probability (0-1), impact (0-1). Return ONLY the JSON array.';
104
+
105
+ /** Valid step types for plan steps. */
106
+ const VALID_STEP_TYPES = new Set(['generate', 'validate', 'deploy', 'configure', 'verify']);
107
+
108
+ /** Valid severity levels for risks. */
109
+ const VALID_SEVERITIES = new Set(['low', 'medium', 'high', 'critical']);
110
+
111
+ /** Valid risk categories. */
112
+ const VALID_CATEGORIES = new Set(['security', 'cost', 'availability', 'performance', 'compliance']);
113
+
114
+ // ==========================================
115
+ // Planner
116
+ // ==========================================
117
+
118
+ export class Planner {
119
+ private router: LLMRouter;
120
+
121
+ constructor() {
122
+ this.router = new LLMRouter();
123
+ }
124
+
125
+ /**
126
+ * Generate an execution plan for a task
127
+ */
128
+ async generatePlan(task: AgentTask): Promise<AgentPlan> {
129
+ logger.info(`Generating plan for task: ${task.id}`);
130
+
131
+ const steps = await this.generateSteps(task);
132
+ const dependencies = this.analyzeDependencies(steps);
133
+ const risks = await this.assessRisks(task, steps);
134
+ const riskLevel = this.calculateOverallRiskLevel(risks);
135
+
136
+ const plan: AgentPlan = {
137
+ id: this.generatePlanId(),
138
+ task_id: task.id,
139
+ status: 'draft',
140
+ created_at: new Date(),
141
+ updated_at: new Date(),
142
+ steps,
143
+ dependencies,
144
+ risks,
145
+ risk_level: riskLevel,
146
+ requires_approval: riskLevel === 'high' || riskLevel === 'critical',
147
+ };
148
+
149
+ // Estimate duration and cost
150
+ plan.estimated_duration = this.estimateDuration(steps);
151
+ plan.estimated_cost = await this.estimateCost(task, steps);
152
+
153
+ logger.info(`Generated plan ${plan.id} with ${steps.length} steps, risk level: ${riskLevel}`);
154
+
155
+ return plan;
156
+ }
157
+
158
+ /**
159
+ * Generate execution steps for a task.
160
+ * Attempts LLM-based generation first, falls back to heuristic logic.
161
+ */
162
+ private async generateSteps(task: AgentTask): Promise<PlanStep[]> {
163
+ try {
164
+ const llmSteps = await this.generateStepsWithLLM(task);
165
+ if (llmSteps.length > 0) {
166
+ logger.info(`Using LLM-generated steps (${llmSteps.length} steps)`);
167
+ return llmSteps;
168
+ }
169
+ } catch (error) {
170
+ logger.debug(
171
+ `LLM step generation failed, falling back to heuristics: ${(error as Error).message}`
172
+ );
173
+ }
174
+
175
+ return this.generateStepsHeuristic(task);
176
+ }
177
+
178
+ /**
179
+ * Generate steps using the embedded LLM router.
180
+ */
181
+ private async generateStepsWithLLM(task: AgentTask): Promise<PlanStep[]> {
182
+ const response = await this.router.route({
183
+ messages: [
184
+ { role: 'system', content: PLANNING_PROMPT },
185
+ { role: 'user', content: JSON.stringify(task.context) },
186
+ ],
187
+ });
188
+
189
+ const content = response?.content;
190
+ if (!content) {
191
+ throw new Error('LLM response missing content');
192
+ }
193
+
194
+ const parsed: unknown = JSON.parse(content);
195
+
196
+ if (!Array.isArray(parsed) || parsed.length === 0) {
197
+ throw new Error('LLM response is not a non-empty array');
198
+ }
199
+
200
+ const steps: PlanStep[] = [];
201
+ for (const item of parsed) {
202
+ if (
203
+ typeof item !== 'object' ||
204
+ item === null ||
205
+ typeof item.id !== 'string' ||
206
+ typeof item.description !== 'string' ||
207
+ !VALID_STEP_TYPES.has(item.type)
208
+ ) {
209
+ throw new Error('LLM response contains invalid step structure');
210
+ }
211
+
212
+ steps.push({
213
+ id: item.id,
214
+ order: typeof item.order === 'number' ? item.order : steps.length + 1,
215
+ type: item.type as PlanStep['type'],
216
+ description: item.description,
217
+ action: item.type as string,
218
+ parameters: {},
219
+ status: 'pending',
220
+ });
221
+ }
222
+
223
+ return steps;
224
+ }
225
+
226
+ /**
227
+ * Generate execution steps using heuristic logic (fallback).
228
+ */
229
+ private generateStepsHeuristic(task: AgentTask): PlanStep[] {
230
+ const steps: PlanStep[] = [];
231
+ let order = 1;
232
+
233
+ // Step 1: Validate requirements
234
+ steps.push({
235
+ id: `step_${order++}`,
236
+ order: steps.length + 1,
237
+ type: 'validate',
238
+ description: 'Validate infrastructure requirements and constraints',
239
+ action: 'validate_requirements',
240
+ parameters: {
241
+ provider: task.context.provider,
242
+ components: task.context.components,
243
+ requirements: task.context.requirements,
244
+ },
245
+ status: 'pending',
246
+ });
247
+
248
+ // Step 2-N: Generate infrastructure components
249
+ for (const component of task.context.components) {
250
+ steps.push({
251
+ id: `step_${order++}`,
252
+ order: steps.length + 1,
253
+ type: 'generate',
254
+ description: `Generate ${component.toUpperCase()} configuration`,
255
+ component,
256
+ action: 'generate_component',
257
+ parameters: {
258
+ component,
259
+ provider: task.context.provider,
260
+ environment: task.context.environment,
261
+ requirements: task.context.requirements,
262
+ },
263
+ status: 'pending',
264
+ depends_on: ['step_1'], // Depends on validation
265
+ });
266
+ }
267
+
268
+ // Step: Validate generated code
269
+ steps.push({
270
+ id: `step_${order++}`,
271
+ order: steps.length + 1,
272
+ type: 'validate',
273
+ description: 'Validate generated infrastructure code',
274
+ action: 'validate_generated_code',
275
+ parameters: {
276
+ components: task.context.components,
277
+ },
278
+ status: 'pending',
279
+ depends_on: steps.slice(1, -1).map(s => s.id), // Depends on all generation steps
280
+ });
281
+
282
+ // Step: Apply best practices
283
+ steps.push({
284
+ id: `step_${order++}`,
285
+ order: steps.length + 1,
286
+ type: 'validate',
287
+ description: 'Apply security and best practices',
288
+ action: 'apply_best_practices',
289
+ parameters: {
290
+ components: task.context.components,
291
+ autofix: true,
292
+ },
293
+ status: 'pending',
294
+ depends_on: [steps[steps.length - 1].id],
295
+ });
296
+
297
+ // If deployment is requested
298
+ if (task.type === 'deploy') {
299
+ // Step: Plan deployment
300
+ steps.push({
301
+ id: `step_${order++}`,
302
+ order: steps.length + 1,
303
+ type: 'deploy',
304
+ description: 'Plan infrastructure deployment (terraform plan)',
305
+ action: 'plan_deployment',
306
+ parameters: {
307
+ provider: task.context.provider,
308
+ environment: task.context.environment,
309
+ },
310
+ status: 'pending',
311
+ depends_on: [steps[steps.length - 1].id],
312
+ });
313
+
314
+ // Step: Apply deployment
315
+ steps.push({
316
+ id: `step_${order++}`,
317
+ order: steps.length + 1,
318
+ type: 'deploy',
319
+ description: 'Apply infrastructure deployment (terraform apply)',
320
+ action: 'apply_deployment',
321
+ parameters: {
322
+ provider: task.context.provider,
323
+ environment: task.context.environment,
324
+ auto_approve: false,
325
+ },
326
+ status: 'pending',
327
+ depends_on: [steps[steps.length - 1].id],
328
+ rollback_action: 'destroy_deployment',
329
+ rollback_parameters: {
330
+ provider: task.context.provider,
331
+ environment: task.context.environment,
332
+ },
333
+ });
334
+
335
+ // Step: Verify deployment
336
+ steps.push({
337
+ id: `step_${order++}`,
338
+ order: steps.length + 1,
339
+ type: 'verify',
340
+ description: 'Verify deployed infrastructure',
341
+ action: 'verify_deployment',
342
+ parameters: {
343
+ components: task.context.components,
344
+ environment: task.context.environment,
345
+ },
346
+ status: 'pending',
347
+ depends_on: [steps[steps.length - 1].id],
348
+ });
349
+ }
350
+
351
+ // Final step: Generate documentation
352
+ steps.push({
353
+ id: `step_${order++}`,
354
+ order: steps.length + 1,
355
+ type: 'generate',
356
+ description: 'Generate infrastructure documentation',
357
+ action: 'generate_documentation',
358
+ parameters: {
359
+ components: task.context.components,
360
+ include_diagrams: true,
361
+ },
362
+ status: 'pending',
363
+ depends_on: [steps[steps.length - 1].id],
364
+ });
365
+
366
+ return steps;
367
+ }
368
+
369
+ /**
370
+ * Analyze dependencies between steps
371
+ */
372
+ private analyzeDependencies(steps: PlanStep[]): PlanDependency[] {
373
+ return steps
374
+ .filter(step => step.depends_on && step.depends_on.length > 0)
375
+ .map(step => ({
376
+ step_id: step.id,
377
+ depends_on: step.depends_on!,
378
+ type: (step.depends_on!.length === 1 ? 'sequential' : 'parallel') as
379
+ | 'sequential'
380
+ | 'parallel',
381
+ }));
382
+ }
383
+
384
+ /**
385
+ * Assess risks for the plan.
386
+ * Attempts LLM-based assessment first, falls back to heuristic logic.
387
+ */
388
+ private async assessRisks(task: AgentTask, steps: PlanStep[]): Promise<Risk[]> {
389
+ try {
390
+ const llmRisks = await this.assessRisksWithLLM(task, steps);
391
+ if (llmRisks.length > 0) {
392
+ logger.info(`Using LLM-assessed risks (${llmRisks.length} risks)`);
393
+ return llmRisks;
394
+ }
395
+ } catch (error) {
396
+ logger.debug(
397
+ `LLM risk assessment failed, falling back to heuristics: ${(error as Error).message}`
398
+ );
399
+ }
400
+
401
+ return this.assessRisksHeuristic(task, steps);
402
+ }
403
+
404
+ /**
405
+ * Assess risks using the embedded LLM router.
406
+ */
407
+ private async assessRisksWithLLM(task: AgentTask, steps: PlanStep[]): Promise<Risk[]> {
408
+ const response = await this.router.route({
409
+ messages: [
410
+ { role: 'system', content: RISK_ASSESSMENT_PROMPT },
411
+ {
412
+ role: 'user',
413
+ content: JSON.stringify({
414
+ task: task.context,
415
+ steps: steps.map(s => ({ id: s.id, type: s.type, description: s.description })),
416
+ }),
417
+ },
418
+ ],
419
+ });
420
+
421
+ const content = response?.content;
422
+ if (!content) {
423
+ throw new Error('LLM response missing content');
424
+ }
425
+
426
+ const parsed: unknown = JSON.parse(content);
427
+
428
+ if (!Array.isArray(parsed) || parsed.length === 0) {
429
+ throw new Error('LLM response is not a non-empty array');
430
+ }
431
+
432
+ const risks: Risk[] = [];
433
+ for (const item of parsed) {
434
+ if (
435
+ typeof item !== 'object' ||
436
+ item === null ||
437
+ typeof item.id !== 'string' ||
438
+ typeof item.description !== 'string' ||
439
+ !VALID_SEVERITIES.has(item.severity) ||
440
+ !VALID_CATEGORIES.has(item.category) ||
441
+ typeof item.probability !== 'number' ||
442
+ typeof item.impact !== 'number'
443
+ ) {
444
+ throw new Error('LLM response contains invalid risk structure');
445
+ }
446
+
447
+ risks.push({
448
+ id: item.id,
449
+ severity: item.severity as Risk['severity'],
450
+ category: item.category as Risk['category'],
451
+ description: item.description,
452
+ mitigation: typeof item.mitigation === 'string' ? item.mitigation : undefined,
453
+ probability: item.probability,
454
+ impact: item.impact,
455
+ });
456
+ }
457
+
458
+ return risks;
459
+ }
460
+
461
+ /**
462
+ * Assess risks using heuristic logic (fallback).
463
+ */
464
+ private assessRisksHeuristic(task: AgentTask, steps: PlanStep[]): Risk[] {
465
+ const risks: Risk[] = [];
466
+
467
+ // Security risks
468
+ if (task.context.environment === 'production') {
469
+ risks.push({
470
+ id: 'risk_prod_deploy',
471
+ severity: 'high',
472
+ category: 'availability',
473
+ description: 'Deploying to production environment',
474
+ mitigation: 'Requires approval, automated testing, and gradual rollout',
475
+ probability: 0.3,
476
+ impact: 0.8,
477
+ });
478
+ }
479
+
480
+ // Cost risks
481
+ const hasExpensiveComponents = task.context.components.some(c => ['eks', 'rds'].includes(c));
482
+ if (hasExpensiveComponents) {
483
+ risks.push({
484
+ id: 'risk_high_cost',
485
+ severity: 'medium',
486
+ category: 'cost',
487
+ description: 'Infrastructure includes high-cost components',
488
+ mitigation: 'Review instance types and enable autoscaling',
489
+ probability: 0.6,
490
+ impact: 0.5,
491
+ });
492
+ }
493
+
494
+ // Compliance risks
495
+ if (task.context.components.includes('s3')) {
496
+ risks.push({
497
+ id: 'risk_data_security',
498
+ severity: 'high',
499
+ category: 'security',
500
+ description: 'Storage component requires encryption and access controls',
501
+ mitigation: 'Enable encryption at rest and in transit, implement least privilege access',
502
+ probability: 0.4,
503
+ impact: 0.9,
504
+ });
505
+ }
506
+
507
+ // Deployment risks
508
+ const hasDeploymentSteps = steps.some(s => s.type === 'deploy');
509
+ if (hasDeploymentSteps && !task.context.requirements?.backup_enabled) {
510
+ risks.push({
511
+ id: 'risk_no_backup',
512
+ severity: 'high',
513
+ category: 'availability',
514
+ description: 'No backup strategy defined',
515
+ mitigation: 'Enable automated backups and test restoration procedures',
516
+ probability: 0.5,
517
+ impact: 0.7,
518
+ });
519
+ }
520
+
521
+ return risks;
522
+ }
523
+
524
+ /**
525
+ * Calculate overall risk level
526
+ */
527
+ private calculateOverallRiskLevel(risks: Risk[]): 'low' | 'medium' | 'high' | 'critical' {
528
+ if (risks.length === 0) {
529
+ return 'low';
530
+ }
531
+
532
+ const hasCritical = risks.some(r => r.severity === 'critical');
533
+ if (hasCritical) {
534
+ return 'critical';
535
+ }
536
+
537
+ const highRisks = risks.filter(r => r.severity === 'high');
538
+ if (highRisks.length >= 2) {
539
+ return 'high';
540
+ }
541
+ if (highRisks.length === 1) {
542
+ return 'high';
543
+ }
544
+
545
+ const mediumRisks = risks.filter(r => r.severity === 'medium');
546
+ if (mediumRisks.length >= 3) {
547
+ return 'high';
548
+ }
549
+ if (mediumRisks.length >= 1) {
550
+ return 'medium';
551
+ }
552
+
553
+ return 'low';
554
+ }
555
+
556
+ /**
557
+ * Estimate duration in seconds
558
+ */
559
+ private estimateDuration(steps: PlanStep[]): number {
560
+ const durations: Record<string, number> = {
561
+ validate: 30,
562
+ generate: 60,
563
+ deploy: 600, // 10 minutes for deployment
564
+ verify: 120,
565
+ };
566
+
567
+ return steps.reduce((total, step) => {
568
+ return total + (durations[step.type] || 60);
569
+ }, 0);
570
+ }
571
+
572
+ /**
573
+ * Estimate cost in USD
574
+ */
575
+ private async estimateCost(task: AgentTask, _steps: PlanStep[]): Promise<number> {
576
+ let monthlyCost = 0;
577
+
578
+ const componentCosts: Record<string, number> = {
579
+ vpc: 0, // VPC itself is free, NAT gateway costs ~$32/month
580
+ eks: 73, // $0.10/hour * 730 hours
581
+ rds: 50, // t3.micro ~$15/month + storage
582
+ s3: 5, // Minimal storage estimate
583
+ };
584
+
585
+ for (const component of task.context.components) {
586
+ monthlyCost += componentCosts[component] || 0;
587
+ }
588
+
589
+ // Add NAT gateway cost if VPC is included
590
+ if (task.context.components.includes('vpc')) {
591
+ monthlyCost += 32;
592
+ }
593
+
594
+ return Math.round(monthlyCost);
595
+ }
596
+
597
+ /**
598
+ * Optimize plan for parallel execution
599
+ */
600
+ optimizePlan(plan: AgentPlan): AgentPlan {
601
+ // Identify steps that can run in parallel
602
+ const optimized = { ...plan };
603
+
604
+ // Group independent generation steps
605
+ const generationSteps = plan.steps.filter(s => s.type === 'generate' && s.component);
606
+
607
+ // Mark independent steps as parallelizable
608
+ for (let i = 0; i < generationSteps.length; i++) {
609
+ const step = generationSteps[i];
610
+ // If steps don't have interdependencies, they can run in parallel
611
+ if (!this.hasInterdependency(step, generationSteps, i)) {
612
+ step.parameters.parallel_group = 'generation';
613
+ }
614
+ }
615
+
616
+ return optimized;
617
+ }
618
+
619
+ /**
620
+ * Check if step has interdependency with others
621
+ */
622
+ private hasInterdependency(step: PlanStep, steps: PlanStep[], _index: number): boolean {
623
+ // Check if this step's output is needed by another step in the group
624
+ // Simplified: assume VPC must be created before EKS/RDS
625
+ if (step.component === 'vpc') {
626
+ return false;
627
+ }
628
+ if (step.component === 'eks' || step.component === 'rds') {
629
+ return steps.some(s => s.component === 'vpc');
630
+ }
631
+ return false;
632
+ }
633
+
634
+ /**
635
+ * Validate plan is executable
636
+ */
637
+ validatePlan(plan: AgentPlan): { valid: boolean; errors: string[] } {
638
+ const errors: string[] = [];
639
+
640
+ // Check for circular dependencies
641
+ if (this.hasCircularDependencies(plan)) {
642
+ errors.push('Plan contains circular dependencies');
643
+ }
644
+
645
+ // Check all dependencies exist
646
+ const stepIds = new Set(plan.steps.map(s => s.id));
647
+ for (const step of plan.steps) {
648
+ if (step.depends_on) {
649
+ for (const depId of step.depends_on) {
650
+ if (!stepIds.has(depId)) {
651
+ errors.push(`Step ${step.id} depends on non-existent step ${depId}`);
652
+ }
653
+ }
654
+ }
655
+ }
656
+
657
+ // Check step order matches dependencies
658
+ for (const step of plan.steps) {
659
+ if (step.depends_on) {
660
+ for (const depId of step.depends_on) {
661
+ const depStep = plan.steps.find(s => s.id === depId);
662
+ if (depStep && depStep.order >= step.order) {
663
+ errors.push(`Step ${step.id} has invalid order relative to dependency ${depId}`);
664
+ }
665
+ }
666
+ }
667
+ }
668
+
669
+ return {
670
+ valid: errors.length === 0,
671
+ errors,
672
+ };
673
+ }
674
+
675
+ /**
676
+ * Check for circular dependencies
677
+ */
678
+ private hasCircularDependencies(plan: AgentPlan): boolean {
679
+ const visited = new Set<string>();
680
+ const recursionStack = new Set<string>();
681
+
682
+ const hasCycle = (stepId: string): boolean => {
683
+ visited.add(stepId);
684
+ recursionStack.add(stepId);
685
+
686
+ const step = plan.steps.find(s => s.id === stepId);
687
+ if (step?.depends_on) {
688
+ for (const depId of step.depends_on) {
689
+ if (!visited.has(depId)) {
690
+ if (hasCycle(depId)) {
691
+ return true;
692
+ }
693
+ } else if (recursionStack.has(depId)) {
694
+ return true;
695
+ }
696
+ }
697
+ }
698
+
699
+ recursionStack.delete(stepId);
700
+ return false;
701
+ };
702
+
703
+ for (const step of plan.steps) {
704
+ if (!visited.has(step.id)) {
705
+ if (hasCycle(step.id)) {
706
+ return true;
707
+ }
708
+ }
709
+ }
710
+
711
+ return false;
712
+ }
713
+
714
+ /**
715
+ * Generate plan ID
716
+ */
717
+ private generatePlanId(): string {
718
+ return `plan_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
719
+ }
720
+ }