@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.
Files changed (166) hide show
  1. package/bin/registry.js +243 -152
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +544 -19
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +103 -11
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +47 -0
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/terminal-ui.js +113 -1
  109. package/bin/runners/lib/unified-cli-output.js +603 -430
  110. package/bin/runners/lib/validators/contract-validator.js +283 -0
  111. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  112. package/bin/runners/lib/validators/dep-audit.js +245 -0
  113. package/bin/runners/lib/validators/env-validator.js +319 -0
  114. package/bin/runners/lib/validators/index.js +120 -0
  115. package/bin/runners/lib/validators/license-checker.js +252 -0
  116. package/bin/runners/lib/validators/route-validator.js +290 -0
  117. package/bin/runners/runAIAgent.js +5 -10
  118. package/bin/runners/runAgent.js +3 -0
  119. package/bin/runners/runApprove.js +1233 -1200
  120. package/bin/runners/runAuth.js +22 -1
  121. package/bin/runners/runAuthority.js +528 -0
  122. package/bin/runners/runCheckpoint.js +4 -24
  123. package/bin/runners/runClassify.js +862 -859
  124. package/bin/runners/runConductor.js +772 -0
  125. package/bin/runners/runContainer.js +366 -0
  126. package/bin/runners/runContext.js +3 -0
  127. package/bin/runners/runDoctor.js +28 -41
  128. package/bin/runners/runEasy.js +410 -0
  129. package/bin/runners/runFirewall.js +3 -0
  130. package/bin/runners/runFirewallHook.js +3 -0
  131. package/bin/runners/runFix.js +76 -66
  132. package/bin/runners/runGuard.js +411 -18
  133. package/bin/runners/runIaC.js +372 -0
  134. package/bin/runners/runInit.js +10 -60
  135. package/bin/runners/runMcp.js +11 -12
  136. package/bin/runners/runPolish.js +240 -64
  137. package/bin/runners/runPromptFirewall.js +5 -12
  138. package/bin/runners/runProve.js +20 -55
  139. package/bin/runners/runReality.js +68 -59
  140. package/bin/runners/runReport.js +31 -5
  141. package/bin/runners/runRuntime.js +5 -8
  142. package/bin/runners/runScan.js +194 -1286
  143. package/bin/runners/runShip.js +695 -47
  144. package/bin/runners/runTruth.js +3 -0
  145. package/bin/runners/runValidate.js +7 -11
  146. package/bin/runners/runVibe.js +791 -0
  147. package/bin/runners/runWatch.js +14 -23
  148. package/bin/vibecheck.js +175 -56
  149. package/mcp-server/index.js +190 -14
  150. package/mcp-server/package.json +1 -1
  151. package/mcp-server/tools-v3.js +397 -64
  152. package/mcp-server/tools.js +495 -0
  153. package/package.json +1 -1
  154. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  155. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  156. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  157. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  166. 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
- traverse(ast, {
27
- CallExpression(path) {
28
- const node = path.node;
29
-
30
- // Check for addEventListener without corresponding removeEventListener
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
- if (t.isIdentifier(prop) && prop.name === "addEventListener") {
35
- // Check if there's a corresponding removeEventListener in the same scope
36
- const scope = path.scope;
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
- // Also check if it's in a useEffect cleanup (React)
42
- const parent = path.findParent(p =>
43
- t.isCallExpression(p.node) &&
44
- t.isIdentifier(p.node.callee, { name: "useEffect" })
45
- );
46
-
47
- if (!hasRemoveListener && !parent) {
48
- const line = node.loc.start.line;
49
- findings.push({
50
- type: "memory_leak",
51
- severity: "WARN",
52
- category: "Performance",
53
- file: filePath,
54
- line,
55
- column: node.loc.start.column,
56
- title: "Potential memory leak: Event listener not removed",
57
- message: "addEventListener called without corresponding removeEventListener",
58
- codeSnippet: lines[line - 1]?.trim(),
59
- confidence: "med",
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