@build-astron-co/nimbus 0.3.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 (441) hide show
  1. package/bin/nimbus.cmd +41 -0
  2. package/bin/nimbus.mjs +70 -0
  3. package/completions/nimbus.bash +38 -0
  4. package/completions/nimbus.fish +48 -0
  5. package/completions/nimbus.zsh +81 -0
  6. package/dist/src/agent/compaction-agent.js +215 -0
  7. package/dist/src/agent/context-manager.js +385 -0
  8. package/dist/src/agent/context.js +322 -0
  9. package/dist/src/agent/deploy-preview.js +395 -0
  10. package/dist/src/agent/expand-files.js +95 -0
  11. package/dist/src/agent/index.js +18 -0
  12. package/dist/src/agent/loop.js +1535 -0
  13. package/dist/src/agent/modes.js +347 -0
  14. package/dist/src/agent/permissions.js +396 -0
  15. package/dist/src/agent/subagents/base.js +67 -0
  16. package/dist/src/agent/subagents/cost.js +45 -0
  17. package/dist/src/agent/subagents/explore.js +36 -0
  18. package/dist/src/agent/subagents/general.js +41 -0
  19. package/dist/src/agent/subagents/index.js +88 -0
  20. package/dist/src/agent/subagents/infra.js +52 -0
  21. package/dist/src/agent/subagents/security.js +60 -0
  22. package/dist/src/agent/system-prompt.js +860 -0
  23. package/dist/src/app.js +152 -0
  24. package/dist/src/audit/activity-log.js +209 -0
  25. package/dist/src/audit/compliance-checker.js +419 -0
  26. package/dist/src/audit/cost-tracker.js +231 -0
  27. package/dist/src/audit/index.js +10 -0
  28. package/dist/src/audit/security-scanner.js +490 -0
  29. package/dist/src/auth/guard.js +64 -0
  30. package/dist/src/auth/index.js +19 -0
  31. package/dist/src/auth/keychain.js +79 -0
  32. package/dist/src/auth/oauth.js +389 -0
  33. package/dist/src/auth/providers.js +415 -0
  34. package/dist/src/auth/sso.js +87 -0
  35. package/dist/src/auth/store.js +424 -0
  36. package/dist/src/auth/types.js +5 -0
  37. package/dist/src/cli/index.js +8 -0
  38. package/dist/src/cli/init.js +1048 -0
  39. package/dist/src/cli/openapi-spec.js +346 -0
  40. package/dist/src/cli/run.js +505 -0
  41. package/dist/src/cli/serve-auth.js +56 -0
  42. package/dist/src/cli/serve.js +432 -0
  43. package/dist/src/cli/web.js +50 -0
  44. package/dist/src/cli.js +1574 -0
  45. package/dist/src/clients/core-engine-client.js +156 -0
  46. package/dist/src/clients/enterprise-client.js +246 -0
  47. package/dist/src/clients/generator-client.js +219 -0
  48. package/dist/src/clients/git-client.js +367 -0
  49. package/dist/src/clients/github-client.js +229 -0
  50. package/dist/src/clients/helm-client.js +299 -0
  51. package/dist/src/clients/index.js +18 -0
  52. package/dist/src/clients/k8s-client.js +270 -0
  53. package/dist/src/clients/llm-client.js +119 -0
  54. package/dist/src/clients/rest-client.js +104 -0
  55. package/dist/src/clients/service-discovery.js +35 -0
  56. package/dist/src/clients/terraform-client.js +302 -0
  57. package/dist/src/clients/tools-client.js +1227 -0
  58. package/dist/src/clients/ws-client.js +93 -0
  59. package/dist/src/commands/alias.js +91 -0
  60. package/dist/src/commands/analyze/index.js +313 -0
  61. package/dist/src/commands/apply/helm.js +375 -0
  62. package/dist/src/commands/apply/index.js +176 -0
  63. package/dist/src/commands/apply/k8s.js +350 -0
  64. package/dist/src/commands/apply/terraform.js +465 -0
  65. package/dist/src/commands/ask.js +137 -0
  66. package/dist/src/commands/audit/index.js +322 -0
  67. package/dist/src/commands/auth-cloud.js +345 -0
  68. package/dist/src/commands/auth-list.js +112 -0
  69. package/dist/src/commands/auth-profile.js +104 -0
  70. package/dist/src/commands/auth-refresh.js +161 -0
  71. package/dist/src/commands/auth-status.js +122 -0
  72. package/dist/src/commands/aws/ec2.js +402 -0
  73. package/dist/src/commands/aws/iam.js +304 -0
  74. package/dist/src/commands/aws/index.js +108 -0
  75. package/dist/src/commands/aws/lambda.js +317 -0
  76. package/dist/src/commands/aws/rds.js +345 -0
  77. package/dist/src/commands/aws/s3.js +346 -0
  78. package/dist/src/commands/aws/vpc.js +302 -0
  79. package/dist/src/commands/aws-discover.js +413 -0
  80. package/dist/src/commands/aws-terraform.js +618 -0
  81. package/dist/src/commands/azure/aks.js +305 -0
  82. package/dist/src/commands/azure/functions.js +200 -0
  83. package/dist/src/commands/azure/index.js +93 -0
  84. package/dist/src/commands/azure/storage.js +378 -0
  85. package/dist/src/commands/azure/vm.js +291 -0
  86. package/dist/src/commands/billing/index.js +224 -0
  87. package/dist/src/commands/chat.js +259 -0
  88. package/dist/src/commands/completions.js +255 -0
  89. package/dist/src/commands/config.js +291 -0
  90. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  91. package/dist/src/commands/cost/estimator.js +73 -0
  92. package/dist/src/commands/cost/index.js +625 -0
  93. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  94. package/dist/src/commands/cost/parsers/types.js +4 -0
  95. package/dist/src/commands/cost/pricing/aws.js +501 -0
  96. package/dist/src/commands/cost/pricing/azure.js +462 -0
  97. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  98. package/dist/src/commands/cost/pricing/index.js +24 -0
  99. package/dist/src/commands/demo.js +196 -0
  100. package/dist/src/commands/deploy.js +215 -0
  101. package/dist/src/commands/doctor.js +1291 -0
  102. package/dist/src/commands/drift/index.js +674 -0
  103. package/dist/src/commands/explain.js +235 -0
  104. package/dist/src/commands/export.js +120 -0
  105. package/dist/src/commands/feedback.js +319 -0
  106. package/dist/src/commands/fix.js +263 -0
  107. package/dist/src/commands/fs/index.js +338 -0
  108. package/dist/src/commands/gcp/compute.js +266 -0
  109. package/dist/src/commands/gcp/functions.js +221 -0
  110. package/dist/src/commands/gcp/gke.js +357 -0
  111. package/dist/src/commands/gcp/iam.js +295 -0
  112. package/dist/src/commands/gcp/index.js +105 -0
  113. package/dist/src/commands/gcp/storage.js +232 -0
  114. package/dist/src/commands/generate-helm.js +1026 -0
  115. package/dist/src/commands/generate-k8s.js +1263 -0
  116. package/dist/src/commands/generate-terraform.js +1058 -0
  117. package/dist/src/commands/gh/index.js +663 -0
  118. package/dist/src/commands/git/index.js +1208 -0
  119. package/dist/src/commands/helm/index.js +985 -0
  120. package/dist/src/commands/help.js +639 -0
  121. package/dist/src/commands/history.js +120 -0
  122. package/dist/src/commands/import.js +782 -0
  123. package/dist/src/commands/incident.js +144 -0
  124. package/dist/src/commands/index.js +109 -0
  125. package/dist/src/commands/init.js +955 -0
  126. package/dist/src/commands/k8s/index.js +979 -0
  127. package/dist/src/commands/login.js +588 -0
  128. package/dist/src/commands/logout.js +61 -0
  129. package/dist/src/commands/logs.js +160 -0
  130. package/dist/src/commands/onboarding.js +382 -0
  131. package/dist/src/commands/pipeline.js +153 -0
  132. package/dist/src/commands/plan/display.js +216 -0
  133. package/dist/src/commands/plan/index.js +525 -0
  134. package/dist/src/commands/plugin.js +325 -0
  135. package/dist/src/commands/preview.js +356 -0
  136. package/dist/src/commands/profile.js +297 -0
  137. package/dist/src/commands/questionnaire.js +1021 -0
  138. package/dist/src/commands/resume.js +35 -0
  139. package/dist/src/commands/rollback.js +259 -0
  140. package/dist/src/commands/rollout.js +74 -0
  141. package/dist/src/commands/runbook.js +307 -0
  142. package/dist/src/commands/schedule.js +202 -0
  143. package/dist/src/commands/status.js +213 -0
  144. package/dist/src/commands/team/index.js +309 -0
  145. package/dist/src/commands/team-context.js +200 -0
  146. package/dist/src/commands/template.js +204 -0
  147. package/dist/src/commands/tf/index.js +989 -0
  148. package/dist/src/commands/upgrade.js +515 -0
  149. package/dist/src/commands/usage/index.js +118 -0
  150. package/dist/src/commands/version.js +145 -0
  151. package/dist/src/commands/watch.js +127 -0
  152. package/dist/src/compat/index.js +2 -0
  153. package/dist/src/compat/runtime.js +10 -0
  154. package/dist/src/compat/sqlite.js +144 -0
  155. package/dist/src/config/index.js +6 -0
  156. package/dist/src/config/manager.js +469 -0
  157. package/dist/src/config/mode-store.js +57 -0
  158. package/dist/src/config/profiles.js +66 -0
  159. package/dist/src/config/safety-policy.js +251 -0
  160. package/dist/src/config/schema.js +107 -0
  161. package/dist/src/config/types.js +311 -0
  162. package/dist/src/config/workspace-state.js +38 -0
  163. package/dist/src/context/context-db.js +138 -0
  164. package/dist/src/demo/index.js +295 -0
  165. package/dist/src/demo/scenarios/full-journey.js +226 -0
  166. package/dist/src/demo/scenarios/getting-started.js +124 -0
  167. package/dist/src/demo/scenarios/helm-release.js +334 -0
  168. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  169. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  170. package/dist/src/demo/types.js +6 -0
  171. package/dist/src/engine/cost-estimator.js +334 -0
  172. package/dist/src/engine/diagram-generator.js +192 -0
  173. package/dist/src/engine/drift-detector.js +688 -0
  174. package/dist/src/engine/executor.js +832 -0
  175. package/dist/src/engine/index.js +39 -0
  176. package/dist/src/engine/orchestrator.js +436 -0
  177. package/dist/src/engine/planner.js +616 -0
  178. package/dist/src/engine/safety.js +609 -0
  179. package/dist/src/engine/verifier.js +664 -0
  180. package/dist/src/enterprise/audit.js +241 -0
  181. package/dist/src/enterprise/auth.js +189 -0
  182. package/dist/src/enterprise/billing.js +512 -0
  183. package/dist/src/enterprise/index.js +16 -0
  184. package/dist/src/enterprise/teams.js +315 -0
  185. package/dist/src/generator/best-practices.js +1375 -0
  186. package/dist/src/generator/helm.js +495 -0
  187. package/dist/src/generator/index.js +11 -0
  188. package/dist/src/generator/intent-parser.js +420 -0
  189. package/dist/src/generator/kubernetes.js +773 -0
  190. package/dist/src/generator/terraform.js +1472 -0
  191. package/dist/src/history/index.js +6 -0
  192. package/dist/src/history/manager.js +199 -0
  193. package/dist/src/history/types.js +6 -0
  194. package/dist/src/hooks/config.js +318 -0
  195. package/dist/src/hooks/engine.js +317 -0
  196. package/dist/src/hooks/index.js +2 -0
  197. package/dist/src/llm/auth-bridge.js +157 -0
  198. package/dist/src/llm/circuit-breaker.js +116 -0
  199. package/dist/src/llm/config-loader.js +172 -0
  200. package/dist/src/llm/cost-calculator.js +137 -0
  201. package/dist/src/llm/index.js +7 -0
  202. package/dist/src/llm/model-aliases.js +99 -0
  203. package/dist/src/llm/provider-registry.js +57 -0
  204. package/dist/src/llm/providers/anthropic.js +430 -0
  205. package/dist/src/llm/providers/bedrock.js +409 -0
  206. package/dist/src/llm/providers/google.js +344 -0
  207. package/dist/src/llm/providers/ollama.js +661 -0
  208. package/dist/src/llm/providers/openai-compatible.js +289 -0
  209. package/dist/src/llm/providers/openai.js +284 -0
  210. package/dist/src/llm/providers/openrouter.js +293 -0
  211. package/dist/src/llm/router.js +844 -0
  212. package/dist/src/llm/types.js +69 -0
  213. package/dist/src/lsp/client.js +239 -0
  214. package/dist/src/lsp/languages.js +95 -0
  215. package/dist/src/lsp/manager.js +243 -0
  216. package/dist/src/mcp/client.js +289 -0
  217. package/dist/src/mcp/index.js +5 -0
  218. package/dist/src/mcp/manager.js +113 -0
  219. package/dist/src/nimbus.js +212 -0
  220. package/dist/src/plugins/index.js +13 -0
  221. package/dist/src/plugins/loader.js +280 -0
  222. package/dist/src/plugins/manager.js +282 -0
  223. package/dist/src/plugins/types.js +23 -0
  224. package/dist/src/scanners/cicd-scanner.js +230 -0
  225. package/dist/src/scanners/cloud-scanner.js +415 -0
  226. package/dist/src/scanners/framework-scanner.js +430 -0
  227. package/dist/src/scanners/iac-scanner.js +350 -0
  228. package/dist/src/scanners/index.js +454 -0
  229. package/dist/src/scanners/language-scanner.js +258 -0
  230. package/dist/src/scanners/package-manager-scanner.js +252 -0
  231. package/dist/src/scanners/types.js +6 -0
  232. package/dist/src/sessions/manager.js +395 -0
  233. package/dist/src/sessions/types.js +4 -0
  234. package/dist/src/sharing/sync.js +238 -0
  235. package/dist/src/sharing/viewer.js +131 -0
  236. package/dist/src/snapshots/index.js +1 -0
  237. package/dist/src/snapshots/manager.js +432 -0
  238. package/dist/src/state/artifacts.js +94 -0
  239. package/dist/src/state/audit.js +73 -0
  240. package/dist/src/state/billing.js +126 -0
  241. package/dist/src/state/checkpoints.js +81 -0
  242. package/dist/src/state/config.js +58 -0
  243. package/dist/src/state/conversations.js +7 -0
  244. package/dist/src/state/credentials.js +96 -0
  245. package/dist/src/state/db.js +53 -0
  246. package/dist/src/state/index.js +23 -0
  247. package/dist/src/state/messages.js +76 -0
  248. package/dist/src/state/projects.js +92 -0
  249. package/dist/src/state/schema.js +233 -0
  250. package/dist/src/state/sessions.js +79 -0
  251. package/dist/src/state/teams.js +131 -0
  252. package/dist/src/telemetry.js +91 -0
  253. package/dist/src/tools/aws-ops.js +747 -0
  254. package/dist/src/tools/azure-ops.js +491 -0
  255. package/dist/src/tools/file-ops.js +451 -0
  256. package/dist/src/tools/gcp-ops.js +559 -0
  257. package/dist/src/tools/git-ops.js +557 -0
  258. package/dist/src/tools/github-ops.js +460 -0
  259. package/dist/src/tools/helm-ops.js +634 -0
  260. package/dist/src/tools/index.js +16 -0
  261. package/dist/src/tools/k8s-ops.js +579 -0
  262. package/dist/src/tools/schemas/converter.js +129 -0
  263. package/dist/src/tools/schemas/devops.js +3319 -0
  264. package/dist/src/tools/schemas/index.js +19 -0
  265. package/dist/src/tools/schemas/standard.js +966 -0
  266. package/dist/src/tools/schemas/types.js +409 -0
  267. package/dist/src/tools/spawn-exec.js +109 -0
  268. package/dist/src/tools/terraform-ops.js +627 -0
  269. package/dist/src/types/config.js +1 -0
  270. package/dist/src/types/drift.js +4 -0
  271. package/dist/src/types/enterprise.js +5 -0
  272. package/dist/src/types/index.js +14 -0
  273. package/dist/src/types/plan.js +1 -0
  274. package/dist/src/types/request.js +1 -0
  275. package/dist/src/types/response.js +1 -0
  276. package/dist/src/types/service.js +1 -0
  277. package/dist/src/ui/App.js +1672 -0
  278. package/dist/src/ui/DeployPreview.js +60 -0
  279. package/dist/src/ui/FileDiffModal.js +108 -0
  280. package/dist/src/ui/Header.js +46 -0
  281. package/dist/src/ui/HelpModal.js +9 -0
  282. package/dist/src/ui/InputBox.js +408 -0
  283. package/dist/src/ui/MessageList.js +795 -0
  284. package/dist/src/ui/PermissionPrompt.js +72 -0
  285. package/dist/src/ui/StatusBar.js +109 -0
  286. package/dist/src/ui/TerminalPane.js +31 -0
  287. package/dist/src/ui/ToolCallDisplay.js +303 -0
  288. package/dist/src/ui/TreePane.js +83 -0
  289. package/dist/src/ui/chat-ui.js +721 -0
  290. package/dist/src/ui/index.js +11 -0
  291. package/dist/src/ui/ink/index.js +1325 -0
  292. package/dist/src/ui/streaming.js +137 -0
  293. package/dist/src/ui/theme.js +78 -0
  294. package/dist/src/ui/types.js +7 -0
  295. package/dist/src/utils/analytics.js +61 -0
  296. package/dist/src/utils/cost-warning.js +25 -0
  297. package/dist/src/utils/env.js +42 -0
  298. package/dist/src/utils/errors.js +54 -0
  299. package/dist/src/utils/event-bus.js +22 -0
  300. package/dist/src/utils/index.js +16 -0
  301. package/dist/src/utils/logger.js +150 -0
  302. package/dist/src/utils/rate-limiter.js +90 -0
  303. package/dist/src/utils/service-auth.js +36 -0
  304. package/dist/src/utils/validation.js +39 -0
  305. package/dist/src/version.js +3 -0
  306. package/dist/src/watcher/index.js +192 -0
  307. package/dist/src/wizard/approval.js +275 -0
  308. package/dist/src/wizard/index.js +13 -0
  309. package/dist/src/wizard/prompts.js +273 -0
  310. package/dist/src/wizard/types.js +4 -0
  311. package/dist/src/wizard/ui.js +453 -0
  312. package/dist/src/wizard/wizard.js +227 -0
  313. package/package.json +22 -15
  314. package/src/__tests__/alias.test.ts +133 -0
  315. package/src/__tests__/cli-run.test.ts +237 -1
  316. package/src/__tests__/compat-sqlite.test.ts +68 -0
  317. package/src/__tests__/context-manager.test.ts +130 -0
  318. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  319. package/src/__tests__/doctor.test.ts +48 -0
  320. package/src/__tests__/export.test.ts +236 -0
  321. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  322. package/src/__tests__/helm-streaming.test.ts +127 -0
  323. package/src/__tests__/incident.test.ts +179 -0
  324. package/src/__tests__/init.test.ts +54 -3
  325. package/src/__tests__/logs.test.ts +107 -0
  326. package/src/__tests__/loop-errors.test.ts +244 -0
  327. package/src/__tests__/perf-optimizations.test.ts +847 -0
  328. package/src/__tests__/pipeline.test.ts +50 -0
  329. package/src/__tests__/polish-phase3.test.ts +340 -0
  330. package/src/__tests__/profile.test.ts +237 -0
  331. package/src/__tests__/rollback.test.ts +83 -0
  332. package/src/__tests__/runbook.test.ts +219 -0
  333. package/src/__tests__/schedule.test.ts +206 -0
  334. package/src/__tests__/sessions.test.ts +95 -0
  335. package/src/__tests__/standalone-migration.test.ts +199 -0
  336. package/src/__tests__/status.test.ts +158 -0
  337. package/src/__tests__/stream-with-tools.test.ts +49 -1
  338. package/src/__tests__/system-prompt.test.ts +81 -2
  339. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  340. package/src/__tests__/terminal-parity.test.ts +393 -0
  341. package/src/__tests__/tf-apply.test.ts +187 -0
  342. package/src/__tests__/tool-schemas.test.ts +209 -4
  343. package/src/__tests__/version-json.test.ts +184 -0
  344. package/src/__tests__/watch.test.ts +129 -0
  345. package/src/agent/compaction-agent.ts +40 -1
  346. package/src/agent/context-manager.ts +67 -3
  347. package/src/agent/deploy-preview.ts +62 -1
  348. package/src/agent/expand-files.ts +108 -0
  349. package/src/agent/loop.ts +1312 -31
  350. package/src/agent/permissions.ts +51 -4
  351. package/src/agent/system-prompt.ts +573 -19
  352. package/src/app.ts +58 -0
  353. package/src/audit/security-scanner.ts +45 -0
  354. package/src/auth/keychain.ts +82 -0
  355. package/src/cli/init.ts +378 -5
  356. package/src/cli/run.ts +407 -16
  357. package/src/cli/serve.ts +78 -1
  358. package/src/cli/web.ts +10 -6
  359. package/src/cli.ts +312 -1
  360. package/src/clients/service-discovery.ts +30 -25
  361. package/src/commands/alias.ts +100 -0
  362. package/src/commands/audit/index.ts +121 -2
  363. package/src/commands/auth-cloud.ts +113 -0
  364. package/src/commands/auth-refresh.ts +187 -0
  365. package/src/commands/aws-discover.ts +144 -251
  366. package/src/commands/aws-terraform.ts +68 -118
  367. package/src/commands/chat.ts +9 -3
  368. package/src/commands/completions.ts +268 -0
  369. package/src/commands/config.ts +26 -0
  370. package/src/commands/cost/index.ts +218 -2
  371. package/src/commands/deploy.ts +260 -0
  372. package/src/commands/doctor.ts +744 -152
  373. package/src/commands/drift/index.ts +371 -23
  374. package/src/commands/export.ts +146 -0
  375. package/src/commands/generate-k8s.ts +9 -61
  376. package/src/commands/generate-terraform.ts +191 -449
  377. package/src/commands/help.ts +212 -36
  378. package/src/commands/history.ts +8 -1
  379. package/src/commands/incident.ts +166 -0
  380. package/src/commands/init.ts +5 -0
  381. package/src/commands/login.ts +86 -1
  382. package/src/commands/logs.ts +167 -0
  383. package/src/commands/onboarding.ts +211 -34
  384. package/src/commands/pipeline.ts +186 -0
  385. package/src/commands/plugin.ts +398 -0
  386. package/src/commands/profile.ts +342 -0
  387. package/src/commands/questionnaire.ts +0 -98
  388. package/src/commands/resume.ts +26 -34
  389. package/src/commands/rollback.ts +315 -0
  390. package/src/commands/rollout.ts +88 -0
  391. package/src/commands/runbook.ts +346 -0
  392. package/src/commands/schedule.ts +236 -0
  393. package/src/commands/status.ts +252 -0
  394. package/src/commands/team-context.ts +220 -0
  395. package/src/commands/template.ts +58 -57
  396. package/src/commands/tf/index.ts +70 -11
  397. package/src/commands/upgrade.ts +57 -0
  398. package/src/commands/version.ts +54 -50
  399. package/src/commands/watch.ts +153 -0
  400. package/src/compat/sqlite.ts +75 -5
  401. package/src/config/mode-store.ts +62 -0
  402. package/src/config/profiles.ts +84 -0
  403. package/src/config/types.ts +83 -1
  404. package/src/config/workspace-state.ts +53 -0
  405. package/src/engine/cost-estimator.ts +52 -10
  406. package/src/engine/executor.ts +33 -2
  407. package/src/engine/planner.ts +68 -1
  408. package/src/generator/terraform.ts +8 -0
  409. package/src/history/manager.ts +2 -74
  410. package/src/llm/cost-calculator.ts +2 -2
  411. package/src/llm/providers/anthropic.ts +50 -21
  412. package/src/llm/router.ts +76 -7
  413. package/src/lsp/languages.ts +3 -0
  414. package/src/lsp/manager.ts +20 -4
  415. package/src/nimbus.ts +34 -1
  416. package/src/sessions/manager.ts +108 -1
  417. package/src/sharing/viewer.ts +66 -0
  418. package/src/tools/file-ops.ts +22 -0
  419. package/src/tools/schemas/devops.ts +3007 -117
  420. package/src/tools/schemas/standard.ts +5 -1
  421. package/src/tools/schemas/types.ts +31 -1
  422. package/src/tools/spawn-exec.ts +148 -0
  423. package/src/ui/App.tsx +1183 -66
  424. package/src/ui/DeployPreview.tsx +62 -57
  425. package/src/ui/FileDiffModal.tsx +162 -0
  426. package/src/ui/Header.tsx +87 -24
  427. package/src/ui/HelpModal.tsx +57 -0
  428. package/src/ui/InputBox.tsx +163 -10
  429. package/src/ui/MessageList.tsx +487 -40
  430. package/src/ui/PermissionPrompt.tsx +17 -5
  431. package/src/ui/StatusBar.tsx +122 -3
  432. package/src/ui/TerminalPane.tsx +84 -0
  433. package/src/ui/ToolCallDisplay.tsx +252 -18
  434. package/src/ui/TreePane.tsx +132 -0
  435. package/src/ui/chat-ui.ts +41 -44
  436. package/src/ui/ink/index.ts +771 -38
  437. package/src/ui/theme.ts +104 -0
  438. package/src/ui/types.ts +18 -0
  439. package/src/version.ts +1 -1
  440. package/src/watcher/index.ts +66 -15
  441. package/src/wizard/types.ts +1 -0
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@build-astron-co/nimbus",
3
- "version": "0.3.0",
4
- "description": "AI-Powered Cloud Engineering Agent",
5
- "main": "src/nimbus.ts",
3
+ "version": "0.4.0",
4
+ "description": "AI-Powered DevOps Engineering Agent",
5
+ "main": "dist/src/nimbus.js",
6
6
  "bin": {
7
- "nimbus": "bin/nimbus"
7
+ "nimbus": "bin/nimbus.mjs"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "dist/",
13
+ "completions/",
12
14
  "package.json",
13
15
  "tsconfig.json"
14
16
  ],
