@build-astron-co/nimbus 0.4.1 → 0.4.3

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 (435) hide show
  1. package/CHANGELOG.md +268 -89
  2. package/README.md +26 -567
  3. package/dist/src/agent/compaction-agent.js +24 -12
  4. package/dist/src/agent/context-manager.js +2 -1
  5. package/dist/src/agent/expand-files.js +2 -1
  6. package/dist/src/agent/loop.js +71 -33
  7. package/dist/src/agent/permissions.js +4 -2
  8. package/dist/src/agent/system-prompt.js +34 -17
  9. package/dist/src/app.js +1 -1
  10. package/dist/src/auth/keychain.js +8 -4
  11. package/dist/src/auth/store.js +70 -107
  12. package/dist/src/cli/init.js +35 -19
  13. package/dist/src/cli/run.js +18 -10
  14. package/dist/src/cli/serve.js +4 -2
  15. package/dist/src/cli.js +52 -11
  16. package/dist/src/commands/alias.js +5 -3
  17. package/dist/src/commands/audit/index.js +2 -1
  18. package/dist/src/commands/aws-terraform.js +36 -18
  19. package/dist/src/commands/completions.js +1 -1
  20. package/dist/src/commands/config.js +3 -2
  21. package/dist/src/commands/connect-github.js +92 -0
  22. package/dist/src/commands/cost/index.js +3 -2
  23. package/dist/src/commands/deploy.js +15 -10
  24. package/dist/src/commands/doctor.js +9 -6
  25. package/dist/src/commands/drift/index.js +2 -1
  26. package/dist/src/commands/export.js +5 -3
  27. package/dist/src/commands/generate-terraform.js +110 -2
  28. package/dist/src/commands/import.js +3 -3
  29. package/dist/src/commands/incident.js +10 -5
  30. package/dist/src/commands/login.js +8 -93
  31. package/dist/src/commands/logs.js +16 -8
  32. package/dist/src/commands/onboarding.js +6 -4
  33. package/dist/src/commands/pipeline.js +6 -3
  34. package/dist/src/commands/plugin.js +3 -2
  35. package/dist/src/commands/profile.js +27 -14
  36. package/dist/src/commands/questionnaire.js +1 -1
  37. package/dist/src/commands/rollback.js +3 -2
  38. package/dist/src/commands/rollout.js +5 -3
  39. package/dist/src/commands/runbook.js +17 -10
  40. package/dist/src/commands/schedule.js +10 -5
  41. package/dist/src/commands/status.js +2 -1
  42. package/dist/src/commands/team-context.js +12 -7
  43. package/dist/src/commands/template.js +1 -1
  44. package/dist/src/commands/tf/index.js +6 -3
  45. package/dist/src/commands/upgrade.js +5 -3
  46. package/dist/src/commands/version.js +6 -3
  47. package/dist/src/commands/watch.js +6 -3
  48. package/dist/src/compat/sqlite.js +5 -3
  49. package/dist/src/config/mode-store.js +2 -1
  50. package/dist/src/config/profiles.js +4 -2
  51. package/dist/src/config/types.js +2 -1
  52. package/dist/src/engine/executor.js +8 -4
  53. package/dist/src/engine/planner.js +9 -5
  54. package/dist/src/llm/providers/anthropic.js +6 -3
  55. package/dist/src/llm/providers/ollama.js +1 -1
  56. package/dist/src/llm/router.js +22 -7
  57. package/dist/src/nimbus.js +1 -0
  58. package/dist/src/sessions/manager.js +6 -3
  59. package/dist/src/sharing/viewer.js +2 -1
  60. package/dist/src/tools/file-ops.js +1 -2
  61. package/dist/src/tools/schemas/devops.js +197 -108
  62. package/dist/src/tools/schemas/standard.js +1 -1
  63. package/dist/src/ui/App.js +25 -13
  64. package/dist/src/ui/FileDiffModal.js +22 -11
  65. package/dist/src/ui/HelpModal.js +2 -1
  66. package/dist/src/ui/InputBox.js +6 -3
  67. package/dist/src/ui/MessageList.js +40 -20
  68. package/dist/src/ui/TerminalPane.js +2 -1
  69. package/dist/src/ui/ToolCallDisplay.js +12 -6
  70. package/dist/src/ui/TreePane.js +2 -1
  71. package/dist/src/ui/ink/index.js +37 -21
  72. package/dist/src/version.js +1 -1
  73. package/dist/src/watcher/index.js +8 -4
  74. package/package.json +3 -5
  75. package/src/__tests__/alias.test.ts +0 -133
  76. package/src/__tests__/app.test.ts +0 -76
  77. package/src/__tests__/audit.test.ts +0 -877
  78. package/src/__tests__/circuit-breaker.test.ts +0 -116
  79. package/src/__tests__/cli-run.test.ts +0 -351
  80. package/src/__tests__/compat-sqlite.test.ts +0 -68
  81. package/src/__tests__/context-manager.test.ts +0 -632
  82. package/src/__tests__/context.test.ts +0 -242
  83. package/src/__tests__/devops-terminal-gaps.test.ts +0 -718
  84. package/src/__tests__/doctor.test.ts +0 -48
  85. package/src/__tests__/enterprise.test.ts +0 -401
  86. package/src/__tests__/export.test.ts +0 -236
  87. package/src/__tests__/gap-11-18-20.test.ts +0 -958
  88. package/src/__tests__/generator.test.ts +0 -433
  89. package/src/__tests__/helm-streaming.test.ts +0 -127
  90. package/src/__tests__/hooks.test.ts +0 -582
  91. package/src/__tests__/incident.test.ts +0 -179
  92. package/src/__tests__/init.test.ts +0 -487
  93. package/src/__tests__/intent-parser.test.ts +0 -229
  94. package/src/__tests__/llm-router.test.ts +0 -209
  95. package/src/__tests__/logs.test.ts +0 -107
  96. package/src/__tests__/loop-errors.test.ts +0 -244
  97. package/src/__tests__/lsp.test.ts +0 -293
  98. package/src/__tests__/modes.test.ts +0 -336
  99. package/src/__tests__/perf-optimizations.test.ts +0 -847
  100. package/src/__tests__/permissions.test.ts +0 -338
  101. package/src/__tests__/pipeline.test.ts +0 -50
  102. package/src/__tests__/polish-phase3.test.ts +0 -340
  103. package/src/__tests__/profile.test.ts +0 -237
  104. package/src/__tests__/rollback.test.ts +0 -83
  105. package/src/__tests__/runbook.test.ts +0 -219
  106. package/src/__tests__/schedule.test.ts +0 -206
  107. package/src/__tests__/serve.test.ts +0 -275
  108. package/src/__tests__/sessions.test.ts +0 -322
  109. package/src/__tests__/sharing.test.ts +0 -340
  110. package/src/__tests__/snapshots.test.ts +0 -581
  111. package/src/__tests__/standalone-migration.test.ts +0 -199
  112. package/src/__tests__/state-db.test.ts +0 -334
  113. package/src/__tests__/status.test.ts +0 -158
  114. package/src/__tests__/stream-with-tools.test.ts +0 -778
  115. package/src/__tests__/subagents.test.ts +0 -176
  116. package/src/__tests__/system-prompt.test.ts +0 -248
  117. package/src/__tests__/terminal-gap-v2.test.ts +0 -395
  118. package/src/__tests__/terminal-parity.test.ts +0 -393
  119. package/src/__tests__/tf-apply.test.ts +0 -187
  120. package/src/__tests__/tool-converter.test.ts +0 -256
  121. package/src/__tests__/tool-schemas.test.ts +0 -602
  122. package/src/__tests__/tools.test.ts +0 -144
  123. package/src/__tests__/version-json.test.ts +0 -184
  124. package/src/__tests__/version.test.ts +0 -49
  125. package/src/__tests__/watch.test.ts +0 -129
  126. package/src/agent/compaction-agent.ts +0 -266
  127. package/src/agent/context-manager.ts +0 -499
  128. package/src/agent/context.ts +0 -427
  129. package/src/agent/deploy-preview.ts +0 -487
  130. package/src/agent/expand-files.ts +0 -108
  131. package/src/agent/index.ts +0 -68
  132. package/src/agent/loop.ts +0 -1998
  133. package/src/agent/modes.ts +0 -429
  134. package/src/agent/permissions.ts +0 -513
  135. package/src/agent/subagents/base.ts +0 -116
  136. package/src/agent/subagents/cost.ts +0 -51
  137. package/src/agent/subagents/explore.ts +0 -42
  138. package/src/agent/subagents/general.ts +0 -54
  139. package/src/agent/subagents/index.ts +0 -102
  140. package/src/agent/subagents/infra.ts +0 -59
  141. package/src/agent/subagents/security.ts +0 -69
  142. package/src/agent/system-prompt.ts +0 -990
  143. package/src/app.ts +0 -180
  144. package/src/audit/activity-log.ts +0 -290
  145. package/src/audit/compliance-checker.ts +0 -540
  146. package/src/audit/cost-tracker.ts +0 -318
  147. package/src/audit/index.ts +0 -23
  148. package/src/audit/security-scanner.ts +0 -641
  149. package/src/auth/guard.ts +0 -75
  150. package/src/auth/index.ts +0 -56
  151. package/src/auth/keychain.ts +0 -82
  152. package/src/auth/oauth.ts +0 -465
  153. package/src/auth/providers.ts +0 -470
  154. package/src/auth/sso.ts +0 -113
  155. package/src/auth/store.ts +0 -505
  156. package/src/auth/types.ts +0 -187
  157. package/src/build.ts +0 -141
  158. package/src/cli/index.ts +0 -16
  159. package/src/cli/init.ts +0 -1227
  160. package/src/cli/openapi-spec.ts +0 -356
  161. package/src/cli/run.ts +0 -628
  162. package/src/cli/serve-auth.ts +0 -80
  163. package/src/cli/serve.ts +0 -539
  164. package/src/cli/web.ts +0 -71
  165. package/src/cli.ts +0 -1728
  166. package/src/clients/core-engine-client.ts +0 -227
  167. package/src/clients/enterprise-client.ts +0 -334
  168. package/src/clients/generator-client.ts +0 -351
  169. package/src/clients/git-client.ts +0 -627
  170. package/src/clients/github-client.ts +0 -410
  171. package/src/clients/helm-client.ts +0 -504
  172. package/src/clients/index.ts +0 -80
  173. package/src/clients/k8s-client.ts +0 -497
  174. package/src/clients/llm-client.ts +0 -161
  175. package/src/clients/rest-client.ts +0 -130
  176. package/src/clients/service-discovery.ts +0 -38
  177. package/src/clients/terraform-client.ts +0 -482
  178. package/src/clients/tools-client.ts +0 -1843
  179. package/src/clients/ws-client.ts +0 -115
  180. package/src/commands/alias.ts +0 -100
  181. package/src/commands/analyze/index.ts +0 -352
  182. package/src/commands/apply/helm.ts +0 -473
  183. package/src/commands/apply/index.ts +0 -213
  184. package/src/commands/apply/k8s.ts +0 -454
  185. package/src/commands/apply/terraform.ts +0 -582
  186. package/src/commands/ask.ts +0 -167
  187. package/src/commands/audit/index.ts +0 -357
  188. package/src/commands/auth-cloud.ts +0 -407
  189. package/src/commands/auth-list.ts +0 -134
  190. package/src/commands/auth-profile.ts +0 -121
  191. package/src/commands/auth-refresh.ts +0 -187
  192. package/src/commands/auth-status.ts +0 -141
  193. package/src/commands/aws/ec2.ts +0 -501
  194. package/src/commands/aws/iam.ts +0 -397
  195. package/src/commands/aws/index.ts +0 -133
  196. package/src/commands/aws/lambda.ts +0 -396
  197. package/src/commands/aws/rds.ts +0 -439
  198. package/src/commands/aws/s3.ts +0 -439
  199. package/src/commands/aws/vpc.ts +0 -393
  200. package/src/commands/aws-discover.ts +0 -542
  201. package/src/commands/aws-terraform.ts +0 -755
  202. package/src/commands/azure/aks.ts +0 -376
  203. package/src/commands/azure/functions.ts +0 -253
  204. package/src/commands/azure/index.ts +0 -116
  205. package/src/commands/azure/storage.ts +0 -478
  206. package/src/commands/azure/vm.ts +0 -355
  207. package/src/commands/billing/index.ts +0 -256
  208. package/src/commands/chat.ts +0 -320
  209. package/src/commands/completions.ts +0 -268
  210. package/src/commands/config.ts +0 -372
  211. package/src/commands/cost/cloud-cost-estimator.ts +0 -266
  212. package/src/commands/cost/estimator.ts +0 -79
  213. package/src/commands/cost/index.ts +0 -810
  214. package/src/commands/cost/parsers/terraform.ts +0 -273
  215. package/src/commands/cost/parsers/types.ts +0 -25
  216. package/src/commands/cost/pricing/aws.ts +0 -544
  217. package/src/commands/cost/pricing/azure.ts +0 -499
  218. package/src/commands/cost/pricing/gcp.ts +0 -396
  219. package/src/commands/cost/pricing/index.ts +0 -40
  220. package/src/commands/demo.ts +0 -250
  221. package/src/commands/deploy.ts +0 -260
  222. package/src/commands/doctor.ts +0 -1386
  223. package/src/commands/drift/index.ts +0 -787
  224. package/src/commands/explain.ts +0 -277
  225. package/src/commands/export.ts +0 -146
  226. package/src/commands/feedback.ts +0 -389
  227. package/src/commands/fix.ts +0 -324
  228. package/src/commands/fs/index.ts +0 -402
  229. package/src/commands/gcp/compute.ts +0 -325
  230. package/src/commands/gcp/functions.ts +0 -271
  231. package/src/commands/gcp/gke.ts +0 -438
  232. package/src/commands/gcp/iam.ts +0 -344
  233. package/src/commands/gcp/index.ts +0 -129
  234. package/src/commands/gcp/storage.ts +0 -284
  235. package/src/commands/generate-helm.ts +0 -1249
  236. package/src/commands/generate-k8s.ts +0 -1508
  237. package/src/commands/generate-terraform.ts +0 -1202
  238. package/src/commands/gh/index.ts +0 -863
  239. package/src/commands/git/index.ts +0 -1343
  240. package/src/commands/helm/index.ts +0 -1126
  241. package/src/commands/help.ts +0 -715
  242. package/src/commands/history.ts +0 -149
  243. package/src/commands/import.ts +0 -868
  244. package/src/commands/incident.ts +0 -166
  245. package/src/commands/index.ts +0 -367
  246. package/src/commands/init.ts +0 -1051
  247. package/src/commands/k8s/index.ts +0 -1137
  248. package/src/commands/login.ts +0 -716
  249. package/src/commands/logout.ts +0 -83
  250. package/src/commands/logs.ts +0 -167
  251. package/src/commands/onboarding.ts +0 -405
  252. package/src/commands/pipeline.ts +0 -186
  253. package/src/commands/plan/display.ts +0 -279
  254. package/src/commands/plan/index.ts +0 -599
  255. package/src/commands/plugin.ts +0 -398
  256. package/src/commands/preview.ts +0 -452
  257. package/src/commands/profile.ts +0 -342
  258. package/src/commands/questionnaire.ts +0 -1172
  259. package/src/commands/resume.ts +0 -47
  260. package/src/commands/rollback.ts +0 -315
  261. package/src/commands/rollout.ts +0 -88
  262. package/src/commands/runbook.ts +0 -346
  263. package/src/commands/schedule.ts +0 -236
  264. package/src/commands/status.ts +0 -252
  265. package/src/commands/team/index.ts +0 -346
  266. package/src/commands/team-context.ts +0 -220
  267. package/src/commands/template.ts +0 -233
  268. package/src/commands/tf/index.ts +0 -1093
  269. package/src/commands/upgrade.ts +0 -607
  270. package/src/commands/usage/index.ts +0 -134
  271. package/src/commands/version.ts +0 -174
  272. package/src/commands/watch.ts +0 -153
  273. package/src/compat/index.ts +0 -2
  274. package/src/compat/runtime.ts +0 -12
  275. package/src/compat/sqlite.ts +0 -177
  276. package/src/config/index.ts +0 -17
  277. package/src/config/manager.ts +0 -530
  278. package/src/config/mode-store.ts +0 -62
  279. package/src/config/profiles.ts +0 -84
  280. package/src/config/safety-policy.ts +0 -358
  281. package/src/config/schema.ts +0 -125
  282. package/src/config/types.ts +0 -609
  283. package/src/config/workspace-state.ts +0 -53
  284. package/src/context/context-db.ts +0 -199
  285. package/src/demo/index.ts +0 -349
  286. package/src/demo/scenarios/full-journey.ts +0 -229
  287. package/src/demo/scenarios/getting-started.ts +0 -127
  288. package/src/demo/scenarios/helm-release.ts +0 -341
  289. package/src/demo/scenarios/k8s-deployment.ts +0 -194
  290. package/src/demo/scenarios/terraform-vpc.ts +0 -170
  291. package/src/demo/types.ts +0 -92
  292. package/src/engine/cost-estimator.ts +0 -480
  293. package/src/engine/diagram-generator.ts +0 -256
  294. package/src/engine/drift-detector.ts +0 -902
  295. package/src/engine/executor.ts +0 -1066
  296. package/src/engine/index.ts +0 -76
  297. package/src/engine/orchestrator.ts +0 -636
  298. package/src/engine/planner.ts +0 -787
  299. package/src/engine/safety.ts +0 -743
  300. package/src/engine/verifier.ts +0 -770
  301. package/src/enterprise/audit.ts +0 -348
  302. package/src/enterprise/auth.ts +0 -270
  303. package/src/enterprise/billing.ts +0 -822
  304. package/src/enterprise/index.ts +0 -17
  305. package/src/enterprise/teams.ts +0 -443
  306. package/src/generator/best-practices.ts +0 -1608
  307. package/src/generator/helm.ts +0 -630
  308. package/src/generator/index.ts +0 -37
  309. package/src/generator/intent-parser.ts +0 -514
  310. package/src/generator/kubernetes.ts +0 -976
  311. package/src/generator/terraform.ts +0 -1875
  312. package/src/history/index.ts +0 -8
  313. package/src/history/manager.ts +0 -250
  314. package/src/history/types.ts +0 -34
  315. package/src/hooks/config.ts +0 -432
  316. package/src/hooks/engine.ts +0 -392
  317. package/src/hooks/index.ts +0 -4
  318. package/src/llm/auth-bridge.ts +0 -198
  319. package/src/llm/circuit-breaker.ts +0 -140
  320. package/src/llm/config-loader.ts +0 -201
  321. package/src/llm/cost-calculator.ts +0 -171
  322. package/src/llm/index.ts +0 -8
  323. package/src/llm/model-aliases.ts +0 -115
  324. package/src/llm/provider-registry.ts +0 -63
  325. package/src/llm/providers/anthropic.ts +0 -462
  326. package/src/llm/providers/bedrock.ts +0 -477
  327. package/src/llm/providers/google.ts +0 -405
  328. package/src/llm/providers/ollama.ts +0 -767
  329. package/src/llm/providers/openai-compatible.ts +0 -340
  330. package/src/llm/providers/openai.ts +0 -328
  331. package/src/llm/providers/openrouter.ts +0 -338
  332. package/src/llm/router.ts +0 -1104
  333. package/src/llm/types.ts +0 -232
  334. package/src/lsp/client.ts +0 -298
  335. package/src/lsp/languages.ts +0 -119
  336. package/src/lsp/manager.ts +0 -294
  337. package/src/mcp/client.ts +0 -402
  338. package/src/mcp/index.ts +0 -5
  339. package/src/mcp/manager.ts +0 -133
  340. package/src/nimbus.ts +0 -233
  341. package/src/plugins/index.ts +0 -27
  342. package/src/plugins/loader.ts +0 -334
  343. package/src/plugins/manager.ts +0 -376
  344. package/src/plugins/types.ts +0 -284
  345. package/src/scanners/cicd-scanner.ts +0 -258
  346. package/src/scanners/cloud-scanner.ts +0 -466
  347. package/src/scanners/framework-scanner.ts +0 -469
  348. package/src/scanners/iac-scanner.ts +0 -388
  349. package/src/scanners/index.ts +0 -539
  350. package/src/scanners/language-scanner.ts +0 -276
  351. package/src/scanners/package-manager-scanner.ts +0 -277
  352. package/src/scanners/types.ts +0 -172
  353. package/src/sessions/manager.ts +0 -472
  354. package/src/sessions/types.ts +0 -44
  355. package/src/sharing/sync.ts +0 -300
  356. package/src/sharing/viewer.ts +0 -163
  357. package/src/snapshots/index.ts +0 -2
  358. package/src/snapshots/manager.ts +0 -530
  359. package/src/state/artifacts.ts +0 -147
  360. package/src/state/audit.ts +0 -137
  361. package/src/state/billing.ts +0 -240
  362. package/src/state/checkpoints.ts +0 -117
  363. package/src/state/config.ts +0 -67
  364. package/src/state/conversations.ts +0 -14
  365. package/src/state/credentials.ts +0 -154
  366. package/src/state/db.ts +0 -58
  367. package/src/state/index.ts +0 -26
  368. package/src/state/messages.ts +0 -115
  369. package/src/state/projects.ts +0 -123
  370. package/src/state/schema.ts +0 -236
  371. package/src/state/sessions.ts +0 -147
  372. package/src/state/teams.ts +0 -200
  373. package/src/telemetry.ts +0 -108
  374. package/src/tools/aws-ops.ts +0 -952
  375. package/src/tools/azure-ops.ts +0 -579
  376. package/src/tools/file-ops.ts +0 -615
  377. package/src/tools/gcp-ops.ts +0 -625
  378. package/src/tools/git-ops.ts +0 -773
  379. package/src/tools/github-ops.ts +0 -799
  380. package/src/tools/helm-ops.ts +0 -943
  381. package/src/tools/index.ts +0 -17
  382. package/src/tools/k8s-ops.ts +0 -819
  383. package/src/tools/schemas/converter.ts +0 -184
  384. package/src/tools/schemas/devops.ts +0 -3502
  385. package/src/tools/schemas/index.ts +0 -73
  386. package/src/tools/schemas/standard.ts +0 -1148
  387. package/src/tools/schemas/types.ts +0 -735
  388. package/src/tools/spawn-exec.ts +0 -148
  389. package/src/tools/terraform-ops.ts +0 -862
  390. package/src/types/ambient.d.ts +0 -193
  391. package/src/types/config.ts +0 -83
  392. package/src/types/drift.ts +0 -116
  393. package/src/types/enterprise.ts +0 -335
  394. package/src/types/index.ts +0 -20
  395. package/src/types/plan.ts +0 -44
  396. package/src/types/request.ts +0 -65
  397. package/src/types/response.ts +0 -54
  398. package/src/types/service.ts +0 -51
  399. package/src/ui/App.tsx +0 -2114
  400. package/src/ui/DeployPreview.tsx +0 -174
  401. package/src/ui/FileDiffModal.tsx +0 -162
  402. package/src/ui/Header.tsx +0 -131
  403. package/src/ui/HelpModal.tsx +0 -57
  404. package/src/ui/InputBox.tsx +0 -503
  405. package/src/ui/MessageList.tsx +0 -1032
  406. package/src/ui/PermissionPrompt.tsx +0 -163
  407. package/src/ui/StatusBar.tsx +0 -277
  408. package/src/ui/TerminalPane.tsx +0 -84
  409. package/src/ui/ToolCallDisplay.tsx +0 -643
  410. package/src/ui/TreePane.tsx +0 -132
  411. package/src/ui/chat-ui.ts +0 -850
  412. package/src/ui/index.ts +0 -33
  413. package/src/ui/ink/index.ts +0 -1444
  414. package/src/ui/streaming.ts +0 -176
  415. package/src/ui/theme.ts +0 -104
  416. package/src/ui/types.ts +0 -75
  417. package/src/utils/analytics.ts +0 -72
  418. package/src/utils/cost-warning.ts +0 -27
  419. package/src/utils/env.ts +0 -46
  420. package/src/utils/errors.ts +0 -69
  421. package/src/utils/event-bus.ts +0 -38
  422. package/src/utils/index.ts +0 -24
  423. package/src/utils/logger.ts +0 -171
  424. package/src/utils/rate-limiter.ts +0 -121
  425. package/src/utils/service-auth.ts +0 -49
  426. package/src/utils/validation.ts +0 -53
  427. package/src/version.ts +0 -4
  428. package/src/watcher/index.ts +0 -214
  429. package/src/wizard/approval.ts +0 -383
  430. package/src/wizard/index.ts +0 -25
  431. package/src/wizard/prompts.ts +0 -338
  432. package/src/wizard/types.ts +0 -172
  433. package/src/wizard/ui.ts +0 -556
  434. package/src/wizard/wizard.ts +0 -304
  435. package/tsconfig.json +0 -24
