@vibecheckai/cli 3.2.2 → 3.2.4

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 (170) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  6. package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
  7. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
  8. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
  9. package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
  10. package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
  11. package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
  12. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
  14. package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
  15. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  16. package/bin/runners/lib/analyzers.js +606 -325
  17. package/bin/runners/lib/auth-truth.js +193 -193
  18. package/bin/runners/lib/backup.js +62 -62
  19. package/bin/runners/lib/billing.js +107 -107
  20. package/bin/runners/lib/claims.js +118 -118
  21. package/bin/runners/lib/cli-ui.js +540 -540
  22. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  23. package/bin/runners/lib/contracts/env-contract.js +181 -181
  24. package/bin/runners/lib/contracts/external-contract.js +206 -206
  25. package/bin/runners/lib/contracts/guard.js +168 -168
  26. package/bin/runners/lib/contracts/index.js +89 -89
  27. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  28. package/bin/runners/lib/contracts/route-contract.js +199 -199
  29. package/bin/runners/lib/contracts.js +804 -804
  30. package/bin/runners/lib/detect.js +89 -89
  31. package/bin/runners/lib/doctor/autofix.js +254 -254
  32. package/bin/runners/lib/doctor/index.js +37 -37
  33. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  34. package/bin/runners/lib/doctor/modules/index.js +46 -46
  35. package/bin/runners/lib/doctor/modules/network.js +250 -250
  36. package/bin/runners/lib/doctor/modules/project.js +312 -312
  37. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  38. package/bin/runners/lib/doctor/modules/security.js +348 -348
  39. package/bin/runners/lib/doctor/modules/system.js +213 -213
  40. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  41. package/bin/runners/lib/doctor/reporter.js +262 -262
  42. package/bin/runners/lib/doctor/service.js +262 -262
  43. package/bin/runners/lib/doctor/types.js +113 -113
  44. package/bin/runners/lib/doctor/ui.js +263 -263
  45. package/bin/runners/lib/doctor-v2.js +608 -608
  46. package/bin/runners/lib/drift.js +425 -425
  47. package/bin/runners/lib/enforcement.js +72 -72
  48. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  49. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  50. package/bin/runners/lib/engines/ast-cache.js +99 -0
  51. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  52. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  53. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  54. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  55. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  56. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  57. package/bin/runners/lib/engines/file-filter.js +131 -0
  58. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  59. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  60. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  61. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  62. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  63. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  64. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  65. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  66. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  67. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  68. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  69. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  70. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  71. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  72. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  73. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  74. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  75. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  76. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  77. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  78. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  79. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  80. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  81. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  82. package/bin/runners/lib/enterprise-detect.js +603 -603
  83. package/bin/runners/lib/enterprise-init.js +942 -942
  84. package/bin/runners/lib/env-resolver.js +417 -417
  85. package/bin/runners/lib/env-template.js +66 -66
  86. package/bin/runners/lib/env.js +189 -189
  87. package/bin/runners/lib/extractors/client-calls.js +990 -990
  88. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  89. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  90. package/bin/runners/lib/extractors/index.js +363 -363
  91. package/bin/runners/lib/extractors/next-routes.js +524 -524
  92. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  93. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  94. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  95. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  96. package/bin/runners/lib/findings-schema.js +281 -281
  97. package/bin/runners/lib/firewall-prompt.js +50 -50
  98. package/bin/runners/lib/global-flags.js +213 -213
  99. package/bin/runners/lib/graph/graph-builder.js +265 -265
  100. package/bin/runners/lib/graph/html-renderer.js +413 -413
  101. package/bin/runners/lib/graph/index.js +32 -32
  102. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  103. package/bin/runners/lib/graph/static-extractor.js +518 -518
  104. package/bin/runners/lib/html-report.js +650 -650
  105. package/bin/runners/lib/interactive-menu.js +1496 -1496
  106. package/bin/runners/lib/llm.js +75 -75
  107. package/bin/runners/lib/meter.js +61 -61
  108. package/bin/runners/lib/missions/evidence.js +126 -126
  109. package/bin/runners/lib/patch.js +40 -40
  110. package/bin/runners/lib/permissions/auth-model.js +213 -213
  111. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  112. package/bin/runners/lib/permissions/index.js +45 -45
  113. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  114. package/bin/runners/lib/pkgjson.js +28 -28
  115. package/bin/runners/lib/policy.js +295 -295
  116. package/bin/runners/lib/preflight.js +142 -142
  117. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  118. package/bin/runners/lib/reality/index.js +318 -318
  119. package/bin/runners/lib/reality/request-hashing.js +416 -416
  120. package/bin/runners/lib/reality/request-mapper.js +453 -453
  121. package/bin/runners/lib/reality/safety-rails.js +463 -463
  122. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  123. package/bin/runners/lib/reality/toast-detector.js +393 -393
  124. package/bin/runners/lib/reality-findings.js +84 -84
  125. package/bin/runners/lib/receipts.js +179 -179
  126. package/bin/runners/lib/redact.js +29 -29
  127. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  128. package/bin/runners/lib/replay/index.js +263 -263
  129. package/bin/runners/lib/replay/player.js +348 -348
  130. package/bin/runners/lib/replay/recorder.js +331 -331
  131. package/bin/runners/lib/report-output.js +187 -187
  132. package/bin/runners/lib/report.js +135 -135
  133. package/bin/runners/lib/route-detection.js +1140 -1140
  134. package/bin/runners/lib/sandbox/index.js +59 -59
  135. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  136. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  137. package/bin/runners/lib/sandbox/worktree.js +174 -174
  138. package/bin/runners/lib/scan-output.js +525 -190
  139. package/bin/runners/lib/schema-validator.js +350 -350
  140. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  141. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  142. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  143. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  144. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  145. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  146. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  147. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  148. package/bin/runners/lib/schemas/validator.js +438 -438
  149. package/bin/runners/lib/score-history.js +282 -282
  150. package/bin/runners/lib/share-pack.js +239 -239
  151. package/bin/runners/lib/snippets.js +67 -67
  152. package/bin/runners/lib/status-output.js +253 -253
  153. package/bin/runners/lib/terminal-ui.js +351 -271
  154. package/bin/runners/lib/upsell.js +510 -510
  155. package/bin/runners/lib/usage.js +153 -153
  156. package/bin/runners/lib/validate-patch.js +156 -156
  157. package/bin/runners/lib/verdict-engine.js +628 -628
  158. package/bin/runners/reality/engine.js +917 -917
  159. package/bin/runners/reality/flows.js +122 -122
  160. package/bin/runners/reality/report.js +378 -378
  161. package/bin/runners/reality/session.js +193 -193
  162. package/bin/runners/runGuard.js +168 -168
  163. package/bin/runners/runProof.zip +0 -0
  164. package/bin/runners/runProve.js +8 -0
  165. package/bin/runners/runReality.js +14 -0
  166. package/bin/runners/runScan.js +17 -1
  167. package/bin/runners/runTruth.js +15 -3
  168. package/mcp-server/tier-auth.js +4 -4
  169. package/mcp-server/tools/index.js +72 -72
  170. package/package.json +1 -1