@@ -28,20 +30,24 @@
28
30
  "build:binary": "./scripts/build-binary.sh",
29
31
  "build:binary:all": "./scripts/build-binary.sh all",
30
32
  "nimbus": "node --loader ./node_modules/tsx/dist/esm/index.mjs src/nimbus.ts",
31
- "precommit": "npm run lint && npm run format:check && npm run type-check"
33
+ "build:npm": "tsc --project tsconfig.build.json",
34
+ "prepare": "npm run build:npm",
35
+ "prepack": "tsc --project tsconfig.build.json",
36
+ "precommit": "npm run lint && npm run format:check && npm run type-check",
37
+ "postinstall": "node -e \"process.stdout.write('\\n Nimbus installed! Next steps:\\n 1. Run: nimbus login (configure your LLM provider)\\n 2. Run: nimbus doctor (check your DevOps toolchain)\\n 3. Run: nimbus completions install (enable tab completion)\\n 4. Run: nimbus (start the DevOps agent)\\n\\n')\""
32
38
  },
33
39
  "devDependencies": {
34
40
  "@types/better-sqlite3": "^7.6.0",
35
41
  "@types/js-yaml": "^4.0.9",
36
42
  "@types/node": "^20.0.0",
37
43
  "@types/react": "^19.2.14",
38
- "@typescript-eslint/eslint-plugin": "^6.21.0",
39
- "@typescript-eslint/parser": "^6.21.0",
40
- "@vitest/coverage-v8": "^2.0.0",
44
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
45
+ "@typescript-eslint/parser": "^8.56.1",
46
+ "@vitest/coverage-v8": "^3.2.4",
41
47
  "eslint": "^8.57.0",
42
48
  "prettier": "^3.2.5",
43
49
  "typescript": "^5.3.3",
44
- "vitest": "^2.0.0"
50
+ "vitest": "^3.2.4"
45
51
  },