@@ -1,1066 +0,0 @@
1
- import { logger } from '../utils';
2
- import type {
3
- AgentPlan,
4
- PlanStep,
5
- ExecutionResult,
6
- ExecutionLog,
7
- ExecutionArtifact,
8
- } from './orchestrator';
9
- import { TerraformOperations } from '../tools/terraform-ops';
10
- import { FileSystemOperations } from '../tools/file-ops';
11
- import { saveCheckpoint, getLatestCheckpoint, deleteCheckpoints } from '../state/checkpoints';
12
-
13
- // ==========================================
14
- // Error Classes
15
- // ==========================================
16
-
17
- /**
18
- * Non-retryable errors for deterministic validation failures.
19
- * These will not be retried by executeWithRetry.
20
- */
21
- class NonRetryableError extends Error {
22
- constructor(message: string) {
23
- super(message);
24
- this.name = 'NonRetryableError';
25
- }
26
- }
27
-
28
- /**
29
- * Structured error for executor step failures.
30
- * Propagates actionable context instead of returning mock data.
31
- */
32
- class ExecutionError extends Error {
33
- public readonly step: string;
34
- public readonly cause: unknown;
35
-
36
- constructor(message: string, opts: { step: string; cause: unknown }) {
37
- super(message);
38
- this.name = 'ExecutionError';
39
- this.step = opts.step;
40
- this.cause = opts.cause;
41
- }
42
- }
43
-
44
- // ==========================================
45
- // Generator/Best-Practices helpers (inline — no HTTP)
46
- // ==========================================
47
-
48
- /** Thin adapter for generator functionality used by executor steps. */
49
- interface GeneratorAdapter {
50
- renderTemplate(
51
- templateId: string,
52
- variables: Record<string, unknown>
53
- ): Promise<{ rendered_content: string }>;
54
- analyzeBestPractices(
55
- component: string,
56
- config: Record<string, unknown>
57
- ): Promise<{ summary?: { total_violations?: number } }>;
58
- applyAutofixes(
59
- component: string,
60
- config: Record<string, unknown>
61
- ): Promise<{ fixes_applied?: number }>;
62
- }
63
-
64
- /**
65
- * Local in-process generator adapter.
66
- * Generates simple template content without HTTP round-trips.
67
- */
68
- class LocalGeneratorAdapter implements GeneratorAdapter {
69
- async renderTemplate(
70
- templateId: string,
71
- variables: Record<string, unknown>
72
- ): Promise<{ rendered_content: string }> {
73
- // Simple inline template rendering for common terraform templates
74
- const parts = templateId.split('/'); // e.g. terraform/aws/vpc
75
- const component = parts[2] || parts[parts.length - 1] || 'unknown';
76
- const provider = parts[1] || 'aws';
77
-
78
- const rendered_content =
79
- `# Generated ${component.toUpperCase()} configuration\n` +
80
- `# Provider: ${provider}\n` +
81
- `# Variables: ${JSON.stringify(variables, null, 2)}\n\n` +
82
- `resource "${provider}_${component}" "main" {\n` +
83
- ` # Configuration generated by Nimbus\n` +
84
- ` tags = {\n` +
85
- ` ManagedBy = "nimbus"\n` +
86
- ` }\n` +
87
- `}\n`;
88
-
89
- return { rendered_content };
90
- }
91
-
92
- async analyzeBestPractices(
93
- _component: string,
94
- _config: Record<string, unknown>
95
- ): Promise<{ summary?: { total_violations?: number } }> {
96
- // Delegate to the embedded BestPracticesEngine when available
97
- try {
98
- const { BestPracticesEngine } = await import('../generator/best-practices');
99
- const engine = new BestPracticesEngine();
100
- const report = engine.analyze(_component, _config);
101
- return { summary: { total_violations: report.summary.violations_found } };
102
- } catch {
103
- return { summary: { total_violations: 0 } };
104
- }
105
- }
106
-
107
- async applyAutofixes(
108
- _component: string,
109
- _config: Record<string, unknown>
110
- ): Promise<{ fixes_applied?: number }> {
111
- try {
112
- const { BestPracticesEngine } = await import('../generator/best-practices');
113
- const engine = new BestPracticesEngine();
114
- const result = engine.autofix(_component, _config);
115
- return { fixes_applied: result.applied_fixes.length };
116
- } catch {
117
- return { fixes_applied: 0 };
118
- }
119
- }
120
- }
121
-
122
- // ==========================================
123
- // Executor
124
- // ==========================================
125
-
126
- export class Executor {
127
- private logs: Map<string, ExecutionLog[]>;
128
- private artifacts: Map<string, ExecutionArtifact[]>;
129
- private generatorAdapter: GeneratorAdapter;
130
- private terraformOps: TerraformOperations;
131
- private fsOps: FileSystemOperations;
132
-
133
- constructor() {
134
- this.logs = new Map();
135
- this.artifacts = new Map();
136
- this.generatorAdapter = new LocalGeneratorAdapter();
137
- this.terraformOps = new TerraformOperations();
138
- this.fsOps = new FileSystemOperations();
139
- }
140
-
141
- /**
142
- * Create a TerraformOperations instance bound to a specific working directory.
143
- */
144
- private tfOpsForDir(workDir: string): TerraformOperations {
145
- return new TerraformOperations(workDir);
146
- }
147
-
148
- /**
149
- * Execute a plan
150
- */
151
- async executePlan(plan: AgentPlan): Promise<ExecutionResult[]> {
152
- logger.info(`Starting execution of plan: ${plan.id}`);
153
-
154
- const results: ExecutionResult[] = [];
155
- const executedSteps = new Set<string>();
156
-
157
- // Check for existing checkpoint to enable resume
158
- try {
159
- const checkpoint = await getLatestCheckpoint(plan.id);
160
- if (checkpoint) {
161
- logger.info(`Found checkpoint for plan ${plan.id} at step ${checkpoint.step}, resuming`);
162
-
163
- // Mark previously completed steps as executed
164
- const completedStepIds = (checkpoint.state.completedStepIds as string[]) || [];
165
- for (const stepId of completedStepIds) {
166
- executedSteps.add(stepId);
167
- }
168
-
169
- // Restore any previous results from checkpoint
170
- const previousResults = (checkpoint.state.results as ExecutionResult[]) || [];
171
- results.push(...previousResults);
172
- }
173
- } catch (error) {
174
- logger.warn('Could not check for checkpoint, starting fresh', error);
175
- }
176
-
177
- // Execute steps respecting dependencies
178
- while (executedSteps.size < plan.steps.length) {
179
- const readySteps = this.getReadySteps(plan.steps, executedSteps);
180
-
181
- if (readySteps.length === 0) {
182
- logger.error('No steps ready for execution, possible circular dependency');
183
- break;
184
- }
185
-
186
- // Execute ready steps in parallel with retry
187
- const stepResults = await Promise.allSettled(
188
- readySteps.map(step => this.executeWithRetry(plan.id, step))
189
- );
190
-
191
- // Process results
192
- for (let i = 0; i < stepResults.length; i++) {
193
- const stepResult = stepResults[i];
194
- const step = readySteps[i];
195
-
196
- if (stepResult.status === 'fulfilled') {
197
- results.push(stepResult.value);
198
- executedSteps.add(step.id);
199
-
200
- // Save checkpoint after each successful step
201
- try {
202
- const checkpointId = `ckpt_${plan.id}_${step.order}`;
203
- await saveCheckpoint(checkpointId, plan.id, step.order, {
204
- completedStepIds: Array.from(executedSteps),
205
- results: results.map(r => ({
206
- id: r.id,
207
- plan_id: r.plan_id,
208
- step_id: r.step_id,
209
- status: r.status,
210
- started_at: r.started_at,
211
- completed_at: r.completed_at,
212
- duration: r.duration,
213
- outputs: r.outputs,
214
- })),
215
- lastCompletedStep: step.order,
216
- });
217
- } catch (checkpointError) {
218
- logger.warn(`Failed to save checkpoint for step ${step.id}`, checkpointError);
219
- }
220
-
221
- if (stepResult.value.status === 'failure') {
222
- logger.error(`Step ${step.id} failed, stopping execution`);
223
- return results;
224
- }
225
- } else {
226
- logger.error(`Step ${step.id} execution error`, stepResult.reason);
227
- results.push({
228
- id: this.generateResultId(),
229
- plan_id: plan.id,
230
- step_id: step.id,
231
- status: 'failure',
232
- started_at: new Date(),
233
- completed_at: new Date(),
234
- duration: 0,
235
- error: {
236
- code: 'EXECUTION_ERROR',
237
- message: stepResult.reason.message,
238
- stack_trace: stepResult.reason.stack,
239
- },
240
- });
241
- return results;
242
- }
243
- }
244
- }
245
-
246
- // Clean up checkpoints on successful completion
247
- try {
248
- await deleteCheckpoints(plan.id);
249
- logger.info(`Cleaned up checkpoints for completed plan ${plan.id}`);
250
- } catch (error) {
251
- logger.warn(`Failed to clean up checkpoints for plan ${plan.id}`, error);
252
- }
253
-
254
- logger.info(`Plan execution completed: ${results.length} steps executed`);
255
- return results;
256
- }
257
-
258
- /**
259
- * Resume a plan from its last checkpoint
260
- */
261
- async resumePlan(planId: string): Promise<ExecutionResult[]> {
262
- logger.info(`Attempting to resume plan: ${planId}`);
263
-
264
- const checkpoint = await getLatestCheckpoint(planId);
265
- if (!checkpoint) {
266
- throw new Error(`No checkpoint found for plan ${planId}. Cannot resume.`);
267
- }
268
-
269
- logger.info(`Resuming plan ${planId} from step ${checkpoint.step}`);
270
-
271
- // The executePlan method already handles checkpoint-based resume internally.
272
- // We need the original plan to call executePlan. Since we store completed step IDs
273
- // in the checkpoint state, we reconstruct what we need.
274
- // The orchestrator will provide the plan; this method is a convenience wrapper
275
- // that confirms a checkpoint exists before the orchestrator re-invokes executePlan.
276
-
277
- return (checkpoint.state.results as ExecutionResult[]) || [];
278
- }
279
-
280
- /**
281
- * Execute a step with retry logic and exponential backoff
282
- */
283
- private async executeWithRetry(
284
- planId: string,
285
- step: PlanStep,
286
- maxRetries = 3
287
- ): Promise<ExecutionResult> {
288
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
289
- try {
290
- const result = await this.executeStep(planId, step);
291
- if (result.status === 'success' || attempt === maxRetries) {
292
- return result;
293
- }
294
- // Don't retry deterministic failures (validation errors, business logic)
295
- if (result.error?.code === 'NON_RETRYABLE_ERROR') {
296
- return result;
297
- }
298
- // Retry on transient failure results
299
- logger.warn(
300
- `Step ${step.id} failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying...`
301
- );
302
- await this.delay(1000 * Math.pow(2, attempt));
303
- // Reset step status for retry
304
- step.status = 'pending';
305
- } catch (error) {
306
- if (attempt === maxRetries) {
307
- logger.error(`Step ${step.id} failed after ${maxRetries + 1} attempts`, error);
308
- return {
309
- id: this.generateResultId(),
310
- plan_id: planId,
311
- step_id: step.id,
312
- status: 'failure',
313
- started_at: new Date(),
314
- completed_at: new Date(),
315
- duration: 0,
316
- error: {
317
- code: 'RETRY_EXHAUSTED',
318
- message: `Step failed after ${maxRetries + 1} attempts: ${(error as Error).message}`,
319
- stack_trace: (error as Error).stack,
320
- },
321
- };
322
- }
323
- logger.warn(
324
- `Step ${step.id} threw error (attempt ${attempt + 1}/${maxRetries + 1}), retrying...`
325
- );
326
- await this.delay(1000 * Math.pow(2, attempt));
327
- step.status = 'pending';
328
- }
329
- }
330
- // Should not reach here, but satisfy TypeScript
331
- throw new Error('Unexpected retry loop exit');
332
- }
333
-
334
- /**
335
- * Delay helper for retry backoff
336
- */
337
- private delay(ms: number): Promise<void> {
338
- return new Promise(resolve => setTimeout(resolve, ms));
339
- }
340
-
341
- /**
342
- * Execute a single step
343
- */
344
- private async executeStep(planId: string, step: PlanStep): Promise<ExecutionResult> {
345
- const executionId = this.generateResultId();
346
- const startedAt = new Date();
347
-
348
- this.log(executionId, 'info', `Executing step: ${step.description}`);
349
- logger.info(`Executing step ${step.id}: ${step.description}`);
350
-
351
- try {
352
- // Update step status
353
- step.status = 'running';
354
- step.started_at = startedAt;
355
-
356
- // Execute based on step type
357
- let outputs: Record<string, unknown> = {};
358
- let artifacts: ExecutionArtifact[] = [];
359
-
360
- switch (step.action) {
361
- case 'validate_requirements':
362
- outputs = await this.validateRequirements(step, executionId);
363
- break;
364
-
365
- case 'generate_component': {
366
- const generateResult = await this.generateComponent(step, executionId);
367
- outputs = generateResult.outputs;
368
- artifacts = generateResult.artifacts;
369
- break;
370
- }
371
-
372
- case 'validate_generated_code':
373
- outputs = await this.validateGeneratedCode(step, executionId);
374
- break;
375
-
376
- case 'apply_best_practices':
377
- outputs = await this.applyBestPractices(step, executionId);
378
- break;
379
-
380
- case 'plan_deployment':
381
- outputs = await this.planDeployment(step, executionId);
382
- break;
383
-
384
- case 'apply_deployment':
385
- outputs = await this.applyDeployment(step, executionId);
386
- break;
387
-
388
- case 'verify_deployment':
389
- outputs = await this.verifyDeployment(step, executionId);
390
- break;
391
-
392
- case 'generate_documentation': {
393
- const docResult = await this.generateDocumentation(step, executionId);
394
- outputs = docResult.outputs;
395
- artifacts = docResult.artifacts;
396
- break;
397
- }
398
-
399
- default:
400
- throw new Error(`Unknown action: ${step.action}`);
401
- }
402
-
403
- // Mark step as completed
404
- const completedAt = new Date();
405
- step.status = 'completed';
406
- step.completed_at = completedAt;
407
- step.duration = completedAt.getTime() - startedAt.getTime();
408
-
409
- this.log(executionId, 'info', `Step completed successfully in ${step.duration}ms`);
410
-
411
- // Store artifacts
412
- if (artifacts.length > 0) {
413
- this.artifacts.set(executionId, artifacts);
414
- }
415
-
416
- return {
417
- id: executionId,
418
- plan_id: planId,
419
- step_id: step.id,
420
- status: 'success',
421
- started_at: startedAt,
422
- completed_at: completedAt,
423
- duration: step.duration,
424
- outputs,
425
- artifacts,
426
- logs: this.logs.get(executionId),
427
- };
428
- } catch (error) {
429
- const completedAt = new Date();
430
- step.status = 'failed';
431
- step.completed_at = completedAt;
432
- step.duration = completedAt.getTime() - startedAt.getTime();
433
-
434
- this.log(executionId, 'error', `Step failed: ${(error as Error).message}`);
435
- logger.error(`Step ${step.id} failed`, error);
436
-
437
- return {
438
- id: executionId,
439
- plan_id: planId,
440
- step_id: step.id,
441
- status: 'failure',
442
- started_at: startedAt,
443
- completed_at: completedAt,
444
- duration: step.duration!,
445
- error: {
446
- code: error instanceof NonRetryableError ? 'NON_RETRYABLE_ERROR' : 'STEP_EXECUTION_ERROR',
447
- message: (error as Error).message,
448
- stack_trace: (error as Error).stack,
449
- },
450
- logs: this.logs.get(executionId),
451
- };
452
- }
453
- }
454
-
455
- /**
456
- * Get steps that are ready for execution
457
- */
458
- private getReadySteps(steps: PlanStep[], executedSteps: Set<string>): PlanStep[] {
459
- return steps.filter(step => {
460
- // Skip already executed steps
461
- if (executedSteps.has(step.id)) {
462
- return false;
463
- }
464
-
465
- // Skip failed/completed steps
466
- if (step.status === 'completed' || step.status === 'failed') {
467
- return false;
468
- }
469
-
470
- // Check if all dependencies are satisfied
471
- if (step.depends_on && step.depends_on.length > 0) {
472
- return step.depends_on.every(depId => executedSteps.has(depId));
473
- }
474
-
475
- return true;
476
- });
477
- }
478
-
479
- /**
480
- * Validate requirements
481
- */
482
- private async validateRequirements(
483
- step: PlanStep,
484
- executionId: string
485
- ): Promise<Record<string, unknown>> {
486
- this.log(executionId, 'info', 'Validating infrastructure requirements');
487
-
488
- const { provider, components, requirements: _requirements } = step.parameters;
489
-
490
- // Validate provider
491
- if (!['aws', 'gcp', 'azure'].includes(provider as string)) {
492
- throw new NonRetryableError(`Invalid provider: ${provider}`);
493
- }
494
-
495
- // Validate components
496
- if (!Array.isArray(components) || components.length === 0) {
497
- throw new NonRetryableError('No components specified');
498
- }
499
-
500
- const validComponents = ['vpc', 'eks', 'rds', 's3', 'gke', 'gcs', 'aks'];
501
- for (const component of components) {
502
- if (!validComponents.includes(component)) {
503
- throw new NonRetryableError(`Invalid component: ${component}`);
504
- }
505
- }
506
-
507
- this.log(executionId, 'info', `Validated ${components.length} components for ${provider}`);
508
-
509
- return {
510
- validated: true,
511
- provider,
512
- components,
513
- };
514
- }
515
-
516
- /**
517
- * Generate component
518
- */
519
- private async generateComponent(
520
- step: PlanStep,
521
- executionId: string
522
- ): Promise<{ outputs: Record<string, unknown>; artifacts: ExecutionArtifact[] }> {
523
- this.log(executionId, 'info', `Generating ${step.parameters.component} component`);
524
-
525
- const { component, provider, variables } = step.parameters;
526
-
527
- let generatedCode: string;
528
-
529
- try {
530
- // Call local generator adapter to render template (no HTTP)
531
- const templateId = `terraform/${provider}/${component}`;
532
- const result = await this.generatorAdapter.renderTemplate(
533
- templateId,
534
- (variables as Record<string, unknown>) || {}
535
- );
536
- generatedCode = result.rendered_content;
537
- this.log(executionId, 'info', `Local generator rendered template: ${templateId}`);
538
- } catch (error) {
539
- throw new ExecutionError(
540
- `Step 'generate_component' failed for ${component}: ${(error as Error).message}.`,
541
- { step: 'generate_component', cause: error }
542
- );
543
- }
544
-
545
- // Write to filesystem using embedded FileSystemOperations
546
- const outputPath = `/tmp/nimbus/${executionId}/${component}.tf`;
547
-
548
- try {
549
- await this.fsOps.writeFile(outputPath, generatedCode, { createDirs: true });
550
- this.log(executionId, 'info', `Wrote generated code to: ${outputPath}`);
551
- } catch (error) {
552
- this.log(
553
- executionId,
554
- 'warn',
555
- `File write unavailable, file not written: ${(error as Error).message}`
556
- );
557
- }
558
-
559
- // Create artifact
560
- const artifact: ExecutionArtifact = {
561
- id: `artifact_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
562
- type: 'terraform',
563
- name: `${component}.tf`,
564
- path: outputPath,
565
- size: generatedCode.length,
566
- checksum: this.calculateChecksum(generatedCode),
567
- created_at: new Date(),
568
- };
569
-
570
- this.log(executionId, 'info', `Generated artifact: ${artifact.name} (${artifact.size} bytes)`);
571
-
572
- return {
573
- outputs: {
574
- component,
575
- code_size: generatedCode.length,
576
- artifact_id: artifact.id,
577
- generated_code: generatedCode,
578
- },
579
- artifacts: [artifact],
580
- };
581
- }
582
-
583
- /**
584
- * Validate generated code
585
- */
586
- private async validateGeneratedCode(
587
- step: PlanStep,
588
- executionId: string
589
- ): Promise<Record<string, unknown>> {
590
- this.log(executionId, 'info', 'Validating generated infrastructure code');
591
-
592
- const { components } = step.parameters;
593
- const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
594
-
595
- try {
596
- const tfOps = this.tfOpsForDir(workDir);
597
-
598
- // Initialize terraform first (needed for validation)
599
- this.log(executionId, 'info', 'Initializing Terraform for validation...');
600
- await tfOps.init();
601
-
602
- // Run terraform validate
603
- this.log(executionId, 'info', 'Running Terraform validate...');
604
- const validateResult = await tfOps.validate();
605
-
606
- // Format terraform files
607
- this.log(executionId, 'info', 'Formatting Terraform files...');
608
- await tfOps.fmt({ recursive: true });
609
-
610
- const validationResults = {
611
- syntax_valid: validateResult.valid,
612
- error_count: validateResult.errorCount,
613
- warning_count: validateResult.warningCount,
614
- diagnostics: validateResult.diagnostics,
615
- resources_count: (components as string[]).length * 5, // Estimate
616
- };
617
-
618
- if (validateResult.valid) {
619
- this.log(executionId, 'info', `Code validation passed`);
620
- } else {
621
- this.log(executionId, 'warn', `Code validation found ${validateResult.errorCount} errors`);
622
- }
623
-
624
- return validationResults;
625
- } catch (error) {
626
- throw new ExecutionError(
627
- `Step 'validate_generated_code' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
628
- { step: 'validate_generated_code', cause: error }
629
- );
630
- }
631
- }
632
-
633
- /**
634
- * Apply best practices
635
- */
636
- private async applyBestPractices(
637
- step: PlanStep,
638
- executionId: string
639
- ): Promise<Record<string, unknown>> {
640
- this.log(executionId, 'info', 'Applying security and best practices');
641
-
642
- const { components, autofix, config } = step.parameters;
643
- let totalViolations = 0;
644
- let totalFixed = 0;
645
- const componentReports: Array<{ component: string; violations: number; fixed: number }> = [];
646
-
647
- try {
648
- // Analyze best practices for each component using local adapter
649
- for (const component of components as string[]) {
650
- this.log(executionId, 'info', `Analyzing best practices for ${component}...`);
651
-
652
- const report = await this.generatorAdapter.analyzeBestPractices(
653
- component,
654
- (config as Record<string, unknown>) || {}
655
- );
656
-
657
- const violations = report.summary?.total_violations || 0;
658
- totalViolations += violations;
659
-
660
- // Apply autofixes if requested
661
- if (autofix && violations > 0) {
662
- this.log(executionId, 'info', `Applying autofixes for ${component}...`);
663
- const fixResult = await this.generatorAdapter.applyAutofixes(
664
- component,
665
- (config as Record<string, unknown>) || {}
666
- );
667
- const fixed = fixResult.fixes_applied || 0;
668
- totalFixed += fixed;
669
- componentReports.push({ component, violations, fixed });
670
- } else {
671
- componentReports.push({ component, violations, fixed: 0 });
672
- }
673
- }
674
-
675
- this.log(
676
- executionId,
677
- 'info',
678
- `Best practices: ${totalViolations} violations found, ${totalFixed} auto-fixed`
679
- );
680
-
681
- return {
682
- violations_found: totalViolations,
683
- violations_fixed: totalFixed,
684
- component_reports: componentReports,
685
- compliance_score:
686
- totalViolations === 0 ? 100 : Math.max(0, 100 - (totalViolations - totalFixed) * 5),
687
- };
688
- } catch (error) {
689
- throw new ExecutionError(`Step 'apply_best_practices' failed: ${(error as Error).message}.`, {
690
- step: 'apply_best_practices',
691
- cause: error,
692
- });
693
- }
694
- }
695
-
696
- /**
697
- * Plan deployment
698
- */
699
- private async planDeployment(
700
- step: PlanStep,
701
- executionId: string
702
- ): Promise<Record<string, unknown>> {
703
- this.log(executionId, 'info', 'Planning infrastructure deployment');
704
-
705
- const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
706
-
707
- try {
708
- const tfOps = this.tfOpsForDir(workDir);
709
-
710
- // Initialize terraform first
711
- this.log(executionId, 'info', 'Initializing Terraform...');
712
- await tfOps.init();
713
-
714
- // Run terraform plan
715
- this.log(executionId, 'info', 'Running Terraform plan...');
716
- const planResult = await tfOps.plan({
717
- out: `${workDir}/plan.tfplan`,
718
- varFile: step.parameters.varFile as string | undefined,
719
- });
720
-
721
- this.log(executionId, 'info', `Plan: hasChanges=${planResult.hasChanges}`);
722
-
723
- return {
724
- plan_output: planResult.output,
725
- has_changes: planResult.hasChanges,
726
- plan_file: `${workDir}/plan.tfplan`,
727
- };
728
- } catch (error) {
729
- throw new ExecutionError(
730
- `Step 'plan_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
731
- { step: 'plan_deployment', cause: error }
732
- );
733
- }
734
- }
735
-
736
- /**
737
- * Apply deployment
738
- */
739
- private async applyDeployment(
740
- step: PlanStep,
741
- executionId: string
742
- ): Promise<Record<string, unknown>> {
743
- this.log(executionId, 'info', 'Applying infrastructure deployment');
744
-
745
- const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
746
- const autoApprove = (step.parameters.autoApprove as boolean) ?? true;
747
- const planFile = step.parameters.planFile as string | undefined;
748
-
749
- try {
750
- const tfOps = this.tfOpsForDir(workDir);
751
- const startTime = Date.now();
752
-
753
- // Run terraform apply
754
- this.log(executionId, 'info', 'Running Terraform apply...');
755
- const applyResult = await tfOps.apply({
756
- autoApprove,
757
- planFile,
758
- varFile: step.parameters.varFile as string | undefined,
759
- parallelism: step.parameters.parallelism as number | undefined,
760
- });
761
-
762
- const deploymentTime = Date.now() - startTime;
763
-
764
- this.log(executionId, 'info', `Deployment completed successfully`);
765
-
766
- return {
767
- applied: applyResult.success,
768
- deployment_time: deploymentTime,
769
- output: applyResult.output,
770
- };
771
- } catch (error) {
772
- throw new ExecutionError(
773
- `Step 'apply_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
774
- { step: 'apply_deployment', cause: error }
775
- );
776
- }
777
- }
778
-
779
- /**
780
- * Verify deployment
781
- */
782
- private async verifyDeployment(
783
- step: PlanStep,
784
- executionId: string
785
- ): Promise<Record<string, unknown>> {
786
- this.log(executionId, 'info', 'Verifying deployed infrastructure');
787
-
788
- const { components } = step.parameters;
789
- const workDir = (step.parameters.workDir as string) || `/tmp/nimbus/${executionId}`;
790
-
791
- try {
792
- const tfOps = this.tfOpsForDir(workDir);
793
-
794
- // Validate terraform state
795
- this.log(executionId, 'info', 'Validating Terraform configuration...');
796
- const validateResult = await tfOps.validate();
797
-
798
- if (!validateResult.valid) {
799
- this.log(
800
- executionId,
801
- 'error',
802
- `Validation failed with ${validateResult.errorCount} errors`
803
- );
804
- return {
805
- verification_passed: false,
806
- validation_errors: validateResult.diagnostics.filter((d: any) => d.severity === 'error'),
807
- validation_warnings: validateResult.diagnostics.filter(
808
- (d: any) => d.severity === 'warning'
809
- ),
810
- };
811
- }
812
-
813
- // Get terraform outputs
814
- this.log(executionId, 'info', 'Retrieving Terraform outputs...');
815
- const outputs = await tfOps.output();
816
-
817
- // Build component checks
818
- const checks = (components as string[]).map(component => ({
819
- component,
820
- status: 'passed',
821
- checks_passed: 10,
822
- checks_failed: 0,
823
- }));
824
-
825
- this.log(executionId, 'info', `Verification passed for ${checks.length} components`);
826
-
827
- return {
828
- verification_passed: true,
829
- checks,
830
- outputs,
831
- };
832
- } catch (error) {
833
- throw new ExecutionError(
834
- `Step 'verify_deployment' failed: ${(error as Error).message}. Ensure Terraform is installed.`,
835
- { step: 'verify_deployment', cause: error }
836
- );
837
- }
838
- }
839
-
840
- /**
841
- * Generate documentation
842
- */
843
- private async generateDocumentation(
844
- step: PlanStep,
845
- executionId: string
846
- ): Promise<{ outputs: Record<string, unknown>; artifacts: ExecutionArtifact[] }> {
847
- this.log(executionId, 'info', 'Generating infrastructure documentation');
848
-
849
- const { components, include_diagrams } = step.parameters;
850
-
851
- // Generate README
852
- const readmeContent = this.generateReadme(components as string[]);
853
- const readmeArtifact: ExecutionArtifact = {
854
- id: `artifact_${Date.now()}_readme`,
855
- type: 'documentation',
856
- name: 'README.md',
857
- path: `/tmp/nimbus/${executionId}/README.md`,
858
- size: readmeContent.length,
859
- checksum: this.calculateChecksum(readmeContent),
860
- created_at: new Date(),
861
- };
862
-
863
- const artifacts = [readmeArtifact];
864
-
865
- // Generate diagram if requested
866
- if (include_diagrams) {
867
- const { DiagramGenerator } = await import('./diagram-generator');
868
- const diagramGen = new DiagramGenerator();
869
- const diagramContent = diagramGen.generateInfrastructureDiagram(
870
- components as string[],
871
- 'aws'
872
- );
873
- const diagramPath = `/tmp/nimbus/${executionId}/architecture.txt`;
874
-
875
- try {
876
- await this.fsOps.writeFile(diagramPath, diagramContent, { createDirs: true });
877
- } catch {
878
- // Best-effort write
879
- }
880
-
881
- const diagramArtifact: ExecutionArtifact = {
882
- id: `artifact_${Date.now()}_diagram`,
883
- type: 'documentation',
884
- name: 'architecture.txt',
885
- path: diagramPath,
886
- size: diagramContent.length,
887
- checksum: this.calculateChecksum(diagramContent),
888
- created_at: new Date(),
889
- };
890
- artifacts.push(diagramArtifact);
891
- }
892
-
893
- this.log(executionId, 'info', `Generated ${artifacts.length} documentation artifacts`);
894
-
895
- return {
896
- outputs: {
897
- artifacts_generated: artifacts.length,
898
- },
899
- artifacts,
900
- };
901
- }
902
-
903
- /**
904
- * Rollback a step
905
- */
906
- async rollbackStep(step: PlanStep): Promise<ExecutionResult> {
907
- logger.info(`Rolling back step: ${step.id}`);
908
-
909
- if (!step.rollback_action) {
910
- throw new Error(`Step ${step.id} does not have a rollback action defined`);
911
- }
912
-
913
- const executionId = this.generateResultId();
914
- const startedAt = new Date();
915
-
916
- try {
917
- // Execute rollback action
918
- this.log(executionId, 'info', `Executing rollback: ${step.rollback_action}`);
919
-
920
- // Parse and execute the rollback action
921
- const action = step.rollback_action;
922
- const [prefix, ...rest] = action.split(':');
923
-
924
- let rollbackCmd: string;
925
- if (prefix === 'terraform_destroy') {
926
- const workdir = rest.join(':');
927
- rollbackCmd = `terraform -chdir=${workdir} destroy -auto-approve -no-color`;
928
- } else if (prefix === 'kubectl_rollout_undo') {
929
- const [resource, namespace] = rest;
930
- rollbackCmd = `kubectl rollout undo ${resource}${namespace ? ` -n ${namespace}` : ''}`;
931
- } else if (prefix === 'helm_rollback') {
932
- const [release, revision, namespace] = rest;
933
- rollbackCmd = `helm rollback ${release} ${revision ?? '0'}${namespace ? ` -n ${namespace}` : ''}`;
934
- } else if (prefix === 'bash') {
935
- rollbackCmd = rest.join(':');
936
- } else {
937
- this.log(executionId, 'warn', `Unknown rollback prefix "${prefix}" — skipping`);
938
- rollbackCmd = '';
939
- }
940
-
941
- if (rollbackCmd) {
942
- this.log(executionId, 'info', `Running: ${rollbackCmd}`);
943
- await new Promise<void>((resolve, reject) => {
944
- const { exec } = require('node:child_process') as typeof import('node:child_process');
945
- exec(rollbackCmd, { timeout: 600_000 }, (error, stdout, stderr) => {
946
- if (stdout) this.log(executionId, 'info', stdout);
947
- if (stderr) this.log(executionId, 'info', stderr);
948
- if (error) reject(error);
949
- else resolve();
950
- });
951
- });
952
- }
953
-
954
- const completedAt = new Date();
955
-
956
- this.log(executionId, 'info', 'Rollback completed successfully');
957
-
958
- return {
959
- id: executionId,
960
- plan_id: 'rollback',
961
- step_id: step.id,
962
- status: 'success',
963
- started_at: startedAt,
964
- completed_at: completedAt,
965
- duration: completedAt.getTime() - startedAt.getTime(),
966
- outputs: {
967
- rolled_back: true,
968
- },
969
- logs: this.logs.get(executionId),
970
- };
971
- } catch (error) {
972
- const completedAt = new Date();
973
- this.log(executionId, 'error', `Rollback failed: ${(error as Error).message}`);
974
-
975
- return {
976
- id: executionId,
977
- plan_id: 'rollback',
978
- step_id: step.id,
979
- status: 'failure',
980
- started_at: startedAt,
981
- completed_at: completedAt,
982
- duration: completedAt.getTime() - startedAt.getTime(),
983
- error: {
984
- code: 'ROLLBACK_ERROR',
985
- message: (error as Error).message,
986
- },
987
- logs: this.logs.get(executionId),
988
- };
989
- }
990
- }
991
-
992
- /**
993
- * Get execution logs
994
- */
995
- getLogs(executionId: string): ExecutionLog[] {
996
- return this.logs.get(executionId) || [];
997
- }
998
-
999
- /**
1000
- * Get execution artifacts
1001
- */
1002
- getArtifacts(executionId: string): ExecutionArtifact[] {
1003
- return this.artifacts.get(executionId) || [];
1004
- }
1005
-
1006
- /**
1007
- * Log a message
1008
- */
1009
- private log(
1010
- executionId: string,
1011
- level: 'debug' | 'info' | 'warn' | 'error',
1012
- message: string,
1013
- context?: Record<string, unknown>
1014
- ): void {
1015
- if (!this.logs.has(executionId)) {
1016
- this.logs.set(executionId, []);
1017
- }
1018
-
1019
- this.logs.get(executionId)!.push({
1020
- timestamp: new Date(),
1021
- level,
1022
- message,
1023
- context,
1024
- });
1025
- }
1026
-
1027
- /**
1028
- * Helper: Generate README
1029
- */
1030
- private generateReadme(components: string[]): string {
1031
- return (
1032
- `# Infrastructure Documentation\n\n` +
1033
- `## Components\n\n${components
1034
- .map(c => `- ${c.toUpperCase()}`)
1035
- .join('\n')}\n\n## Deployment\n\nRun \`terraform apply\` to deploy.\n`
1036
- );
1037
- }
1038
-
1039
- /**
1040
- * Helper: Calculate checksum
1041
- */
1042
- private calculateChecksum(content: string): string {
1043
- // Simple hash function (in production, use proper crypto)
1044
- let hash = 0;
1045
- for (let i = 0; i < content.length; i++) {
1046
- const char = content.charCodeAt(i);
1047
- hash = (hash << 5) - hash + char;
1048
- hash = hash & hash;
1049
- }
1050
- return Math.abs(hash).toString(16);
1051
- }
1052
-
1053
- /**
1054
- * Helper: Sleep
1055
- */
1056
- private sleep(ms: number): Promise<void> {
1057
- return new Promise(resolve => setTimeout(resolve, ms));
1058
- }
1059
-
1060
- /**
1061
- * Generate result ID
1062
- */
1063
- private generateResultId(): string {
1064
- return `exec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1065
- }
1066
- }