@vibecheckai/cli 3.5.0 → 3.5.2

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 (224) hide show
  1. package/bin/registry.js +214 -237
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/analyzer.js +52 -1
  4. package/bin/runners/context/generators/cursor.js +2 -49
  5. package/bin/runners/context/git-context.js +3 -1
  6. package/bin/runners/context/team-conventions.js +33 -7
  7. package/bin/runners/lib/analysis-core.js +25 -5
  8. package/bin/runners/lib/analyzers.js +431 -481
  9. package/bin/runners/lib/default-config.js +127 -0
  10. package/bin/runners/lib/doctor/modules/security.js +3 -1
  11. package/bin/runners/lib/engine/ast-cache.js +210 -0
  12. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  13. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  14. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  15. package/bin/runners/lib/engine/env-extractor.js +207 -0
  16. package/bin/runners/lib/engine/express-extractor.js +208 -0
  17. package/bin/runners/lib/engine/extractors.js +849 -0
  18. package/bin/runners/lib/engine/index.js +207 -0
  19. package/bin/runners/lib/engine/repo-index.js +514 -0
  20. package/bin/runners/lib/engine/types.js +124 -0
  21. package/bin/runners/lib/engines/accessibility-engine.js +18 -218
  22. package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
  23. package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
  24. package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
  25. package/bin/runners/lib/engines/mock-data-engine.js +10 -53
  26. package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
  27. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
  28. package/bin/runners/lib/engines/type-aware-engine.js +39 -263
  29. package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
  30. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  31. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  32. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  33. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  34. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  35. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  36. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  37. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
  38. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  39. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  40. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  41. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  42. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  43. package/bin/runners/lib/entitlements-v2.js +73 -97
  44. package/bin/runners/lib/error-handler.js +44 -3
  45. package/bin/runners/lib/error-messages.js +289 -0
  46. package/bin/runners/lib/evidence-pack.js +7 -1
  47. package/bin/runners/lib/finding-id.js +69 -0
  48. package/bin/runners/lib/finding-sorter.js +89 -0
  49. package/bin/runners/lib/html-proof-report.js +700 -350
  50. package/bin/runners/lib/missions/plan.js +6 -46
  51. package/bin/runners/lib/missions/templates.js +0 -232
  52. package/bin/runners/lib/next-action.js +560 -0
  53. package/bin/runners/lib/prerequisites.js +149 -0
  54. package/bin/runners/lib/route-detection.js +137 -68
  55. package/bin/runners/lib/scan-output.js +91 -76
  56. package/bin/runners/lib/scan-runner.js +135 -0
  57. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  58. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  59. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  60. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  61. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  62. package/bin/runners/lib/schemas/validator.js +27 -0
  63. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  64. package/bin/runners/lib/ship-output-enterprise.js +23 -23
  65. package/bin/runners/lib/ship-output.js +75 -31
  66. package/bin/runners/lib/terminal-ui.js +6 -113
  67. package/bin/runners/lib/truth.js +351 -10
  68. package/bin/runners/lib/unified-cli-output.js +430 -603
  69. package/bin/runners/lib/unified-output.js +13 -9
  70. package/bin/runners/runAIAgent.js +10 -5
  71. package/bin/runners/runAgent.js +0 -3
  72. package/bin/runners/runAllowlist.js +389 -0
  73. package/bin/runners/runApprove.js +0 -33
  74. package/bin/runners/runAuth.js +73 -45
  75. package/bin/runners/runCheckpoint.js +51 -11
  76. package/bin/runners/runClassify.js +85 -21
  77. package/bin/runners/runContext.js +0 -3
  78. package/bin/runners/runDoctor.js +41 -28
  79. package/bin/runners/runEvidencePack.js +362 -0
  80. package/bin/runners/runFirewall.js +0 -3
  81. package/bin/runners/runFirewallHook.js +0 -3
  82. package/bin/runners/runFix.js +66 -76
  83. package/bin/runners/runGuard.js +18 -411
  84. package/bin/runners/runInit.js +113 -30
  85. package/bin/runners/runLabs.js +424 -0
  86. package/bin/runners/runMcp.js +19 -25
  87. package/bin/runners/runPolish.js +64 -240
  88. package/bin/runners/runPromptFirewall.js +12 -5
  89. package/bin/runners/runProve.js +57 -22
  90. package/bin/runners/runQuickstart.js +531 -0
  91. package/bin/runners/runReality.js +59 -68
  92. package/bin/runners/runReport.js +38 -33
  93. package/bin/runners/runRuntime.js +8 -5
  94. package/bin/runners/runScan.js +1413 -190
  95. package/bin/runners/runShip.js +113 -719
  96. package/bin/runners/runTruth.js +0 -3
  97. package/bin/runners/runValidate.js +13 -9
  98. package/bin/runners/runWatch.js +23 -14
  99. package/bin/scan.js +6 -1
  100. package/bin/vibecheck.js +204 -185
  101. package/mcp-server/deprecation-middleware.js +282 -0
  102. package/mcp-server/handlers/index.ts +15 -0
  103. package/mcp-server/handlers/tool-handler.ts +554 -0
  104. package/mcp-server/index-v1.js +698 -0
  105. package/mcp-server/index.js +210 -238
  106. package/mcp-server/lib/cache-wrapper.cjs +383 -0
  107. package/mcp-server/lib/error-envelope.js +138 -0
  108. package/mcp-server/lib/executor.ts +499 -0
  109. package/mcp-server/lib/index.ts +19 -0
  110. package/mcp-server/lib/rate-limiter.js +166 -0
  111. package/mcp-server/lib/sandbox.test.ts +519 -0
  112. package/mcp-server/lib/sandbox.ts +395 -0
  113. package/mcp-server/lib/types.ts +267 -0
  114. package/mcp-server/package.json +12 -3
  115. package/mcp-server/registry/tool-registry.js +794 -0
  116. package/mcp-server/registry/tools.json +605 -0
  117. package/mcp-server/registry.test.ts +334 -0
  118. package/mcp-server/tests/tier-gating.test.js +297 -0
  119. package/mcp-server/tier-auth.js +378 -45
  120. package/mcp-server/tools-v3.js +353 -442
  121. package/mcp-server/tsconfig.json +37 -0
  122. package/mcp-server/vibecheck-2.0-tools.js +14 -1
  123. package/package.json +1 -1
  124. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  125. package/bin/runners/lib/audit-logger.js +0 -532
  126. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  127. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  128. package/bin/runners/lib/authority/authorities/human.js +0 -343
  129. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  130. package/bin/runners/lib/authority/authorities/security.js +0 -228
  131. package/bin/runners/lib/authority/index.js +0 -293
  132. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  133. package/bin/runners/lib/cli-charts.js +0 -368
  134. package/bin/runners/lib/cli-config-display.js +0 -405
  135. package/bin/runners/lib/cli-demo.js +0 -275
  136. package/bin/runners/lib/cli-errors.js +0 -438
  137. package/bin/runners/lib/cli-help-formatter.js +0 -439
  138. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  139. package/bin/runners/lib/cli-prompts.js +0 -441
  140. package/bin/runners/lib/cli-scan-cards.js +0 -362
  141. package/bin/runners/lib/compliance-reporter.js +0 -710
  142. package/bin/runners/lib/conductor/index.js +0 -671
  143. package/bin/runners/lib/easy/README.md +0 -123
  144. package/bin/runners/lib/easy/index.js +0 -140
  145. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  146. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  147. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  148. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  149. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  150. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  151. package/bin/runners/lib/engines/context-detection.js +0 -264
  152. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  153. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  154. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  155. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  156. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  157. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  158. package/bin/runners/lib/engines/framework-detection.js +0 -508
  159. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  160. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  161. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  162. package/bin/runners/lib/engines/orchestrator.js +0 -334
  163. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  167. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  168. package/bin/runners/lib/enhanced-features/index.js +0 -305
  169. package/bin/runners/lib/enhanced-output.js +0 -631
  170. package/bin/runners/lib/enterprise.js +0 -300
  171. package/bin/runners/lib/firewall/command-validator.js +0 -351
  172. package/bin/runners/lib/firewall/config.js +0 -341
  173. package/bin/runners/lib/firewall/content-validator.js +0 -519
  174. package/bin/runners/lib/firewall/index.js +0 -101
  175. package/bin/runners/lib/firewall/path-validator.js +0 -256
  176. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  177. package/bin/runners/lib/mcp-utils.js +0 -425
  178. package/bin/runners/lib/output/index.js +0 -1022
  179. package/bin/runners/lib/policy-engine.js +0 -652
  180. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  181. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  182. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  183. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  184. package/bin/runners/lib/polish/autofix/index.js +0 -200
  185. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  186. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  187. package/bin/runners/lib/polish/backend-checks.js +0 -148
  188. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  189. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  190. package/bin/runners/lib/polish/index.js +0 -71
  191. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  192. package/bin/runners/lib/polish/library-detection.js +0 -175
  193. package/bin/runners/lib/polish/performance-checks.js +0 -100
  194. package/bin/runners/lib/polish/security-checks.js +0 -148
  195. package/bin/runners/lib/polish/utils.js +0 -203
  196. package/bin/runners/lib/prompt-builder.js +0 -540
  197. package/bin/runners/lib/proof-certificate.js +0 -634
  198. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  199. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  200. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  201. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  202. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  203. package/bin/runners/lib/reality/visual-regression.js +0 -852
  204. package/bin/runners/lib/reality-profiler.js +0 -717
  205. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  206. package/bin/runners/lib/review/ai-code-review.js +0 -832
  207. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  208. package/bin/runners/lib/sbom-generator.js +0 -641
  209. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  210. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  211. package/bin/runners/lib/validators/contract-validator.js +0 -283
  212. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  213. package/bin/runners/lib/validators/dep-audit.js +0 -245
  214. package/bin/runners/lib/validators/env-validator.js +0 -319
  215. package/bin/runners/lib/validators/index.js +0 -120
  216. package/bin/runners/lib/validators/license-checker.js +0 -252
  217. package/bin/runners/lib/validators/route-validator.js +0 -290
  218. package/bin/runners/runAuthority.js +0 -528
  219. package/bin/runners/runConductor.js +0 -772
  220. package/bin/runners/runContainer.js +0 -366
  221. package/bin/runners/runEasy.js +0 -410
  222. package/bin/runners/runIaC.js +0 -372
  223. package/bin/runners/runVibe.js +0 -791
  224. package/mcp-server/tools.js +0 -495