46
52
  "engines": {
47
53
  "node": ">=18.0.0"
@@ -62,20 +68,21 @@
62
68
  "openai": "^6.22.0",
63
69
  "react": "^19.2.4",
64
70
  "simple-git": "^3.32.1",
65
- "better-sqlite3": "^11.0.0",
66
71
  "tsx": "^4.7.0",
67
72
  "zod": "^4.3.6"
68
73
  },
69
74
  "optionalDependencies": {
70
- "@azure/identity": "^4.0.0",
75
+ "better-sqlite3": "^11.0.0",
76
+ "sql.js": "^1.12.0",
71
77
  "@azure/arm-compute": "^22.0.0",
72
- "@azure/arm-storage": "^18.0.0",
73
78
  "@azure/arm-containerservice": "^21.0.0",
74
79
  "@azure/arm-network": "^33.0.0",
75
- "google-auth-library": "^9.0.0",
80
+ "@azure/arm-storage": "^18.0.0",
81
+ "@azure/identity": "^4.0.0",
76
82
  "@google-cloud/compute": "^4.0.0",
77
- "@google-cloud/storage": "^7.0.0",
78
83
  "@google-cloud/container": "^5.0.0",
79
- "@octokit/rest": "^21.0.0"
84
+ "@google-cloud/storage": "^7.0.0",
85
+ "@octokit/rest": "^21.0.0",
86
+ "google-auth-library": "^9.0.0"
80
87
  }
