@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.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- 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
|
+
}
|