@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
@@ -1018,108 +1018,177 @@ async function resolvePythonClientRefs(repoRoot) {
1018
1018
  // MAIN: RESOLVE ALL ROUTES
1019
1019
  // ============================================================================
1020
1020
 
1021
- async function resolveAllRoutes(repoRoot) {
1021
+ // Smart orchestration: only run expensive scanners when signals say they matter.
1022
+ // You can force the old behavior by setting:
1023
+ // VIBECHECK_ROUTE_SCAN_MODE=deep
1024
+ // or calling resolveAllRoutes(repoRoot, { mode: "deep" }).
1025
+ async function resolveAllRoutes(repoRoot, opts = {}) {
1026
+ const mode = (opts.mode || process.env.VIBECHECK_ROUTE_SCAN_MODE || "smart").toLowerCase();
1027
+ const verbose = !!(opts.verbose || process.env.VIBECHECK_VERBOSE_ROUTES);
1028
+ const skipExpress = !!opts.skipExpress; // When true, skip Express (handled by optimized extractor)
1029
+
1022
1030
  const frameworks = await detectFrameworks(repoRoot);
1023
-
1031
+
1024
1032
  const allRoutes = [];
1025
1033
  const allClientRefs = [];
1026
1034
  const gaps = [];
1027
-
1028
- console.log(` 📦 Detected frameworks: ${frameworks.length > 0 ? frameworks.join(", ") : "none detected, scanning all"}`);
1029
-
1030
- // Always scan for routes regardless of detected frameworks
1031
- // JavaScript/TypeScript frameworks
1035
+
1036
+ const fw = new Set(frameworks.map((x) => String(x).toLowerCase()));
1037
+
1038
+ // Lightweight language presence checks (avoid globbing millions of files)
1039
+ const has = {
1040
+ python: exists(path.join(repoRoot, "requirements.txt")) || exists(path.join(repoRoot, "pyproject.toml")),
1041
+ ruby: exists(path.join(repoRoot, "Gemfile")),
1042
+ go: exists(path.join(repoRoot, "go.mod")),
1043
+ };
1044
+
1045
+ // If we don't have explicit deps, do a quick filesystem sniff.
1046
+ // (We intentionally keep this cheap: just check for any matching file.)
1047
+ if (!has.python) {
1048
+ try {
1049
+ const py = await fg(["**/*.py"], { cwd: repoRoot, absolute: true, ignore: ["**/node_modules/**", "**/.venv/**", "**/venv/**", "**/.git/**", "**/dist/**", "**/build/**"] , deep: 4 });
1050
+ has.python = py.length > 0;
1051
+ } catch {}
1052
+ }
1053
+
1054
+ const shouldDeepScan = mode === "deep";
1055
+
1056
+ if (verbose) {
1057
+ const list = frameworks.length ? frameworks.join(", ") : "none";
1058
+ console.log(` 📦 Detected frameworks: ${list} (mode=${mode})`);
1059
+ }
1060
+
1061
+ // JS/TS frameworks
1062
+ const shouldExpress = shouldDeepScan || fw.has("express");
1063
+ const shouldHono = shouldDeepScan || fw.has("hono");
1064
+ const shouldKoa = shouldDeepScan || fw.has("koa");
1065
+ const shouldNest = shouldDeepScan || fw.has("nestjs");
1066
+
1067
+ // Python frameworks
1068
+ const shouldFlask = shouldDeepScan || fw.has("flask") || (has.python && fw.size === 0);
1069
+ const shouldFastAPI = shouldDeepScan || fw.has("fastapi") || (has.python && fw.size === 0);
1070
+ const shouldDjango = shouldDeepScan || fw.has("django") || (has.python && fw.size === 0);
1071
+
1072
+ // Go frameworks
1073
+ const shouldGo = shouldDeepScan || fw.has("go") || has.go;
1074
+
1075
+ // OpenAPI/GraphQL are cheap and high-signal, so include when present.
1076
+ const shouldOpenAPI = shouldDeepScan || fw.has("openapi");
1077
+ const shouldGraphQL = shouldDeepScan || fw.has("graphql");
1078
+
1079
+ // If nothing detected and not deep mode, keep JS scanning conservative.
1080
+ // (Truthpack already handles Next/Fastify separately; this is a safety net.)
1081
+ const unknownRepo = fw.size === 0 && !has.python && !has.go && !has.ruby;
1082
+ const safeJsScan = shouldDeepScan || !unknownRepo;
1083
+
1032
1084
  try {
1033
- const expressRoutes = await resolveExpressRoutes(repoRoot);
1034
- if (expressRoutes.length > 0) {
1035
- console.log(` ✓ Express: ${expressRoutes.length} routes`);
1036
- allRoutes.push(...expressRoutes);
1085
+ if (safeJsScan && shouldExpress && !skipExpress) {
1086
+ const expressRoutes = await resolveExpressRoutes(repoRoot);
1087
+ if (expressRoutes.length > 0) {
1088
+ if (verbose) console.log(` ✓ Express: ${expressRoutes.length} routes`);
1089
+ allRoutes.push(...expressRoutes);
1090
+ }
1091
+ } else if (skipExpress && verbose) {
1092
+ console.log(` ⏭️ Express: skipped (using optimized extractor)`);
1037
1093
  }
1038
1094
  } catch (e) { gaps.push({ kind: "express_scan_error", error: e.message }); }
1039
-
1095
+
1040
1096
  try {
1041
- const honoRoutes = await resolveHonoRoutes(repoRoot);
1042
- if (honoRoutes.length > 0) {
1043
- console.log(` ✓ Hono: ${honoRoutes.length} routes`);
1044
- allRoutes.push(...honoRoutes);
1097
+ if (safeJsScan && shouldHono) {
1098
+ const honoRoutes = await resolveHonoRoutes(repoRoot);
1099
+ if (honoRoutes.length > 0) {
1100
+ if (verbose) console.log(` ✓ Hono: ${honoRoutes.length} routes`);
1101
+ allRoutes.push(...honoRoutes);
1102
+ }
1045
1103
  }
1046
1104
  } catch (e) { gaps.push({ kind: "hono_scan_error", error: e.message }); }
1047
-
1105
+
1048
1106
  try {
1049
- const koaRoutes = await resolveKoaRoutes(repoRoot);
1050
- if (koaRoutes.length > 0) {
1051
- console.log(` ✓ Koa: ${koaRoutes.length} routes`);
1052
- allRoutes.push(...koaRoutes);
1107
+ if (safeJsScan && shouldKoa) {
1108
+ const koaRoutes = await resolveKoaRoutes(repoRoot);
1109
+ if (koaRoutes.length > 0) {
1110
+ if (verbose) console.log(` ✓ Koa: ${koaRoutes.length} routes`);
1111
+ allRoutes.push(...koaRoutes);
1112
+ }
1053
1113
  }
1054
1114
  } catch (e) { gaps.push({ kind: "koa_scan_error", error: e.message }); }
1055
-
1056
- // Python frameworks
1115
+
1116
+ // NestJS route extraction is expensive and flaky; only run it when clearly present.
1117
+ if (shouldNest) {
1118
+ // (No dedicated resolver today; keep placeholder for future.)
1119
+ }
1120
+
1057
1121
  try {
1058
- const flaskRoutes = await resolveFlaskRoutes(repoRoot);
1059
- if (flaskRoutes.length > 0) {
1060
- console.log(` ✓ Flask: ${flaskRoutes.length} routes`);
1061
- allRoutes.push(...flaskRoutes);
1122
+ if (shouldFlask) {
1123
+ const flaskRoutes = await resolveFlaskRoutes(repoRoot);
1124
+ if (flaskRoutes.length > 0) {
1125
+ if (verbose) console.log(` ✓ Flask: ${flaskRoutes.length} routes`);
1126
+ allRoutes.push(...flaskRoutes);
1127
+ }
1062
1128
  }
1063
1129
  } catch (e) { gaps.push({ kind: "flask_scan_error", error: e.message }); }
1064
-
1130
+
1065
1131
  try {
1066
- const fastapiRoutes = await resolveFastAPIRoutes(repoRoot);
1067
- if (fastapiRoutes.length > 0) {
1068
- console.log(` ✓ FastAPI: ${fastapiRoutes.length} routes`);
1069
- allRoutes.push(...fastapiRoutes);
1132
+ if (shouldFastAPI) {
1133
+ const fastapiRoutes = await resolveFastAPIRoutes(repoRoot);
1134
+ if (fastapiRoutes.length > 0) {
1135
+ if (verbose) console.log(` ✓ FastAPI: ${fastapiRoutes.length} routes`);
1136
+ allRoutes.push(...fastapiRoutes);
1137
+ }
1070
1138
  }
1071
1139
  } catch (e) { gaps.push({ kind: "fastapi_scan_error", error: e.message }); }
1072
-
1140
+
1073
1141
  try {
1074
- const djangoRoutes = await resolveDjangoRoutes(repoRoot);
1075
- if (djangoRoutes.length > 0) {
1076
- console.log(` ✓ Django: ${djangoRoutes.length} routes`);
1077
- allRoutes.push(...djangoRoutes);
1142
+ if (shouldDjango) {
1143
+ const djangoRoutes = await resolveDjangoRoutes(repoRoot);
1144
+ if (djangoRoutes.length > 0) {
1145
+ if (verbose) console.log(` ✓ Django: ${djangoRoutes.length} routes`);
1146
+ allRoutes.push(...djangoRoutes);
1147
+ }
1078
1148
  }
1079
1149
  } catch (e) { gaps.push({ kind: "django_scan_error", error: e.message }); }
1080
-
1081
- // Python client refs
1150
+
1082
1151
  try {
1083
- const pythonRefs = await resolvePythonClientRefs(repoRoot);
1084
- if (pythonRefs.length > 0) {
1085
- console.log(` ✓ Python client refs: ${pythonRefs.length}`);
1086
- allClientRefs.push(...pythonRefs);
1152
+ if (has.python && (shouldDeepScan || shouldFlask || shouldFastAPI || shouldDjango)) {
1153
+ const pythonRefs = await resolvePythonClientRefs(repoRoot);
1154
+ if (pythonRefs.length > 0) {
1155
+ if (verbose) console.log(` ✓ Python client refs: ${pythonRefs.length}`);
1156
+ allClientRefs.push(...pythonRefs);
1157
+ }
1087
1158
  }
1088
1159
  } catch (e) { gaps.push({ kind: "python_client_scan_error", error: e.message }); }
1089
-
1090
- // OpenAPI/Swagger specs (high accuracy)
1160
+
1091
1161
  try {
1092
- const openapiRoutes = await resolveOpenAPIRoutes(repoRoot);
1093
- if (openapiRoutes.length > 0) {
1094
- console.log(` ✓ OpenAPI spec: ${openapiRoutes.length} routes`);
1095
- allRoutes.push(...openapiRoutes);
1162
+ if (shouldOpenAPI) {
1163
+ const openapiRoutes = await resolveOpenAPIRoutes(repoRoot);
1164
+ if (openapiRoutes.length > 0) {
1165
+ if (verbose) console.log(` ✓ OpenAPI spec: ${openapiRoutes.length} routes`);
1166
+ allRoutes.push(...openapiRoutes);
1167
+ }
1096
1168
  }
1097
1169
  } catch (e) { gaps.push({ kind: "openapi_scan_error", error: e.message }); }
1098
-
1099
- // GraphQL endpoints
1170
+
1100
1171
  try {
1101
- const graphqlRoutes = await resolveGraphQLRoutes(repoRoot);
1102
- if (graphqlRoutes.length > 0) {
1103
- console.log(` ✓ GraphQL: ${graphqlRoutes.length} endpoints`);
1104
- allRoutes.push(...graphqlRoutes);
1172
+ if (shouldGraphQL) {
1173
+ const graphqlRoutes = await resolveGraphQLRoutes(repoRoot);
1174
+ if (graphqlRoutes.length > 0) {
1175
+ if (verbose) console.log(` ✓ GraphQL: ${graphqlRoutes.length} endpoints`);
1176
+ allRoutes.push(...graphqlRoutes);
1177
+ }
1105
1178
  }
1106
1179
  } catch (e) { gaps.push({ kind: "graphql_scan_error", error: e.message }); }
1107
-
1108
- // Go frameworks (Gin, Echo, Fiber, Chi, Gorilla, stdlib)
1180
+
1109
1181
  try {
1110
- const goRoutes = await resolveGoRoutes(repoRoot);
1111
- if (goRoutes.length > 0) {
1112
- console.log(` ✓ Go: ${goRoutes.length} routes`);
1113
- allRoutes.push(...goRoutes);
1182
+ if (shouldGo) {
1183
+ const goRoutes = await resolveGoRoutes(repoRoot);
1184
+ if (goRoutes.length > 0) {
1185
+ if (verbose) console.log(` ✓ Go: ${goRoutes.length} routes`);
1186
+ allRoutes.push(...goRoutes);
1187
+ }
1114
1188
  }
1115
1189
  } catch (e) { gaps.push({ kind: "go_scan_error", error: e.message }); }
1116
-
1117
- return {
1118
- routes: allRoutes,
1119
- clientRefs: allClientRefs,
1120
- gaps,
1121
- frameworks
1122
- };
1190
+
1191
+ return { routes: allRoutes, clientRefs: allClientRefs, gaps, frameworks };
1123
1192
  }
1124
1193
 
1125
1194
  module.exports = {
@@ -3,11 +3,13 @@
3
3
  * Features:
4
4
  * - "SCAN" ASCII Art
5
5
  * - Fixed alignment tables
6
- * - "Redacted" Upsell section for Scan context
6
+ * - Auto-fix upsell section for Scan context
7
+ * - Deterministic output ordering
7
8
  */
8
9
 
9
10
  // Use ANSI codes directly (chalk v5 is ESM-only, this is CommonJS)
10
11
  const ESC = '\x1b';
12
+ const { sortFindings } = require('./finding-sorter');
11
13
  const chalk = {
12
14
  reset: `${ESC}[0m`,
13
15
  bold: `${ESC}[1m`,
@@ -99,6 +101,11 @@ function formatScanOutput(result, options = {}) {
99
101
  let duration = 0;
100
102
  let scannedFiles = 0;
101
103
 
104
+ // Sort findings deterministically for stable output
105
+ if (result.findings && Array.isArray(result.findings)) {
106
+ result.findings = sortFindings(result.findings);
107
+ }
108
+
102
109
  // Helper function to calculate score from findings
103
110
  function calculateScoreFromFindings(findings) {
104
111
  if (!findings || findings.length === 0) return 100;
@@ -245,17 +252,33 @@ function formatScanOutput(result, options = {}) {
245
252
  });
246
253
 
247
254
  const categoryLabels = {
248
- 'EnvContract': '🔐 Env',
249
- 'MissingRoute': '🔗 Routes',
250
- 'GhostAuth': '🛡️ Auth',
251
- 'FakeSuccess': '⚠️ Fake',
252
- 'MockData': '🎭 Mocks',
255
+ // AI Hallucination Categories (primary)
256
+ 'MissingRoute': '🛤️ Routes',
257
+ 'FakeSuccess': '👻 Fake',
258
+ 'GhostAuth': '🔒 Auth',
259
+ 'EnvContract': '🌍 Env',
260
+ 'EnvGap': '🌍 Env',
261
+ 'DeadUI': '💀 Dead UI',
262
+ 'OwnerModeBypass': '🔐 Bypass',
263
+ 'OptimisticNoRollback': '↩️ Rollback',
264
+ 'SilentCatch': '🔇 Silent',
265
+ // Billing/Monetization
266
+ 'Billing': '💰 Billing',
267
+ 'StripeWebhook': '💰 Webhook',
268
+ 'PaidSurface': '💰 Paid',
269
+ 'Entitlements': '💳 Entitle',
270
+ // Security
271
+ 'Security': '🛡️ Security',
253
272
  'Secrets': '🔑 Secrets',
273
+ 'SECRET': '🔑 Secrets',
274
+ // Quality
275
+ 'MockData': '🎭 Mocks',
276
+ 'MOCK': '🎭 Mocks',
254
277
  'ConsoleLog': '📝 Logs',
255
278
  'TodoFixme': '📋 TODOs',
256
279
  'CodeQuality': '✨ Quality',
257
- 'Security': '⚡ Security',
258
280
  'Performance': '⚡ Perf',
281
+ 'ContractDrift': '📜 Drift',
259
282
  };
260
283
 
261
284
  const summaryItems = Object.entries(categoryCounts)
@@ -382,6 +405,25 @@ function formatScanOutput(result, options = {}) {
382
405
  lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
383
406
  lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
384
407
 
408
+ // DASHBOARD LINK (outside frame)
409
+ const runId = result.runId || result.verdict?.runId;
410
+ if (runId) {
411
+ lines.push('');
412
+ lines.push(` ${chalk.dim}🔗 Dashboard:${chalk.reset} https://app.vibecheckai.dev/runs/${runId}`);
413
+ }
414
+
415
+ // NEXT BEST ACTION (outside frame)
416
+ try {
417
+ const { formatNextActionOneLine, getNextActionForCommand } = require('./next-action');
418
+ const tier = options.tier || 'free';
419
+ const verdict = typeof result.verdict === 'object' ? result.verdict.verdict : result.verdict;
420
+ const nextAction = getNextActionForCommand('scan', { verdict, runId, findings }, tier);
421
+ lines.push('');
422
+ lines.push(` ${formatNextActionOneLine(nextAction)}`);
423
+ } catch (e) {
424
+ // Next action module might not be available, skip gracefully
425
+ }
426
+
385
427
  return lines.join('\n');
386
428
  }
387
429
 
@@ -461,36 +503,56 @@ function printError(error, context = '') {
461
503
  * Format SARIF output (placeholder - full implementation in report-engine.js)
462
504
  */
463
505
  function formatSARIF(findings, options = {}) {
464
- // Basic SARIF structure - full implementation should be in report-engine.js
465
- return JSON.stringify({
506
+ const path = require('path');
507
+ // Sort findings deterministically for stable output
508
+ const sortedFindings = sortFindings(findings || []);
509
+
510
+ const projectPath = options.projectPath || process.cwd();
511
+
512
+ // Valid SARIF 2.1.0 structure
513
+ const sarif = {
466
514
  version: "2.1.0",
467
- $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema.json",
515
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
468
516
  runs: [{
469
517
  tool: {
470
518
  driver: {
471
519
  name: "vibecheck",
472
- version: options.version || "1.0.0"
520
+ version: options.version || "1.0.0",
521
+ informationUri: "https://vibecheckai.dev"
473
522
  }
474
523
  },
475
- results: findings.map((f, i) => ({
476
- ruleId: f.ruleId || f.category || `rule-${i}`,
477
- message: {
478
- text: f.message || f.title || ""
479
- },
480
- level: f.severity === 'critical' ? 'error' : f.severity === 'warning' ? 'warning' : 'note',
481
- locations: f.file ? [{
482
- physicalLocation: {
483
- artifactLocation: {
484
- uri: f.file
485
- },
486
- region: f.line ? {
487
- startLine: f.line
488
- } : undefined
489
- }
490
- }] : []
491
- }))
524
+ results: sortedFindings.map((f, i) => {
525
+ const filePath = f.file || f.path;
526
+ const relativePath = filePath && projectPath
527
+ ? path.relative(projectPath, filePath).replace(/\\/g, '/')
528
+ : filePath || 'unknown';
529
+
530
+ return {
531
+ ruleId: f.ruleId || f.id || f.category || f.type || `rule-${i}`,
532
+ message: {
533
+ text: f.message || f.title || f.description || "Issue detected"
534
+ },
535
+ level: f.severity === 'critical' || f.severity === 'BLOCK' ? 'error' :
536
+ f.severity === 'warning' || f.severity === 'WARN' ? 'warning' : 'note',
537
+ locations: filePath ? [{
538
+ physicalLocation: {
539
+ artifactLocation: {
540
+ uri: relativePath
541
+ },
542
+ region: f.line ? {
543
+ startLine: f.line,
544
+ startColumn: f.column || 1,
545
+ endLine: f.endLine || f.line,
546
+ endColumn: f.endColumn || f.column || 1
547
+ } : undefined
548
+ }
549
+ }] : []
550
+ };
551
+ })
492
552
  }]
493
- }, null, 2);
553
+ };
554
+
555
+ return JSON.stringify(sarif, null, 2);
494
556
  }
495
557
 
496
558
  // Placeholder functions for compatibility
@@ -510,52 +572,8 @@ function renderLayers(layers) {
510
572
  return ''; // Implement if needed
511
573
  }
512
574
 
513
- // ═══════════════════════════════════════════════════════════════════════════════
514
- // ENHANCED OUTPUT (with charts and Pro upselling)
515
- // ═══════════════════════════════════════════════════════════════════════════════
516
-
517
- let enhancedOutput;
518
- try {
519
- enhancedOutput = require('./scan-output-enhanced');
520
- } catch (e) {
521
- enhancedOutput = null;
522
- }
523
-
524
- /**
525
- * Format scan output with enhanced visuals (charts, upsell)
526
- * Falls back to standard output if enhanced module not available
527
- */
528
- function formatScanOutputEnhanced(result, options = {}) {
529
- if (enhancedOutput && !options.legacy) {
530
- enhancedOutput.renderEnhancedScanOutput(result, options);
531
- return ''; // Output is printed directly
532
- }
533
- return formatScanOutput(result, options);
534
- }
535
-
536
- /**
537
- * Get Pro upsell prompt for current context
538
- */
539
- function getProUpsellPrompt(context, data = {}) {
540
- if (enhancedOutput) {
541
- return enhancedOutput.getProPrompt(context, data);
542
- }
543
- return null;
544
- }
545
-
546
- /**
547
- * Render inline Pro upsell
548
- */
549
- function renderProUpsell(context, data = {}) {
550
- if (enhancedOutput) {
551
- return enhancedOutput.renderInlineProPrompt(context, data);
552
- }
553
- return '';
554
- }
555
-
556
575
  module.exports = {
557
576
  formatScanOutput,
558
- formatScanOutputEnhanced,
559
577
  calculateScore,
560
578
  getExitCode,
561
579
  printError,
@@ -565,7 +583,4 @@ module.exports = {
565
583
  renderBreakdown,
566
584
  renderBlockers,
567
585
  renderLayers,
568
- // Pro upsell
569
- getProUpsellPrompt,
570
- renderProUpsell,
571
586
  };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Scan Runner - Timeout and Cancellation Support
3
+ *
4
+ * Provides timeout and cancellation capabilities for scan execution.
5
+ * Prevents runaway scans from hanging indefinitely.
6
+ */
7
+
8
+ const EventEmitter = require('events');
9
+
10
+ /**
11
+ * @typedef {Object} ScanOptions
12
+ * @property {number} [timeout] - Timeout in milliseconds (default: 5 minutes)
13
+ * @property {AbortSignal} [signal] - AbortSignal for cancellation
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} ScanResult
18
+ * @property {'completed'|'cancelled'|'failed'} status
19
+ * @property {Array} findings
20
+ * @property {string} [error]
21
+ */
22
+
23
+ class ScanRunner extends EventEmitter {
24
+ constructor() {
25
+ super();
26
+ this.aborted = false;
27
+ this.timeoutId = null;
28
+ }
29
+ constructor() {
30
+ super();
31
+ this.aborted = false;
32
+ this.timeoutId = null;
33
+ }
34
+
35
+ /**
36
+ * Run a scan with timeout and cancellation support
37
+ * @param {string} projectPath - Path to project to scan
38
+ * @param {ScanOptions} options - Scan options
39
+ * @returns {Promise<ScanResult>}
40
+ */
41
+ async run(projectPath, options = {}) {
42
+ const timeout = options.timeout || 5 * 60 * 1000; // Default 5 minutes
43
+
44
+ // Set up abort handling
45
+ if (options.signal) {
46
+ options.signal.addEventListener('abort', () => {
47
+ this.abort('Scan aborted via signal');
48
+ });
49
+ }
50
+
51
+ // Set up timeout
52
+ this.timeoutId = setTimeout(() => {
53
+ this.abort('Scan timed out');
54
+ }, timeout);
55
+
56
+ try {
57
+ this.emit('start', { projectPath, timeout });
58
+
59
+ const result = await this.executeAnalyzers(projectPath);
60
+
61
+ if (this.aborted) {
62
+ return { status: 'cancelled', findings: [] };
63
+ }
64
+
65
+ return result;
66
+ } catch (error) {
67
+ if (this.aborted) {
68
+ return { status: 'cancelled', findings: [], error: error.message };
69
+ }
70
+ return { status: 'failed', findings: [], error: error.message };
71
+ } finally {
72
+ if (this.timeoutId) {
73
+ clearTimeout(this.timeoutId);
74
+ this.timeoutId = null;
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Abort the current scan
81
+ * @param {string} reason - Reason for abortion
82
+ */
83
+ abort(reason = 'Scan aborted') {
84
+ this.aborted = true;
85
+ this.emit('abort', { reason });
86
+ }
87
+
88
+ /**
89
+ * Execute analyzers with abort checks between each
90
+ * @private
91
+ * @param {string} projectPath - Path to project
92
+ * @returns {Promise<ScanResult>}
93
+ */
94
+ async executeAnalyzers(projectPath) {
95
+ // Import analysis core dynamically to avoid circular dependencies
96
+ const { runAnalysis } = require('./analysis-core');
97
+
98
+ // Check abort before starting
99
+ if (this.aborted) {
100
+ return { status: 'cancelled', findings: [] };
101
+ }
102
+
103
+ try {
104
+ // Run analysis with progress tracking
105
+ this.emit('progress', { phase: 'analysis', status: 'running' });
106
+
107
+ const result = await runAnalysis({
108
+ repoRoot: projectPath,
109
+ extended: true,
110
+ noWrite: false
111
+ });
112
+
113
+ // Check abort after analysis
114
+ if (this.aborted) {
115
+ return { status: 'cancelled', findings: result.findings || [] };
116
+ }
117
+
118
+ this.emit('progress', { phase: 'analysis', status: 'complete', count: result.findings?.length || 0 });
119
+
120
+ return {
121
+ status: 'completed',
122
+ findings: result.findings || [],
123
+ verdict: result.verdict,
124
+ report: result.report
125
+ };
126
+ } catch (error) {
127
+ if (this.aborted) {
128
+ return { status: 'cancelled', findings: [], error: error.message };
129
+ }
130
+ throw error;
131
+ }
132
+ }
133
+ }
134
+
135
+ module.exports = { ScanRunner };