81
88
  }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Alias Command Tests — L2
3
+ *
4
+ * Tests resolveAlias expansion and alias file management.
5
+ */
6
+
7
+ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import * as os from 'node:os';
11
+
12
+ // Patch homedir so aliases are stored in a temp dir during tests
13
+ let tmpDir: string;
14
+
15
+ vi.mock('node:os', async () => {
16
+ const actual = await vi.importActual<typeof os>('node:os');
17
+ return {
18
+ ...actual,
19
+ homedir: () => tmpDir ?? actual.homedir(),
20
+ };
21
+ });
22
+
23
+ // Re-import after mock is set up (dynamic to pick up the homedir mock)
24
+ async function getAliasModule() {
25
+ // Force re-import so homedir mock is active
26
+ return await import('../commands/alias');
27
+ }
28
+
29
+ describe('resolveAlias (L2)', () => {
30
+ beforeEach(() => {
31
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nimbus-alias-test-'));
32
+ // Clear module cache so homedir mock takes effect
33
+ vi.resetModules();
34
+ });
35
+
36
+ afterEach(() => {
37
+ fs.rmSync(tmpDir, { recursive: true, force: true });
38
+ vi.resetModules();
39
+ });
40
+
41
+ test('returns args unchanged when no alias exists', async () => {
42
+ const { resolveAlias } = await getAliasModule();
43
+ expect(resolveAlias(['run', '--help'])).toEqual(['run', '--help']);
44
+ });
45
+
46
+ test('returns empty array for empty input', async () => {
47
+ const { resolveAlias } = await getAliasModule();
48
+ expect(resolveAlias([])).toEqual([]);
49
+ });
50
+
51
+ test('expands a defined alias', async () => {
52
+ // Write alias file manually
53
+ const nimbusDir = path.join(tmpDir, '.nimbus');
54
+ fs.mkdirSync(nimbusDir, { recursive: true });
55
+ fs.writeFileSync(
56
+ path.join(nimbusDir, 'aliases.json'),
57
+ JSON.stringify({ deploy: 'run --auto-approve "deploy staging"' }),
58
+ 'utf-8'
59
+ );
60
+
61
+ const { resolveAlias } = await getAliasModule();
62
+ const result = resolveAlias(['deploy']);
63
+ expect(result[0]).toBe('run');
64
+ expect(result).toContain('--auto-approve');
65
+ });
66
+
67
+ test('appends remaining args after expanding alias', async () => {
68
+ const nimbusDir = path.join(tmpDir, '.nimbus');
69
+ fs.mkdirSync(nimbusDir, { recursive: true });
70
+ fs.writeFileSync(
71
+ path.join(nimbusDir, 'aliases.json'),
72
+ JSON.stringify({ tf: 'run "terraform plan"' }),
73
+ 'utf-8'
74
+ );
75
+
76
+ const { resolveAlias } = await getAliasModule();
77
+ const result = resolveAlias(['tf', '--verbose']);
78
+ expect(result[result.length - 1]).toBe('--verbose');
79
+ });
80
+ });
81
+
82
+ describe('aliasCommand list/add/remove (L2)', () => {
83
+ beforeEach(() => {
84
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nimbus-alias-cmd-test-'));
85
+ vi.resetModules();
86
+ });
87
+
88
+ afterEach(() => {
89
+ fs.rmSync(tmpDir, { recursive: true, force: true });
90
+ vi.resetModules();
91
+ });
92
+
93
+ test('list shows "no aliases" when file is absent', async () => {
94
+ const { aliasCommand } = await getAliasModule();
95
+ const logs: string[] = [];
96
+ vi.spyOn(console, 'log').mockImplementation((...args) => logs.push(args.join(' ')));
97
+ // Should not throw even if file doesn't exist
98
+ await expect(aliasCommand('list', [])).resolves.not.toThrow();
99
+ vi.restoreAllMocks();
100
+ });
101
+
102
+ test('set writes alias to file', async () => {
103
+ const { aliasCommand } = await getAliasModule();
104
+ vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
105
+ vi.spyOn(console, 'log').mockImplementation(() => {});
106
+
107
+ await aliasCommand('myalias=run "do something"', []);
108
+
109
+ const aliasFile = path.join(tmpDir, '.nimbus', 'aliases.json');
110
+ const data = JSON.parse(fs.readFileSync(aliasFile, 'utf-8')) as Record<string, string>;
111
+ expect(data['myalias']).toBe('run "do something"');
112
+ vi.restoreAllMocks();
113
+ });
114
+
115
+ test('remove deletes alias from file', async () => {
116
+ const nimbusDir = path.join(tmpDir, '.nimbus');
117
+ fs.mkdirSync(nimbusDir, { recursive: true });
118
+ fs.writeFileSync(
119
+ path.join(nimbusDir, 'aliases.json'),
120
+ JSON.stringify({ myalias: 'run stuff' }),
121
+ 'utf-8'
122
+ );
123
+
124
+ const { aliasCommand } = await getAliasModule();
125
+ vi.spyOn(console, 'log').mockImplementation(() => {});
126
+
127
+ await aliasCommand('remove', ['myalias']);
128
+
129
+ const data = JSON.parse(fs.readFileSync(path.join(nimbusDir, 'aliases.json'), 'utf-8')) as Record<string, string>;
130
+ expect(data['myalias']).toBeUndefined();
131
+ vi.restoreAllMocks();
132
+ });
133
+ });
@@ -5,7 +5,7 @@
5
5
  * structured RunOptions for the non-interactive nimbus run command.
