@build-astron-co/nimbus 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Safety Policy Configuration
3
+ *
4
+ * Defines safety policies for infrastructure operations
5
+ */
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+
10
+ /**
11
+ * Risk severity levels
12
+ */
13
+ export type RiskSeverity = 'critical' | 'high' | 'medium' | 'low';
14
+
15
+ /**
16
+ * Risk definition
17
+ */
18
+ export interface Risk {
19
+ id: string;
20
+ severity: RiskSeverity;
21
+ message: string;
22
+ details?: Record<string, unknown>;
23
+ canProceed: boolean;
24
+ requiresApproval: boolean;
25
+ }
26
+
27
+ /**
28
+ * Safety check result
29
+ */
30
+ export interface SafetyCheckResult {
31
+ passed: boolean;
32
+ risks: Risk[];
33
+ blockers: Risk[];
34
+ requiresApproval: boolean;
35
+ estimatedCost?: number;
36
+ affectedResources?: string[];
37
+ }
38
+
39
+ /**
40
+ * Safety policy configuration
41
+ */
42
+ export interface SafetyPolicy {
43
+ /** Operations that always require approval */
44
+ alwaysRequireApproval: string[];
45
+ /** Protected environments that require extra caution */
46
+ protectedEnvironments: string[];
47
+ /** Cost threshold that triggers approval ($) */
48
+ costThreshold: number;
49
+ /** Operations to skip safety checks for */
50
+ skipSafetyFor: string[];
51
+ /** Custom rules */
52
+ customRules?: SafetyRule[];
53
+ }
54
+
55
+ /**
56
+ * Custom safety rule
57
+ */
58
+ export interface SafetyRule {
59
+ id: string;
60
+ name: string;
61
+ description: string;
62
+ severity: RiskSeverity;
63
+ check: (context: SafetyContext) => boolean;
64
+ message: string;
65
+ }
66
+
67
+ /**
68
+ * Context for safety checks
69
+ */
70
+ export interface SafetyContext {
71
+ operation: string;
72
+ type: 'terraform' | 'kubernetes' | 'helm' | 'aws' | 'gcp' | 'azure';
73
+ environment?: string;
74
+ resources?: string[];
75
+ estimatedCost?: number;
76
+ planOutput?: string;
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+
80
+ /**
81
+ * Default safety policy
82
+ */
83
+ export const defaultSafetyPolicy: SafetyPolicy = {
84
+ alwaysRequireApproval: ['destroy', 'delete', 'terminate', 'update', 'apply', 'create'],
85
+ protectedEnvironments: ['production', 'prod', 'prd', 'live', 'main', 'master'],
86
+ costThreshold: 500,
87
+ skipSafetyFor: ['plan', 'validate', 'show', 'list', 'get', 'describe', 'logs', 'status'],
88
+ customRules: [],
89
+ };
90
+
91
+ /**
92
+ * Load safety policy from config file or use defaults
93
+ */
94
+ export function loadSafetyPolicy(configPath?: string): SafetyPolicy {
95
+ // Try to load from workspace config
96
+ const nimbusDir = path.join(process.cwd(), '.nimbus');
97
+ const configFile = configPath || path.join(nimbusDir, 'config.yaml');
98
+
99
+ if (fs.existsSync(configFile)) {
100
+ try {
101
+ const content = fs.readFileSync(configFile, 'utf-8');
102
+ const policy = parseSafetyConfig(content);
103
+ if (policy) {
104
+ return { ...defaultSafetyPolicy, ...policy };
105
+ }
106
+ } catch {
107
+ // Use defaults
108
+ }
109
+ }
110
+
111
+ return defaultSafetyPolicy;
112
+ }
113
+
114
+ /**
115
+ * Parse safety config from YAML content
116
+ */
117
+ function parseSafetyConfig(content: string): Partial<SafetyPolicy> | null {
118
+ // Simple YAML parsing for safety section
119
+ const safetyMatch = content.match(/safety:\s*\n((?:[ \t]+.+\n?)*)/);
120
+ if (!safetyMatch) {
121
+ return null;
122
+ }
123
+
124
+ const safetySection = safetyMatch[1];
125
+ const policy: Partial<SafetyPolicy> = {};
126
+
127
+ // Parse requireApproval
128
+ const approvalMatch = safetySection.match(/requireApproval:\s*(true|false)/);
129
+ if (approvalMatch) {
130
+ if (approvalMatch[1] === 'false') {
131
+ policy.alwaysRequireApproval = [];
132
+ }
133
+ }
134
+
135
+ // Parse protectedEnvironments
136
+ const envMatch = safetySection.match(/protectedEnvironments:\s*\[([^\]]+)\]/);
137
+ if (envMatch) {
138
+ policy.protectedEnvironments = envMatch[1].split(',').map(s => s.trim().replace(/['"]/g, ''));
139
+ }
140
+
141
+ // Parse costThreshold
142
+ const costMatch = safetySection.match(/costThreshold:\s*(\d+)/);
143
+ if (costMatch) {
144
+ policy.costThreshold = parseInt(costMatch[1], 10);
145
+ }
146
+
147
+ return policy;
148
+ }
149
+
150
+ /**
151
+ * Check if an operation requires safety checks
152
+ */
153
+ export function requiresSafetyCheck(
154
+ operation: string,
155
+ policy: SafetyPolicy = defaultSafetyPolicy
156
+ ): boolean {
157
+ const normalizedOp = operation.toLowerCase();
158
+ return !policy.skipSafetyFor.some(skip => normalizedOp.includes(skip.toLowerCase()));
159
+ }
160
+
161
+ /**
162
+ * Check if an operation requires approval
163
+ */
164
+ export function requiresApproval(
165
+ operation: string,
166
+ context: SafetyContext,
167
+ policy: SafetyPolicy = defaultSafetyPolicy
168
+ ): boolean {
169
+ const normalizedOp = operation.toLowerCase();
170
+
171
+ // Check if operation is in always require list
172
+ if (policy.alwaysRequireApproval.some(op => normalizedOp.includes(op.toLowerCase()))) {
173
+ return true;
174
+ }
175
+
176
+ // Check if environment is protected
177
+ if (context.environment) {
178
+ const normalizedEnv = context.environment.toLowerCase();
179
+ if (policy.protectedEnvironments.some(env => normalizedEnv.includes(env.toLowerCase()))) {
180
+ return true;
181
+ }
182
+ }
183
+
184
+ // Check cost threshold
185
+ if (context.estimatedCost && context.estimatedCost > policy.costThreshold) {
186
+ return true;
187
+ }
188
+
189
+ return false;
190
+ }
191
+
192
+ /**
193
+ * Evaluate safety for an operation
194
+ */
195
+ export function evaluateSafety(
196
+ context: SafetyContext,
197
+ policy: SafetyPolicy = defaultSafetyPolicy
198
+ ): SafetyCheckResult {
199
+ const risks: Risk[] = [];
200
+ const blockers: Risk[] = [];
201
+ let requiresApprovalFlag = false;
202
+
203
+ // Check for destroy/delete operations
204
+ if (['destroy', 'delete', 'terminate'].some(op => context.operation.toLowerCase().includes(op))) {
205
+ const risk: Risk = {
206
+ id: 'destructive-operation',
207
+ severity: 'critical',
208
+ message: `Destructive operation: ${context.operation}`,
209
+ canProceed: true,
210
+ requiresApproval: true,
211
+ };
212
+ risks.push(risk);
213
+ requiresApprovalFlag = true;
214
+ }
215
+
216
+ // Check for protected environment
217
+ if (context.environment) {
218
+ const normalizedEnv = context.environment.toLowerCase();
219
+ if (policy.protectedEnvironments.some(env => normalizedEnv.includes(env.toLowerCase()))) {
220
+ const risk: Risk = {
221
+ id: 'protected-environment',
222
+ severity: 'high',
223
+ message: `Operating on protected environment: ${context.environment}`,
224
+ canProceed: true,
225
+ requiresApproval: true,
226
+ };
227
+ risks.push(risk);
228
+ requiresApprovalFlag = true;
229
+ }
230
+ }
231
+
232
+ // Check cost threshold
233
+ if (context.estimatedCost && context.estimatedCost > policy.costThreshold) {
234
+ const risk: Risk = {
235
+ id: 'high-cost',
236
+ severity: 'high',
237
+ message: `Estimated cost $${context.estimatedCost} exceeds threshold $${policy.costThreshold}`,
238
+ details: {
239
+ estimatedCost: context.estimatedCost,
240
+ threshold: policy.costThreshold,
241
+ },
242
+ canProceed: true,
243
+ requiresApproval: true,
244
+ };
245
+ risks.push(risk);
246
+ requiresApprovalFlag = true;
247
+ }
248
+
249
+ // Check for mutations in apply
250
+ if (context.operation.toLowerCase().includes('apply')) {
251
+ const risk: Risk = {
252
+ id: 'mutation-operation',
253
+ severity: 'medium',
254
+ message: 'This operation will modify infrastructure',
255
+ canProceed: true,
256
+ requiresApproval: true,
257
+ };
258
+ risks.push(risk);
259
+ requiresApprovalFlag = true;
260
+ }
261
+
262
+ // Check custom rules
263
+ if (policy.customRules) {
264
+ for (const rule of policy.customRules) {
265
+ try {
266
+ if (rule.check(context)) {
267
+ const risk: Risk = {
268
+ id: rule.id,
269
+ severity: rule.severity,
270
+ message: rule.message,
271
+ canProceed: rule.severity !== 'critical',
272
+ requiresApproval: rule.severity === 'critical' || rule.severity === 'high',
273
+ };
274
+
275
+ if (!risk.canProceed) {
276
+ blockers.push(risk);
277
+ } else {
278
+ risks.push(risk);
279
+ if (risk.requiresApproval) {
280
+ requiresApprovalFlag = true;
281
+ }
282
+ }
283
+ }
284
+ } catch {
285
+ // Skip rule on error
286
+ }
287
+ }
288
+ }
289
+
290
+ // Analyze plan output for resource changes
291
+ if (context.planOutput) {
292
+ const changes = analyzePlanOutput(context.planOutput);
293
+ if (changes.destroy > 0) {
294
+ const risk: Risk = {
295
+ id: 'resource-destruction',
296
+ severity: 'high',
297
+ message: `${changes.destroy} resources will be destroyed`,
298
+ details: { count: changes.destroy },
299
+ canProceed: true,
300
+ requiresApproval: true,
301
+ };
302
+ risks.push(risk);
303
+ requiresApprovalFlag = true;
304
+ }
305
+ }
306
+
307
+ return {
308
+ passed: blockers.length === 0,
309
+ risks,
310
+ blockers,
311
+ requiresApproval: requiresApprovalFlag,
312
+ estimatedCost: context.estimatedCost,
313
+ affectedResources: context.resources,
314
+ };
315
+ }
316
+
317
+ /**
318
+ * Analyze Terraform plan output for changes
319
+ */
320
+ function analyzePlanOutput(output: string): { add: number; change: number; destroy: number } {
321
+ const addMatch = output.match(/(\d+) to add/);
322
+ const changeMatch = output.match(/(\d+) to change/);
323
+ const destroyMatch = output.match(/(\d+) to destroy/);
324
+
325
+ return {
326
+ add: addMatch ? parseInt(addMatch[1], 10) : 0,
327
+ change: changeMatch ? parseInt(changeMatch[1], 10) : 0,
328
+ destroy: destroyMatch ? parseInt(destroyMatch[1], 10) : 0,
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Format risks for display
334
+ */
335
+ export function formatRisks(risks: Risk[]): string[] {
336
+ return risks.map(risk => {
337
+ const icon = getSeverityIcon(risk.severity);
338
+ return `${icon} [${risk.severity.toUpperCase()}] ${risk.message}`;
339
+ });
340
+ }
341
+
342
+ /**
343
+ * Get icon for severity level
344
+ */
345
+ function getSeverityIcon(severity: RiskSeverity): string {
346
+ switch (severity) {
347
+ case 'critical':
348
+ return '🔴';
349
+ case 'high':
350
+ return '🟠';
351
+ case 'medium':
352
+ return '🟡';
353
+ case 'low':
354
+ return '🔵';
355
+ default:
356
+ return '⚪';
357
+ }
358
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Zod Config Schema
3
+ *
4
+ * Validates NimbusConfig at load time and on set() operations.
5
+ * Uses safeParse to gracefully handle invalid configs without crashing.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ export const WorkspaceConfigSchema = z.object({
11
+ defaultProvider: z.string().optional(),
12
+ outputDirectory: z.string().optional(),
13
+ name: z.string().optional(),
14
+ });
15
+
16
+ export const CostOptimizationConfigSchema = z.object({
17
+ enabled: z.boolean().optional(),
18
+ cheap_model: z.string().optional(),
19
+ expensive_model: z.string().optional(),
20
+ // A5/A6: task-category routing arrays
21
+ use_cheap_model_for: z.array(z.string()).optional(),
22
+ use_expensive_model_for: z.array(z.string()).optional(),
23
+ });
24
+
25
+ // A3/A4: per-provider configuration map entry
26
+ export const ProviderConfigSchema = z.object({
27
+ api_key: z.string().optional(),
28
+ base_url: z.string().url().optional(),
29
+ models: z.array(z.string()).optional(),
30
+ });
31
+
32
+ // A7/A8: provider fallback configuration
33
+ export const FallbackConfigSchema = z.object({
34
+ enabled: z.boolean().optional(),
35
+ providers: z.array(z.string()).optional(),
36
+ });
37
+
38
+ export const LLMConfigSchema = z.object({
39
+ defaultModel: z.string().optional(),
40
+ // A2: explicit default provider name
41
+ default_provider: z.string().optional(),
42
+ temperature: z.number().min(0).max(1).optional(),
43
+ maxTokens: z.number().positive().optional(),
44
+ cost_optimization: CostOptimizationConfigSchema.optional(),
45
+ // A3/A4: per-provider configuration keyed by provider name
46
+ providers: z.record(z.string(), ProviderConfigSchema).optional(),
47
+ // A7/A8: fallback provider chain
48
+ fallback: FallbackConfigSchema.optional(),
49
+ });
50
+
51
+ export const HistoryConfigSchema = z.object({
52
+ maxEntries: z.number().positive().optional(),
53
+ enabled: z.boolean().optional(),
54
+ });
55
+
56
+ export const AutoApproveConfigSchema = z.object({
57
+ read: z.boolean().optional(),
58
+ generate: z.boolean().optional(),
59
+ create: z.boolean().optional(),
60
+ update: z.boolean().optional(),
61
+ delete: z.boolean().optional(),
62
+ });
63
+
64
+ export const SafetyConfigSchema = z.object({
65
+ requireConfirmation: z.boolean().optional(),
66
+ dryRunByDefault: z.boolean().optional(),
67
+ auto_approve: AutoApproveConfigSchema.optional(),
68
+ });
69
+
70
+ export const UIConfigSchema = z.object({
71
+ theme: z.enum(['dark', 'light', 'auto']).optional(),
72
+ colors: z.boolean().optional(),
73
+ spinner: z.enum(['dots', 'line', 'simple']).optional(),
74
+ });
75
+
76
+ export const PersonaConfigSchema = z.object({
77
+ // A9: added 'custom' to the mode enum
78
+ mode: z
79
+ .enum(['professional', 'assistant', 'expert', 'standard', 'concise', 'detailed', 'custom'])
80
+ .optional(),
81
+ verbosity: z.enum(['minimal', 'normal', 'detailed', 'verbose']).optional(),
82
+ custom: z.string().optional(),
83
+ });
84
+
85
+ export const CloudProviderConfigSchema = z.object({
86
+ default_region: z.string().optional(),
87
+ default_profile: z.string().optional(),
88
+ default_project: z.string().optional(),
89
+ default_subscription: z.string().optional(),
90
+ });
91
+
92
+ export const CloudConfigSchema = z.object({
93
+ default_provider: z.enum(['aws', 'gcp', 'azure']).optional(),
94
+ aws: CloudProviderConfigSchema.optional(),
95
+ gcp: CloudProviderConfigSchema.optional(),
96
+ azure: CloudProviderConfigSchema.optional(),
97
+ });
98
+
99
+ export const TerraformDefaultsSchema = z.object({
100
+ default_backend: z.enum(['s3', 'gcs', 'azurerm', 'local']).optional(),
101
+ state_bucket: z.string().optional(),
102
+ lock_table: z.string().optional(),
103
+ });
104
+
105
+ export const KubernetesDefaultsSchema = z.object({
106
+ default_context: z.string().optional(),
107
+ default_namespace: z.string().optional(),
108
+ });
109
+
110
+ export const NimbusConfigSchema = z.object({
111
+ version: z.number().optional(),
112
+ // A1: opt-in/out of anonymous telemetry
113
+ telemetry: z.boolean().optional(),
114
+ workspace: WorkspaceConfigSchema.optional(),
115
+ llm: LLMConfigSchema.optional(),
116
+ history: HistoryConfigSchema.optional(),
117
+ safety: SafetyConfigSchema.optional(),
118
+ ui: UIConfigSchema.optional(),
119
+ persona: PersonaConfigSchema.optional(),
120
+ cloud: CloudConfigSchema.optional(),
121
+ terraform: TerraformDefaultsSchema.optional(),
122
+ kubernetes: KubernetesDefaultsSchema.optional(),
123
+ });
124
+
125
+ export type ValidatedNimbusConfig = z.infer<typeof NimbusConfigSchema>;