@@ -1182,6 +1182,7 @@ function findOwnerModeBypass(repoRoot) {
1182
1182
  * ========================================================================== */
1183
1183
 
1184
1184
  function findMockData(repoRoot) {
1185
+ const engines = require("./engines/vibecheck-engines");
1185
1186
  const findings = [];
1186
1187
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1187
1188
  cwd: repoRoot,
@@ -1196,64 +1197,39 @@ function findMockData(repoRoot) {
1196
1197
  "**/tests/**",
1197
1198
  "**/test/**",
1198
1199
  "**/__tests__/**",
1199
- "**/mocks/**",
1200
- "**/__mocks__/**",
1201
1200
  ],
1202
1201
  });
1203
1202
 
1204
- const mockPatterns = [
1205
- { rx: /\bmockData\b/i, label: "mockData variable" },
1206
- { rx: /\bfakeData\b/i, label: "fakeData variable" },
1207
- { rx: /\bdummyData\b/i, label: "dummyData variable" },
1208
- { rx: /\btestData\b/i, label: "testData variable (in production code)" },
1209
- { rx: /\bsampleData\b/i, label: "sampleData variable" },
1210
- { rx: /['"]fake[_-]?user['"]|['"]test[_-]?user['"]|['"]demo[_-]?user['"]/i, label: "Hardcoded test user" },
1211
- { rx: /['"]password123['"]|['"]test123['"]|['"]admin123['"]|['"]secret123['"]/i, label: "Hardcoded test password" },
1212
- { rx: /['"]test@(test|example|fake)\.com['"]/i, label: "Hardcoded test email" },
1213
- { rx: /\b(MOCK_API|FAKE_API|DUMMY_API)\b/i, label: "Mock API reference" },
1214
- { rx: /setTimeout\([^)]*[5-9]\d{3,}|setTimeout\([^)]*\d{5,}/i, label: "Long setTimeout (simulated delay?)" },
1215
- { rx: /Math\.random\(\)\s*[*<>]\s*\d+/i, label: "Random data generation" },
1216
- { rx: /\bplaceholder\b.*\bdata\b|\bdata\b.*\bplaceholder\b/i, label: "Placeholder data" },
1217
- ];
1218
-
1219
1203
  for (const fileAbs of files) {
1220
1204
  try {
1221
1205
  const code = readFileCached(fileAbs);
1222
1206
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1223
1207
 
1224
- if (/\.(test|spec|mock|fake|stub)\./i.test(fileRel)) continue;
1225
- if (/mock|fake|test|spec|fixture/i.test(fileRel) && !/src\//.test(fileRel)) continue;
1226
-
1227
- const lines = code.split("\n");
1228
-
1229
- for (const { rx, label } of mockPatterns) {
1230
- if (!rxTest(rx, code)) continue;
1231
-
1232
- let lineNum = 1;
1233
- for (let i = 0; i < lines.length; i++) {
1234
- if (rxTest(rx, lines[i])) {
1235
- lineNum = i + 1;
1236
- break;
1237
- }
1238
- }
1239
-
1208
+ // Use unified engines
1209
+ const engineFindings = engines.analyzeMockData(code, fileRel);
1210
+
1211
+ // Convert engine findings to analyzer format
1212
+ for (const finding of engineFindings) {
1240
1213
  findings.push({
1241
- id: stableId("F_MOCK_DATA", `${fileRel}:${label}`),
1242
- severity: "WARN",
1243
- category: "MockData",
1244
- title: `${label} in production code: ${fileRel}`,
1214
+ id: stableId("F_MOCK_DATA", `${fileRel}:${finding.type}:${finding.line}`),
1215
+ severity: finding.severity,
1216
+ category: finding.category,
1217
+ title: finding.title,
1218
+ message: finding.message,
1219
+ file: finding.file,
1220
+ line: finding.line,
1245
1221
  why: "Mock/fake data in production causes embarrassing bugs and makes your app look unfinished.",
1246
- confidence: "med",
1247
- evidence: [{ file: fileRel, lines: `${lineNum}`, reason: label }],
1222
+ confidence: finding.confidence,
1223
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1248
1224
  fixHints: [
1249
1225
  "Replace mock data with real API calls or database queries.",
1250
1226
  "If intentional sample data, move to a clearly marked demo mode.",
1251
1227
  ],
1252
1228
  });
1253
- break;
1254
1229
  }
1255
- } catch {
1256
- // skip
1230
+ } catch (err) {
1231
+ // Skip files that can't be analyzed
1232
+ continue;
1257
1233
  }
1258
1234
  }
1259
1235
 
@@ -1265,89 +1241,73 @@ function findMockData(repoRoot) {
1265
1241
  * ========================================================================== */
1266
1242
 
1267
1243
  function findTodoFixme(repoRoot) {
1244
+ // TODO/FIXME engine not in unified engines yet - keep using existing
1245
+ const { analyzeTodoFixme } = require("./engines/todo-fixme-engine");
1246
+ const engines = require("./engines/vibecheck-engines");
1268
1247
  const findings = [];
1269
1248
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1270
1249
  cwd: repoRoot,
1271
1250
  absolute: true,
1272
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"],
1251
+ ignore: [
1252
+ "**/node_modules/**",
1253
+ "**/.next/**",
1254
+ "**/dist/**",
1255
+ "**/build/**",
1256
+ "**/*.d.ts",
1257
+ ],
1273
1258
  });
1274
1259
 
1275
- const todoPatterns = [
1276
- { rx: /\/\/\s*TODO[\s:]/i, label: "TODO comment", severity: "WARN" },
1277
- { rx: /\/\/\s*FIXME[\s:]/i, label: "FIXME comment", severity: "WARN" },
1278
- { rx: /\/\/\s*HACK[\s:]/i, label: "HACK comment", severity: "WARN" },
1279
- { rx: /\/\/\s*XXX[\s:]/i, label: "XXX comment", severity: "WARN" },
1280
- { rx: /\/\/\s*BUG[\s:]/i, label: "BUG comment", severity: "BLOCK" },
1281
- { rx: /\/\/\s*BROKEN[\s:]/i, label: "BROKEN comment", severity: "BLOCK" },
1282
- { rx: /\/\/\s*URGENT[\s:]/i, label: "URGENT comment", severity: "BLOCK" },
1283
- { rx: /\/\/\s*SECURITY[\s:]/i, label: "SECURITY comment", severity: "BLOCK" },
1284
- { rx: /\/\/\s*DANGER[\s:]/i, label: "DANGER comment", severity: "BLOCK" },
1285
- { rx: /\/\*\s*TODO[\s:]/i, label: "TODO block comment", severity: "WARN" },
1286
- { rx: /\/\*\s*FIXME[\s:]/i, label: "FIXME block comment", severity: "WARN" },
1287
- ];
1288
-
1289
- let todoCount = 0;
1290
- let fixmeCount = 0;
1291
- const MAX_INDIVIDUAL_FINDINGS = 20;
1292
-
1293
1260
  for (const fileAbs of files) {
1294
1261
  try {
1295
1262
  const code = readFileCached(fileAbs);
1296
1263
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1297
- const lines = code.split("\n");
1298
-
1299
- for (let i = 0; i < lines.length; i++) {
1300
- const line = lines[i];
1301
- for (const { rx, label, severity } of todoPatterns) {
1302
- if (!rxTest(rx, line)) continue;
1303
-
1304
- if (label.includes("TODO")) todoCount++;
1305
- if (label.includes("FIXME")) fixmeCount++;
1306
-
1307
- if (findings.length < MAX_INDIVIDUAL_FINDINGS) {
1308
- const snippet = line.trim().slice(0, 80);
1309
- findings.push({
1310
- id: stableId("F_TODO", `${fileRel}:${i + 1}:${label}`),
1311
- severity,
1312
- category: "TodoFixme",
1313
- title: `${label}: ${snippet}${line.length > 80 ? "..." : ""}`,
1314
- why:
1315
- severity === "BLOCK"
1316
- ? "This comment indicates a known critical issue that must be addressed before shipping."
1317
- : "Unfinished work markers suggest the code isn't production-ready.",
1318
- confidence: "high",
1319
- evidence: [{ file: fileRel, lines: `${i + 1}`, reason: label }],
1320
- fixHints: [
1321
- "Complete the TODO or remove it if already done.",
1322
- "If deferring, create a tracked issue and reference it in the comment.",
1323
- ],
1324
- });
1325
- }
1326
- break;
1264
+
1265
+ // Use AST-based engine (not in unified engines yet)
1266
+ const engineFindings = analyzeTodoFixme(code, fileRel);
1267
+
1268
+ // Convert engine findings to analyzer format
1269
+ for (const finding of engineFindings) {
1270
+ if (finding.type === "summary") {
1271
+ findings.push({
1272
+ id: "F_TODO_SUMMARY",
1273
+ severity: finding.severity,
1274
+ category: finding.category,
1275
+ title: finding.title,
1276
+ why: "Large numbers of TODO comments indicate significant unfinished work.",
1277
+ confidence: finding.confidence,
1278
+ evidence: [],
1279
+ fixHints: [
1280
+ "Review and address high-priority TODOs before shipping.",
1281
+ `Run: grep -rn "TODO\\|FIXME" --include="*.ts" --include="*.js" .`,
1282
+ ],
1283
+ });
1284
+ } else {
1285
+ findings.push({
1286
+ id: stableId("F_TODO", `${fileRel}:${finding.line}:${finding.type}`),
1287
+ severity: finding.severity,
1288
+ category: finding.category,
1289
+ title: finding.title,
1290
+ message: finding.message,
1291
+ file: finding.file,
1292
+ line: finding.line,
1293
+ why: finding.severity === "BLOCK"
1294
+ ? "This comment indicates a known critical issue that must be addressed before shipping."
1295
+ : "Unfinished work markers suggest the code isn't production-ready.",
1296
+ confidence: finding.confidence,
1297
+ evidence: [{ file: fileRel, lines: `${finding.line}`, reason: finding.title }],
1298
+ fixHints: [
1299
+ "Complete the TODO or remove it if already done.",
1300
+ "If deferring, create a tracked issue and reference it in the comment.",
1301
+ ],
1302
+ });
1327
1303
  }
1328
1304
  }
1329
- } catch {
1330
- // skip
1305
+ } catch (err) {
1306
+ // Skip files that can't be analyzed
1307
+ continue;
1331
1308
  }
1332
1309
  }
1333
1310
 
1334
- const totalTodos = todoCount + fixmeCount;
1335
- if (totalTodos > MAX_INDIVIDUAL_FINDINGS) {
1336
- findings.push({
1337
- id: "F_TODO_SUMMARY",
1338
- severity: "WARN",
1339
- category: "TodoFixme",
1340
- title: `${totalTodos} TODO/FIXME comments found (${totalTodos - MAX_INDIVIDUAL_FINDINGS} more not shown)`,
1341
- why: "Large numbers of TODO comments indicate significant unfinished work.",
1342
- confidence: "high",
1343
- evidence: [],
1344
- fixHints: [
1345
- "Review and address high-priority TODOs before shipping.",
1346
- `Run: grep -rn "TODO\\|FIXME" --include="*.ts" --include="*.js" .`,
1347
- ],
1348
- });
1349
- }
1350
-
1351
1311
  return findings;
1352
1312
  }
1353
1313
 
@@ -1356,6 +1316,7 @@ function findTodoFixme(repoRoot) {
1356
1316
  * ========================================================================== */
1357
1317
 
1358
1318
  function findConsoleLogs(repoRoot) {
1319
+ const engines = require("./engines/vibecheck-engines");
1359
1320
  const findings = [];
1360
1321
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1361
1322
  cwd: repoRoot,
@@ -1365,67 +1326,40 @@ function findConsoleLogs(repoRoot) {
1365
1326
  "**/.next/**",
1366
1327
  "**/dist/**",
1367
1328
  "**/build/**",
1368
- "**/*.test.*",
1369
- "**/*.spec.*",
1370
- "**/tests/**",
1371
- "**/__tests__/**",
1372
- "**/scripts/**",
1373
- "**/bin/**",
1329
+ "**/*.d.ts",
1374
1330
  ],
1375
1331
  });
1376
1332
 
1377
- let consoleCount = 0;
1378
- const MAX_INDIVIDUAL_FINDINGS = 15;
1379
-
1380
1333
  for (const fileAbs of files) {
1381
1334
  try {
1382
1335
  const code = readFileCached(fileAbs);
1383
1336
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1384
1337
 
1385
- if (/config|setup|jest|vitest|eslint|prettier/i.test(fileRel)) continue;
1386
-
1387
- const lines = code.split("\n");
1388
-
1389
- for (let i = 0; i < lines.length; i++) {
1390
- const line = lines[i];
1391
- if (/console\.(log|warn|debug|info|trace)\s*\(/.test(line)) {
1392
- if (/^\s*\/\//.test(line)) continue;
1393
-
1394
- consoleCount++;
1395
-
1396
- if (findings.length < MAX_INDIVIDUAL_FINDINGS) {
1397
- const snippet = line.trim().slice(0, 60);
1398
- findings.push({
1399
- id: stableId("F_CONSOLE_LOG", `${fileRel}:${i + 1}`),
1400
- severity: "WARN",
1401
- category: "ConsoleLog",
1402
- title: `console.log in production code: ${fileRel}:${i + 1}`,
1403
- why: "Console statements leak debugging info and clutter logs/console.",
1404
- confidence: "high",
1405
- evidence: [{ file: fileRel, lines: `${i + 1}`, reason: snippet }],
1406
- fixHints: ["Remove console.log or replace with a proper logger.", "Use a logger that can be silenced in production."],
1407
- });
1408
- }
1409
- }
1338
+ // Use unified engines
1339
+ const engineFindings = engines.analyzeConsoleLogs(code, fileRel);
1340
+
1341
+ // Convert engine findings to analyzer format
1342
+ for (const finding of engineFindings) {
1343
+ findings.push({
1344
+ id: stableId("F_CONSOLE_LOG", `${fileRel}:${finding.line}:${finding.type}`),
1345
+ severity: finding.severity,
1346
+ category: finding.category,
1347
+ title: finding.title,
1348
+ message: finding.message,
1349
+ file: finding.file,
1350
+ line: finding.line,
1351
+ why: "Console statements leak debugging info and clutter logs/console.",
1352
+ confidence: finding.confidence,
1353
+ evidence: [{ file: fileRel, lines: `${finding.line}`, reason: finding.codeSnippet || finding.title }],
1354
+ fixHints: ["Remove console.log or replace with a proper logger.", "Use a logger that can be silenced in production."],
1355
+ });
1410
1356
  }
1411
- } catch {
1412
- // skip
1357
+ } catch (err) {
1358
+ // Skip files that can't be analyzed
1359
+ continue;
1413
1360
  }
1414
1361
  }
1415
1362
 
1416
- if (consoleCount > MAX_INDIVIDUAL_FINDINGS) {
1417
- findings.push({
1418
- id: "F_CONSOLE_LOG_SUMMARY",
1419
- severity: "WARN",
1420
- category: "ConsoleLog",
1421
- title: `${consoleCount} console.log statements found (${consoleCount - MAX_INDIVIDUAL_FINDINGS} more not shown)`,
1422
- why: "Large numbers of console statements suggest debugging code left in production.",
1423
- confidence: "high",
1424
- evidence: [],
1425
- fixHints: ["Use ESLint no-console to catch automatically.", "Replace with a proper logging library (pino, winston, etc.)."],
1426
- });
1427
- }
1428
-
1429
1363
  return findings;
1430
1364
  }
1431
1365
 
@@ -1434,104 +1368,61 @@ function findConsoleLogs(repoRoot) {
1434
1368
  * ========================================================================== */
1435
1369
 
1436
1370
  function findHardcodedSecrets(repoRoot) {
1371
+ const engines = require("./engines/vibecheck-engines");
1437
1372
  const findings = [];
1438
1373
  const files = fg.sync(["**/*.{ts,tsx,js,jsx,json}"], {
1439
1374
  cwd: repoRoot,
1440
1375
  absolute: true,
1441
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**", "**/package*.json", "**/*.test.*", "**/tests/**"],
1376
+ ignore: [
1377
+ "**/node_modules/**",
1378
+ "**/.next/**",
1379
+ "**/dist/**",
1380
+ "**/build/**",
1381
+ "**/package*.json",
1382
+ "**/*.test.*",
1383
+ "**/tests/**",
1384
+ "**/*.d.ts",
1385
+ ],
1442
1386
  });
1443
1387
 
1444
- // V3: Split patterns into "specific" (prefix-based, high confidence) and "generic" (entropy-based)
1445
- // Specific patterns have distinctive prefixes - no entropy check needed
1446
- const specificPatterns = [
1447
- { rx: /['"]sk_live_[a-zA-Z0-9]{20,}['"]/g, label: "Stripe live secret key", severity: "BLOCK" },
1448
- { rx: /['"]sk_test_[a-zA-Z0-9]{20,}['"]/g, label: "Stripe test secret key", severity: "WARN" },
1449
- { rx: /['"]pk_live_[a-zA-Z0-9]{20,}['"]/g, label: "Stripe live publishable key", severity: "BLOCK" },
1450
- { rx: /['"]AKIA[0-9A-Z]{16}['"]/g, label: "AWS Access Key ID", severity: "BLOCK" },
1451
- { rx: /['"]ghp_[a-zA-Z0-9]{36}['"]/g, label: "GitHub Personal Access Token", severity: "BLOCK" },
1452
- { rx: /['"]gho_[a-zA-Z0-9]{36}['"]/g, label: "GitHub OAuth Token", severity: "BLOCK" },
1453
- { rx: /['"]xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}['"]/g, label: "Slack Token", severity: "BLOCK" },
1454
- { rx: /['"]eyJ[a-zA-Z0-9_-]{100,}\.[a-zA-Z0-9_-]{100,}\.[a-zA-Z0-9_-]{43,}['"]/g, label: "JWT Token (hardcoded)", severity: "WARN" },
1455
- ];
1456
-
1457
- // V3: Generic patterns need Shannon entropy check to avoid false positives (Git SHAs, image IDs, etc.)
1458
- const genericPatterns = [
1459
- { rx: /['"]([a-zA-Z0-9+/]{40})['"]/g, label: "Possible AWS Secret Key", minEntropy: 4.5 },
1460
- { rx: /password\s*[:=]\s*['"]([^'"]{8,})['"]/gi, label: "Hardcoded password", minEntropy: 3.0 },
1461
- { rx: /api[_-]?key\s*[:=]\s*['"]([a-zA-Z0-9]{20,})['"]/gi, label: "Hardcoded API key", minEntropy: 4.0 },
1462
- { rx: /secret\s*[:=]\s*['"]([a-zA-Z0-9]{16,})['"]/gi, label: "Hardcoded secret", minEntropy: 4.0 },
1463
- ];
1464
-
1465
1388
  for (const fileAbs of files) {
1466
1389
  try {
1467
1390
  const code = readFileCached(fileAbs);
1468
1391
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1469
- if (/\.env/.test(fileRel)) continue;
1470
1392
 
1471
- let foundInFile = false;
1472
-
1473
- // 1. Check specific patterns (prefix-based, high confidence - no entropy needed)
1474
- for (const { rx, label, severity } of specificPatterns) {
1475
- rx.lastIndex = 0;
1476
- const matches = code.match(rx);
1477
- if (matches && matches.length > 0) {
1478
- findings.push({
1479
- id: stableId("F_SECRET", `${fileRel}:${label}`),
1480
- severity,
1481
- category: "HardcodedSecret",
1482
- title: `${label} detected in: ${fileRel}`,
1483
- why: "Hardcoded secrets get committed and leaked. This is critical.",
1484
- confidence: "high",
1485
- evidence: [{ file: fileRel, reason: label }],
1486
- fixHints: [
1487
- "Move the secret to environment variables.",
1488
- "Rotate the compromised secret immediately.",
1489
- "Add the file to .gitignore if it shouldn't be committed.",
1490
- ],
1491
- });
1492
- foundInFile = true;
1493
- break;
1494
- }
1495
- }
1393
+ // Use unified engines
1394
+ const engineFindings = engines.analyzeHardcodedSecrets(code, fileRel);
1496
1395
 
1497
- if (foundInFile) continue;
1498
-
1499
- // 2. Check generic patterns WITH Shannon entropy to reduce false positives
1500
- for (const { rx, label, minEntropy } of genericPatterns) {
1501
- rx.lastIndex = 0;
1502
- let match;
1503
- while ((match = rx.exec(code)) !== null) {
1504
- const potentialSecret = match[1] || match[0];
1505
-
1506
- // Skip hex-only strings (likely Git SHAs, image IDs, not secrets)
1507
- if (/^[a-f0-9]+$/i.test(potentialSecret)) continue;
1508
-
1509
- // Skip common false positive patterns
1510
- if (/^(undefined|null|true|false|localhost|example|placeholder)/i.test(potentialSecret)) continue;
1511
-
1512
- const entropy = getShannonEntropy(potentialSecret);
1513
- if (entropy >= minEntropy) {
1514
- findings.push({
1515
- id: stableId("F_SECRET_ENTROPY", `${fileRel}:${label}:${potentialSecret.slice(0, 8)}`),
1516
- severity: "WARN", // Entropy is probabilistic, use WARN not BLOCK
1517
- category: "HardcodedSecret",
1518
- title: `${label} detected (high entropy ${entropy.toFixed(2)}): ${fileRel}`,
1519
- why: "This string looks mathematically random, which usually indicates a hardcoded secret key.",
1520
- confidence: "med",
1521
- evidence: [{ file: fileRel, reason: `Entropy: ${entropy.toFixed(2)} >= ${minEntropy}` }],
1522
- fixHints: [
1396
+ // Convert engine findings to analyzer format
1397
+ for (const finding of engineFindings) {
1398
+ findings.push({
1399
+ id: stableId("F_SECRET", `${fileRel}:${finding.type}:${finding.line}`),
1400
+ severity: finding.severity,
1401
+ category: finding.category,
1402
+ title: finding.title,
1403
+ message: finding.message,
1404
+ file: finding.file,
1405
+ line: finding.line,
1406
+ why: finding.severity === "BLOCK"
1407
+ ? "Hardcoded secrets get committed and leaked. This is critical."
1408
+ : "This string looks mathematically random, which usually indicates a hardcoded secret key.",
1409
+ confidence: finding.confidence,
1410
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1411
+ fixHints: finding.severity === "BLOCK"
1412
+ ? [
1413
+ "Move the secret to environment variables.",
1414
+ "Rotate the compromised secret immediately.",
1415
+ "Add the file to .gitignore if it shouldn't be committed.",
1416
+ ]
1417
+ : [
1523
1418
  "Move the secret to environment variables.",
1524
1419
  "If this is not a secret, consider using a more descriptive variable name.",
1525
1420
  ],
1526
- });
1527
- foundInFile = true;
1528
- break;
1529
- }
1530
- }
1531
- if (foundInFile) break;
1421
+ });
1532
1422
  }
1533
- } catch {
1534
- // skip
1423
+ } catch (err) {
1424
+ // Skip files that can't be analyzed
1425
+ continue;
1535
1426
  }
1536
1427
  }
1537
1428
 
@@ -1539,47 +1430,51 @@ function findHardcodedSecrets(repoRoot) {
1539
1430
  }
1540
1431
 
1541
1432
  /* ============================================================================
1542
- * DEAD CODE / UNUSED EXPORTS DETECTOR (fixed /g+.test() bug)
1433
+ * DEAD CODE / UNUSED EXPORTS DETECTOR (AST-based engine)
1543
1434
  * ========================================================================== */
1544
1435
 
1545
1436
  function findDeadCode(repoRoot) {
1437
+ const engines = require("./engines/vibecheck-engines");
1546
1438
  const findings = [];
1547
1439
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1548
1440
  cwd: repoRoot,
1549
1441
  absolute: true,
1550
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**", "**/*.d.ts"],
1442
+ ignore: [
1443
+ "**/node_modules/**",
1444
+ "**/.next/**",
1445
+ "**/dist/**",
1446
+ "**/build/**",
1447
+ "**/*.d.ts",
1448
+ ],
1551
1449
  });
1552
1450
 
1553
- const deadCodePatterns = [
1554
- { rx: /^\s*\/\/\s*export\s+(const|function|class|interface|type)/m, label: "Commented out export" },
1555
- { rx: /^\s*\/\*[\s\S]*?export[\s\S]*?\*\//m, label: "Block-commented export" },
1556
- { rx: /if\s*\(\s*false\s*\)\s*\{/m, label: "if (false) block" },
1557
- { rx: /if\s*\(\s*0\s*\)\s*\{/m, label: "if (0) block" },
1558
- { rx: /return;\s*\n\s*[^}]/m, label: "Unreachable code after return" },
1559
- { rx: /throw\s+new\s+Error[^;]*;\s*\n\s*[^}]/m, label: "Unreachable code after throw" },
1560
- ];
1561
-
1562
1451
  for (const fileAbs of files) {
1563
1452
  try {
1564
1453
  const code = readFileCached(fileAbs);
1565
1454
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1566
-
1567
- for (const { rx, label } of deadCodePatterns) {
1568
- if (!rxTest(rx, code)) continue;
1455
+
1456
+ // Use unified engines
1457
+ const engineFindings = engines.analyzeDeadCode(code, fileRel);
1458
+
1459
+ // Convert engine findings to analyzer format
1460
+ for (const finding of engineFindings) {
1569
1461
  findings.push({
1570
- id: stableId("F_DEAD_CODE", `${fileRel}:${label}`),
1571
- severity: "WARN",
1572
- category: "DeadCode",
1573
- title: `${label} in: ${fileRel}`,
1462
+ id: stableId("F_DEAD_CODE", `${fileRel}:${finding.type}:${finding.line}`),
1463
+ severity: finding.severity,
1464
+ category: finding.category,
1465
+ title: finding.title,
1466
+ message: finding.message,
1467
+ file: finding.file,
1468
+ line: finding.line,
1574
1469
  why: "Dead code adds confusion and maintenance burden and usually indicates incomplete refactoring.",
1575
- confidence: "med",
1576
- evidence: [{ file: fileRel, reason: label }],
1470
+ confidence: finding.confidence,
1471
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1577
1472
  fixHints: ["Remove the dead code entirely.", "If needed for reference, use git history instead of commenting."],
1578
1473
  });
1579
- break;
1580
1474
  }
1581
- } catch {
1582
- // skip
1475
+ } catch (err) {
1476
+ // Skip files that can't be analyzed
1477
+ continue;
1583
1478
  }
1584
1479
  }
1585
1480
 
@@ -1591,47 +1486,47 @@ function findDeadCode(repoRoot) {
1591
1486
  * ========================================================================== */
1592
1487
 
1593
1488
  function findDeprecatedApis(repoRoot) {
1489
+ const engines = require("./engines/vibecheck-engines");
1594
1490
  const findings = [];
1595
1491
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1596
1492
  cwd: repoRoot,
1597
1493
  absolute: true,
1598
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"],
1494
+ ignore: [
1495
+ "**/node_modules/**",
1496
+ "**/.next/**",
1497
+ "**/dist/**",
1498
+ "**/build/**",
1499
+ "**/*.d.ts",
1500
+ ],
1599
1501
  });
1600
1502
 
1601
- const deprecatedPatterns = [
1602
- { rx: /\bcomponentWillMount\b/, label: "componentWillMount (deprecated React lifecycle)" },
1603
- { rx: /\bcomponentWillReceiveProps\b/, label: "componentWillReceiveProps (deprecated)" },
1604
- { rx: /\bcomponentWillUpdate\b/, label: "componentWillUpdate (deprecated)" },
1605
- { rx: /\bgetInitialProps\b/, label: "getInitialProps (legacy Next.js)" },
1606
- { rx: /\bsubstr\s*\(/, label: "String.substr() (deprecated, use slice)" },
1607
- { rx: /\bdocument\.write\b/, label: "document.write (deprecated)" },
1608
- { rx: /new\s+Buffer\s*\(/, label: "new Buffer() (deprecated, use Buffer.from)" },
1609
- { rx: /\brequire\(['"]fs['"]\)\.exists\b/, label: "fs.exists (deprecated)" },
1610
- { rx: /\.__proto__\b/, label: "__proto__ (deprecated)" },
1611
- ];
1612
-
1613
1503
  for (const fileAbs of files) {
1614
1504
  try {
1615
1505
  const code = readFileCached(fileAbs);
1616
1506
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1617
-
1618
- for (const { rx, label } of deprecatedPatterns) {
1619
- if (!rxTest(rx, code)) continue;
1620
- const matches = code.match(new RegExp(rx.source, "g")) || [];
1507
+
1508
+ // Use unified engines
1509
+ const engineFindings = engines.analyzeDeprecatedApi(code, fileRel);
1510
+
1511
+ // Convert engine findings to analyzer format
1512
+ for (const finding of engineFindings) {
1621
1513
  findings.push({
1622
- id: stableId("F_DEPRECATED", `${fileRel}:${label}`),
1623
- severity: "WARN",
1624
- category: "DeprecatedApi",
1625
- title: `${label}: ${fileRel}`,
1514
+ id: stableId("F_DEPRECATED", `${fileRel}:${finding.type}:${finding.line}`),
1515
+ severity: finding.severity,
1516
+ category: finding.category,
1517
+ title: finding.title,
1518
+ message: finding.message,
1519
+ file: finding.file,
1520
+ line: finding.line,
1626
1521
  why: "Deprecated APIs may break in future versions and sometimes carry security issues.",
1627
- confidence: "high",
1628
- evidence: [{ file: fileRel, reason: `${matches.length} occurrence(s)` }],
1629
- fixHints: ["Update to the modern API equivalent.", "Check migration guides for the specific deprecation."],
1522
+ confidence: finding.confidence,
1523
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1524
+ fixHints: finding.replacement ? [`Use ${finding.replacement} instead.`, "Check migration guides for the specific deprecation."] : ["Update to the modern API equivalent.", "Check migration guides for the specific deprecation."],
1630
1525
  });
1631
- break;
1632
1526
  }
1633
- } catch {
1634
- // skip
1527
+ } catch (err) {
1528
+ // Skip files that can't be analyzed
1529
+ continue;
1635
1530
  }
1636
1531
  }
1637
1532
 
@@ -1639,39 +1534,51 @@ function findDeprecatedApis(repoRoot) {
1639
1534
  }
1640
1535
 
1641
1536
  /* ============================================================================
1642
- * EMPTY CATCH BLOCKS DETECTOR (kept)
1537
+ * EMPTY CATCH BLOCKS DETECTOR (AST-based engine)
1643
1538
  * ========================================================================== */
1644
1539
 
1645
1540
  function findEmptyCatch(repoRoot) {
1541
+ const engines = require("./engines/vibecheck-engines");
1646
1542
  const findings = [];
1647
1543
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1648
1544
  cwd: repoRoot,
1649
1545
  absolute: true,
1650
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"],
1546
+ ignore: [
1547
+ "**/node_modules/**",
1548
+ "**/.next/**",
1549
+ "**/dist/**",
1550
+ "**/build/**",
1551
+ "**/*.d.ts",
1552
+ ],
1651
1553
  });
1652
1554
 
1653
1555
  for (const fileAbs of files) {
1654
1556
  try {
1655
1557
  const code = readFileCached(fileAbs);
1656
1558
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1657
-
1658
- const emptyCatchRx = /catch\s*\([^)]*\)\s*\{\s*(\/\/[^\n]*)?\s*\}/g;
1659
- const matches = code.match(emptyCatchRx);
1660
-
1661
- if (matches && matches.length > 0) {
1559
+
1560
+ // Use unified engines
1561
+ const engineFindings = engines.analyzeEmptyCatch(code, fileRel);
1562
+
1563
+ // Convert engine findings to analyzer format
1564
+ for (const finding of engineFindings) {
1662
1565
  findings.push({
1663
- id: stableId("F_EMPTY_CATCH", fileRel),
1664
- severity: "WARN",
1665
- category: "EmptyCatch",
1666
- title: `Empty catch block(s) in: ${fileRel} (${matches.length} found)`,
1566
+ id: stableId("F_EMPTY_CATCH", `${fileRel}:${finding.type}:${finding.line}`),
1567
+ severity: finding.severity,
1568
+ category: finding.category,
1569
+ title: finding.title,
1570
+ message: finding.message,
1571
+ file: finding.file,
1572
+ line: finding.line,
1667
1573
  why: "Empty catch blocks swallow errors and make debugging impossible.",
1668
- confidence: "high",
1669
- evidence: [{ file: fileRel, reason: `${matches.length} empty catch block(s)` }],
1574
+ confidence: finding.confidence,
1575
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1670
1576
  fixHints: ["Log the error or handle it appropriately.", "If intentionally ignoring, add a comment explaining why."],
1671
1577
  });
1672
1578
  }
1673
- } catch {
1674
- // skip
1579
+ } catch (err) {
1580
+ // Skip files that can't be analyzed
1581
+ continue;
1675
1582
  }
1676
1583
  }
1677
1584
 
@@ -1683,40 +1590,405 @@ function findEmptyCatch(repoRoot) {
1683
1590
  * ========================================================================== */
1684
1591
 
1685
1592
  function findUnsafeRegex(repoRoot) {
1593
+ const engines = require("./engines/vibecheck-engines");
1686
1594
  const findings = [];
1687
1595
  const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1688
1596
  cwd: repoRoot,
1689
1597
  absolute: true,
1690
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"],
1598
+ ignore: [
1599
+ "**/node_modules/**",
1600
+ "**/.next/**",
1601
+ "**/dist/**",
1602
+ "**/build/**",
1603
+ "**/*.d.ts",
1604
+ ],
1691
1605
  });
1692
1606
 
1693
- const unsafePatterns = [
1694
- { rx: /new\s+RegExp\s*\([^)]*\+[^)]*\)/, label: "Dynamic regex with concatenation" },
1695
- { rx: /\(\.\*\)\+|\(\.\+\)\+|\(\.\*\)\*|\(\.\+\)\*/, label: "Nested quantifiers (ReDoS risk)" },
1696
- { rx: /\([^)]+\|[^)]+\)\+/, label: "Alternation with quantifier (ReDoS risk)" },
1697
- ];
1698
-
1699
1607
  for (const fileAbs of files) {
1700
1608
  try {
1701
1609
  const code = readFileCached(fileAbs);
1702
1610
  const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1703
-
1704
- for (const { rx, label } of unsafePatterns) {
1705
- if (!rxTest(rx, code)) continue;
1611
+
1612
+ // Use unified engines
1613
+ const engineFindings = engines.analyzeUnsafeRegex(code, fileRel);
1614
+
1615
+ // Convert engine findings to analyzer format
1616
+ for (const finding of engineFindings) {
1706
1617
  findings.push({
1707
- id: stableId("F_UNSAFE_REGEX", `${fileRel}:${label}`),
1708
- severity: "WARN",
1709
- category: "UnsafeRegex",
1710
- title: `${label}: ${fileRel}`,
1618
+ id: stableId("F_UNSAFE_REGEX", `${fileRel}:${finding.type}:${finding.line}`),
1619
+ severity: finding.severity,
1620
+ category: finding.category,
1621
+ title: finding.title,
1622
+ message: finding.message,
1623
+ file: finding.file,
1624
+ line: finding.line,
1711
1625
  why: "Unsafe regex patterns can cause denial of service via catastrophic backtracking.",
1712
- confidence: "med",
1713
- evidence: [{ file: fileRel, reason: label }],
1626
+ confidence: finding.confidence,
1627
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1714
1628
  fixHints: ["Validate input length before applying regex.", "Consider safer parsing or a regex linter.", "Avoid nested quantifiers."],
1715
1629
  });
1716
- break;
1717
1630
  }
1718
- } catch {
1719
- // skip
1631
+ } catch (err) {
1632
+ // Skip files that can't be analyzed
1633
+ continue;
1634
+ }
1635
+ }
1636
+
1637
+ return findings;
1638
+ }
1639
+
1640
+ /* ============================================================================
1641
+ * NEW SECURITY & PERFORMANCE ANALYZERS
1642
+ * ========================================================================== */
1643
+
1644
+ function findSecurityVulnerabilities(repoRoot) {
1645
+ // Note: Security vulnerabilities engine not in unified engines yet
1646
+ // Keep using the existing engine for now
1647
+ const { analyzeSecurityVulnerabilities } = require("./engines/security-vulnerabilities-engine");
1648
+ const findings = [];
1649
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1650
+ cwd: repoRoot,
1651
+ absolute: true,
1652
+ ignore: [
1653
+ "**/node_modules/**",
1654
+ "**/.next/**",
1655
+ "**/dist/**",
1656
+ "**/build/**",
1657
+ "**/*.d.ts",
1658
+ ],
1659
+ });
1660
+
1661
+ for (const fileAbs of files) {
1662
+ try {
1663
+ const code = readFileCached(fileAbs);
1664
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1665
+
1666
+ const engineFindings = analyzeSecurityVulnerabilities(code, fileRel);
1667
+
1668
+ for (const finding of engineFindings) {
1669
+ findings.push({
1670
+ id: stableId("F_SECURITY", `${fileRel}:${finding.type}:${finding.line}`),
1671
+ severity: finding.severity,
1672
+ category: finding.category,
1673
+ title: finding.title,
1674
+ message: finding.message,
1675
+ file: finding.file,
1676
+ line: finding.line,
1677
+ why: "Security vulnerabilities can lead to data breaches, unauthorized access, or system compromise.",
1678
+ confidence: finding.confidence,
1679
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1680
+ fixHints: [
1681
+ "Review security best practices for the detected vulnerability type.",
1682
+ "Use parameterized queries for SQL operations.",
1683
+ "Sanitize and validate all user inputs.",
1684
+ "Use Content Security Policy (CSP) headers for XSS protection.",
1685
+ ],
1686
+ });
1687
+ }
1688
+ } catch (err) {
1689
+ continue;
1690
+ }
1691
+ }
1692
+
1693
+ return findings;
1694
+ }
1695
+
1696
+ function findPerformanceIssues(repoRoot) {
1697
+ const engines = require("./engines/vibecheck-engines");
1698
+ const findings = [];
1699
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1700
+ cwd: repoRoot,
1701
+ absolute: true,
1702
+ ignore: [
1703
+ "**/node_modules/**",
1704
+ "**/.next/**",
1705
+ "**/dist/**",
1706
+ "**/build/**",
1707
+ "**/*.d.ts",
1708
+ ],
1709
+ });
1710
+
1711
+ for (const fileAbs of files) {
1712
+ try {
1713
+ const code = readFileCached(fileAbs);
1714
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1715
+
1716
+ // Use unified engines
1717
+ const engineFindings = engines.analyzePerformanceIssues(code, fileRel);
1718
+
1719
+ for (const finding of engineFindings) {
1720
+ findings.push({
1721
+ id: stableId("F_PERF", `${fileRel}:${finding.type}:${finding.line}`),
1722
+ severity: finding.severity,
1723
+ category: finding.category,
1724
+ title: finding.title,
1725
+ message: finding.message,
1726
+ file: finding.file,
1727
+ line: finding.line,
1728
+ why: "Performance issues can degrade user experience and increase server costs.",
1729
+ confidence: finding.confidence,
1730
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1731
+ fixHints: [
1732
+ "Optimize algorithms and data structures.",
1733
+ "Use pagination for large datasets.",
1734
+ "Remove unnecessary re-renders.",
1735
+ "Use async/await for I/O operations.",
1736
+ ],
1737
+ });
1738
+ }
1739
+ } catch (err) {
1740
+ continue;
1741
+ }
1742
+ }
1743
+
1744
+ return findings;
1745
+ }
1746
+
1747
+ function findCodeQualityIssues(repoRoot) {
1748
+ const engines = require("./engines/vibecheck-engines");
1749
+ const findings = [];
1750
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1751
+ cwd: repoRoot,
1752
+ absolute: true,
1753
+ ignore: [
1754
+ "**/node_modules/**",
1755
+ "**/.next/**",
1756
+ "**/dist/**",
1757
+ "**/build/**",
1758
+ "**/*.d.ts",
1759
+ ],
1760
+ });
1761
+
1762
+ for (const fileAbs of files) {
1763
+ try {
1764
+ const code = readFileCached(fileAbs);
1765
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1766
+
1767
+ // Use unified engines
1768
+ const engineFindings = engines.analyzeCodeQuality(code, fileRel);
1769
+
1770
+ for (const finding of engineFindings) {
1771
+ findings.push({
1772
+ id: stableId("F_QUALITY", `${fileRel}:${finding.type}:${finding.line}`),
1773
+ severity: finding.severity,
1774
+ category: finding.category,
1775
+ title: finding.title,
1776
+ message: finding.message,
1777
+ file: finding.file,
1778
+ line: finding.line,
1779
+ why: "Code quality issues make code harder to maintain, test, and extend.",
1780
+ confidence: finding.confidence,
1781
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1782
+ fixHints: [
1783
+ "Break down complex functions into smaller, focused functions.",
1784
+ "Extract magic numbers into named constants.",
1785
+ "Reduce nesting depth with early returns.",
1786
+ "Consider design patterns for common problems.",
1787
+ ],
1788
+ });
1789
+ }
1790
+ } catch (err) {
1791
+ continue;
1792
+ }
1793
+ }
1794
+
1795
+ return findings;
1796
+ }
1797
+
1798
+ function findCrossFileIssues(repoRoot) {
1799
+ const { analyzeCrossFile } = require("./engines/cross-file-analysis-engine");
1800
+ const fg = require("fast-glob");
1801
+
1802
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1803
+ cwd: repoRoot,
1804
+ absolute: true,
1805
+ ignore: [
1806
+ "**/node_modules/**",
1807
+ "**/.next/**",
1808
+ "**/dist/**",
1809
+ "**/build/**",
1810
+ "**/*.d.ts",
1811
+ "**/*.test.*",
1812
+ "**/*.spec.*",
1813
+ "**/tests/**",
1814
+ ],
1815
+ });
1816
+
1817
+ const engineFindings = analyzeCrossFile(files, repoRoot);
1818
+ const findings = [];
1819
+
1820
+ for (const finding of engineFindings) {
1821
+ findings.push({
1822
+ id: stableId("F_CROSSFILE", `${finding.file}:${finding.type}`),
1823
+ severity: finding.severity,
1824
+ category: finding.category,
1825
+ title: finding.title,
1826
+ message: finding.message,
1827
+ file: finding.file,
1828
+ line: finding.line,
1829
+ why: "Cross-file issues indicate architectural problems that can cause maintenance difficulties.",
1830
+ confidence: finding.confidence,
1831
+ evidence: [{ file: finding.file, reason: finding.title }],
1832
+ fixHints: [
1833
+ "Remove unused exports or mark them as internal.",
1834
+ "Refactor to break circular dependencies.",
1835
+ "Standardize import paths across the codebase.",
1836
+ ],
1837
+ });
1838
+ }
1839
+
1840
+ return findings;
1841
+ }
1842
+
1843
+ function findTypeSafetyIssues(repoRoot) {
1844
+ const engines = require("./engines/vibecheck-engines");
1845
+ const findings = [];
1846
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1847
+ cwd: repoRoot,
1848
+ absolute: true,
1849
+ ignore: [
1850
+ "**/node_modules/**",
1851
+ "**/.next/**",
1852
+ "**/dist/**",
1853
+ "**/build/**",
1854
+ "**/*.d.ts",
1855
+ ],
1856
+ });
1857
+
1858
+ for (const fileAbs of files) {
1859
+ try {
1860
+ const code = readFileCached(fileAbs);
1861
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1862
+
1863
+ // Use unified engines
1864
+ const engineFindings = engines.analyzeTypeAware(code, fileRel);
1865
+
1866
+ for (const finding of engineFindings) {
1867
+ findings.push({
1868
+ id: stableId("F_TYPE", `${fileRel}:${finding.type}:${finding.line}`),
1869
+ severity: finding.severity,
1870
+ category: finding.category,
1871
+ title: finding.title,
1872
+ message: finding.message,
1873
+ file: finding.file,
1874
+ line: finding.line,
1875
+ why: "Type safety issues can lead to runtime errors and make code harder to maintain.",
1876
+ confidence: finding.confidence,
1877
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1878
+ fixHints: [
1879
+ "Use proper TypeScript types instead of 'any'.",
1880
+ "Fix underlying type errors instead of suppressing them.",
1881
+ "Add explicit return type annotations.",
1882
+ ],
1883
+ });
1884
+ }
1885
+ } catch (err) {
1886
+ continue;
1887
+ }
1888
+ }
1889
+
1890
+ return findings;
1891
+ }
1892
+
1893
+ function findAccessibilityIssues(repoRoot) {
1894
+ const { analyzeAccessibility } = require("./engines/accessibility-engine");
1895
+ const findings = [];
1896
+ const files = fg.sync(["**/*.{tsx,jsx}"], {
1897
+ cwd: repoRoot,
1898
+ absolute: true,
1899
+ ignore: [
1900
+ "**/node_modules/**",
1901
+ "**/.next/**",
1902
+ "**/dist/**",
1903
+ "**/build/**",
1904
+ "**/*.d.ts",
1905
+ "**/*.test.*",
1906
+ "**/*.spec.*",
1907
+ "**/tests/**",
1908
+ ],
1909
+ });
1910
+
1911
+ for (const fileAbs of files) {
1912
+ try {
1913
+ const code = readFileCached(fileAbs);
1914
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1915
+
1916
+ const engineFindings = analyzeAccessibility(code, fileRel);
1917
+
1918
+ for (const finding of engineFindings) {
1919
+ findings.push({
1920
+ id: stableId("F_A11Y", `${fileRel}:${finding.type}:${finding.line}`),
1921
+ severity: finding.severity,
1922
+ category: finding.category,
1923
+ title: finding.title,
1924
+ message: finding.message,
1925
+ file: finding.file,
1926
+ line: finding.line,
1927
+ why: "Accessibility issues prevent users with disabilities from using your application.",
1928
+ confidence: finding.confidence,
1929
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1930
+ fixHints: [
1931
+ "Add alt text to all images.",
1932
+ "Ensure all interactive elements have accessible labels.",
1933
+ "Add keyboard handlers for custom interactive elements.",
1934
+ "Test with screen readers.",
1935
+ ],
1936
+ });
1937
+ }
1938
+ } catch (err) {
1939
+ continue;
1940
+ }
1941
+ }
1942
+
1943
+ return findings;
1944
+ }
1945
+
1946
+ function findAPIConsistencyIssues(repoRoot) {
1947
+ const { analyzeAPIConsistency } = require("./engines/api-consistency-engine");
1948
+ const findings = [];
1949
+ const files = fg.sync(["**/*.{ts,tsx,js,jsx}"], {
1950
+ cwd: repoRoot,
1951
+ absolute: true,
1952
+ ignore: [
1953
+ "**/node_modules/**",
1954
+ "**/.next/**",
1955
+ "**/dist/**",
1956
+ "**/build/**",
1957
+ "**/*.d.ts",
1958
+ "**/*.test.*",
1959
+ "**/*.spec.*",
1960
+ "**/tests/**",
1961
+ ],
1962
+ });
1963
+
1964
+ for (const fileAbs of files) {
1965
+ try {
1966
+ const code = readFileCached(fileAbs);
1967
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
1968
+
1969
+ const engineFindings = analyzeAPIConsistency(code, fileRel);
1970
+
1971
+ for (const finding of engineFindings) {
1972
+ findings.push({
1973
+ id: stableId("F_API", `${fileRel}:${finding.type}:${finding.line}`),
1974
+ severity: finding.severity,
1975
+ category: finding.category,
1976
+ title: finding.title,
1977
+ message: finding.message,
1978
+ file: finding.file,
1979
+ line: finding.line,
1980
+ why: "API consistency issues make APIs harder to use and maintain.",
1981
+ confidence: finding.confidence,
1982
+ evidence: [{ file: fileRel, reason: finding.title, line: finding.line }],
1983
+ fixHints: [
1984
+ "Standardize response formats across all API routes.",
1985
+ "Add consistent error handling.",
1986
+ "Always return explicit HTTP status codes.",
1987
+ ],
1988
+ });
1989
+ }
1990
+ } catch (err) {
1991
+ continue;
1720
1992
  }
1721
1993
  }
1722
1994
 
@@ -1746,4 +2018,13 @@ module.exports = {
1746
2018
  findDeprecatedApis,
1747
2019
  findEmptyCatch,
1748
2020
  findUnsafeRegex,
2021
+ // Enhanced analyzers
2022
+ findSecurityVulnerabilities,
2023
+ findPerformanceIssues,
2024
+ findCodeQualityIssues,
2025
+ // Advanced analyzers
2026
+ findCrossFileIssues,
2027
+ findTypeSafetyIssues,
2028
+ findAccessibilityIssues,
2029
+ findAPIConsistencyIssues,
1749
2030
  };