6
6
  */
7
7
 
8
- import { describe, test, expect } from 'vitest';
8
+ import { describe, test, it, expect } from 'vitest';
9
9
  import { parseRunArgs } from '../cli/run';
10
10
 
11
11
  // ===========================================================================
@@ -29,6 +29,12 @@ describe('parseRunArgs', () => {
29
29
  expect(result.format).toBe('json');
30
30
  });
31
31
 
32
+ test('parses --format table (H4)', () => {
33
+ const result = parseRunArgs(['--format', 'table', 'my prompt']);
34
+ expect(result.format).toBe('table');
35
+ expect(result.prompt).toBe('my prompt');
36
+ });
37
+
32
38
  test('parses --auto-approve', () => {
33
39
  const result = parseRunArgs(['--auto-approve', 'do', 'stuff']);
34
40
  expect(result.autoApprove).toBe(true);
@@ -112,4 +118,234 @@ describe('parseRunArgs', () => {
112
118
  expect(result.model).toBe('openai/gpt-4');
113
119
  expect(result.prompt).toBe('deploy everything');
114
120
  });
121
+
122
+ // G13: --timeout flag
123
+ test('G13: parses --timeout <seconds> into milliseconds', () => {
124
+ const result = parseRunArgs(['--timeout', '30', 'my prompt']);
125
+ expect(result.timeout).toBe(30000);
126
+ expect(result.prompt).toBe('my prompt');
127
+ });
128
+
129
+ test('G13: timeout defaults to undefined when not specified', () => {
130
+ const result = parseRunArgs(['my prompt']);
131
+ expect(result.timeout).toBeUndefined();
132
+ });
133
+
134
+ test('G13: parses --timeout 0 as 0 ms', () => {
135
+ const result = parseRunArgs(['--timeout', '0', 'prompt']);
136
+ expect(result.timeout).toBe(0);
137
+ });
138
+
139
+ // G15: --raw-tool-output flag
140
+ test('G15: parses --raw-tool-output flag', () => {
141
+ const result = parseRunArgs(['--raw-tool-output', 'show pod status']);
142
+ expect(result.rawToolOutput).toBe(true);
143
+ expect(result.prompt).toBe('show pod status');
144
+ });
145
+
146
+ test('G15: rawToolOutput defaults to false', () => {
147
+ const result = parseRunArgs(['prompt']);
148
+ expect(result.rawToolOutput).toBe(false);
149
+ });
150
+
151
+ test('G13 + G15: combines timeout and raw-tool-output with other flags', () => {
152
+ const result = parseRunArgs([
153
+ '--timeout', '60',
154
+ '--raw-tool-output',
155
+ '--auto-approve',
156
+ 'list all pods',
157
+ ]);
158
+ expect(result.timeout).toBe(60000);
159
+ expect(result.rawToolOutput).toBe(true);
160
+ expect(result.autoApprove).toBe(true);
161
+ expect(result.prompt).toBe('list all pods');
162
+ });
163
+
164
+ // H3: CI/CD flags
165
+ test('H3: parses --exit-code-on-error flag', () => {
166
+ const result = parseRunArgs(['--exit-code-on-error', 'run tests']);
167
+ expect(result.exitOnError).toBe(true);
168
+ expect(result.prompt).toBe('run tests');
169
+ });
170
+
171
+ test('C5: exitOnError defaults to true (POSIX convention)', () => {
172
+ const result = parseRunArgs(['prompt']);
173
+ expect(result.exitOnError).toBe(true);
174
+ });
175
+
176
+ test('C5: --no-exit-on-error disables exitOnError', () => {
177
+ const result = parseRunArgs(['--no-exit-on-error', 'prompt']);
178
+ expect(result.exitOnError).toBe(false);
179
+ });
180
+
181
+ test('H3: parses --context <kubectl-context>', () => {
182
+ const result = parseRunArgs(['--context', 'prod-cluster', 'deploy app']);
183
+ expect(result.context).toBe('prod-cluster');
184
+ expect(result.prompt).toBe('deploy app');
185
+ });
186
+
187
+ test('H3: context defaults to undefined', () => {
188
+ const result = parseRunArgs(['prompt']);
189
+ expect(result.context).toBeUndefined();
190
+ });
191
+
192
+ test('H3: parses --workspace <tf-workspace>', () => {
193
+ const result = parseRunArgs(['--workspace', 'production', 'run plan']);
194
+ expect(result.workspace).toBe('production');
195
+ expect(result.prompt).toBe('run plan');
196
+ });
197
+
198
+ test('H3: workspace defaults to undefined', () => {
199
+ const result = parseRunArgs(['prompt']);
200
+ expect(result.workspace).toBeUndefined();
201
+ });
202
+
203
+ test('H3: parses --namespace <k8s-namespace>', () => {
204
+ const result = parseRunArgs(['--namespace', 'prod', 'list pods']);
205
+ expect(result.namespace).toBe('prod');
206
+ expect(result.prompt).toBe('list pods');
207
+ });
208
+
209
+ test('H3: parses -n as short form for --namespace', () => {
210
+ const result = parseRunArgs(['-n', 'kube-system', 'get pods']);
211
+ expect(result.namespace).toBe('kube-system');
212
+ expect(result.prompt).toBe('get pods');
213
+ });
214
+
215
+ test('H3: namespace defaults to undefined', () => {
216
+ const result = parseRunArgs(['prompt']);
217
+ expect(result.namespace).toBeUndefined();
218
+ });
219
+
220
+ test('H3: parses --notify <url>', () => {
221
+ const result = parseRunArgs(['--notify', 'https://hooks.example.com/notify', 'do work']);
222
+ expect(result.notify).toBe('https://hooks.example.com/notify');
223
+ expect(result.prompt).toBe('do work');
224
+ });
225
+
226
+ test('H3: parses --notify-slack <url>', () => {
227
+ const result = parseRunArgs(['--notify-slack', 'https://hooks.slack.com/T123/B456', 'deploy']);
228
+ expect(result.notifySlack).toBe('https://hooks.slack.com/T123/B456');
229
+ expect(result.prompt).toBe('deploy');
230
+ });
231
+
232
+ test('H3: all CI/CD flags combined', () => {
233
+ const result = parseRunArgs([
234
+ '--exit-code-on-error',
235
+ '--context', 'staging-cluster',
236
+ '--workspace', 'staging',
237
+ '--namespace', 'staging',
238
+ '--notify', 'https://example.com/webhook',
239
+ '--notify-slack', 'https://hooks.slack.com/abc',
240
+ 'run deployment',
241
+ ]);
242
+ expect(result.exitOnError).toBe(true);
243
+ expect(result.context).toBe('staging-cluster');
244
+ expect(result.workspace).toBe('staging');
245
+ expect(result.namespace).toBe('staging');
246
+ expect(result.notify).toBe('https://example.com/webhook');
247
+ expect(result.notifySlack).toBe('https://hooks.slack.com/abc');
248
+ expect(result.prompt).toBe('run deployment');
249
+ });
250
+
251
+ test('H3: --context sets KUBECTL_CONTEXT env var when executeRun processes it', () => {
252
+ // Test that parseRunArgs properly captures context for env injection
253
+ const result = parseRunArgs(['--context', 'my-context', 'test prompt']);
254
+ expect(result.context).toBe('my-context');
255
+ // The env var injection happens in executeRun; we test parseRunArgs captures it correctly
256
+ });
257
+ });
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // L2: planSummary in JSON output
261
+ // ---------------------------------------------------------------------------
262
+
263
+ describe('planSummary in RunJsonOutput (L2)', () => {
264
+ it('RunJsonOutput interface has planSummary field', async () => {
265
+ const { readFileSync } = await import('node:fs');
266
+ const { join } = await import('node:path');
267
+ const src = readFileSync(join(process.cwd(), 'src/cli/run.ts'), 'utf-8');
268
+ expect(src).toContain('planSummary');
269
+ });
270
+
271
+ it('planSummary regex matches terraform plan output', () => {
272
+ const planOutput = 'Plan: 3 to add, 1 to change, 0 to destroy.';
273
+ const planLine = planOutput.match(/Plan:\s*(\d+)\s*to add,\s*(\d+)\s*to change,\s*(\d+)\s*to destroy/i);
274
+ expect(planLine).not.toBeNull();
275
+ expect(parseInt(planLine![1])).toBe(3);
276
+ expect(parseInt(planLine![2])).toBe(1);
277
+ expect(parseInt(planLine![3])).toBe(0);
278
+ });
279
+
280
+ it('planSummary regex works with large numbers', () => {
281
+ const planOutput = 'Plan: 150 to add, 42 to change, 7 to destroy.';
282
+ const planLine = planOutput.match(/Plan:\s*(\d+)\s*to add,\s*(\d+)\s*to change,\s*(\d+)\s*to destroy/i);
283
+ expect(planLine).not.toBeNull();
284
+ expect(parseInt(planLine![1])).toBe(150);
285
+ expect(parseInt(planLine![2])).toBe(42);
286
+ expect(parseInt(planLine![3])).toBe(7);
287
+ });
288
+ });
289
+
290
+ // ===========================================================================
291
+ // C2: No staleness check in bin/nimbus.mjs
292
+ // ===========================================================================
293
+
294
+ describe('C2 — bin/nimbus.mjs startup optimizations', () => {
295
+ it('bin/nimbus.mjs does not import statSync', async () => {
296
+ const { readFileSync } = await import('node:fs');
297
+ const { join } = await import('node:path');
298
+ const src = readFileSync(join(process.cwd(), 'bin/nimbus.mjs'), 'utf-8');
299
+ expect(src).not.toContain('statSync');
300
+ });
301
+
302
+ it('bin/nimbus.mjs uses simple existsSync check for dist entry', async () => {
303
+ const { readFileSync } = await import('node:fs');
304
+ const { join } = await import('node:path');
305
+ const src = readFileSync(join(process.cwd(), 'bin/nimbus.mjs'), 'utf-8');
306
+ expect(src).toContain('existsSync(DIST_ENTRY)');
307
+ });
308
+ });
309
+
310
+ // ===========================================================================
311
+ // H5: nimbus whoami wiring
312
+ // ===========================================================================
313
+
314
+ describe('H5 — nimbus whoami command', () => {
315
+ it('cli.ts contains whoami handler', async () => {
316
+ const { readFileSync } = await import('node:fs');
317
+ const { join } = await import('node:path');
318
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
319
+ expect(src).toContain("command === 'whoami'");
320
+ });
321
+
322
+ it('cli.ts whoami calls authStatusCommand', async () => {
323
+ const { readFileSync } = await import('node:fs');
324
+ const { join } = await import('node:path');
325
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
326
+ // whoami section should call authStatusCommand
327
+ const whoamiIdx = src.indexOf("command === 'whoami'");
328
+ const authStatusIdx = src.indexOf('authStatusCommand({})', whoamiIdx);
329
+ expect(authStatusIdx).toBeGreaterThan(whoamiIdx);
330
+ });
331
+ });
332
+
333
+ // ===========================================================================
334
+ // M1: nimbus diff top-level alias
335
+ // ===========================================================================
336
+
337
+ describe('M1 — nimbus diff alias', () => {
338
+ it('cli.ts contains diff command handler', async () => {
339
+ const { readFileSync } = await import('node:fs');
340
+ const { join } = await import('node:path');
341
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
342
+ expect(src).toContain("command === 'diff'");
343
+ });
344
+
345
+ it('cli.ts diff calls fsCommand with "diff" subcommand', async () => {
346
+ const { readFileSync } = await import('node:fs');
347
+ const { join } = await import('node:path');
348
+ const src = readFileSync(join(process.cwd(), 'src/cli.ts'), 'utf-8');
349
+ expect(src).toContain("fsCommand('diff', args.slice(1))");
350
+ });
115
351
  });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * SQLite Compat Layer Tests — G1
