@vibecheckai/cli 3.4.0 → 3.5.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/bin/registry.js +243 -152
- package/bin/runners/cli-utils.js +2 -33
- package/bin/runners/context/generators/cursor.js +49 -2
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
- package/bin/runners/lib/analyzers.js +544 -19
- package/bin/runners/lib/audit-logger.js +532 -0
- package/bin/runners/lib/authority/authorities/architecture.js +364 -0
- package/bin/runners/lib/authority/authorities/compliance.js +341 -0
- package/bin/runners/lib/authority/authorities/human.js +343 -0
- package/bin/runners/lib/authority/authorities/quality.js +420 -0
- package/bin/runners/lib/authority/authorities/security.js +228 -0
- package/bin/runners/lib/authority/index.js +293 -0
- package/bin/runners/lib/authority-badge.js +425 -425
- package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
- package/bin/runners/lib/cli-charts.js +368 -0
- package/bin/runners/lib/cli-config-display.js +405 -0
- package/bin/runners/lib/cli-demo.js +275 -0
- package/bin/runners/lib/cli-errors.js +438 -0
- package/bin/runners/lib/cli-help-formatter.js +439 -0
- package/bin/runners/lib/cli-interactive-menu.js +509 -0
- package/bin/runners/lib/cli-prompts.js +441 -0
- package/bin/runners/lib/cli-scan-cards.js +362 -0
- package/bin/runners/lib/compliance-reporter.js +710 -0
- package/bin/runners/lib/conductor/index.js +671 -0
- package/bin/runners/lib/easy/README.md +123 -0
- package/bin/runners/lib/easy/index.js +140 -0
- package/bin/runners/lib/easy/interactive-wizard.js +788 -0
- package/bin/runners/lib/easy/one-click-firewall.js +564 -0
- package/bin/runners/lib/easy/zero-config-reality.js +714 -0
- package/bin/runners/lib/engines/accessibility-engine.js +218 -18
- package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
- package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
- package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
- package/bin/runners/lib/engines/confidence-scoring.js +276 -0
- package/bin/runners/lib/engines/context-detection.js +264 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
- package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
- package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
- package/bin/runners/lib/engines/env-variables-engine.js +458 -0
- package/bin/runners/lib/engines/error-handling-engine.js +437 -0
- package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
- package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
- package/bin/runners/lib/engines/framework-detection.js +508 -0
- package/bin/runners/lib/engines/import-order-engine.js +429 -0
- package/bin/runners/lib/engines/mock-data-engine.js +53 -10
- package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
- package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
- package/bin/runners/lib/engines/orchestrator.js +334 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
- package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
- package/bin/runners/lib/engines/type-aware-engine.js +263 -39
- package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
- package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
- package/bin/runners/lib/enhanced-features/index.js +305 -0
- package/bin/runners/lib/enhanced-output.js +631 -0
- package/bin/runners/lib/enterprise.js +300 -0
- package/bin/runners/lib/entitlements-v2.js +103 -11
- package/bin/runners/lib/firewall/command-validator.js +351 -0
- package/bin/runners/lib/firewall/config.js +341 -0
- package/bin/runners/lib/firewall/content-validator.js +519 -0
- package/bin/runners/lib/firewall/index.js +101 -0
- package/bin/runners/lib/firewall/path-validator.js +256 -0
- package/bin/runners/lib/html-proof-report.js +350 -700
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
- package/bin/runners/lib/mcp-utils.js +425 -0
- package/bin/runners/lib/missions/plan.js +46 -6
- package/bin/runners/lib/missions/templates.js +232 -0
- package/bin/runners/lib/output/index.js +1022 -0
- package/bin/runners/lib/policy-engine.js +652 -0
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
- package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
- package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
- package/bin/runners/lib/polish/autofix/index.js +200 -0
- package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
- package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
- package/bin/runners/lib/polish/backend-checks.js +148 -0
- package/bin/runners/lib/polish/documentation-checks.js +111 -0
- package/bin/runners/lib/polish/frontend-checks.js +168 -0
- package/bin/runners/lib/polish/index.js +71 -0
- package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
- package/bin/runners/lib/polish/library-detection.js +175 -0
- package/bin/runners/lib/polish/performance-checks.js +100 -0
- package/bin/runners/lib/polish/security-checks.js +148 -0
- package/bin/runners/lib/polish/utils.js +203 -0
- package/bin/runners/lib/prompt-builder.js +540 -0
- package/bin/runners/lib/proof-certificate.js +634 -0
- package/bin/runners/lib/reality/accessibility-audit.js +946 -0
- package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
- package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
- package/bin/runners/lib/reality/performance-tracker.js +1077 -0
- package/bin/runners/lib/reality/scenario-generator.js +1404 -0
- package/bin/runners/lib/reality/visual-regression.js +852 -0
- package/bin/runners/lib/reality-profiler.js +717 -0
- package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
- package/bin/runners/lib/review/ai-code-review.js +832 -0
- package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
- package/bin/runners/lib/sbom-generator.js +641 -0
- package/bin/runners/lib/scan-output-enhanced.js +512 -0
- package/bin/runners/lib/scan-output.js +47 -0
- package/bin/runners/lib/security/owasp-scanner.js +939 -0
- package/bin/runners/lib/terminal-ui.js +113 -1
- package/bin/runners/lib/unified-cli-output.js +603 -430
- package/bin/runners/lib/validators/contract-validator.js +283 -0
- package/bin/runners/lib/validators/dead-export-detector.js +279 -0
- package/bin/runners/lib/validators/dep-audit.js +245 -0
- package/bin/runners/lib/validators/env-validator.js +319 -0
- package/bin/runners/lib/validators/index.js +120 -0
- package/bin/runners/lib/validators/license-checker.js +252 -0
- package/bin/runners/lib/validators/route-validator.js +290 -0
- package/bin/runners/runAIAgent.js +5 -10
- package/bin/runners/runAgent.js +3 -0
- package/bin/runners/runApprove.js +1233 -1200
- package/bin/runners/runAuth.js +22 -1
- package/bin/runners/runAuthority.js +528 -0
- package/bin/runners/runCheckpoint.js +4 -24
- package/bin/runners/runClassify.js +862 -859
- package/bin/runners/runConductor.js +772 -0
- package/bin/runners/runContainer.js +366 -0
- package/bin/runners/runContext.js +3 -0
- package/bin/runners/runDoctor.js +28 -41
- package/bin/runners/runEasy.js +410 -0
- package/bin/runners/runFirewall.js +3 -0
- package/bin/runners/runFirewallHook.js +3 -0
- package/bin/runners/runFix.js +76 -66
- package/bin/runners/runGuard.js +411 -18
- package/bin/runners/runIaC.js +372 -0
- package/bin/runners/runInit.js +10 -60
- package/bin/runners/runMcp.js +11 -12
- package/bin/runners/runPolish.js +240 -64
- package/bin/runners/runPromptFirewall.js +5 -12
- package/bin/runners/runProve.js +20 -55
- package/bin/runners/runReality.js +68 -59
- package/bin/runners/runReport.js +31 -5
- package/bin/runners/runRuntime.js +5 -8
- package/bin/runners/runScan.js +194 -1286
- package/bin/runners/runShip.js +695 -47
- package/bin/runners/runTruth.js +3 -0
- package/bin/runners/runValidate.js +7 -11
- package/bin/runners/runVibe.js +791 -0
- package/bin/runners/runWatch.js +14 -23
- package/bin/vibecheck.js +175 -56
- package/mcp-server/index.js +190 -14
- package/mcp-server/package.json +1 -1
- package/mcp-server/tools-v3.js +397 -64
- package/mcp-server/tools.js +495 -0
- package/package.json +1 -1
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
- package/mcp-server/index-v1.js +0 -698
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Manages which engines run for SCAN vs SHIP commands:
|
|
5
|
+
* - SCAN (Quick/FREE): 5 essential engines, <3s target
|
|
6
|
+
* - SHIP (Comprehensive/PRO): All 17+ engines + ship-only validators
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
"use strict";
|
|
10
|
+
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const fg = require("fast-glob");
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// ENGINE DEFINITIONS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SCAN engines - Quick analysis for free tier
|
|
21
|
+
* Target: <3 seconds on medium project (100-500 files)
|
|
22
|
+
*/
|
|
23
|
+
const SCAN_ENGINES = [
|
|
24
|
+
"hardcoded-secrets-engine",
|
|
25
|
+
"console-logs-engine",
|
|
26
|
+
"ai-hallucination-engine",
|
|
27
|
+
"type-aware-engine",
|
|
28
|
+
"error-handling-engine",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ALL engines - Comprehensive analysis for Pro tier
|
|
33
|
+
* Used by SHIP command
|
|
34
|
+
*/
|
|
35
|
+
const ALL_ENGINES = [
|
|
36
|
+
// Core security
|
|
37
|
+
"hardcoded-secrets-engine",
|
|
38
|
+
"security-vulnerabilities-engine",
|
|
39
|
+
|
|
40
|
+
// Code quality
|
|
41
|
+
"console-logs-engine",
|
|
42
|
+
"code-quality-engine",
|
|
43
|
+
"dead-code-engine",
|
|
44
|
+
"deprecated-api-engine",
|
|
45
|
+
"empty-catch-engine",
|
|
46
|
+
"unsafe-regex-engine",
|
|
47
|
+
|
|
48
|
+
// Type safety & patterns
|
|
49
|
+
"type-aware-engine",
|
|
50
|
+
"mock-data-engine",
|
|
51
|
+
"error-handling-engine",
|
|
52
|
+
|
|
53
|
+
// Framework patterns
|
|
54
|
+
"async-patterns-engine",
|
|
55
|
+
"react-patterns-engine",
|
|
56
|
+
"database-patterns-engine",
|
|
57
|
+
|
|
58
|
+
// Advanced analysis
|
|
59
|
+
"accessibility-engine",
|
|
60
|
+
"api-consistency-engine",
|
|
61
|
+
"cross-file-analysis-engine",
|
|
62
|
+
|
|
63
|
+
// AI hallucination detection
|
|
64
|
+
"ai-hallucination-engine",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// Standard ignore patterns
|
|
68
|
+
const STANDARD_IGNORE_PATTERNS = [
|
|
69
|
+
"**/node_modules/**",
|
|
70
|
+
"**/.next/**",
|
|
71
|
+
"**/dist/**",
|
|
72
|
+
"**/build/**",
|
|
73
|
+
"**/*.d.ts",
|
|
74
|
+
"**/*.d.ts.map",
|
|
75
|
+
"**/__tests__/**",
|
|
76
|
+
"**/tests/**",
|
|
77
|
+
"**/*.test.ts",
|
|
78
|
+
"**/*.test.tsx",
|
|
79
|
+
"**/*.test.js",
|
|
80
|
+
"**/*.spec.ts",
|
|
81
|
+
"**/*.spec.tsx",
|
|
82
|
+
"**/*.spec.js",
|
|
83
|
+
"**/fixtures/**",
|
|
84
|
+
"**/mcp-server/**",
|
|
85
|
+
"**/bin/**",
|
|
86
|
+
"**/packages/cli/**",
|
|
87
|
+
"**/examples/**",
|
|
88
|
+
"**/templates/**",
|
|
89
|
+
"**/docs/**",
|
|
90
|
+
"**/.guardrail/**",
|
|
91
|
+
"**/.cursor/**",
|
|
92
|
+
"**/.vibecheck/**",
|
|
93
|
+
"**/coverage/**",
|
|
94
|
+
"**/_archive/**",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// File cache for performance
|
|
98
|
+
const _FILE_CACHE = new Map();
|
|
99
|
+
|
|
100
|
+
function readFileCached(filePath) {
|
|
101
|
+
if (_FILE_CACHE.has(filePath)) {
|
|
102
|
+
return _FILE_CACHE.get(filePath);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
106
|
+
_FILE_CACHE.set(filePath, content);
|
|
107
|
+
return content;
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function clearFileCache() {
|
|
114
|
+
_FILE_CACHE.clear();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// ENGINE LOADER
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
function loadEngine(engineName) {
|
|
122
|
+
const enginePaths = [
|
|
123
|
+
`./${engineName}`,
|
|
124
|
+
`./vibecheck-engines/lib/${engineName.replace("-engine", "-engine")}`,
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
for (const enginePath of enginePaths) {
|
|
128
|
+
try {
|
|
129
|
+
return require(enginePath);
|
|
130
|
+
} catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Special cases
|
|
136
|
+
if (engineName === "ai-hallucination-engine") {
|
|
137
|
+
try {
|
|
138
|
+
return require("./vibecheck-engines/lib/ai-hallucination-engine");
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// ORCHESTRATOR
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Run specified engines in parallel on project files
|
|
153
|
+
* @param {string} projectPath - Path to project root
|
|
154
|
+
* @param {Object} options - Configuration
|
|
155
|
+
* @param {string|string[]} options.engines - "scan", "all", or array of engine names
|
|
156
|
+
* @param {boolean} options.parallel - Run engines in parallel (default: true)
|
|
157
|
+
* @param {number} options.maxFiles - Max files to analyze (default: 500 for scan, 2000 for ship)
|
|
158
|
+
* @param {Function} options.onProgress - Progress callback
|
|
159
|
+
* @returns {Promise<{findings: Array, stats: Object}>}
|
|
160
|
+
*/
|
|
161
|
+
async function runAllEngines(projectPath, options = {}) {
|
|
162
|
+
const {
|
|
163
|
+
engines = "scan",
|
|
164
|
+
parallel = true,
|
|
165
|
+
maxFiles = engines === "scan" ? 500 : 2000,
|
|
166
|
+
onProgress = null,
|
|
167
|
+
} = options;
|
|
168
|
+
|
|
169
|
+
const startTime = Date.now();
|
|
170
|
+
const findings = [];
|
|
171
|
+
const stats = {
|
|
172
|
+
enginesRun: 0,
|
|
173
|
+
filesScanned: 0,
|
|
174
|
+
findingsPerEngine: {},
|
|
175
|
+
duration: 0,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Determine which engines to run
|
|
179
|
+
let engineList;
|
|
180
|
+
if (engines === "scan") {
|
|
181
|
+
engineList = SCAN_ENGINES;
|
|
182
|
+
} else if (engines === "all") {
|
|
183
|
+
engineList = ALL_ENGINES;
|
|
184
|
+
} else if (Array.isArray(engines)) {
|
|
185
|
+
engineList = engines;
|
|
186
|
+
} else {
|
|
187
|
+
engineList = SCAN_ENGINES;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Get files to analyze
|
|
191
|
+
const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
|
|
192
|
+
cwd: projectPath,
|
|
193
|
+
absolute: true,
|
|
194
|
+
ignore: STANDARD_IGNORE_PATTERNS,
|
|
195
|
+
}).slice(0, maxFiles);
|
|
196
|
+
|
|
197
|
+
stats.filesScanned = files.length;
|
|
198
|
+
|
|
199
|
+
if (onProgress) onProgress("discovery", 100);
|
|
200
|
+
|
|
201
|
+
// Load engines
|
|
202
|
+
const loadedEngines = [];
|
|
203
|
+
for (const engineName of engineList) {
|
|
204
|
+
const engine = loadEngine(engineName);
|
|
205
|
+
if (engine) {
|
|
206
|
+
loadedEngines.push({ name: engineName, module: engine });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
stats.enginesRun = loadedEngines.length;
|
|
211
|
+
|
|
212
|
+
// Run engines
|
|
213
|
+
const engineResults = await Promise.all(
|
|
214
|
+
loadedEngines.map(async ({ name, module }) => {
|
|
215
|
+
const engineFindings = [];
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// Determine analyzer function
|
|
219
|
+
const analyzerFn = getAnalyzerFunction(name, module);
|
|
220
|
+
if (!analyzerFn) return { name, findings: [] };
|
|
221
|
+
|
|
222
|
+
// Run on each file
|
|
223
|
+
for (const filePath of files) {
|
|
224
|
+
try {
|
|
225
|
+
const content = readFileCached(filePath);
|
|
226
|
+
if (!content) continue;
|
|
227
|
+
|
|
228
|
+
const fileRel = path.relative(projectPath, filePath).replace(/\\/g, "/");
|
|
229
|
+
const fileFindings = analyzerFn(content, fileRel);
|
|
230
|
+
|
|
231
|
+
if (Array.isArray(fileFindings)) {
|
|
232
|
+
engineFindings.push(...fileFindings);
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
// Skip files that fail analysis
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { name, findings: engineFindings };
|
|
240
|
+
} catch {
|
|
241
|
+
return { name, findings: [] };
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Aggregate findings
|
|
247
|
+
for (const result of engineResults) {
|
|
248
|
+
stats.findingsPerEngine[result.name] = result.findings.length;
|
|
249
|
+
findings.push(...result.findings.map(f => ({
|
|
250
|
+
...f,
|
|
251
|
+
engine: result.name,
|
|
252
|
+
})));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Clear cache
|
|
256
|
+
clearFileCache();
|
|
257
|
+
|
|
258
|
+
stats.duration = Date.now() - startTime;
|
|
259
|
+
|
|
260
|
+
if (onProgress) onProgress("analysis", 100);
|
|
261
|
+
|
|
262
|
+
return { findings, stats };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the appropriate analyzer function from an engine module
|
|
267
|
+
*/
|
|
268
|
+
function getAnalyzerFunction(engineName, module) {
|
|
269
|
+
// Map engine names to their exported functions
|
|
270
|
+
const fnMap = {
|
|
271
|
+
"hardcoded-secrets-engine": module.analyzeHardcodedSecrets,
|
|
272
|
+
"console-logs-engine": module.analyzeConsoleLogs,
|
|
273
|
+
"ai-hallucination-engine": module.analyzeAIHallucinations,
|
|
274
|
+
"type-aware-engine": module.analyzeTypeAware,
|
|
275
|
+
"error-handling-engine": module.analyzeErrorHandling,
|
|
276
|
+
"mock-data-engine": module.analyzeMockData,
|
|
277
|
+
"code-quality-engine": module.analyzeCodeQuality,
|
|
278
|
+
"dead-code-engine": module.analyzeDeadCode,
|
|
279
|
+
"deprecated-api-engine": module.analyzeDeprecatedApi,
|
|
280
|
+
"empty-catch-engine": module.analyzeEmptyCatch,
|
|
281
|
+
"unsafe-regex-engine": module.analyzeUnsafeRegex,
|
|
282
|
+
"async-patterns-engine": module.analyzeAsyncPatterns,
|
|
283
|
+
"react-patterns-engine": module.analyzeReactPatterns,
|
|
284
|
+
"database-patterns-engine": module.analyzeDatabasePatterns,
|
|
285
|
+
"accessibility-engine": module.analyzeAccessibility,
|
|
286
|
+
"api-consistency-engine": module.analyzeAPIConsistency,
|
|
287
|
+
"cross-file-analysis-engine": module.analyzeCrossFile,
|
|
288
|
+
"security-vulnerabilities-engine": module.analyzeSecurityVulnerabilities,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return fnMap[engineName] || module.analyze || module.run || Object.values(module).find(v => typeof v === "function");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Run SCAN engines only (quick, free tier)
|
|
296
|
+
*/
|
|
297
|
+
async function runScanEngines(projectPath, options = {}) {
|
|
298
|
+
return runAllEngines(projectPath, {
|
|
299
|
+
...options,
|
|
300
|
+
engines: "scan",
|
|
301
|
+
maxFiles: options.maxFiles || 500,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Run ALL engines (comprehensive, pro tier)
|
|
307
|
+
*/
|
|
308
|
+
async function runShipEngines(projectPath, options = {}) {
|
|
309
|
+
return runAllEngines(projectPath, {
|
|
310
|
+
...options,
|
|
311
|
+
engines: "all",
|
|
312
|
+
maxFiles: options.maxFiles || 2000,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// EXPORTS
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
module.exports = {
|
|
321
|
+
// Constants
|
|
322
|
+
SCAN_ENGINES,
|
|
323
|
+
ALL_ENGINES,
|
|
324
|
+
STANDARD_IGNORE_PATTERNS,
|
|
325
|
+
|
|
326
|
+
// Main orchestrator
|
|
327
|
+
runAllEngines,
|
|
328
|
+
runScanEngines,
|
|
329
|
+
runShipEngines,
|
|
330
|
+
|
|
331
|
+
// Utilities
|
|
332
|
+
loadEngine,
|
|
333
|
+
clearFileCache,
|
|
334
|
+
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Performance Issues Detection Engine
|
|
3
3
|
* Detects memory leaks, inefficient loops, large bundle sizes, and performance anti-patterns
|
|
4
|
+
* Enhanced with framework-aware detection:
|
|
5
|
+
* - React Server Components (different rules apply)
|
|
6
|
+
* - Next.js App Router patterns
|
|
7
|
+
* - Client-only hooks in server components
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
const { getAST } = require("./ast-cache");
|
|
@@ -8,6 +12,32 @@ const traverse = require("@babel/traverse").default;
|
|
|
8
12
|
const t = require("@babel/types");
|
|
9
13
|
const { shouldExcludeFile } = require("./file-filter");
|
|
10
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Check if file is a React Server Component
|
|
17
|
+
*/
|
|
18
|
+
function isServerComponent(code, filePath) {
|
|
19
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
20
|
+
|
|
21
|
+
// Check if it's in app directory (Next.js App Router)
|
|
22
|
+
if (!normalizedPath.includes("/app/")) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for "use client" directive - if present, it's NOT a server component
|
|
27
|
+
if (/['"]use client['"]/.test(code)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if file is a client component
|
|
36
|
+
*/
|
|
37
|
+
function isClientComponent(code) {
|
|
38
|
+
return /['"]use client['"]/.test(code);
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
/**
|
|
12
42
|
* Analyze a file for performance issues
|
|
13
43
|
*/
|
|
@@ -21,48 +51,77 @@ function analyzePerformanceIssues(code, filePath) {
|
|
|
21
51
|
if (!ast) return findings;
|
|
22
52
|
|
|
23
53
|
const lines = code.split("\n");
|
|
54
|
+
|
|
55
|
+
// Determine component type
|
|
56
|
+
const isRSC = isServerComponent(code, filePath);
|
|
57
|
+
const isCC = isClientComponent(code);
|
|
24
58
|
|
|
25
59
|
// Memory leaks: Event listeners not removed
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (t.isMemberExpression(node.callee)) {
|
|
32
|
-
const prop = node.callee.property;
|
|
60
|
+
// Skip for Server Components (no DOM access)
|
|
61
|
+
if (!isRSC) {
|
|
62
|
+
traverse(ast, {
|
|
63
|
+
CallExpression(path) {
|
|
64
|
+
const node = path.node;
|
|
33
65
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const hasRemoveListener = scope.getAllBindings().some((binding, name) => {
|
|
38
|
-
return name.includes("removeEventListener") || name.includes("removeListener");
|
|
39
|
-
});
|
|
66
|
+
// Check for addEventListener without corresponding removeEventListener
|
|
67
|
+
if (t.isMemberExpression(node.callee)) {
|
|
68
|
+
const prop = node.callee.property;
|
|
40
69
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
if (t.isIdentifier(prop) && prop.name === "addEventListener") {
|
|
71
|
+
// Check if there's a corresponding removeEventListener in the same scope
|
|
72
|
+
const scope = path.scope;
|
|
73
|
+
let hasRemoveListener = false;
|
|
74
|
+
try {
|
|
75
|
+
const bindings = scope.getAllBindings();
|
|
76
|
+
for (const name in bindings) {
|
|
77
|
+
if (name.includes("removeEventListener") || name.includes("removeListener")) {
|
|
78
|
+
hasRemoveListener = true;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Scope traversal can fail, assume safe
|
|
84
|
+
hasRemoveListener = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Also check if it's in a useEffect cleanup (React)
|
|
88
|
+
const parent = path.findParent(p =>
|
|
89
|
+
t.isCallExpression(p.node) &&
|
|
90
|
+
t.isIdentifier(p.node.callee, { name: "useEffect" })
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Check if there's a cleanup function returning removeEventListener
|
|
94
|
+
if (parent) {
|
|
95
|
+
const useEffectArg = parent.node.arguments[0];
|
|
96
|
+
if (t.isArrowFunctionExpression(useEffectArg) || t.isFunctionExpression(useEffectArg)) {
|
|
97
|
+
const funcCode = code.substring(useEffectArg.start, useEffectArg.end);
|
|
98
|
+
if (/return.*removeEventListener|removeEventListener.*return/s.test(funcCode)) {
|
|
99
|
+
hasRemoveListener = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!hasRemoveListener && !parent) {
|
|
105
|
+
const line = node.loc.start.line;
|
|
106
|
+
findings.push({
|
|
107
|
+
type: "memory_leak",
|
|
108
|
+
severity: "WARN",
|
|
109
|
+
category: "Performance",
|
|
110
|
+
file: filePath,
|
|
111
|
+
line,
|
|
112
|
+
column: node.loc.start.column,
|
|
113
|
+
title: "Potential memory leak: Event listener not removed",
|
|
114
|
+
message: "addEventListener called without corresponding removeEventListener. Use useEffect cleanup.",
|
|
115
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
116
|
+
confidence: "med",
|
|
117
|
+
fixHint: "Add cleanup in useEffect: useEffect(() => { el.addEventListener(...); return () => el.removeEventListener(...); }, [])",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
61
120
|
}
|
|
62
121
|
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
66
125
|
|
|
67
126
|
// Inefficient loops: nested loops with O(n²) complexity
|
|
68
127
|
// Only flag if depth >= 4 (3 levels is often acceptable)
|
|
@@ -251,12 +310,93 @@ function analyzePerformanceIssues(code, filePath) {
|
|
|
251
310
|
message: `Importing entire ${source} library - use tree-shaking or import specific functions`,
|
|
252
311
|
codeSnippet: lines[line - 1]?.trim(),
|
|
253
312
|
confidence: "med",
|
|
313
|
+
fixHint: `Import specific functions: import { specificFn } from '${source}/specificFn'`,
|
|
254
314
|
});
|
|
255
315
|
}
|
|
256
316
|
}
|
|
257
317
|
},
|
|
258
318
|
});
|
|
259
319
|
|
|
320
|
+
// React Server Components: Detect client-only hooks used in server components
|
|
321
|
+
if (isRSC) {
|
|
322
|
+
const clientOnlyHooks = [
|
|
323
|
+
"useState",
|
|
324
|
+
"useEffect",
|
|
325
|
+
"useLayoutEffect",
|
|
326
|
+
"useRef",
|
|
327
|
+
"useCallback",
|
|
328
|
+
"useMemo",
|
|
329
|
+
"useReducer",
|
|
330
|
+
"useContext",
|
|
331
|
+
"useImperativeHandle",
|
|
332
|
+
"useDebugValue",
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
traverse(ast, {
|
|
336
|
+
CallExpression(path) {
|
|
337
|
+
const node = path.node;
|
|
338
|
+
|
|
339
|
+
if (t.isIdentifier(node.callee)) {
|
|
340
|
+
const hookName = node.callee.name;
|
|
341
|
+
|
|
342
|
+
if (clientOnlyHooks.includes(hookName)) {
|
|
343
|
+
const line = node.loc.start.line;
|
|
344
|
+
findings.push({
|
|
345
|
+
type: "client_hook_in_server_component",
|
|
346
|
+
severity: "BLOCK",
|
|
347
|
+
category: "Performance",
|
|
348
|
+
file: filePath,
|
|
349
|
+
line,
|
|
350
|
+
column: node.loc.start.column,
|
|
351
|
+
title: `Client-only hook '${hookName}' used in Server Component`,
|
|
352
|
+
message: `${hookName} cannot be used in Server Components. Add 'use client' directive or move to a client component.`,
|
|
353
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
354
|
+
confidence: "high",
|
|
355
|
+
fixHint: "Add 'use client' at the top of the file, or extract this logic to a client component",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Detect async/await in client components that should be server components
|
|
364
|
+
if (isCC) {
|
|
365
|
+
// Check for database queries in client components
|
|
366
|
+
const serverOnlyPatterns = [
|
|
367
|
+
/prisma\./,
|
|
368
|
+
/db\./,
|
|
369
|
+
/\.findUnique\(/,
|
|
370
|
+
/\.findMany\(/,
|
|
371
|
+
/\.create\(/,
|
|
372
|
+
/\.update\(/,
|
|
373
|
+
/\.delete\(/,
|
|
374
|
+
/fs\./,
|
|
375
|
+
/readFile/,
|
|
376
|
+
/writeFile/,
|
|
377
|
+
];
|
|
378
|
+
|
|
379
|
+
if (serverOnlyPatterns.some(p => p.test(code))) {
|
|
380
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
381
|
+
// Only warn if in app directory where RSC is available
|
|
382
|
+
if (normalizedPath.includes("/app/")) {
|
|
383
|
+
findings.push({
|
|
384
|
+
type: "server_code_in_client_component",
|
|
385
|
+
severity: "WARN",
|
|
386
|
+
category: "Performance",
|
|
387
|
+
file: filePath,
|
|
388
|
+
line: 1,
|
|
389
|
+
column: 0,
|
|
390
|
+
title: "Server-only code in Client Component",
|
|
391
|
+
message: "Database/filesystem operations should be in Server Components for better performance",
|
|
392
|
+
codeSnippet: "'use client'",
|
|
393
|
+
confidence: "med",
|
|
394
|
+
fixHint: "Remove 'use client' and move client interactivity to child components",
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
260
400
|
return findings;
|
|
261
401
|
}
|
|
262
402
|
|