@@ -12,24 +12,6 @@ const t = require("@babel/types");
12
12
 
13
13
  const { routeMatches } = require("./claims");
14
14
  const { matcherCoversPath } = require("./auth-truth");
15
- const {
16
- getFrameworks,
17
- hasFramework,
18
- getFileContext,
19
- isServerComponent,
20
- isClientComponent,
21
- isRouteHandler,
22
- FRAMEWORKS,
23
- } = require("./engines/framework-detection");
24
-
25
- // V6: Bulletproof noise reduction and false positive prevention
26
- let noiseReduction, falsePositivePrevention;
27
- try {
28
- noiseReduction = require("./engines/noise-reduction-engine");
29
- falsePositivePrevention = require("./engines/false-positive-prevention");
30
- } catch {
31
- // Engines not available - continue without them
32
- }
33
15
 
34
16
  /* ============================================================================
35
17
  * STANDARD IGNORE PATTERNS
@@ -141,7 +123,22 @@ function rxTest(rx, s) {
141
123
  return rx.test(s);
142
124
  }
143
125
 
126
+ // Try to use the engine's globalASTCache if available for better performance
127
+ let _globalASTCache = null;
128
+ try {
129
+ _globalASTCache = require("./engine/ast-cache").globalASTCache;
130
+ } catch {
131
+ // Engine not available, will use direct parsing
132
+ }
133
+
144
134
  function parseFile(code, fileAbsForErrors = "") {
135
+ // Use globalASTCache if available (engine v2 integration)
136
+ if (_globalASTCache) {
137
+ const result = _globalASTCache.parse(code, fileAbsForErrors);
138
+ if (result.ast) return result.ast;
139
+ // Fall through to direct parse if cache failed
140
+ }
141
+
145
142
  // Error recovery avoids hard-failing on mixed TS/JS/JSX edge cases.
146
143
  return parser.parse(code, {
147
144
  sourceType: "unambiguous",
@@ -889,8 +886,11 @@ function findEnvGaps(truthpack) {
889
886
  confidence: isReallyRequired ? "high" : "low",
890
887
  evidence: v.references || [],
891
888
  fixHints: [
892
- `Add ${v.name}= to .env.example (or .env.template).`,
893
- "If optional, ensure there's an explicit fallback or guard (and document expected behavior).",
889
+ `Add to .env.example: ${v.name}=your_value_here`,
890
+ isReallyRequired
891
+ ? `Add fallback: const value = process.env.${v.name} ?? 'default';`
892
+ : `Guard usage: if (process.env.${v.name}) { /* use it */ }`,
893
+ "Docs: https://12factor.net/config",
894
894
  ],
895
895
  });
896
896
  }
@@ -981,21 +981,6 @@ function findFakeSuccess(repoRoot) {
981
981
  // relevant keywords (toast/push/navigate AND fetch/axios).
982
982
  const hasSuccessUI = /\b(toast|\.push|navigate)\b/.test(code);
983
983
  const hasNetworkCall = /\b(fetch|axios)\b/.test(code);
984
-
985
- // Enhanced: Also check for TanStack Query / React Query mutations
986
- // These have built-in error handling via onError callback
987
- const hasTanstackMutation = /\buseMutation\b/.test(code);
988
- const hasTRPCMutation = /\.useMutation\b/.test(code);
989
-
990
- // Skip files that use TanStack Query mutations with onSuccess/onError
991
- // These are properly handling async state
992
- if (hasTanstackMutation || hasTRPCMutation) {
993
- const hasProperCallbacks = /onSuccess\s*:|onError\s*:|onSettled\s*:/.test(code);
994
- if (hasProperCallbacks) {
995
- continue; // Properly handled
996
- }
997
- }
998
-
999
984
  if (!hasSuccessUI || !hasNetworkCall) {
1000
985
  continue;
1001
986
  }
@@ -1031,7 +1016,16 @@ function findFakeSuccess(repoRoot) {
1031
1016
  IfStatement(p) {
1032
1017
  const test = p.node.test;
1033
1018
  const txt = code.slice(test.start || 0, test.end || 0);
1019
+ // Check for response status validation (res.ok, status)
1034
1020
  if (/\b(res|response)\b/i.test(txt) && /\b(ok|status)\b/i.test(txt)) okChecks.push({ pos: p.node.start ?? 0 });
1021
+ // Check for response body validation (data, error, success property checks)
1022
+ if (/\b(data|result|response)\b/i.test(txt) && /\b(error|success|\.data|\.result)\b/i.test(txt)) okChecks.push({ pos: p.node.start ?? 0 });
1023
+ },
1024
+ // Also check for try-catch around the network call as a form of validation
1025
+ TryStatement(tryPath) {
1026
+ if (tryPath.node.handler) {
1027
+ okChecks.push({ pos: tryPath.node.start ?? 0 });
1028
+ }
1035
1029
  },
1036
1030
  });
1037
1031
 
@@ -1063,8 +1057,9 @@ function findFakeSuccess(repoRoot) {
1063
1057
  confidence: "med",
1064
1058
  evidence: ev ? [ev] : [],
1065
1059
  fixHints: [
1066
- "Await the network call (await fetch/await axios...).",
1067
- "Gate success UI behind res.ok / status checks; surface errors otherwise.",
1060
+ "const res = await fetch(...); if (!res.ok) throw new Error('Request failed');",
1061
+ "const data = await res.json(); if (data.success) toast.success('Done!'); else toast.error(data.error);",
1062
+ "Use try-catch: try { await api(); toast.success(); } catch (e) { toast.error(e.message); }",
1068
1063
  ],
1069
1064
  });
1070
1065
  }
@@ -1084,58 +1079,18 @@ function findFakeSuccess(repoRoot) {
1084
1079
  * ========================================================================== */
1085
1080
 
1086
1081
  function looksSensitive(pathStr) {
1087
- const p = String(pathStr || "").toLowerCase();
1088
-
1089
- // Sensitive path prefixes
1090
- const sensitivePathPrefixes = [
1091
- "/api/admin",
1092
- "/api/billing",
1093
- "/api/stripe",
1094
- "/api/org",
1095
- "/api/team",
1096
- "/api/account",
1097
- "/api/settings",
1098
- "/api/users",
1099
- "/api/user",
1100
- "/api/profile",
1101
- "/api/dashboard",
1102
- "/api/private",
1103
- "/api/internal",
1104
- "/api/manage",
1105
- "/api/payment",
1106
- "/api/subscription",
1107
- "/api/checkout",
1108
- "/api/webhook", // Webhooks need verification
1109
- "/api/upload",
1110
- "/api/delete",
1111
- "/api/export",
1112
- "/api/import",
1113
- ];
1114
-
1115
- // Sensitive path patterns (regex)
1116
- const sensitivePathPatterns = [
1117
- /\/api\/.*\/admin/,
1118
- /\/api\/.*\/delete/,
1119
- /\/api\/.*\/edit/,
1120
- /\/api\/.*\/update/,
1121
- /\/api\/.*\/create/,
1122
- /\/api\/.*\/remove/,
1123
- /\/api\/.*\/modify/,
1124
- /\/api\/me\b/,
1125
- /\/api\/\[.*id\]/, // Dynamic user/resource routes
1126
- ];
1127
-
1128
- // Check prefixes
1129
- if (sensitivePathPrefixes.some(prefix => p.startsWith(prefix))) {
1130
- return true;
1131
- }
1132
-
1133
- // Check patterns
1134
- if (sensitivePathPatterns.some(pattern => pattern.test(p))) {
1135
- return true;
1136
- }
1137
-
1138
- return false;
1082
+ const p = String(pathStr || "");
1083
+ return (
1084
+ p.startsWith("/api/admin") ||
1085
+ p.startsWith("/api/billing") ||
1086
+ p.startsWith("/api/stripe") ||
1087
+ p.startsWith("/api/org") ||
1088
+ p.startsWith("/api/team") ||
1089
+ p.startsWith("/api/account") ||
1090
+ p.startsWith("/api/settings") ||
1091
+ p.startsWith("/api/users") ||
1092
+ p.startsWith("/api/user")
1093
+ );
1139
1094
  }
1140
1095
 
1141
1096
  function hasRouteLevelProtection(routeDef) {
@@ -1148,69 +1103,11 @@ function handlerHasAuthSignal(repoRoot, handlerRel) {
1148
1103
  if (!fs.existsSync(abs)) return false;
1149
1104
  const code = readFileCached(abs);
1150
1105
 
1151
- // Next.js / NextAuth patterns
1152
- const nextAuthPatterns = [
1153
- /\bgetServerSession\b/,
1154
- /\bauth\(\)\b/,
1155
- /\bgetSession\b/,
1156
- /\buseSession\b/,
1157
- /\bgetToken\b/,
1158
- ];
1159
-
1160
- // Clerk patterns
1161
- const clerkPatterns = [
1162
- /\bclerk\b/i,
1163
- /@clerk\/nextjs/,
1164
- /\bauth\(\)\s*\.\s*userId/,
1165
- /\bcurrentUser\b/,
1166
- /\bgetAuth\b/,
1167
- ];
1168
-
1169
- // Supabase patterns
1170
- const supabasePatterns = [
1171
- /\bcreateRouteHandlerClient\b/,
1172
- /\bcreateServerComponentClient\b/,
1173
- /\bsupabase\b.*\bauth\b/i,
1174
- /@supabase/,
1175
- /\bgetUser\(\)/,
1176
- ];
1177
-
1178
- // General auth patterns
1179
- const generalAuthPatterns = [
1180
- /\b(jwtVerify|verifyToken|verifyJWT)\b/i,
1181
- /\bauthorization\b/i,
1182
- /\bbearer\s+token/i,
1183
- /\b(isAdmin|adminOnly|permissions|rbac)\b/i,
1184
- /\bauthenticated\b/i,
1185
- /\brequireAuth\b/i,
1186
- /\bprotectedProcedure\b/, // tRPC
1187
- ];
1188
-
1189
- // Next.js App Router specific patterns
1190
- const appRouterPatterns = [
1191
- /\bcookies\(\)\s*\.\s*get\s*\(\s*['"](?:session|token|auth)/i,
1192
- /\bheaders\(\)\s*\.\s*get\s*\(\s*['"]authorization/i,
1193
- /\bNextResponse\.redirect\s*\(.*(?:login|signin|unauthorized)/i,
1194
- ];
1195
-
1196
- // tRPC context patterns (context often has auth)
1197
- const trpcPatterns = [
1198
- /ctx\.session/,
1199
- /ctx\.user/,
1200
- /ctx\.auth/,
1201
- /\.protectedProcedure/,
1202
- ];
1203
-
1204
- const allPatterns = [
1205
- ...nextAuthPatterns,
1206
- ...clerkPatterns,
1207
- ...supabasePatterns,
1208
- ...generalAuthPatterns,
1209
- ...appRouterPatterns,
1210
- ...trpcPatterns,
1211
- ];
1212
-
1213
- return allPatterns.some(pattern => pattern.test(code));
1106
+ return (
1107
+ /\bgetServerSession\b|\bauth\(\)\b|\bclerk\b|@clerk\/nextjs|\bcreateRouteHandlerClient\b|@supabase/i.test(code) ||
1108
+ /\b(jwtVerify|authorization|bearer|verifyToken|verifyJWT)\b/i.test(code) ||
1109
+ /\b(isAdmin|adminOnly|permissions|rbac)\b/i.test(code)
1110
+ );
1214
1111
  }
1215
1112
 
1216
1113
  function isProtectedByNextMiddleware(truthpack, routePath) {
@@ -1222,7 +1119,12 @@ function findGhostAuth(truthpack, repoRoot) {
1222
1119
  const findings = [];
1223
1120
  const server = truthpack?.routes?.server || [];
1224
1121
 
1122
+ // Track mutation routes without CSRF protection
1123
+ const mutationMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
1124
+
1225
1125
  for (const r of server) {
1126
+ const isMutation = mutationMethods.has(String(r.method).toUpperCase());
1127
+
1226
1128
  if (!looksSensitive(r.path)) continue;
1227
1129
 
1228
1130
  const middlewareProtected = isProtectedByNextMiddleware(truthpack, r.path);
@@ -1241,12 +1143,41 @@ function findGhostAuth(truthpack, repoRoot) {
1241
1143
  confidence: "med",
1242
1144
  evidence: (r.evidence || []).slice(0, 2),
1243
1145
  fixHints: [
1244
- "Add server-side auth verification (session/jwt).",
1245
- "Or protect the path via middleware matcher (verify it actually applies).",
1246
- "If Fastify: add preHandler/onRequest auth hook and ensure it's registered for this route.",
1146
+ `Next.js: const session = await getServerSession(); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });`,
1147
+ `Fastify: fastify.addHook('preHandler', async (req, reply) => { if (!req.user) reply.code(401).send({ error: 'Unauthorized' }); });`,
1148
+ "Or add to middleware.ts matcher: export const config = { matcher: ['/api/admin/:path*'] };",
1149
+ "Docs: https://nextjs.org/docs/app/building-your-application/authentication",
1247
1150
  ],
1248
1151
  });
1249
1152
  }
1153
+
1154
+ // Check for CSRF protection on mutations
1155
+ if (isMutation && r.handler) {
1156
+ const abs = path.join(repoRoot, r.handler);
1157
+ if (fs.existsSync(abs)) {
1158
+ const code = readFileCached(abs);
1159
+ const hasCSRFCheck = /\b(csrf|csrfToken|_csrf|x-csrf-token|xsrf|anti-forgery)\b/i.test(code);
1160
+ const isAPIRoute = r.path.startsWith("/api/");
1161
+
1162
+ // Only warn for non-API routes (API routes typically use bearer tokens)
1163
+ if (!hasCSRFCheck && !isAPIRoute) {
1164
+ findings.push({
1165
+ id: stableId("F_GHOST_AUTH_NO_CSRF", `${r.method} ${r.path}`),
1166
+ severity: "WARN",
1167
+ category: "GhostAuth",
1168
+ title: `Mutation endpoint without CSRF protection: ${r.method} ${r.path}`,
1169
+ why: "State-changing endpoints should verify CSRF tokens to prevent cross-site request forgery attacks.",
1170
+ confidence: "low",
1171
+ evidence: (r.evidence || []).slice(0, 2),
1172
+ fixHints: [
1173
+ "Add CSRF token validation for form submissions.",
1174
+ "Or use SameSite cookies + Origin header validation.",
1175
+ "API routes using bearer tokens are generally exempt.",
1176
+ ],
1177
+ });
1178
+ }
1179
+ }
1180
+ }
1250
1181
  }
1251
1182
 
1252
1183
  const patterns = truthpack?.auth?.nextMatcherPatterns || [];
@@ -1494,7 +1425,7 @@ function findTodoFixme(repoRoot) {
1494
1425
  evidence: [],
1495
1426
  fixHints: [
1496
1427
  "Review and address high-priority TODOs before shipping.",
1497
- `Run: grep -rn "TODO\\|FIXME" --include="*.ts" --include="*.js" .`,
1428
+ `Run: vibecheck scan --json | jq '.findings[] | select(.category == "TODO")'`,
1498
1429
  ],
1499
1430
  });
1500
1431
  } else {
@@ -2122,47 +2053,99 @@ function findAPIConsistencyIssues(repoRoot) {
2122
2053
  }
2123
2054
 
2124
2055
  /* ============================================================================
2125
- * REACT PATTERNS ANALYZER
2126
- * Only runs on .tsx/.jsx files for performance
2056
+ * OPTIMISTIC NO ROLLBACK DETECTOR
2057
+ * Finds optimistic UI updates that don't rollback on failure
2127
2058
  * ========================================================================== */
2128
2059
 
2129
- function findReactPatternIssues(repoRoot) {
2130
- const { analyzeReactPatterns } = require("./engines/react-patterns-engine");
2060
+ function findOptimisticNoRollback(repoRoot) {
2131
2061
  const findings = [];
2132
-
2133
- // Only scan React files for performance
2134
- const files = fg.sync(["**/*.{tsx,jsx}"], {
2062
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
2135
2063
  cwd: repoRoot,
2136
2064
  absolute: true,
2137
2065
  ignore: STANDARD_IGNORE_PATTERNS,
2138
2066
  });
2139
2067
 
2140
2068
  for (const fileAbs of files) {
2069
+ const code = readFileCached(fileAbs);
2070
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2071
+
2072
+ // Fast path: skip files without optimistic patterns
2073
+ const hasOptimisticUpdate = /\b(setOptimistic|optimisticUpdate|setState.*\bfetch|useMutation.*onMutate)\b/i.test(code);
2074
+ const hasStateUpdate = /\b(setState|set[A-Z]\w*|dispatch|update[A-Z])\b/.test(code);
2075
+ const hasNetworkCall = /\b(fetch|axios|useMutation|mutate)\b/.test(code);
2076
+
2077
+ if (!hasStateUpdate || !hasNetworkCall) continue;
2078
+
2079
+ let ast;
2141
2080
  try {
2142
- const code = readFileCached(fileAbs);
2143
- const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2144
-
2145
- const engineFindings = analyzeReactPatterns(code, fileRel);
2146
-
2147
- // Only include BLOCK and WARN severity for performance
2148
- for (const finding of engineFindings) {
2149
- if (finding.severity === "INFO") continue;
2150
-
2151
- findings.push({
2152
- id: stableId("F_REACT", `${fileRel}:${finding.type}:${finding.line}`),
2153
- severity: finding.severity,
2154
- category: finding.category || "ReactPatterns",
2155
- title: finding.title,
2156
- message: finding.message,
2157
- file: finding.file,
2158
- line: finding.line,
2159
- why: "React anti-patterns can cause bugs, performance issues, and maintenance problems.",
2160
- confidence: finding.confidence,
2161
- evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
2162
- fixHints: [finding.fixHint].filter(Boolean),
2163
- });
2164
- }
2165
- } catch (err) {
2081
+ ast = parseFile(code, fileAbs);
2082
+ } catch {
2083
+ continue;
2084
+ }
2085
+
2086
+ try {
2087
+ traverse(ast, {
2088
+ CallExpression(pathNode) {
2089
+ const n = pathNode.node;
2090
+
2091
+ // Look for setState/dispatch calls followed by fetch without catch/finally rollback
2092
+ if (!t.isMemberExpression(n.callee)) return;
2093
+
2094
+ const methodName = n.callee.property?.name || "";
2095
+ const isStateUpdate = /^(setState|set[A-Z]|dispatch|update)/.test(methodName);
2096
+
2097
+ if (!isStateUpdate) return;
2098
+
2099
+ // Check if parent function has a try-catch with rollback
2100
+ let parentFn = pathNode.findParent(p => p.isFunction());
2101
+ if (!parentFn) return;
2102
+
2103
+ let hasRollback = false;
2104
+ let hasNetworkInSameBlock = false;
2105
+
2106
+ parentFn.traverse({
2107
+ CallExpression(inner) {
2108
+ const callee = inner.node.callee;
2109
+ if (isFetchCall(inner.node) || isAxiosCall(inner.node)) {
2110
+ hasNetworkInSameBlock = true;
2111
+ }
2112
+ },
2113
+ CatchClause(catchPath) {
2114
+ // Check if catch has a state update (rollback)
2115
+ catchPath.traverse({
2116
+ CallExpression(rollbackCall) {
2117
+ if (t.isMemberExpression(rollbackCall.node.callee)) {
2118
+ const name = rollbackCall.node.callee.property?.name || "";
2119
+ if (/^(setState|set[A-Z]|dispatch|update)/.test(name)) {
2120
+ hasRollback = true;
2121
+ }
2122
+ }
2123
+ }
2124
+ });
2125
+ }
2126
+ });
2127
+
2128
+ // If there's a state update, network call, but no rollback in catch
2129
+ if (hasNetworkInSameBlock && !hasRollback) {
2130
+ const loc = n.loc;
2131
+ findings.push({
2132
+ id: stableId("F_OPTIMISTIC_NO_ROLLBACK", `${fileRel}:${loc?.start?.line || 0}`),
2133
+ severity: "WARN",
2134
+ category: "OptimisticNoRollback",
2135
+ title: "Optimistic update without rollback on failure",
2136
+ why: "State is updated before network call completes, but there's no rollback if the request fails. Users see stale/incorrect data.",
2137
+ confidence: "med",
2138
+ evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Optimistic state update")].filter(Boolean),
2139
+ fixHints: [
2140
+ "Add a catch block that reverts the state to previous value on failure.",
2141
+ "Use react-query's onMutate/onError for automatic rollback.",
2142
+ "Store previous state before update and restore it on error.",
2143
+ ],
2144
+ });
2145
+ }
2146
+ }
2147
+ });
2148
+ } catch {
2166
2149
  continue;
2167
2150
  }
2168
2151
  }
@@ -2171,12 +2154,11 @@ function findReactPatternIssues(repoRoot) {
2171
2154
  }
2172
2155
 
2173
2156
  /* ============================================================================
2174
- * ERROR HANDLING ANALYZER
2175
- * Checks error handling patterns across all JS/TS files
2157
+ * SILENT CATCH DETECTOR (Enhanced)
2158
+ * Finds catch blocks that swallow errors without logging or re-throwing
2176
2159
  * ========================================================================== */
2177
2160
 
2178
- function findErrorHandlingIssues(repoRoot) {
2179
- const { analyzeErrorHandling } = require("./engines/error-handling-engine");
2161
+ function findSilentCatch(repoRoot) {
2180
2162
  const findings = [];
2181
2163
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
2182
2164
  cwd: repoRoot,
@@ -2185,31 +2167,91 @@ function findErrorHandlingIssues(repoRoot) {
2185
2167
  });
2186
2168
 
2187
2169
  for (const fileAbs of files) {
2170
+ const code = readFileCached(fileAbs);
2171
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2172
+
2173
+ // Fast path: skip files without try-catch
2174
+ if (!/\bcatch\s*\(/.test(code)) continue;
2175
+
2176
+ let ast;
2188
2177
  try {
2189
- const code = readFileCached(fileAbs);
2190
- const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2191
-
2192
- const engineFindings = analyzeErrorHandling(code, fileRel);
2193
-
2194
- // Only include BLOCK and WARN severity
2195
- for (const finding of engineFindings) {
2196
- if (finding.severity === "INFO") continue;
2197
-
2198
- findings.push({
2199
- id: stableId("F_ERROR", `${fileRel}:${finding.type}:${finding.line}`),
2200
- severity: finding.severity,
2201
- category: finding.category || "ErrorHandling",
2202
- title: finding.title,
2203
- message: finding.message,
2204
- file: finding.file,
2205
- line: finding.line,
2206
- why: "Poor error handling leads to silent failures, security issues, and hard-to-debug problems.",
2207
- confidence: finding.confidence,
2208
- evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
2209
- fixHints: [finding.fixHint].filter(Boolean),
2210
- });
2211
- }
2212
- } catch (err) {
2178
+ ast = parseFile(code, fileAbs);
2179
+ } catch {
2180
+ continue;
2181
+ }
2182
+
2183
+ try {
2184
+ traverse(ast, {
2185
+ CatchClause(pathNode) {
2186
+ const catchBody = pathNode.node.body;
2187
+ const catchParam = pathNode.node.param?.name || "error";
2188
+
2189
+ // Check if catch body is empty or only has comments
2190
+ if (!catchBody.body || catchBody.body.length === 0) {
2191
+ // Empty catch - already covered by findEmptyCatch
2192
+ return;
2193
+ }
2194
+
2195
+ // Check if catch actually handles the error
2196
+ let logsError = false;
2197
+ let rethrowsError = false;
2198
+ let showsUserError = false;
2199
+ let hasConditionalReturn = false;
2200
+
2201
+ pathNode.traverse({
2202
+ CallExpression(inner) {
2203
+ const callee = inner.node.callee;
2204
+ const args = inner.node.arguments;
2205
+
2206
+ // Check for console.error, console.log, logger.error, etc.
2207
+ if (t.isMemberExpression(callee)) {
2208
+ const obj = callee.object?.name || "";
2209
+ const prop = callee.property?.name || "";
2210
+ if ((obj === "console" || /logger|log/i.test(obj)) && /error|warn|log/.test(prop)) {
2211
+ // Check if it logs the error variable
2212
+ const argsStr = args.map(a => code.slice(a.start, a.end)).join(",");
2213
+ if (argsStr.includes(catchParam) || argsStr.includes("error") || argsStr.includes("err")) {
2214
+ logsError = true;
2215
+ }
2216
+ }
2217
+ // Check for toast.error, notification.error, etc.
2218
+ if (/toast|notification|alert|message/i.test(obj) && /error|warn|fail/.test(prop)) {
2219
+ showsUserError = true;
2220
+ }
2221
+ }
2222
+ },
2223
+ ThrowStatement() {
2224
+ rethrowsError = true;
2225
+ },
2226
+ ReturnStatement(ret) {
2227
+ // Returning early might be intentional error handling
2228
+ const ifParent = ret.findParent(p => p.isIfStatement());
2229
+ if (ifParent) hasConditionalReturn = true;
2230
+ }
2231
+ });
2232
+
2233
+ // Silent catch: doesn't log, doesn't rethrow, doesn't show user error
2234
+ if (!logsError && !rethrowsError && !showsUserError && !hasConditionalReturn) {
2235
+ const loc = pathNode.node.loc;
2236
+ findings.push({
2237
+ id: stableId("F_SILENT_CATCH", `${fileRel}:${loc?.start?.line || 0}`),
2238
+ severity: "WARN",
2239
+ category: "SilentCatch",
2240
+ title: "Catch block swallows error silently",
2241
+ why: "Errors are caught but not logged, reported, or shown to users. This makes debugging nearly impossible and hides failures.",
2242
+ confidence: "med",
2243
+ evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Silent catch block")].filter(Boolean),
2244
+ fixHints: [
2245
+ `Add console.error(${catchParam}) or a logger call.`,
2246
+ "Show user-friendly error message (toast, alert, etc.).",
2247
+ "Re-throw the error if it should propagate.",
2248
+ "If intentionally ignoring, add a comment explaining why.",
2249
+ ],
2250
+ });
2251
+ }
2252
+ }
2253
+ });
2254
+ } catch {
2213
2255
  continue;
2214
2256
  }
2215
2257
  }
@@ -2218,176 +2260,199 @@ function findErrorHandlingIssues(repoRoot) {
2218
2260
  }
2219
2261
 
2220
2262
  /* ============================================================================
2221
- * DATABASE PATTERNS ANALYZER
2222
- * Only runs when ORM usage is detected (Prisma, Drizzle, etc.)
2263
+ * METHOD MISMATCH DETECTOR
2264
+ * Finds client-side GET requests to POST-only endpoints and vice versa
2223
2265
  * ========================================================================== */
2224
2266
 
2225
- function findDatabasePatternIssues(repoRoot) {
2226
- const { analyzeDatabasePatterns, detectORM } = require("./engines/database-patterns-engine");
2227
- const findings = [];
2267
+ function findMethodMismatch(truthpack) {
2268
+ if (!truthpack?.routes) return [];
2228
2269
 
2229
- // Quick check: only run if an ORM is likely in use
2230
- const packageJsonPath = path.join(repoRoot, "package.json");
2231
- let hasORM = false;
2270
+ const findings = [];
2271
+ const serverRoutes = truthpack.routes.server || [];
2272
+ const clientRefs = truthpack.routes.clientRefs || [];
2232
2273
 
2233
- try {
2234
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
2235
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
2236
- const ormPackages = ["@prisma/client", "drizzle-orm", "typeorm", "sequelize", "mongoose", "knex"];
2237
- hasORM = ormPackages.some(pkg => deps[pkg]);
2238
- } catch (err) {
2239
- // No package.json or can't read - check files for ORM patterns
2240
- hasORM = true; // Fall back to checking files
2274
+ // Build a map of route -> allowed methods
2275
+ const routeMethodMap = new Map();
2276
+ for (const route of serverRoutes) {
2277
+ const key = normalizeRoutePath(route.path);
2278
+ if (!routeMethodMap.has(key)) {
2279
+ routeMethodMap.set(key, new Set());
2280
+ }
2281
+ if (route.method) {
2282
+ routeMethodMap.get(key).add(route.method.toUpperCase());
2283
+ }
2241
2284
  }
2242
2285
 
2243
- if (!hasORM) return findings;
2244
-
2245
- const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
2246
- cwd: repoRoot,
2247
- absolute: true,
2248
- ignore: STANDARD_IGNORE_PATTERNS,
2249
- });
2250
-
2251
- for (const fileAbs of files) {
2252
- try {
2253
- const code = readFileCached(fileAbs);
2254
- const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2255
-
2256
- // Skip files without ORM imports for performance
2257
- const orm = detectORM(code);
2258
- if (!orm) continue;
2259
-
2260
- const engineFindings = analyzeDatabasePatterns(code, fileRel);
2261
-
2262
- // Only include BLOCK and WARN severity
2263
- for (const finding of engineFindings) {
2264
- if (finding.severity === "INFO") continue;
2265
-
2266
- findings.push({
2267
- id: stableId("F_DB", `${fileRel}:${finding.type}:${finding.line}`),
2268
- severity: finding.severity,
2269
- category: finding.category || "DatabasePatterns",
2270
- title: finding.title,
2271
- message: finding.message,
2272
- file: finding.file,
2273
- line: finding.line,
2274
- why: "Database anti-patterns cause performance issues, data integrity problems, and security vulnerabilities.",
2275
- confidence: finding.confidence,
2276
- evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
2277
- fixHints: [finding.fixHint].filter(Boolean),
2278
- });
2279
- }
2280
- } catch (err) {
2281
- continue;
2286
+ // Check client refs for method mismatches
2287
+ for (const ref of clientRefs) {
2288
+ if (!ref.method || !ref.path) continue;
2289
+
2290
+ const clientMethod = ref.method.toUpperCase();
2291
+ const normalizedPath = normalizeRoutePath(ref.path);
2292
+
2293
+ // Find matching server route
2294
+ const allowedMethods = routeMethodMap.get(normalizedPath);
2295
+
2296
+ if (allowedMethods && allowedMethods.size > 0 && !allowedMethods.has(clientMethod)) {
2297
+ // Method mismatch found
2298
+ const allowed = Array.from(allowedMethods).join(", ");
2299
+ findings.push({
2300
+ id: stableId("F_METHOD_MISMATCH", `${ref.file || "unknown"}:${ref.line || 0}:${ref.path}`),
2301
+ severity: "BLOCK",
2302
+ category: "MethodMismatch",
2303
+ title: `${clientMethod} request to ${allowed}-only endpoint: ${ref.path}`,
2304
+ why: `Client makes ${clientMethod} request but server only accepts ${allowed}. This will fail with 405 Method Not Allowed.`,
2305
+ confidence: "high",
2306
+ evidence: ref.file ? [{
2307
+ file: ref.file,
2308
+ line: ref.line,
2309
+ reason: `Client ${clientMethod} to ${allowed}-only route`,
2310
+ }] : [],
2311
+ fixHints: [
2312
+ `Change client request method from ${clientMethod} to ${allowed}.`,
2313
+ `Add ${clientMethod} handler to the server route.`,
2314
+ "Verify the API contract matches documentation.",
2315
+ ],
2316
+ });
2282
2317
  }
2283
2318
  }
2284
-
2319
+
2285
2320
  return findings;
2286
2321
  }
2287
2322
 
2323
+ // Helper to normalize route paths for comparison
2324
+ function normalizeRoutePath(routePath) {
2325
+ if (!routePath) return "";
2326
+ return routePath
2327
+ .replace(/\[([^\]]+)\]/g, ":$1") // [id] -> :id
2328
+ .replace(/\/+/g, "/") // multiple slashes -> single
2329
+ .replace(/\/$/, "") // remove trailing slash
2330
+ .toLowerCase();
2331
+ }
2332
+
2288
2333
  /* ============================================================================
2289
- * ASYNC PATTERNS ANALYZER
2290
- * Detects Promise and async/await anti-patterns
2334
+ * DEAD UI DETECTOR (Enhanced)
2335
+ * Finds buttons, forms, and links that do nothing
2291
2336
  * ========================================================================== */
2292
2337
 
2293
- function findAsyncPatternIssues(repoRoot) {
2294
- const engines = require("./engines/vibecheck-engines");
2295
- if (!engines.analyzeAsyncPatterns) return [];
2296
-
2338
+ function findDeadUI(repoRoot) {
2297
2339
  const findings = [];
2298
- const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
2340
+ const files = fg.sync(["**/*.{tsx,jsx}"], {
2299
2341
  cwd: repoRoot,
2300
2342
  absolute: true,
2301
2343
  ignore: STANDARD_IGNORE_PATTERNS,
2302
2344
  });
2303
2345
 
2304
2346
  for (const fileAbs of files) {
2347
+ const code = readFileCached(fileAbs);
2348
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2349
+
2350
+ // Fast path: skip files without interactive elements
2351
+ if (!/<(button|Button|form|Form|a|Link)\b/.test(code)) continue;
2352
+
2353
+ let ast;
2305
2354
  try {
2306
- const code = readFileCached(fileAbs);
2307
- const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2308
-
2309
- const engineFindings = engines.analyzeAsyncPatterns(code, fileRel);
2310
-
2311
- // Only include BLOCK and WARN severity
2312
- for (const finding of engineFindings) {
2313
- if (finding.severity === "INFO") continue;
2314
-
2315
- findings.push({
2316
- id: stableId("F_ASYNC", `${fileRel}:${finding.type}:${finding.line}`),
2317
- severity: finding.severity,
2318
- category: finding.category || "AsyncPatterns",
2319
- title: finding.title,
2320
- message: finding.message,
2321
- file: finding.file,
2322
- line: finding.line,
2323
- why: "Async anti-patterns cause race conditions, memory leaks, and hard-to-debug failures.",
2324
- confidence: finding.confidence,
2325
- evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
2326
- fixHints: [finding.fixHint].filter(Boolean),
2327
- });
2328
- }
2329
- } catch (err) {
2355
+ ast = parseFile(code, fileAbs);
2356
+ } catch {
2330
2357
  continue;
2331
2358
  }
2332
- }
2333
-
2334
- return findings;
2335
- }
2336
2359
 
2337
- /* ============================================================================
2338
- * BUNDLE SIZE ANALYZER
2339
- * Only runs on client-side code to detect heavy imports
2340
- * ========================================================================== */
2341
-
2342
- function findBundleSizeIssues(repoRoot) {
2343
- const engines = require("./engines/vibecheck-engines");
2344
- if (!engines.bundleSizeEngine?.analyzeBundleSize) return [];
2345
-
2346
- const findings = [];
2347
-
2348
- // Focus on likely client-side code paths
2349
- const files = fg.sync([
2350
- "**/app/**/*.{ts,tsx,js,jsx}",
2351
- "**/pages/**/*.{ts,tsx,js,jsx}",
2352
- "**/src/**/*.{ts,tsx,js,jsx}",
2353
- "**/components/**/*.{ts,tsx,js,jsx}",
2354
- ], {
2355
- cwd: repoRoot,
2356
- absolute: true,
2357
- ignore: [
2358
- ...STANDARD_IGNORE_PATTERNS,
2359
- "**/api/**", // Skip API routes (server-side)
2360
- "**/server/**",
2361
- "**/lib/server/**",
2362
- ],
2363
- });
2364
-
2365
- for (const fileAbs of files) {
2366
2360
  try {
2367
- const code = readFileCached(fileAbs);
2368
- const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
2369
-
2370
- const engineFindings = engines.bundleSizeEngine.analyzeBundleSize(code, fileRel);
2371
-
2372
- // Only include BLOCK and WARN severity
2373
- for (const finding of engineFindings) {
2374
- if (finding.severity === "INFO") continue;
2375
-
2376
- findings.push({
2377
- id: stableId("F_BUNDLE", `${fileRel}:${finding.type}:${finding.line}`),
2378
- severity: finding.severity,
2379
- category: finding.category || "BundleSize",
2380
- title: finding.title,
2381
- message: finding.message,
2382
- file: finding.file,
2383
- line: finding.line,
2384
- why: "Large bundles slow down page load and hurt user experience.",
2385
- confidence: finding.confidence,
2386
- evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
2387
- fixHints: [finding.fixHint].filter(Boolean),
2388
- });
2389
- }
2390
- } catch (err) {
2361
+ traverse(ast, {
2362
+ JSXElement(pathNode) {
2363
+ const opening = pathNode.node.openingElement;
2364
+ const tagName = opening.name?.name || opening.name?.property?.name || "";
2365
+
2366
+ // Check for buttons, forms, links
2367
+ if (!/^(button|Button|form|Form|a|Link)$/i.test(tagName)) return;
2368
+
2369
+ const attrs = opening.attributes || [];
2370
+ let hasOnClick = false;
2371
+ let hasOnSubmit = false;
2372
+ let hasHref = false;
2373
+ let hasAction = false;
2374
+ let hasType = false;
2375
+ let isDisabled = false;
2376
+
2377
+ for (const attr of attrs) {
2378
+ if (!t.isJSXAttribute(attr)) continue;
2379
+ const name = attr.name?.name || "";
2380
+
2381
+ if (name === "onClick" || name === "onPress") hasOnClick = true;
2382
+ if (name === "onSubmit") hasOnSubmit = true;
2383
+ if (name === "href" || name === "to") hasHref = true;
2384
+ if (name === "action") hasAction = true;
2385
+ if (name === "type") hasType = true;
2386
+ if (name === "disabled") isDisabled = true;
2387
+ }
2388
+
2389
+ // Button without onClick (unless it's a submit button or disabled)
2390
+ if (/button/i.test(tagName) && !hasOnClick && !isDisabled) {
2391
+ const isSubmitType = attrs.some(a =>
2392
+ t.isJSXAttribute(a) &&
2393
+ a.name?.name === "type" &&
2394
+ t.isStringLiteral(a.value) &&
2395
+ a.value.value === "submit"
2396
+ );
2397
+
2398
+ if (!isSubmitType) {
2399
+ const loc = opening.loc;
2400
+ findings.push({
2401
+ id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:button`),
2402
+ severity: "WARN",
2403
+ category: "DeadUI",
2404
+ title: "Button without onClick handler",
2405
+ why: "This button does nothing when clicked. Users expect buttons to perform actions.",
2406
+ confidence: "med",
2407
+ evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead button")].filter(Boolean),
2408
+ fixHints: [
2409
+ "<Button onClick={() => handleAction()}>Click Me</Button>",
2410
+ "For submit: <Button type=\"submit\">Submit</Button>",
2411
+ "For disabled: <Button disabled>Coming Soon</Button>",
2412
+ ],
2413
+ });
2414
+ }
2415
+ }
2416
+
2417
+ // Form without onSubmit or action
2418
+ if (/form/i.test(tagName) && !hasOnSubmit && !hasAction) {
2419
+ const loc = opening.loc;
2420
+ findings.push({
2421
+ id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:form`),
2422
+ severity: "WARN",
2423
+ category: "DeadUI",
2424
+ title: "Form without onSubmit or action",
2425
+ why: "This form does nothing when submitted. Form data won't be processed.",
2426
+ confidence: "med",
2427
+ evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead form")].filter(Boolean),
2428
+ fixHints: [
2429
+ "Add an onSubmit handler to process form data.",
2430
+ "Or add an action attribute for server-side submission.",
2431
+ ],
2432
+ });
2433
+ }
2434
+
2435
+ // Link without href
2436
+ if (/^(a|Link)$/i.test(tagName) && !hasHref) {
2437
+ const loc = opening.loc;
2438
+ findings.push({
2439
+ id: stableId("F_DEAD_UI", `${fileRel}:${loc?.start?.line || 0}:link`),
2440
+ severity: "WARN",
2441
+ category: "DeadUI",
2442
+ title: "Link without href or to prop",
2443
+ why: "This link goes nowhere. Users expect links to navigate somewhere.",
2444
+ confidence: "med",
2445
+ evidence: [evidenceFromLoc(fileAbs, repoRoot, loc, "Dead link")].filter(Boolean),
2446
+ fixHints: [
2447
+ "Add href prop with the target URL.",
2448
+ "If using Next.js Link, ensure href is provided.",
2449
+ "If it's a button styled as link, use a button element instead.",
2450
+ ],
2451
+ });
2452
+ }
2453
+ }
2454
+ });
2455
+ } catch {
2391
2456
  continue;
2392
2457
  }
2393
2458
  }
@@ -2395,123 +2460,14 @@ function findBundleSizeIssues(repoRoot) {
2395
2460
  return findings;
2396
2461
  }
2397
2462
 
2398
- /* ============================================================================
2399
- * V6: BULLETPROOF FINDINGS FILTER
2400
- * Applies noise reduction and false positive prevention to any findings array
2401
- * ========================================================================== */
2402
-
2403
- /**
2404
- * Apply bulletproof filtering to findings
2405
- * @param {Array} findings - Raw findings from analyzers
2406
- * @param {Object} options - Configuration options
2407
- * @returns {Object} { findings, stats }
2408
- */
2409
- function bulletproofFindings(findings, options = {}) {
2410
- const {
2411
- projectPath = ".",
2412
- projectStats = {},
2413
- config = {},
2414
- minConfidence = 0.4,
2415
- minQualityScore = 0.35,
2416
- verbose = false,
2417
- } = options;
2418
-
2419
- let processed = [...findings];
2420
- const stats = {
2421
- input: findings.length,
2422
- afterFalsePositiveFilter: 0,
2423
- afterNoiseReduction: 0,
2424
- output: 0,
2425
- };
2426
-
2427
- // Step 1: Filter false positives
2428
- if (falsePositivePrevention?.filterFalsePositives) {
2429
- const fpResult = falsePositivePrevention.filterFalsePositives(processed, {
2430
- projectPath,
2431
- minConfidence,
2432
- });
2433
- processed = fpResult.findings;
2434
- stats.afterFalsePositiveFilter = processed.length;
2435
-
2436
- if (verbose && fpResult.removed.length > 0) {
2437
- console.log(` [FP Filter] Removed ${fpResult.removed.length} false positives`);
2438
- }
2439
- } else {
2440
- stats.afterFalsePositiveFilter = processed.length;
2441
- }
2442
-
2443
- // Step 2: Apply noise reduction
2444
- if (noiseReduction?.reduceNoise) {
2445
- const nrResult = noiseReduction.reduceNoise(processed, {
2446
- projectStats,
2447
- config,
2448
- minQualityScore,
2449
- verbose,
2450
- });
2451
- processed = nrResult.findings;
2452
- stats.afterNoiseReduction = processed.length;
2453
- stats.noiseStats = nrResult.stats;
2454
- } else {
2455
- stats.afterNoiseReduction = processed.length;
2456
- }
2457
-
2458
- stats.output = processed.length;
2459
- stats.reduction = Math.round((1 - stats.output / Math.max(stats.input, 1)) * 100);
2460
-
2461
- return { findings: processed, stats };
2462
- }
2463
-
2464
- /**
2465
- * Get project statistics for adaptive caps
2466
- */
2467
- function getProjectStats(projectPath) {
2468
- let fileCount = 0;
2469
- let lineCount = 0;
2470
-
2471
- try {
2472
- const files = fg.sync(["**/*.{ts,tsx,js,jsx,mjs,cjs}"], {
2473
- cwd: projectPath,
2474
- absolute: true,
2475
- ignore: STANDARD_IGNORE_PATTERNS,
2476
- });
2477
-
2478
- fileCount = files.length;
2479
-
2480
- // Sample line count from up to 100 files for performance
2481
- const sampleFiles = files.slice(0, 100);
2482
- let sampleLines = 0;
2483
- for (const file of sampleFiles) {
2484
- try {
2485
- const content = fs.readFileSync(file, "utf8");
2486
- sampleLines += content.split("\n").length;
2487
- } catch {
2488
- // Ignore errors
2489
- }
2490
- }
2491
-
2492
- // Extrapolate line count
2493
- if (sampleFiles.length > 0) {
2494
- lineCount = Math.round((sampleLines / sampleFiles.length) * fileCount);
2495
- }
2496
- } catch {
2497
- // Ignore errors
2498
- }
2499
-
2500
- return { fileCount, lineCount };
2501
- }
2502
-
2503
2463
  module.exports = {
2504
- // V6: Bulletproof filtering (apply to any findings array)
2505
- bulletproofFindings,
2506
- getProjectStats,
2507
-
2508
2464
  // V3: Cache management - call after scan completes to prevent memory leaks
2509
2465
  clearFileCache,
2510
2466
 
2511
2467
  // V3: Entropy helper - exported for testing/reuse
2512
2468
  getShannonEntropy,
2513
2469
 
2514
- // Core analyzers (route integrity, env, auth)
2470
+ // Analyzers
2515
2471
  findMissingRoutes,
2516
2472
  findEnvGaps,
2517
2473
  findFakeSuccess,
@@ -2519,8 +2475,6 @@ module.exports = {
2519
2475
  findStripeWebhookViolations,
2520
2476
  findPaidSurfaceNotEnforced,
2521
2477
  findOwnerModeBypass,
2522
-
2523
- // Code quality analyzers (mock, TODO, console, etc.)
2524
2478
  findMockData,
2525
2479
  findTodoFixme,
2526
2480
  findConsoleLogs,
@@ -2529,22 +2483,18 @@ module.exports = {
2529
2483
  findDeprecatedApis,
2530
2484
  findEmptyCatch,
2531
2485
  findUnsafeRegex,
2532
-
2533
- // Enhanced analyzers (security, performance, quality)
2486
+ // Enhanced analyzers
2534
2487
  findSecurityVulnerabilities,
2535
2488
  findPerformanceIssues,
2536
2489
  findCodeQualityIssues,
2537
-
2538
2490
  // Advanced analyzers
2539
2491
  findCrossFileIssues,
2540
2492
  findTypeSafetyIssues,
2541
2493
  findAccessibilityIssues,
2542
2494
  findAPIConsistencyIssues,
2543
-
2544
- // V5: New pattern analyzers (integrated, not separate commands)
2545
- findReactPatternIssues, // React best practices (BLOCK/WARN only)
2546
- findErrorHandlingIssues, // Error handling patterns
2547
- findDatabasePatternIssues, // N+1, transactions, raw queries
2548
- findAsyncPatternIssues, // Promise/async anti-patterns
2549
- findBundleSizeIssues, // Heavy imports, bundle bloat
2495
+ // NEW: AI Hallucination Detectors
2496
+ findOptimisticNoRollback,
2497
+ findSilentCatch,
2498
+ findMethodMismatch,
2499
+ findDeadUI,
2550
2500
  };