3
+ *
4
+ * Validates that the sqlite compat module:
5
+ * - Has proper fallback entries in package.json optionalDependencies
6
+ * - Contains sql.js fallback code
7
+ * - Contains the in-memory warning text
8
+ */
9
+
10
+ import { describe, it, expect } from 'vitest';
11
+ import { readFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ const ROOT = join(__dirname, '..', '..');
15
+
16
+ describe('package.json SQLite compat configuration (G1)', () => {
17
+ const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')) as {
18
+ scripts?: Record<string, string>;
19
+ optionalDependencies?: Record<string, string>;
20
+ dependencies?: Record<string, string>;
21
+ };
22
+
23
+ it('has better-sqlite3 in optionalDependencies', () => {
24
+ expect(pkg.optionalDependencies).toBeDefined();
25
+ expect(Object.keys(pkg.optionalDependencies!)).toContain('better-sqlite3');
26
+ });
27
+
28
+ it('has sql.js in optionalDependencies', () => {
29
+ expect(pkg.optionalDependencies).toBeDefined();
30
+ expect(Object.keys(pkg.optionalDependencies!)).toContain('sql.js');
31
+ });
32
+
33
+ it('has a prepack script defined', () => {
34
+ expect(pkg.scripts).toBeDefined();
35
+ expect(pkg.scripts!['prepack']).toBeDefined();
36
+ expect(typeof pkg.scripts!['prepack']).toBe('string');
37
+ });
38
+ });
39
+
40
+ describe('src/compat/sqlite.ts fallback implementation (G1)', () => {
41
+ const sqliteSrc = readFileSync(join(ROOT, 'src', 'compat', 'sqlite.ts'), 'utf-8');
42
+
43
+ it('contains the sql.js fallback require call', () => {
44
+ expect(sqliteSrc).toContain('sql.js');
45
+ });
46
+
47
+ it('contains the in-memory warning text', () => {
48
+ expect(sqliteSrc).toContain('in-memory');
49
+ });
50
+
51
+ it('contains the better-sqlite3 require call', () => {
52
+ expect(sqliteSrc).toContain('better-sqlite3');
53
+ });
54
+
55
+ it('defines a SqlJsDatabase class for the fallback path', () => {
56
+ expect(sqliteSrc).toContain('SqlJsDatabase');
57
+ });
58
+
59
+ it('exports exec, prepare, and close methods in SqlJsDatabase', () => {
60
+ expect(sqliteSrc).toContain('exec(sql');
61
+ expect(sqliteSrc).toContain('prepare(sql');
62
+ expect(sqliteSrc).toContain('close()');
63
+ });
64
+
65
+ it('contains install hint for better-sqlite3', () => {
66
+ expect(sqliteSrc).toContain('npm install better-sqlite3');
67
+ });
68
+ });
@@ -500,3 +500,133 @@ describe('Context Breakdown Formatting', () => {
500
500
  expect(display).toContain('1%');
501
501
  });
