@build-astron-co/nimbus 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,115 @@
1
+ import { logger } from '../utils';
2
+
3
+ export interface WebSocketClientOptions {
4
+ reconnect?: boolean;
5
+ reconnectInterval?: number;
6
+ maxReconnectAttempts?: number;
7
+ }
8
+
9
+ /**
10
+ * WebSocket Client for streaming communication
11
+ */
12
+ export class WebSocketClient {
13
+ private url: string;
14
+ private ws: WebSocket | null = null;
15
+ private options: Required<WebSocketClientOptions>;
16
+ private reconnectAttempts = 0;
17
+ private messageHandlers: Set<(data: any) => void> = new Set();
18
+ private errorHandlers: Set<(error: Event) => void> = new Set();
19
+ private closeHandlers: Set<() => void> = new Set();
20
+
21
+ constructor(url: string, options: WebSocketClientOptions = {}) {
22
+ this.url = url;
23
+ this.options = {
24
+ reconnect: options.reconnect ?? true,
25
+ reconnectInterval: options.reconnectInterval ?? 3000,
26
+ maxReconnectAttempts: options.maxReconnectAttempts ?? 5,
27
+ };
28
+ }
29
+
30
+ connect(): Promise<void> {
31
+ return new Promise((resolve, reject) => {
32
+ try {
33
+ this.ws = new WebSocket(this.url);
34
+
35
+ this.ws.onopen = () => {
36
+ logger.info(`WebSocket connected to ${this.url}`);
37
+ this.reconnectAttempts = 0;
38
+ resolve();
39
+ };
40
+
41
+ this.ws.onmessage = event => {
42
+ try {
43
+ const data = JSON.parse(event.data);
44
+ this.messageHandlers.forEach(handler => handler(data));
45
+ } catch (error) {
46
+ logger.error('Failed to parse WebSocket message', error);
47
+ }
48
+ };
49
+
50
+ this.ws.onerror = event => {
51
+ logger.error('WebSocket error', event);
52
+ this.errorHandlers.forEach(handler => handler(event));
53
+ reject(event);
54
+ };
55
+
56
+ this.ws.onclose = () => {
57
+ logger.warn(`WebSocket disconnected from ${this.url}`);
58
+ this.closeHandlers.forEach(handler => handler());
59
+
60
+ // Auto-reconnect
61
+ if (
62
+ this.options.reconnect &&
63
+ this.reconnectAttempts < this.options.maxReconnectAttempts
64
+ ) {
65
+ this.reconnectAttempts++;
66
+ logger.info(
67
+ `Attempting to reconnect (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`
68
+ );
69
+ setTimeout(() => {
70
+ this.connect().catch(() => {
71
+ // Ignore, will retry or give up
72
+ });
73
+ }, this.options.reconnectInterval);
74
+ }
75
+ };
76
+ } catch (error) {
77
+ reject(error);
78
+ }
79
+ });
80
+ }
81
+
82
+ send(data: any): void {
83
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
84
+ throw new Error('WebSocket is not connected');
85
+ }
86
+ this.ws.send(JSON.stringify(data));
87
+ }
88
+
89
+ onMessage(handler: (data: any) => void): () => void {
90
+ this.messageHandlers.add(handler);
91
+ return () => this.messageHandlers.delete(handler);
92
+ }
93
+
94
+ onError(handler: (error: Event) => void): () => void {
95
+ this.errorHandlers.add(handler);
96
+ return () => this.errorHandlers.delete(handler);
97
+ }
98
+
99
+ onClose(handler: () => void): () => void {
100
+ this.closeHandlers.add(handler);
101
+ return () => this.closeHandlers.delete(handler);
102
+ }
103
+
104
+ close(): void {
105
+ if (this.ws) {
106
+ this.options.reconnect = false; // Prevent auto-reconnect
107
+ this.ws.close();
108
+ this.ws = null;
109
+ }
110
+ }
111
+
112
+ isConnected(): boolean {
113
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
114
+ }
115
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Analyze Command
3
+ * Codebase analysis and refactoring suggestions
4
+ */
5
+
6
+ import { ui } from '../../wizard/ui';
7
+ import type { AnalyzeOptions, CodeAnalysis, RefactoringSuggestion } from '../../types';
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+
11
+ /**
12
+ * Parse analyze options
13
+ */
14
+ export function parseAnalyzeOptions(args: string[]): AnalyzeOptions {
15
+ const options: AnalyzeOptions = {
16
+ type: 'all',
17
+ };
18
+
19
+ for (let i = 0; i < args.length; i++) {
20
+ const arg = args[i];
21
+ if (arg === '--type' && args[i + 1]) {
22
+ options.type = args[++i] as AnalyzeOptions['type'];
23
+ } else if (arg === '--path' && args[i + 1]) {
24
+ options.path = args[++i];
25
+ } else if (arg === '--json') {
26
+ options.json = true;
27
+ } else if (arg === '--security') {
28
+ // Shortcut for --type security
29
+ options.type = 'security';
30
+ } else if (arg === '--compliance' && args[i + 1]) {
31
+ // Set compliance standard (soc2, hipaa, pci)
32
+ options.type = 'security';
33
+ (options as any).compliance = args[++i];
34
+ } else if (!arg.startsWith('-') && !options.path) {
35
+ options.path = arg;
36
+ }
37
+ }
38
+
39
+ return options;
40
+ }
41
+
42
+ /**
43
+ * Get files to analyze
44
+ */
45
+ function getFilesToAnalyze(basePath: string, extensions: string[]): string[] {
46
+ const files: string[] = [];
47
+
48
+ function walk(dir: string) {
49
+ try {
50
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
51
+
52
+ for (const entry of entries) {
53
+ const fullPath = path.join(dir, entry.name);
54
+
55
+ // Skip node_modules, .git, etc.
56
+ if (entry.isDirectory()) {
57
+ if (!['node_modules', '.git', 'dist', 'build', 'coverage'].includes(entry.name)) {
58
+ walk(fullPath);
59
+ }
60
+ } else if (entry.isFile()) {
61
+ const ext = path.extname(entry.name);
62
+ if (extensions.includes(ext)) {
63
+ files.push(fullPath);
64
+ }
65
+ }
66
+ }
67
+ } catch {
68
+ // Ignore permission errors
69
+ }
70
+ }
71
+
72
+ walk(basePath);
73
+ return files;
74
+ }
75
+
76
+ /**
77
+ * Analyze a single file for potential improvements
78
+ */
79
+ function analyzeFile(filePath: string, type: AnalyzeOptions['type']): RefactoringSuggestion[] {
80
+ const suggestions: RefactoringSuggestion[] = [];
81
+
82
+ try {
83
+ const content = fs.readFileSync(filePath, 'utf-8');
84
+ const lines = content.split('\n');
85
+ const relativePath = path.relative(process.cwd(), filePath);
86
+
87
+ // Simple pattern-based analysis
88
+ lines.forEach((line, index) => {
89
+ const lineNum = index + 1;
90
+
91
+ // Complexity: Long lines
92
+ if (type === 'all' || type === 'refactor') {
93
+ if (line.length > 120) {
94
+ suggestions.push({
95
+ file: relativePath,
96
+ line: lineNum,
97
+ type: 'style',
98
+ severity: 'info',
99
+ explanation: `Line exceeds 120 characters (${line.length}). Consider breaking it up.`,
100
+ });
101
+ }
102
+
103
+ // Nested callbacks (callback hell indicator)
104
+ const nestedCallbacks = (line.match(/\)\s*=>\s*\{/g) || []).length;
105
+ if (nestedCallbacks >= 2) {
106
+ suggestions.push({
107
+ file: relativePath,
108
+ line: lineNum,
109
+ type: 'complexity',
110
+ severity: 'warning',
111
+ explanation:
112
+ 'Multiple nested arrow functions detected. Consider extracting to named functions.',
113
+ });
114
+ }
115
+
116
+ // TODO/FIXME comments
117
+ if (/\/\/\s*(TODO|FIXME|HACK|XXX)/i.test(line)) {
118
+ suggestions.push({
119
+ file: relativePath,
120
+ line: lineNum,
121
+ type: 'style',
122
+ severity: 'info',
123
+ explanation: 'TODO/FIXME comment found. Consider addressing or tracking this.',
124
+ });
125
+ }
126
+ }
127
+
128
+ // Security checks
129
+ if (type === 'all' || type === 'security') {
130
+ // Hardcoded secrets patterns (variable names or quoted strings)
131
+ if (/\b(?:password|secret|api[_-]?key|token)\b\s*[:=]/i.test(line)) {
132
+ suggestions.push({
133
+ file: relativePath,
134
+ line: lineNum,
135
+ type: 'security',
136
+ severity: 'error',
137
+ explanation:
138
+ 'Potential hardcoded credential detected. Use environment variables instead.',
139
+ });
140
+ }
141
+
142
+ // eval() usage
143
+ if (/\beval\s*\(/.test(line)) {
144
+ suggestions.push({
145
+ file: relativePath,
146
+ line: lineNum,
147
+ type: 'security',
148
+ severity: 'error',
149
+ explanation: 'eval() usage detected. This can lead to code injection vulnerabilities.',
150
+ });
151
+ }
152
+
153
+ // SQL injection potential
154
+ if (/\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/i.test(line)) {
155
+ suggestions.push({
156
+ file: relativePath,
157
+ line: lineNum,
158
+ type: 'security',
159
+ severity: 'warning',
160
+ explanation:
161
+ 'Potential SQL injection. Use parameterized queries instead of string interpolation.',
162
+ });
163
+ }
164
+ }
165
+
166
+ // Documentation checks
167
+ if (type === 'all' || type === 'docs') {
168
+ // Functions without JSDoc
169
+ if (/^(?:export\s+)?(?:async\s+)?function\s+\w+/.test(line)) {
170
+ const prevLine = index > 0 ? lines[index - 1] : '';
171
+ if (!/\*\/\s*$/.test(prevLine)) {
172
+ suggestions.push({
173
+ file: relativePath,
174
+ line: lineNum,
175
+ type: 'style',
176
+ severity: 'info',
177
+ explanation: 'Function lacks JSDoc documentation.',
178
+ });
179
+ }
180
+ }
181
+ }
182
+ });
183
+
184
+ // File-level checks
185
+ if (type === 'all' || type === 'refactor') {
186
+ // Large file
187
+ if (lines.length > 500) {
188
+ suggestions.push({
189
+ file: relativePath,
190
+ line: 1,
191
+ type: 'complexity',
192
+ severity: 'warning',
193
+ explanation: `File has ${lines.length} lines. Consider splitting into smaller modules.`,
194
+ });
195
+ }
196
+ }
197
+ } catch {
198
+ // Skip files we can't read
199
+ }
200
+
201
+ return suggestions;
202
+ }
203
+
204
+ /**
205
+ * Display severity with color
206
+ */
207
+ function formatSeverity(severity: RefactoringSuggestion['severity']): string {
208
+ switch (severity) {
209
+ case 'error':
210
+ return ui.color('ERROR', 'red');
211
+ case 'warning':
212
+ return ui.color('WARN', 'yellow');
213
+ case 'info':
214
+ default:
215
+ return ui.color('INFO', 'blue');
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Analyze command
221
+ */
222
+ export async function analyzeCommand(options: AnalyzeOptions): Promise<void> {
223
+ const targetPath = options.path || process.cwd();
224
+
225
+ if (!fs.existsSync(targetPath)) {
226
+ ui.error(`Path not found: ${targetPath}`);
227
+ return;
228
+ }
229
+
230
+ ui.header('Nimbus Analyze', targetPath);
231
+ ui.startSpinner({ message: 'Analyzing codebase...' });
232
+
233
+ // Get files to analyze
234
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs'];
235
+ const files = getFilesToAnalyze(targetPath, extensions);
236
+
237
+ if (files.length === 0) {
238
+ ui.stopSpinnerSuccess('No files found to analyze');
239
+ return;
240
+ }
241
+
242
+ // Analyze files
243
+ const allSuggestions: RefactoringSuggestion[] = [];
244
+
245
+ for (const file of files) {
246
+ const suggestions = analyzeFile(file, options.type);
247
+ allSuggestions.push(...suggestions);
248
+ }
249
+
250
+ ui.stopSpinnerSuccess(`Analyzed ${files.length} files`);
251
+
252
+ // Track analysis completion
253
+ try {
254
+ const { trackEvent } = await import('../../telemetry');
255
+ trackEvent('analysis_completed', {
256
+ filesAnalyzed: files.length,
257
+ suggestionsCount: allSuggestions.length,
258
+ });
259
+ } catch {
260
+ /* telemetry failure is non-critical */
261
+ }
262
+
263
+ // Build analysis result
264
+ const byType: Record<string, number> = {};
265
+ const bySeverity: Record<string, number> = {};
266
+
267
+ for (const s of allSuggestions) {
268
+ byType[s.type] = (byType[s.type] || 0) + 1;
269
+ bySeverity[s.severity] = (bySeverity[s.severity] || 0) + 1;
270
+ }
271
+
272
+ const analysis: CodeAnalysis = {
273
+ path: targetPath,
274
+ analyzedAt: new Date().toISOString(),
275
+ summary: {
276
+ filesAnalyzed: files.length,
277
+ suggestionsCount: allSuggestions.length,
278
+ byType,
279
+ bySeverity,
280
+ },
281
+ suggestions: allSuggestions,
282
+ };
283
+
284
+ if (options.json) {
285
+ console.log(JSON.stringify(analysis, null, 2));
286
+ return;
287
+ }
288
+
289
+ // Display results
290
+ ui.newLine();
291
+ ui.section('Summary');
292
+ ui.print(` Files analyzed: ${analysis.summary.filesAnalyzed}`);
293
+ ui.print(` Total suggestions: ${analysis.summary.suggestionsCount}`);
294
+
295
+ if (analysis.summary.suggestionsCount > 0) {
296
+ ui.newLine();
297
+ ui.print(' By severity:');
298
+ if (bySeverity.error) {
299
+ ui.print(` ${ui.color('Errors:', 'red')} ${bySeverity.error}`);
300
+ }
301
+ if (bySeverity.warning) {
302
+ ui.print(` ${ui.color('Warnings:', 'yellow')} ${bySeverity.warning}`);
303
+ }
304
+ if (bySeverity.info) {
305
+ ui.print(` ${ui.color('Info:', 'blue')} ${bySeverity.info}`);
306
+ }
307
+
308
+ ui.newLine();
309
+ ui.print(' By type:');
310
+ for (const [type, count] of Object.entries(byType)) {
311
+ ui.print(` ${type}: ${count}`);
312
+ }
313
+ }
314
+
315
+ // Show top suggestions
316
+ if (allSuggestions.length > 0) {
317
+ ui.section('Suggestions');
318
+
319
+ // Sort by severity (error > warning > info)
320
+ const sorted = allSuggestions.sort((a, b) => {
321
+ const order = { error: 0, warning: 1, info: 2 };
322
+ return order[a.severity] - order[b.severity];
323
+ });
324
+
325
+ // Show top 20
326
+ const toShow = sorted.slice(0, 20);
327
+
328
+ for (const suggestion of toShow) {
329
+ ui.newLine();
330
+ ui.print(` ${formatSeverity(suggestion.severity)} ${suggestion.file}:${suggestion.line}`);
331
+ ui.print(` ${ui.dim(suggestion.type)}: ${suggestion.explanation}`);
332
+
333
+ if (suggestion.diff) {
334
+ ui.sideBySideDiff({
335
+ original: suggestion.original || '',
336
+ modified: suggestion.suggested || '',
337
+ });
338
+ }
339
+ }
340
+
341
+ if (allSuggestions.length > 20) {
342
+ ui.newLine();
343
+ ui.dim(` ... and ${allSuggestions.length - 20} more suggestions`);
344
+ ui.dim(` Use --json for full output`);
345
+ }
346
+ } else {
347
+ ui.newLine();
348
+ ui.success('No issues found!');
349
+ }
350
+
351
+ ui.newLine();
352
+ }