@build-astron-co/nimbus 0.2.0 → 0.4.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 (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. package/tsconfig.json +2 -2
@@ -0,0 +1,688 @@
1
+ /**
2
+ * Drift Detection System
3
+ *
4
+ * Detects infrastructure drift between desired state (IaC) and actual state (cloud provider).
5
+ * Supports Terraform, Kubernetes, and Helm.
6
+ *
7
+ * Embedded version: replaces HTTP client calls with direct tool imports.
8
+ */
9
+ import { logger } from '../utils';
10
+ import { TerraformOperations } from '../tools/terraform-ops';
11
+ import { KubernetesOperations } from '../tools/k8s-ops';
12
+ import { HelmOperations } from '../tools/helm-ops';
13
+ // ==========================================
14
+ // DriftDetector
15
+ // ==========================================
16
+ export class DriftDetector {
17
+ terraformOps;
18
+ constructor() {
19
+ // TerraformOperations is stateless — workDir is passed per-call
20
+ this.terraformOps = new TerraformOperations();
21
+ }
22
+ /**
23
+ * Detect drift based on provider type
24
+ */
25
+ async detectDrift(options) {
26
+ const startTime = Date.now();
27
+ logger.info(`Starting drift detection for ${options.provider} in ${options.workDir}`);
28
+ try {
29
+ switch (options.provider) {
30
+ case 'terraform':
31
+ return await this.detectTerraformDrift(options, startTime);
32
+ case 'kubernetes':
33
+ return await this.detectKubernetesDrift(options, startTime);
34
+ case 'helm':
35
+ return await this.detectHelmDrift(options, startTime);
36
+ default:
37
+ throw new Error(`Unsupported provider: ${options.provider}`);
38
+ }
39
+ }
40
+ catch (error) {
41
+ logger.error('Drift detection failed', error);
42
+ throw error;
43
+ }
44
+ }
45
+ /**
46
+ * Detect Terraform drift using terraform plan.
47
+ * Uses the embedded TerraformOperations directly — no HTTP round-trip.
48
+ */
49
+ async detectTerraformDrift(options, startTime) {
50
+ const reportId = this.generateReportId();
51
+ const resources = [];
52
+ const errors = [];
53
+ // Build a TerraformOperations scoped to the working directory
54
+ const tfOps = new TerraformOperations(options.workDir);
55
+ try {
56
+ // Refresh state to get latest actual values
57
+ if (options.refresh !== false) {
58
+ logger.info('Refreshing Terraform state...');
59
+ try {
60
+ // terraform refresh is equivalent to plan -refresh-only; use plan with refresh flag
61
+ await tfOps.plan({ refresh: true, varFile: options.varFile });
62
+ }
63
+ catch (error) {
64
+ errors.push(`State refresh warning: ${error.message}`);
65
+ }
66
+ }
67
+ // Run terraform plan to detect drift
68
+ logger.info('Running Terraform plan to detect drift...');
69
+ const planFile = `${options.workDir}/.drift-plan.tfplan`;
70
+ const planResult = await tfOps.plan({
71
+ varFile: options.varFile,
72
+ out: planFile,
73
+ target: options.targets,
74
+ });
75
+ if (planResult.hasChanges) {
76
+ // Parse the plan output text to extract basic drift information.
77
+ // The embedded TerraformOperations returns text output, not structured JSON,
78
+ // so we extract resource addresses using regex rather than JSON parsing.
79
+ const changeLines = planResult.output
80
+ .split('\n')
81
+ .filter(line => line.includes('will be') || line.includes('must be') || line.includes('resource "'));
82
+ for (const line of changeLines) {
83
+ // Extract resource addresses like: aws_vpc.main will be updated in-place
84
+ const match = line.match(/^\s*([\w.[\]"]+)\s+(?:will|must)\s+be\s+(\w+)/);
85
+ if (match) {
86
+ const address = match[1];
87
+ const action = match[2]; // created, updated, destroyed, replaced
88
+ const parts = address.split('.');
89
+ const resourceType = parts[0] || 'unknown';
90
+ const resourceName = parts[1] || address;
91
+ let driftType = 'unchanged';
92
+ if (action.startsWith('destroy') || action.startsWith('delet')) {
93
+ driftType = 'removed';
94
+ }
95
+ else if (action.startsWith('creat')) {
96
+ driftType = 'added';
97
+ }
98
+ else if (action.startsWith('updat') || action.startsWith('replac')) {
99
+ driftType = 'modified';
100
+ }
101
+ if (driftType !== 'unchanged') {
102
+ resources.push({
103
+ address,
104
+ provider: 'terraform',
105
+ resourceType,
106
+ drifts: [
107
+ {
108
+ resourceId: address,
109
+ resourceType,
110
+ resourceName,
111
+ driftType,
112
+ severity: this.determineSeverity(resourceType, ''),
113
+ expected: `Resource should be ${driftType === 'removed' ? 'present' : 'absent'}`,
114
+ actual: `Resource is ${driftType === 'removed' ? 'absent' : 'present'}`,
115
+ description: `Resource '${address}' ${action}`,
116
+ remediation: `Run 'terraform apply' to reconcile the drift`,
117
+ autoFixable: true,
118
+ },
119
+ ],
120
+ detectedAt: new Date(),
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ const summary = this.calculateSummary(resources);
127
+ return {
128
+ id: reportId,
129
+ provider: 'terraform',
130
+ workDir: options.workDir,
131
+ environment: options.environment,
132
+ summary,
133
+ resources,
134
+ generatedAt: new Date(),
135
+ duration: Date.now() - startTime,
136
+ errors: errors.length > 0 ? errors : undefined,
137
+ };
138
+ }
139
+ catch (error) {
140
+ logger.error('Terraform drift detection failed', error);
141
+ throw new Error(`Terraform drift detection failed: ${error.message}`);
142
+ }
143
+ }
144
+ /**
145
+ * Detect Kubernetes drift by comparing manifests to actual state.
146
+ * Uses the embedded KubernetesOperations directly — no HTTP round-trip.
147
+ */
148
+ async detectKubernetesDrift(options, startTime) {
149
+ const reportId = this.generateReportId();
150
+ const resources = [];
151
+ const errors = [];
152
+ try {
153
+ logger.info('Detecting Kubernetes drift...');
154
+ const diffs = await this.compareKubernetesManifests(options);
155
+ for (const diff of diffs) {
156
+ const resourceDrift = this.parseKubernetesDiff(diff);
157
+ if (resourceDrift.drifts.length > 0) {
158
+ resources.push(resourceDrift);
159
+ }
160
+ }
161
+ const summary = this.calculateSummary(resources);
162
+ return {
163
+ id: reportId,
164
+ provider: 'kubernetes',
165
+ workDir: options.workDir,
166
+ environment: options.environment,
167
+ summary,
168
+ resources,
169
+ generatedAt: new Date(),
170
+ duration: Date.now() - startTime,
171
+ errors: errors.length > 0 ? errors : undefined,
172
+ };
173
+ }
174
+ catch (error) {
175
+ logger.error('Kubernetes drift detection failed', error);
176
+ throw new Error(`Kubernetes drift detection failed: ${error.message}`);
177
+ }
178
+ }
179
+ /**
180
+ * Detect Helm drift by comparing deployed values to chart values.
181
+ * Uses the embedded HelmOperations directly — no HTTP round-trip.
182
+ */
183
+ async detectHelmDrift(options, startTime) {
184
+ const reportId = this.generateReportId();
185
+ const resources = [];
186
+ const errors = [];
187
+ try {
188
+ logger.info('Detecting Helm drift...');
189
+ const diffs = await this.compareHelmReleases(options);
190
+ for (const diff of diffs) {
191
+ const resourceDrift = this.parseHelmDiff(diff);
192
+ if (resourceDrift.drifts.length > 0) {
193
+ resources.push(resourceDrift);
194
+ }
195
+ }
196
+ const summary = this.calculateSummary(resources);
197
+ return {
198
+ id: reportId,
199
+ provider: 'helm',
200
+ workDir: options.workDir,
201
+ environment: options.environment,
202
+ summary,
203
+ resources,
204
+ generatedAt: new Date(),
205
+ duration: Date.now() - startTime,
206
+ errors: errors.length > 0 ? errors : undefined,
207
+ };
208
+ }
209
+ catch (error) {
210
+ logger.error('Helm drift detection failed', error);
211
+ throw new Error(`Helm drift detection failed: ${error.message}`);
212
+ }
213
+ }
214
+ /**
215
+ * Check if a change is not related to drift (e.g., planned new resources).
216
+ * Only used when terraform JSON output is available.
217
+ */
218
+ isNonDriftChange(change) {
219
+ // If the only action is "no-op", it's not drift
220
+ if (change.change.actions.length === 1 && change.change.actions[0] === 'no-op') {
221
+ return true;
222
+ }
223
+ // If it's a create without prior state, it's not drift
224
+ if (change.change.actions.includes('create') && !change.change.before) {
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ /**
230
+ * Parse a structured Terraform change (JSON plan output) into ResourceDrift.
231
+ * Used when terraform show -json is available.
232
+ */
233
+ parseTerraformChange(change) {
234
+ const drifts = [];
235
+ const actions = change.change.actions;
236
+ const before = change.change.before || {};
237
+ const after = change.change.after || {};
238
+ // Determine drift type based on actions
239
+ let driftType = 'unchanged';
240
+ if (actions.includes('delete')) {
241
+ driftType = 'removed';
242
+ }
243
+ else if (actions.includes('create')) {
244
+ driftType = 'added';
245
+ }
246
+ else if (actions.includes('update')) {
247
+ driftType = 'modified';
248
+ }
249
+ // Find specific attribute changes for modifications
250
+ if (driftType === 'modified') {
251
+ const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
252
+ for (const key of allKeys) {
253
+ const beforeVal = before[key];
254
+ const afterVal = after[key];
255
+ if (JSON.stringify(beforeVal) !== JSON.stringify(afterVal)) {
256
+ drifts.push({
257
+ resourceId: change.address,
258
+ resourceType: change.type,
259
+ resourceName: change.name,
260
+ driftType: 'modified',
261
+ severity: this.determineSeverity(change.type, key),
262
+ expected: afterVal,
263
+ actual: beforeVal,
264
+ attribute: key,
265
+ description: `Attribute '${key}' has drifted from expected value`,
266
+ remediation: `Run 'terraform apply' to restore the expected value`,
267
+ autoFixable: true,
268
+ });
269
+ }
270
+ }
271
+ }
272
+ else if (driftType !== 'unchanged') {
273
+ // For added/removed resources, create a single drift item
274
+ drifts.push({
275
+ resourceId: change.address,
276
+ resourceType: change.type,
277
+ resourceName: change.name,
278
+ driftType,
279
+ severity: 'high',
280
+ expected: driftType === 'removed' ? before : after,
281
+ actual: driftType === 'removed' ? null : before,
282
+ description: `Resource ${driftType === 'removed' ? 'exists in state but not in config' : 'exists in config but not in state'}`,
283
+ remediation: `Run 'terraform apply' to ${driftType === 'removed' ? 'remove' : 'create'} the resource`,
284
+ autoFixable: true,
285
+ });
286
+ }
287
+ return {
288
+ address: change.address,
289
+ provider: 'terraform',
290
+ resourceType: change.type,
291
+ drifts,
292
+ detectedAt: new Date(),
293
+ };
294
+ }
295
+ /**
296
+ * Compare Kubernetes manifests to actual cluster state.
297
+ * Uses the embedded KubernetesOperations to query each resource.
298
+ */
299
+ async compareKubernetesManifests(options) {
300
+ const diffs = [];
301
+ try {
302
+ const { readdir, readFile } = await import('fs/promises');
303
+ const { join } = await import('path');
304
+ const jsYaml = await import('js-yaml');
305
+ let files;
306
+ try {
307
+ files = await readdir(options.workDir);
308
+ }
309
+ catch {
310
+ return [];
311
+ }
312
+ const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
313
+ // Build a KubernetesOperations instance scoped to the provided kubeconfig/context
314
+ const k8sOps = new KubernetesOperations({
315
+ kubeconfig: options.kubeconfig,
316
+ context: options.context,
317
+ namespace: options.namespace,
318
+ });
319
+ for (const file of yamlFiles) {
320
+ try {
321
+ const content = await readFile(join(options.workDir, file), 'utf-8');
322
+ const docs = jsYaml.loadAll(content);
323
+ for (const doc of docs) {
324
+ if (!doc || !doc.kind || !doc.metadata?.name) {
325
+ continue;
326
+ }
327
+ const namespace = doc.metadata.namespace || options.namespace || 'default';
328
+ try {
329
+ // Use the embedded KubernetesOperations.get() instead of HTTP fetch
330
+ const result = await k8sOps.get({
331
+ resource: `${doc.kind.toLowerCase()}s`,
332
+ name: doc.metadata.name,
333
+ namespace,
334
+ output: 'json',
335
+ });
336
+ if (result.success && result.output) {
337
+ const actual = JSON.parse(result.output);
338
+ const differences = this.deepCompare(doc.spec || {}, actual.spec || {}, 'spec');
339
+ if (differences.length > 0) {
340
+ diffs.push({
341
+ kind: doc.kind,
342
+ name: doc.metadata.name,
343
+ namespace,
344
+ differences,
345
+ });
346
+ }
347
+ }
348
+ }
349
+ catch {
350
+ // Individual resource fetch failed — skip gracefully
351
+ }
352
+ }
353
+ }
354
+ catch {
355
+ // File parse failed — skip
356
+ }
357
+ }
358
+ }
359
+ catch {
360
+ // Graceful degradation if filesystem or kubectl are unavailable
361
+ logger.warn('Kubernetes drift detection: unable to compare manifests, returning empty diff');
362
+ }
363
+ return diffs;
364
+ }
365
+ /**
366
+ * Deep compare two objects and return a flat list of differences.
367
+ */
368
+ deepCompare(expected, actual, prefix) {
369
+ const differences = [];
370
+ const allKeys = new Set([...Object.keys(expected), ...Object.keys(actual)]);
371
+ for (const key of allKeys) {
372
+ const path = `${prefix}.${key}`;
373
+ const exp = expected[key];
374
+ const act = actual[key];
375
+ if (exp !== null &&
376
+ act !== null &&
377
+ typeof exp === 'object' &&
378
+ typeof act === 'object' &&
379
+ !Array.isArray(exp) &&
380
+ !Array.isArray(act)) {
381
+ differences.push(...this.deepCompare(exp, act, path));
382
+ }
383
+ else if (JSON.stringify(exp) !== JSON.stringify(act)) {
384
+ differences.push({ path, expected: exp, actual: act });
385
+ }
386
+ }
387
+ return differences;
388
+ }
389
+ /**
390
+ * Parse a K8sResourceDiff into a ResourceDrift.
391
+ */
392
+ parseKubernetesDiff(diff) {
393
+ const drifts = [];
394
+ for (const d of diff.differences) {
395
+ drifts.push({
396
+ resourceId: `${diff.kind}/${diff.namespace || 'default'}/${diff.name}`,
397
+ resourceType: diff.kind,
398
+ resourceName: diff.name,
399
+ driftType: 'modified',
400
+ severity: this.determineSeverity(diff.kind, d.path),
401
+ expected: d.expected,
402
+ actual: d.actual,
403
+ attribute: d.path,
404
+ description: `Attribute '${d.path}' has drifted`,
405
+ remediation: `Run 'kubectl apply' to restore the expected value`,
406
+ autoFixable: true,
407
+ });
408
+ }
409
+ return {
410
+ address: `${diff.kind}/${diff.namespace || 'default'}/${diff.name}`,
411
+ provider: 'kubernetes',
412
+ resourceType: diff.kind,
413
+ drifts,
414
+ detectedAt: new Date(),
415
+ };
416
+ }
417
+ /**
418
+ * Compare Helm releases to local expected values.
419
+ * Uses the embedded HelmOperations directly — no HTTP round-trip.
420
+ */
421
+ async compareHelmReleases(options) {
422
+ const diffs = [];
423
+ try {
424
+ // Build a HelmOperations instance scoped to the provided kubeconfig/context/namespace
425
+ const helmOps = new HelmOperations({
426
+ kubeconfig: options.kubeconfig,
427
+ kubeContext: options.context,
428
+ namespace: options.namespace,
429
+ });
430
+ // List all deployed releases in the target namespace
431
+ const listResult = await helmOps.list({
432
+ namespace: options.namespace || 'default',
433
+ });
434
+ if (!listResult.success || !listResult.output) {
435
+ return [];
436
+ }
437
+ let releases;
438
+ try {
439
+ releases = JSON.parse(listResult.output);
440
+ }
441
+ catch {
442
+ return [];
443
+ }
444
+ // Read local expected values from workDir
445
+ const { readdir, readFile } = await import('fs/promises');
446
+ const { join } = await import('path');
447
+ const jsYaml = await import('js-yaml');
448
+ let localFiles;
449
+ try {
450
+ localFiles = await readdir(options.workDir);
451
+ }
452
+ catch {
453
+ return [];
454
+ }
455
+ for (const release of releases) {
456
+ try {
457
+ // Get actual deployed values using embedded HelmOperations
458
+ const valuesResult = await helmOps.getValues({
459
+ name: release.name,
460
+ namespace: release.namespace,
461
+ });
462
+ if (!valuesResult.success || !valuesResult.output) {
463
+ continue;
464
+ }
465
+ let actualValues;
466
+ try {
467
+ actualValues = jsYaml.load(valuesResult.output) || {};
468
+ }
469
+ catch {
470
+ continue;
471
+ }
472
+ // Find matching local values file
473
+ const valuesFile = localFiles.find(f => f === `${release.name}-values.yaml` ||
474
+ f === `${release.name}.values.yaml` ||
475
+ f === 'values.yaml');
476
+ if (valuesFile) {
477
+ const localContent = await readFile(join(options.workDir, valuesFile), 'utf-8');
478
+ const expectedValues = jsYaml.load(localContent) || {};
479
+ const valuesDiff = [];
480
+ const allKeys = new Set([...Object.keys(expectedValues), ...Object.keys(actualValues)]);
481
+ for (const key of allKeys) {
482
+ const exp = expectedValues[key];
483
+ const act = actualValues[key];
484
+ if (JSON.stringify(exp) !== JSON.stringify(act)) {
485
+ valuesDiff.push({ path: key, expected: exp, actual: act });
486
+ }
487
+ }
488
+ if (valuesDiff.length > 0) {
489
+ diffs.push({
490
+ name: release.name,
491
+ namespace: release.namespace,
492
+ valuesDiff,
493
+ });
494
+ }
495
+ }
496
+ }
497
+ catch {
498
+ // Individual release comparison failed — skip gracefully
499
+ }
500
+ }
501
+ }
502
+ catch {
503
+ // Graceful degradation if helm CLI is unavailable
504
+ logger.warn('Helm drift detection: unable to compare releases, returning empty diff');
505
+ }
506
+ return diffs;
507
+ }
508
+ /**
509
+ * Parse a HelmReleaseDiff into a ResourceDrift.
510
+ */
511
+ parseHelmDiff(diff) {
512
+ const drifts = [];
513
+ // Check chart version drift
514
+ if (diff.chartVersion && diff.chartVersion.expected !== diff.chartVersion.actual) {
515
+ drifts.push({
516
+ resourceId: `${diff.namespace}/${diff.name}`,
517
+ resourceType: 'helm-release',
518
+ resourceName: diff.name,
519
+ driftType: 'modified',
520
+ severity: 'medium',
521
+ expected: diff.chartVersion.expected,
522
+ actual: diff.chartVersion.actual,
523
+ attribute: 'chartVersion',
524
+ description: `Chart version has drifted`,
525
+ remediation: `Run 'helm upgrade' to restore the expected version`,
526
+ autoFixable: true,
527
+ });
528
+ }
529
+ // Check values drift
530
+ for (const v of diff.valuesDiff) {
531
+ drifts.push({
532
+ resourceId: `${diff.namespace}/${diff.name}`,
533
+ resourceType: 'helm-release',
534
+ resourceName: diff.name,
535
+ driftType: 'modified',
536
+ severity: 'medium',
537
+ expected: v.expected,
538
+ actual: v.actual,
539
+ attribute: v.path,
540
+ description: `Value '${v.path}' has drifted`,
541
+ remediation: `Run 'helm upgrade' with correct values`,
542
+ autoFixable: true,
543
+ });
544
+ }
545
+ return {
546
+ address: `${diff.namespace}/${diff.name}`,
547
+ provider: 'helm',
548
+ resourceType: 'helm-release',
549
+ drifts,
550
+ detectedAt: new Date(),
551
+ };
552
+ }
553
+ /**
554
+ * Determine drift severity based on resource type and attribute name.
555
+ */
556
+ determineSeverity(resourceType, attribute) {
557
+ const criticalPatterns = [
558
+ 'security_group',
559
+ 'iam',
560
+ 'policy',
561
+ 'password',
562
+ 'secret',
563
+ 'key',
564
+ 'encryption',
565
+ 'kms',
566
+ ];
567
+ const lowerType = resourceType.toLowerCase();
568
+ const lowerAttr = attribute.toLowerCase();
569
+ for (const pattern of criticalPatterns) {
570
+ if (lowerType.includes(pattern) || lowerAttr.includes(pattern)) {
571
+ return 'critical';
572
+ }
573
+ }
574
+ const highPatterns = ['vpc', 'subnet', 'instance', 'cluster', 'node', 'ingress'];
575
+ for (const pattern of highPatterns) {
576
+ if (lowerType.includes(pattern)) {
577
+ return 'high';
578
+ }
579
+ }
580
+ const mediumPatterns = ['bucket', 'storage', 'config', 'database', 'rds'];
581
+ for (const pattern of mediumPatterns) {
582
+ if (lowerType.includes(pattern)) {
583
+ return 'medium';
584
+ }
585
+ }
586
+ // Tag changes are usually low severity
587
+ if (lowerAttr === 'tags' || lowerAttr.includes('tag')) {
588
+ return 'low';
589
+ }
590
+ return 'medium';
591
+ }
592
+ /**
593
+ * Calculate the summary metrics from a list of ResourceDrift objects.
594
+ */
595
+ calculateSummary(resources) {
596
+ const byDriftType = {
597
+ added: 0,
598
+ removed: 0,
599
+ modified: 0,
600
+ unchanged: 0,
601
+ };
602
+ const bySeverity = {
603
+ critical: 0,
604
+ high: 0,
605
+ medium: 0,
606
+ low: 0,
607
+ info: 0,
608
+ };
609
+ let autoFixable = 0;
610
+ let driftedResources = 0;
611
+ for (const resource of resources) {
612
+ if (resource.drifts.length > 0) {
613
+ driftedResources++;
614
+ }
615
+ for (const drift of resource.drifts) {
616
+ byDriftType[drift.driftType]++;
617
+ bySeverity[drift.severity]++;
618
+ if (drift.autoFixable) {
619
+ autoFixable++;
620
+ }
621
+ }
622
+ }
623
+ return {
624
+ totalResources: resources.length,
625
+ driftedResources,
626
+ unchangedResources: resources.length - driftedResources,
627
+ byDriftType,
628
+ bySeverity,
629
+ autoFixable,
630
+ };
631
+ }
632
+ /**
633
+ * Generate a unique report ID.
634
+ */
635
+ generateReportId() {
636
+ return `drift_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
637
+ }
638
+ /**
639
+ * Format a drift report as a Markdown string.
640
+ */
641
+ formatReportAsMarkdown(report) {
642
+ const lines = [
643
+ `# Drift Detection Report`,
644
+ ``,
645
+ `**Provider:** ${report.provider}`,
646
+ `**Working Directory:** ${report.workDir}`,
647
+ `**Environment:** ${report.environment || 'N/A'}`,
648
+ `**Generated:** ${report.generatedAt.toISOString()}`,
649
+ `**Duration:** ${report.duration}ms`,
650
+ ``,
651
+ `## Summary`,
652
+ ``,
653
+ `| Metric | Value |`,
654
+ `|--------|-------|`,
655
+ `| Total Resources | ${report.summary.totalResources} |`,
656
+ `| Drifted Resources | ${report.summary.driftedResources} |`,
657
+ `| Unchanged Resources | ${report.summary.unchangedResources} |`,
658
+ `| Auto-Fixable | ${report.summary.autoFixable} |`,
659
+ ``,
660
+ `### By Severity`,
661
+ ``,
662
+ `| Severity | Count |`,
663
+ `|----------|-------|`,
664
+ `| Critical | ${report.summary.bySeverity.critical} |`,
665
+ `| High | ${report.summary.bySeverity.high} |`,
666
+ `| Medium | ${report.summary.bySeverity.medium} |`,
667
+ `| Low | ${report.summary.bySeverity.low} |`,
668
+ `| Info | ${report.summary.bySeverity.info} |`,
669
+ ``,
670
+ ];
671
+ if (report.resources.length > 0) {
672
+ lines.push(`## Drifted Resources`, ``);
673
+ for (const resource of report.resources) {
674
+ lines.push(`### ${resource.address}`, ``);
675
+ for (const drift of resource.drifts) {
676
+ lines.push(`- **${drift.attribute || 'Resource'}** (${drift.severity})`, ` - Type: ${drift.driftType}`, ` - ${drift.description}`, ` - Remediation: ${drift.remediation}`, ` - Auto-fixable: ${drift.autoFixable ? 'Yes' : 'No'}`, ``);
677
+ }
678
+ }
679
+ }
680
+ if (report.errors && report.errors.length > 0) {
681
+ lines.push(`## Errors`, ``);
682
+ for (const error of report.errors) {
683
+ lines.push(`- ${error}`);
684
+ }
685
+ }
686
+ return lines.join('\n');
687
+ }
688
+ }