502
502
  });
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // PERF-2b: Token count caching in ContextManager
506
+ // ---------------------------------------------------------------------------
507
+
508
+ describe('PERF-2b: ContextManager token count caching', () => {
509
+ let cm: ContextManager;
510
+
511
+ beforeEach(() => {
512
+ cm = new ContextManager({ maxContextTokens: 100_000 });
513
+ });
514
+
515
+ it('clearTokenCache() is a public method and does not throw', () => {
516
+ expect(() => cm.clearTokenCache()).not.toThrow();
517
+ });
518
+
519
+ it('calculateUsage returns the same result before and after clearTokenCache', () => {
520
+ const msgs: LLMMessage[] = [
521
+ { role: 'user', content: 'Hello there' },
522
+ { role: 'assistant', content: 'Hi! How can I help?' },
523
+ ];
524
+ const before = cm.calculateUsage('System prompt', msgs, 50);
525
+ cm.clearTokenCache();
526
+ const after = cm.calculateUsage('System prompt', msgs, 50);
527
+ expect(before.messages).toBe(after.messages);
528
+ expect(before.total).toBe(after.total);
529
+ });
530
+
531
+ it('repeated calculateUsage calls return identical token counts (cache consistent)', () => {
532
+ const msgs: LLMMessage[] = Array.from({ length: 20 }, (_, i) => ({
533
+ role: i % 2 === 0 ? ('user' as const) : ('assistant' as const),
534
+ content: `Message ${i}: ${'x'.repeat(50)}`,
535
+ }));
536
+ const first = cm.calculateUsage('sys', msgs, 0);
537
+ const second = cm.calculateUsage('sys', msgs, 0);
538
+ const third = cm.calculateUsage('sys', msgs, 0);
539
+ expect(first.messages).toBe(second.messages);
540
+ expect(second.messages).toBe(third.messages);
541
+ });
542
+
543
+ it('cache is invalidated by clearTokenCache (new message gets fresh count)', () => {
544
+ const msg: LLMMessage = { role: 'user', content: 'short' };
545
+ const msgs = [msg];
546
+ const before = cm.calculateUsage('sys', msgs, 0);
547
+ cm.clearTokenCache();
548
+ const after = cm.calculateUsage('sys', msgs, 0);
549
+ // Counts should still be equal — same message, same content
550
+ expect(before.messages).toBe(after.messages);
551
+ });
552
+
553
+ it('token cache handles tool call messages without error', () => {
554
+ const msgs: LLMMessage[] = [
555
+ {
556
+ role: 'assistant',
557
+ content: 'Calling tool',
558
+ toolCalls: [
559
+ {
560
+ id: 'tc1',
561
+ type: 'function',
562
+ function: { name: 'bash', arguments: '{"command":"ls"}' },
563
+ },
564
+ ],
565
+ },
566
+ ];
567
+ expect(() => cm.calculateUsage('sys', msgs, 0)).not.toThrow();
568
+ expect(() => cm.calculateUsage('sys', msgs, 0)).not.toThrow();
569
+ });
570
+ });
571
+
572
+ // ---------------------------------------------------------------------------
573
+ // H3: Compaction preserves infraContext
574
+ // ---------------------------------------------------------------------------
575
+
576
+ describe('compaction preserves infraContext (H3)', () => {
577
+ it('compaction-agent.ts CompactionOptions has infraContext field', async () => {
578
+ const { readFileSync } = await import('node:fs');
579
+ const { join } = await import('node:path');
580
+ const src = readFileSync(join(process.cwd(), 'src/agent/compaction-agent.ts'), 'utf-8');
581
+ expect(src).toContain('infraContext?:');
582
+ });
583
+
584
+ it('compaction injects ALWAYS PRESERVE section when infraContext provided', async () => {
585
+ const { readFileSync } = await import('node:fs');
586
+ const { join } = await import('node:path');
587
+ const src = readFileSync(join(process.cwd(), 'src/agent/compaction-agent.ts'), 'utf-8');
588
+ expect(src).toContain('ALWAYS PRESERVE IN SUMMARY');
589
+ });
590
+
591
+ it('compaction prepends Infrastructure Context to summary', async () => {
592
+ const { readFileSync } = await import('node:fs');
593
+ const { join } = await import('node:path');
594
+ const src = readFileSync(join(process.cwd(), 'src/agent/compaction-agent.ts'), 'utf-8');
595
+ expect(src).toContain('## Infrastructure Context');
596
+ });
597
+
598
+ it('context-manager default alwaysInContext includes Infrastructure Context', async () => {
599
+ const { readFileSync } = await import('node:fs');
600
+ const { join } = await import('node:path');
601
+ const src = readFileSync(join(process.cwd(), 'src/agent/context-manager.ts'), 'utf-8');
602
+ expect(src).toContain("'Infrastructure Context'");
603
+ });
604
+
605
+ it('context-manager default alwaysInContext includes Tool Timeouts', async () => {
606
+ const { readFileSync } = await import('node:fs');
607
+ const { join } = await import('node:path');
608
+ const src = readFileSync(join(process.cwd(), 'src/agent/context-manager.ts'), 'utf-8');
609
+ expect(src).toContain("'Tool Timeouts'");
610
+ });
611
+
612
+ it('compaction passes infraContext from runAgentLoop options', async () => {
613
+ const { readFileSync } = await import('node:fs');
614
+ const { join } = await import('node:path');
615
+ const src = readFileSync(join(process.cwd(), 'src/agent/loop.ts'), 'utf-8');
616
+ expect(src).toContain('infraContext: options.infraContext');
617
+ });
618
+
619
+ it('compaction-agent terraformWorkspace is serialized in infraLines', async () => {
620
+ const { readFileSync } = await import('node:fs');
621
+ const { join } = await import('node:path');
622
+ const src = readFileSync(join(process.cwd(), 'src/agent/compaction-agent.ts'), 'utf-8');
623
+ expect(src).toContain('ic.terraformWorkspace');
624
+ });
625
+
626
+ it('compaction-agent kubectlContext is serialized in infraLines', async () => {
627
+ const { readFileSync } = await import('node:fs');
628
+ const { join } = await import('node:path');
629
+ const src = readFileSync(join(process.cwd(), 'src/agent/compaction-agent.ts'), 'utf-8');
630
+ expect(src).toContain('ic.kubectlContext');
631
+ });
632
+ });