@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
@@ -5,7 +5,7 @@
5
5
  * structures for both standard and DevOps tools.
6
6
  */
7
7
 
8
- import { describe, test, expect, beforeEach } from 'bun:test';
8
+ import { describe, test, it, expect, beforeEach } from 'vitest';
9
9
  import { z } from 'zod';
10
10
  import {
11
11
  ToolRegistry,
@@ -14,7 +14,7 @@ import {
14
14
  type ToolDefinition,
15
15
  } from '../tools/schemas/types';
16
16
  import { standardTools } from '../tools/schemas/standard';
17
- import { devopsTools } from '../tools/schemas/devops';
17
+ import { devopsTools, formatKubectlPodsOutput, formatHelmListOutput } from '../tools/schemas/devops';
18
18
 
19
19
  // ---------------------------------------------------------------------------
20
20
  // Helpers
@@ -353,8 +353,8 @@ describe('Tool counts and metadata', () => {
353
353
  expect(standardTools).toHaveLength(12);
354
354
  });
355
355
 
356
- test('devopsTools has exactly 9 tools', () => {
357
- expect(devopsTools).toHaveLength(9);
356
+ test('devopsTools has exactly 28 tools', () => {
357
+ expect(devopsTools).toHaveLength(28);
358
358
  });
359
359
 
360
360
  test('all standard tools have category "standard"', () => {
@@ -395,3 +395,208 @@ describe('Tool counts and metadata', () => {
395
395
  }
396
396
  });
397
397
  });
398
+
399
+ // ---------------------------------------------------------------------------
400
+ // C2: infraContext in ToolExecuteContext
401
+ // ---------------------------------------------------------------------------
402
+
403
+ describe('infraContext in ToolExecuteContext (C2)', () => {
404
+ it('ToolExecuteContext type has infraContext field', async () => {
405
+ const { readFileSync } = await import('node:fs');
406
+ const { join } = await import('node:path');
407
+ const src = readFileSync(join(process.cwd(), 'src/tools/schemas/types.ts'), 'utf-8');
408
+ expect(src).toContain('infraContext?:');
409
+ });
410
+
411
+ it('kubectl tool uses contextFlag from infraContext', async () => {
412
+ const { readFileSync } = await import('node:fs');
413
+ const { join } = await import('node:path');
414
+ const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
415
+ expect(src).toContain('ctx?.infraContext?.kubectlContext');
416
+ });
417
+
418
+ it('terraform tool reads sessionWorkspace from infraContext', async () => {
419
+ const { readFileSync } = await import('node:fs');
420
+ const { join } = await import('node:path');
421
+ const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
422
+ expect(src).toContain('ctx?.infraContext?.terraformWorkspace');
423
+ });
424
+
425
+ it('generateInfraTool is in devopsTools array', async () => {
426
+ const { devopsTools } = await import('../tools/schemas/devops');
427
+ const names = devopsTools.map(t => t.name);
428
+ expect(names).toContain('generate_infra');
429
+ });
430
+
431
+ it('generateInfraTool has ask_once permissionTier', async () => {
432
+ const { devopsTools } = await import('../tools/schemas/devops');
433
+ const tool = devopsTools.find(t => t.name === 'generate_infra');
434
+ expect(tool?.permissionTier).toBe('ask_once');
435
+ });
436
+
437
+ it('generateInfraTool schema accepts terraform/kubernetes/helm types', async () => {
438
+ const { devopsTools } = await import('../tools/schemas/devops');
439
+ const tool = devopsTools.find(t => t.name === 'generate_infra');
440
+ expect(tool).toBeDefined();
441
+ // The schema should parse valid input without throwing
442
+ const { z } = await import('zod');
443
+ // Just verify the tool exists and has a schema
444
+ expect(tool?.inputSchema).toBeDefined();
445
+ });
446
+ });
447
+
448
+ // ---------------------------------------------------------------------------
449
+ // M2: Docker build streaming
450
+ // ---------------------------------------------------------------------------
451
+
452
+ describe('docker build streaming (M2)', () => {
453
+ it('dockerTool execute accepts ctx parameter', async () => {
454
+ const { readFileSync } = await import('node:fs');
455
+ const { join } = await import('node:path');
456
+ const src = readFileSync(join(process.cwd(), 'src/tools/schemas/devops.ts'), 'utf-8');
457
+ expect(src).toContain("input.action === 'build' && ctx?.onProgress");
458
+ });
459
+
460
+ it('docker build output filter keeps Step N/M lines', () => {
461
+ // Inline the filter logic to unit test it
462
+ const filterDockerBuildLine = (line: string): boolean => {
463
+ const trimmed = line.trim();
464
+ if (!trimmed) return false;
465
+ return /^Step\s+\d+\/\d+/i.test(trimmed) ||
466
+ /---> Using cache/i.test(trimmed) ||
467
+ /Successfully built/i.test(trimmed) ||
468
+ /Successfully tagged/i.test(trimmed) ||
469
+ /error/i.test(trimmed) ||
470
+ /warning/i.test(trimmed);
471
+ };
472
+
473
+ expect(filterDockerBuildLine('Step 3/12: RUN npm install')).toBe(true);
474
+ expect(filterDockerBuildLine(' ---> Using cache')).toBe(true);
475
+ expect(filterDockerBuildLine('Successfully built abc123')).toBe(true);
476
+ expect(filterDockerBuildLine('Successfully tagged myapp:latest')).toBe(true);
477
+ expect(filterDockerBuildLine(' ---> sha256:abc123def456')).toBe(false);
478
+ expect(filterDockerBuildLine('Removing intermediate container abc123')).toBe(false);
479
+ });
480
+ });
481
+
482
+ // ===========================================================================
483
+ // H6: kubectl pods output formatting
484
+ // ===========================================================================
485
+
486
+ describe('H6 — formatKubectlPodsOutput', () => {
487
+ const sampleOutput = [
488
+ 'NAME READY STATUS RESTARTS AGE',
489
+ 'web-7d4f6b9c5-abc12 1/1 Running 0 2d',
490
+ 'worker-6b5c8d7f4-xyz99 0/1 CrashLoopBackOff 5 1h',
491
+ 'db-5f4e3d2c1-pqr55 0/1 Pending 0 30m',
492
+ 'init-job-abc 0/1 Init:0/1 0 5m',
493
+ 'old-pod-done 0/1 Completed 0 7d',
494
+ 'failing-app-xyz 0/1 Error 3 2h',
495
+ 'evicted-pod 0/1 Evicted 0 1d',
496
+ ].join('\n');
497
+
498
+ it('prefixes Running pods with [OK]', () => {
499
+ const result = formatKubectlPodsOutput(sampleOutput);
500
+ expect(result).toContain('[OK]');
501
+ const lines = result.split('\n');
502
+ const runningLine = lines.find(l => l.includes('Running'));
503
+ expect(runningLine).toMatch(/^\[OK\]/);
504
+ });
505
+
506
+ it('prefixes CrashLoopBackOff pods with [XX]', () => {
507
+ const result = formatKubectlPodsOutput(sampleOutput);
508
+ const lines = result.split('\n');
509
+ const crashLine = lines.find(l => l.includes('CrashLoopBackOff'));
510
+ expect(crashLine).toMatch(/^\[XX\]/);
511
+ });
512
+
513
+ it('prefixes Pending pods with [!!]', () => {
514
+ const result = formatKubectlPodsOutput(sampleOutput);
515
+ const lines = result.split('\n');
516
+ const pendingLine = lines.find(l => l.includes('Pending'));
517
+ expect(pendingLine).toMatch(/^\[!!\]/);
518
+ });
519
+
520
+ it('prefixes Error pods with [XX]', () => {
521
+ const result = formatKubectlPodsOutput(sampleOutput);
522
+ const lines = result.split('\n');
523
+ const errorLine = lines.find(l => l.includes('Error') && !l.includes('CrashLoop'));
524
+ expect(errorLine).toMatch(/^\[XX\]/);
525
+ });
526
+
527
+ it('prefixes Completed pods with [OK]', () => {
528
+ const result = formatKubectlPodsOutput(sampleOutput);
529
+ const lines = result.split('\n');
530
+ const completedLine = lines.find(l => l.includes('Completed'));
531
+ expect(completedLine).toMatch(/^\[OK\]/);
532
+ });
533
+
534
+ it('preserves header line without emoji prefix', () => {
535
+ const result = formatKubectlPodsOutput(sampleOutput);
536
+ const headerLine = result.split('\n')[0];
537
+ expect(headerLine).toBe('NAME READY STATUS RESTARTS AGE');
538
+ });
539
+
540
+ it('returns original line for empty input', () => {
541
+ const result = formatKubectlPodsOutput('');
542
+ expect(result).toBe('');
543
+ });
544
+
545
+ it('handles single Running pod', () => {
546
+ const input = 'NAME READY STATUS RESTARTS AGE\nmypod 1/1 Running 0 1m';
547
+ const result = formatKubectlPodsOutput(input);
548
+ const lines = result.split('\n');
549
+ expect(lines[1]).toMatch(/^\[OK\]/);
550
+ });
551
+ });
552
+
553
+ // ===========================================================================
554
+ // H6: helm list output formatting
555
+ // ===========================================================================
556
+
557
+ describe('H6 — formatHelmListOutput', () => {
558
+ const sampleJson = JSON.stringify([
559
+ { name: 'nginx', namespace: 'default', revision: '3', status: 'deployed', chart: 'nginx-1.2.0', app_version: '1.21', updated: '2024-01-01' },
560
+ { name: 'redis', namespace: 'cache', revision: '1', status: 'failed', chart: 'redis-7.0.0', app_version: '7.0', updated: '2024-01-02' },
561
+ { name: 'postgres', namespace: 'db', revision: '2', status: 'pending-upgrade', chart: 'postgresql-12.1.0', app_version: '15', updated: '2024-01-03' },
562
+ ]);
563
+
564
+ it('prefixes deployed releases with [OK]', () => {
565
+ const result = formatHelmListOutput(sampleJson);
566
+ expect(result).toContain('[OK]');
567
+ const lines = result.split('\n');
568
+ const deployedLine = lines.find(l => l.includes('deployed'));
569
+ expect(deployedLine).toMatch(/^\[OK\]/);
570
+ });
571
+
572
+ it('prefixes failed releases with [XX]', () => {
573
+ const result = formatHelmListOutput(sampleJson);
574
+ const lines = result.split('\n');
575
+ const failedLine = lines.find(l => l.includes('failed'));
576
+ expect(failedLine).toMatch(/^\[XX\]/);
577
+ });
578
+
579
+ it('prefixes pending releases with [!!]', () => {
580
+ const result = formatHelmListOutput(sampleJson);
581
+ const lines = result.split('\n');
582
+ const pendingLine = lines.find(l => l.includes('pending'));
583
+ expect(pendingLine).toMatch(/^\[!!\]/);
584
+ });
585
+
586
+ it('returns "No Helm releases found." for empty array', () => {
587
+ const result = formatHelmListOutput('[]');
588
+ expect(result).toBe('No Helm releases found.');
589
+ });
590
+
591
+ it('falls back to raw string for invalid JSON', () => {
592
+ const result = formatHelmListOutput('not json');
593
+ expect(result).toBe('not json');
594
+ });
595
+
596
+ it('includes release name and namespace in output', () => {
597
+ const result = formatHelmListOutput(sampleJson);
598
+ expect(result).toContain('nginx');
599
+ expect(result).toContain('default');
600
+ });
601
+ });
602
+
@@ -9,13 +9,14 @@
9
9
  * tests fast and hermetic.
10
10
  */
11
11
 
12
- import { describe, it, expect } from 'bun:test';
13
- import { join } from 'node:path';
12
+ import { describe, it, expect } from 'vitest';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { join, dirname } from 'node:path';
14
15
  import { FileSystemOperations } from '../tools/file-ops';
15
16
  import { GitOperations } from '../tools/git-ops';
16
17
 
17
18
  // The repository root is two levels above this test file: src/__tests__/ -> src/ -> repo-root
18
- const REPO_ROOT = join(import.meta.dir, '..', '..');
19
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
19
20
 
20
21
  // ---------------------------------------------------------------------------
21
22
  // FileSystemOperations
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Version Command Tests — L3
3
+ *
4
+ * Validates that `nimbus version --json` returns valid JSON with
5
+ * required fields: version, node, platform, arch.
6
+ *
7
+ * Also validates that `nimbus update` is an alias for `upgrade` in cli.ts.
8
+ */
9
+
10
+ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // L3: version --json
14
+ // ---------------------------------------------------------------------------
15
+
16
+ describe('versionCommand --json (L3)', () => {
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ let consoleSpy: any;
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ let exitSpy: any;
21
+
22
+ beforeEach(() => {
23
+ consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
24
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => {}) as never);
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ test('outputs valid JSON to stdout when json option is true', async () => {
32
+ const { versionCommand } = await import('../commands/version');
33
+ await versionCommand({ json: true });
34
+
35
+ // process.exit(0) should have been called
36
+ expect(exitSpy).toHaveBeenCalledWith(0);
37
+
38
+ // console.log should have been called with JSON
39
+ expect(consoleSpy).toHaveBeenCalled();
40
+
41
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
42
+ expect(() => JSON.parse(jsonArg)).not.toThrow();
43
+ });
44
+
45
+ test('JSON output contains required fields: version, node, platform, arch', async () => {
46
+ const { versionCommand } = await import('../commands/version');
47
+ await versionCommand({ json: true });
48
+
49
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
50
+ const parsed = JSON.parse(jsonArg);
51
+
52
+ expect(parsed).toHaveProperty('version');
53
+ expect(parsed).toHaveProperty('node');
54
+ expect(parsed).toHaveProperty('platform');
55
+ expect(parsed).toHaveProperty('arch');
56
+ });
57
+
58
+ test('JSON version field is a non-empty string', async () => {
59
+ const { versionCommand } = await import('../commands/version');
60
+ await versionCommand({ json: true });
61
+
62
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
63
+ const parsed = JSON.parse(jsonArg);
64
+
65
+ expect(typeof parsed.version).toBe('string');
66
+ expect(parsed.version.length).toBeGreaterThan(0);
67
+ });
68
+
69
+ test('JSON node field matches process.version', async () => {
70
+ const { versionCommand } = await import('../commands/version');
71
+ await versionCommand({ json: true });
72
+
73
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
74
+ const parsed = JSON.parse(jsonArg);
75
+
76
+ expect(parsed.node).toBe(process.version);
77
+ });
78
+
79
+ test('JSON platform field matches process.platform', async () => {
80
+ const { versionCommand } = await import('../commands/version');
81
+ await versionCommand({ json: true });
82
+
83
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
84
+ const parsed = JSON.parse(jsonArg);
85
+
86
+ expect(parsed.platform).toBe(process.platform);
87
+ });
88
+
89
+ test('JSON arch field matches process.arch', async () => {
90
+ const { versionCommand } = await import('../commands/version');
91
+ await versionCommand({ json: true });
92
+
93
+ const jsonArg = consoleSpy.mock.calls[0][0] as string;
94
+ const parsed = JSON.parse(jsonArg);
95
+
96
+ expect(parsed.arch).toBe(process.arch);
97
+ });
98
+
99
+ test('without --json flag, does not call process.exit(0)', async () => {
100
+ const { versionCommand } = await import('../commands/version');
101
+ await versionCommand({ json: false });
102
+
103
+ expect(exitSpy).not.toHaveBeenCalledWith(0);
104
+ });
105
+ });
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // L1: `nimbus update` alias for `upgrade`
109
+ // ---------------------------------------------------------------------------
110
+
111
+ describe('nimbus update alias (L1)', () => {
112
+ test('runCommand treats "update" the same as "upgrade"', async () => {
113
+ // Verify that both 'upgrade' and 'update' route to the upgrade handler
114
+ // by checking the COMMAND_ALIASES or the if-condition in cli.ts.
115
+ // We test this by importing runCommand and stubbing upgradeCommand.
116
+
117
+ const upgradeMod = await import('../commands/upgrade');
118
+ const upgradeSpy = vi.spyOn(upgradeMod, 'upgradeCommand').mockResolvedValue(undefined);
119
+
120
+ const { runCommand } = await import('../cli');
121
+ await runCommand(['update', '--check']);
122
+
123
+ expect(upgradeSpy).toHaveBeenCalledWith(expect.objectContaining({ check: true }));
124
+
125
+ upgradeSpy.mockRestore();
126
+ });
127
+
128
+ test('runCommand treats "upgrade" the same as "update"', async () => {
129
+ const upgradeMod = await import('../commands/upgrade');
130
+ const upgradeSpy = vi.spyOn(upgradeMod, 'upgradeCommand').mockResolvedValue(undefined);
131
+
132
+ const { runCommand } = await import('../cli');
133
+ await runCommand(['upgrade', '--check']);
134
+
135
+ expect(upgradeSpy).toHaveBeenCalledWith(expect.objectContaining({ check: true }));
136
+
137
+ upgradeSpy.mockRestore();
138
+ });
139
+ });
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // L2: --quiet flag for doctor
143
+ // ---------------------------------------------------------------------------
144
+
145
+ describe('doctorCommand --quiet (L2)', () => {
146
+ test('DoctorOptions interface accepts quiet field', async () => {
147
+ const { doctorCommand } = await import('../commands/doctor');
148
+ // Just verifying the function can be called with quiet option without type errors
149
+ expect(typeof doctorCommand).toBe('function');
150
+ });
151
+ });
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // L2: --quiet flag for logs
155
+ // ---------------------------------------------------------------------------
156
+
157
+ describe('parseLogsArgs --quiet (L2)', () => {
158
+ test('parses -q as quiet option', async () => {
159
+ const { parseLogsArgs } = await import('../commands/logs');
160
+ const { options } = parseLogsArgs(['my-pod', '-q']);
161
+ expect(options.quiet).toBe(true);
162
+ });
163
+
164
+ test('parses --quiet as quiet option', async () => {
165
+ const { parseLogsArgs } = await import('../commands/logs');
166
+ const { options } = parseLogsArgs(['my-pod', '--quiet']);
167
+ expect(options.quiet).toBe(true);
168
+ });
169
+
170
+ test('quiet is false when not provided', async () => {
171
+ const { parseLogsArgs } = await import('../commands/logs');
172
+ const { options } = parseLogsArgs(['my-pod']);
173
+ expect(options.quiet).toBeUndefined();
174
+ });
175
+
176
+ test('parses --quiet along with other flags', async () => {
177
+ const { parseLogsArgs } = await import('../commands/logs');
178
+ const { pod, options } = parseLogsArgs(['my-pod', '-n', 'default', '--quiet', '-f']);
179
+ expect(pod).toBe('my-pod');
180
+ expect(options.namespace).toBe('default');
181
+ expect(options.quiet).toBe(true);
182
+ expect(options.follow).toBe(true);
183
+ });
184
+ });
@@ -5,7 +5,7 @@
5
5
  * non-empty BUILD_DATE string.
6
6
  */
7
7
 
8
- import { describe, it, expect } from 'bun:test';
8
+ import { describe, it, expect } from 'vitest';
9
9
  import { VERSION, BUILD_DATE } from '../version';
10
10
 
11
11
  describe('version', () => {
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Watch Command Tests (M3)
3
+ *
4
+ * Tests the nimbus watch command which watches files and triggers agent runs.
5
+ * Validates glob matching, option parsing, and command export.
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Import and verify exports
14
+ // ---------------------------------------------------------------------------
15
+
16
+ describe('watchCommand exports', () => {
17
+ it('watchCommand is exported from commands/watch.ts', async () => {
18
+ const { watchCommand } = await import('../commands/watch');
19
+ expect(typeof watchCommand).toBe('function');
20
+ });
21
+
22
+ it('WatchOptions interface supports required fields', () => {
23
+ // Type-level check via source inspection
24
+ const src = readFileSync(join(process.cwd(), 'src/commands/watch.ts'), 'utf-8');
25
+ expect(src).toContain('glob: string');
26
+ expect(src).toContain('run?:');
27
+ expect(src).toContain('debounce?:');
28
+ expect(src).toContain('autoApprove?:');
29
+ expect(src).toContain('maxRuns?:');
30
+ });
31
+ });
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Glob matching logic (inline reproduction)
35
+ // ---------------------------------------------------------------------------
36
+
37
+ function matchGlob(filename: string, pattern: string): boolean {
38
+ const f = filename.replace(/\\/g, '/');
39
+ const p = pattern.replace(/\\/g, '/');
40
+
41
+ const regexStr = p
42
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
43
+ .replace(/\*\*/g, '__GLOBSTAR__')
44
+ .replace(/\*/g, '[^/]*')
45
+ .replace(/\?/g, '[^/]')
46
+ .replace(/__GLOBSTAR__/g, '.*');
47
+
48
+ const re = new RegExp(`^${regexStr}$`);
49
+ const { basename } = require('node:path');
50
+ const base = basename(f);
51
+ return re.test(f) || (!/\//.test(p) && re.test(base));
52
+ }
53
+
54
+ describe('matchGlob (M3 — watch command glob matching)', () => {
55
+ it('matches *.tf against main.tf', () => {
56
+ expect(matchGlob('main.tf', '*.tf')).toBe(true);
57
+ });
58
+
59
+ it('matches *.tf against variables.tf', () => {
60
+ expect(matchGlob('variables.tf', '*.tf')).toBe(true);
61
+ });
62
+
63
+ it('does not match *.tf against main.ts', () => {
64
+ expect(matchGlob('main.ts', '*.tf')).toBe(false);
65
+ });
66
+
67
+ it('matches *.yaml against deploy.yaml', () => {
68
+ expect(matchGlob('deploy.yaml', '*.yaml')).toBe(true);
69
+ });
70
+
71
+ it('matches *.yaml against deploy.yml', () => {
72
+ expect(matchGlob('deploy.yml', '*.yaml')).toBe(false);
73
+ });
74
+
75
+ it('matches *.yml against deploy.yml', () => {
76
+ expect(matchGlob('deploy.yml', '*.yml')).toBe(true);
77
+ });
78
+
79
+ it('matches src/** against src/index.ts', () => {
80
+ expect(matchGlob('src/index.ts', 'src/**')).toBe(true);
81
+ });
82
+
83
+ it('matches src/** against src/utils/helper.ts', () => {
84
+ expect(matchGlob('src/utils/helper.ts', 'src/**')).toBe(true);
85
+ });
86
+
87
+ it('does not match src/** against lib/index.ts', () => {
88
+ expect(matchGlob('lib/index.ts', 'src/**')).toBe(false);
89
+ });
90
+
91
+ it('matches Dockerfile exactly', () => {
92
+ expect(matchGlob('Dockerfile', 'Dockerfile')).toBe(true);
93
+ });
94
+
95
+ it('matches nested *.tf in subdir', () => {
96
+ expect(matchGlob('modules/vpc/main.tf', '*.tf')).toBe(true);
97
+ });
98
+ });
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // CLI wiring — watch is registered in cli.ts
102
+ // ---------------------------------------------------------------------------
103
+
104
+ describe('watch command wired in CLI (M3)', () => {
105
+ it('cli.ts contains nimbus watch handler', () => {
106
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
107
+ expect(src).toContain("command === 'watch'");
108
+ });
109
+
110
+ it('cli.ts imports watchCommand from commands/watch', () => {
111
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
112
+ expect(src).toContain("import('./commands/watch')");
113
+ });
114
+
115
+ it('cli.ts parses --run flag for watch', () => {
116
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
117
+ expect(src).toContain("watchOptions.run = args[++i]");
118
+ });
119
+
120
+ it('cli.ts parses --debounce flag for watch', () => {
121
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
122
+ expect(src).toContain("watchOptions.debounce");
123
+ });
124
+
125
+ it('cli.ts parses --auto-approve flag for watch', () => {
126
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
127
+ expect(src).toContain("watchOptions.autoApprove = true");
128
+ });
129
+ });
@@ -33,6 +33,15 @@ export interface CompactionOptions {
33
33
  focusArea?: string;
34
34
  /** Model to use for compaction (default: haiku). */
35
35
  model?: string;
36
+ /** H3: Infrastructure context to preserve during compaction. */
37
+ infraContext?: {
38
+ terraformWorkspace?: string;
39
+ kubectlContext?: string;
40
+ awsProfile?: string;
41
+ awsRegion?: string;
42
+ gcpProject?: string;
43
+ azureSubscription?: string;
44
+ };
36
45
  }
37
46
 
38
47
  // ---------------------------------------------------------------------------
@@ -107,6 +116,21 @@ export async function runCompaction(
107
116
  userPrompt += `\n\nPay special attention to: ${options.focusArea}`;
108
117
  }
109
118
 
119
+ // H3: Inject current infraContext into compaction prompt so it's never omitted
120
+ if (options.infraContext) {
121
+ const ic = options.infraContext;
122
+ const infraLines: string[] = [];
123
+ if (ic.terraformWorkspace) infraLines.push(`- Terraform workspace: ${ic.terraformWorkspace}`);
124
+ if (ic.kubectlContext) infraLines.push(`- kubectl context: ${ic.kubectlContext}`);
125
+ if (ic.awsProfile) infraLines.push(`- AWS profile: ${ic.awsProfile}`);
126
+ if (ic.awsRegion) infraLines.push(`- AWS region: ${ic.awsRegion}`);
127
+ if (ic.gcpProject) infraLines.push(`- GCP project: ${ic.gcpProject}`);
128
+ if (ic.azureSubscription) infraLines.push(`- Azure subscription: ${ic.azureSubscription}`);
129
+ if (infraLines.length > 0) {
130
+ userPrompt += `\n\n## ALWAYS PRESERVE IN SUMMARY (do not omit):\n${infraLines.join('\n')}`;
131
+ }
132
+ }
133
+
110
134
  // Call the LLM for summarization using a fast, cheap model
111
135
  const model = options.model ?? 'haiku';
112
136
  let summary: string;
@@ -126,8 +150,23 @@ export async function runCompaction(
126
150
  summary = fallbackSummary(toSummarize);
127
151
  }
128
152
 
153
+ // H3: Prepend infraContext block to summary so it survives compaction
154
+ let finalSummary = summary;
155
+ if (options.infraContext) {
156
+ const ic = options.infraContext;
157
+ const infraLines: string[] = [];
158
+ if (ic.terraformWorkspace) infraLines.push(`- Terraform workspace: ${ic.terraformWorkspace}`);
159
+ if (ic.kubectlContext) infraLines.push(`- kubectl context: ${ic.kubectlContext}`);
160
+ if (ic.awsProfile) infraLines.push(`- AWS profile: ${ic.awsProfile}`);
161
+ if (ic.awsRegion) infraLines.push(`- AWS region: ${ic.awsRegion}`);
162
+ if (ic.gcpProject) infraLines.push(`- GCP project: ${ic.gcpProject}`);
163
+ if (ic.azureSubscription) infraLines.push(`- Azure subscription: ${ic.azureSubscription}`);
164
+ if (infraLines.length > 0) {
165
+ finalSummary = `## Infrastructure Context\n${infraLines.join('\n')}\n\n${summary}`;
166
+ }
167
+ }
129
168
  // Reassemble the compacted message array
130
- const compactedMessages = contextManager.buildCompactedMessages(preserved, summary);
169
+ const compactedMessages = contextManager.buildCompactedMessages(preserved, finalSummary);
131
170
  const compactedTokens = compactedMessages.reduce(
132
171
  (sum, m) => sum + estimateTokens(getTextContent(m.content)),
133
172
  0