@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
@@ -0,0 +1,164 @@
1
+ /**
2
+ * AST Cache
3
+ * Shared AST parsing cache for analysis engines.
4
+ *
5
+ * Goals:
6
+ * - Parse each source file once (per content hash)
7
+ * - Avoid re-parsing unchanged files across multiple engines
8
+ * - Keep cache bounded (entries + total bytes) to prevent memory blowups
9
+ * - Refuse to parse obviously minified/giant files (should be excluded anyway)
10
+ */
11
+
12
+ const crypto = require("crypto");
13
+ const parser = require("@babel/parser");
14
+
15
+ // Map preserves insertion order. We'll use it as a simple LRU:
16
+ // on get -> delete+set to move entry to end
17
+ const _AST_CACHE = new Map(); // key -> { ast, hash, bytes, lines, ts }
18
+ let _TOTAL_BYTES = 0;
19
+
20
+ const LIMITS = {
21
+ maxEntries: 6000,
22
+ maxTotalBytes: 200 * 1024 * 1024, // 200MB across cached source sizes
23
+ maxFileBytes: 1_200_000, // 1.2MB per file (bigger files likely generated)
24
+ maxLines: 25_000,
25
+ };
26
+
27
+ function sha1(text) {
28
+ return crypto.createHash("sha1").update(text).digest("hex");
29
+ }
30
+
31
+ function countNewlines(text) {
32
+ let n = 1;
33
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) n++;
34
+ return n;
35
+ }
36
+
37
+ /**
38
+ * Heuristic: identify minified/bundled files (super long lines, few newlines).
39
+ */
40
+ function isProbablyMinified(code) {
41
+ if (!code || typeof code !== "string") return false;
42
+ const bytes = Buffer.byteLength(code, "utf8");
43
+ if (bytes < 40_000) return false; // small files aren't our concern
44
+
45
+ const lines = countNewlines(code);
46
+ const avgLine = bytes / Math.max(1, lines);
47
+ if (avgLine > 600) return true;
48
+
49
+ // Count semicolons as a crude "density" marker
50
+ const sample = code.slice(0, Math.min(80_000, code.length));
51
+ const semi = (sample.match(/;/g) || []).length;
52
+ const braces = (sample.match(/[{}]/g) || []).length;
53
+ const density = (semi + braces) / Math.max(1, sample.length);
54
+
55
+ return density > 0.035 && lines < 80;
56
+ }
57
+
58
+ /**
59
+ * Parse code into a Babel AST.
60
+ * Returns null if code is too big/minified or parsing fails.
61
+ */
62
+ function parseCode(code, filePath = "<unknown>") {
63
+ if (!code || typeof code !== "string") return null;
64
+
65
+ const bytes = Buffer.byteLength(code, "utf8");
66
+ if (bytes > LIMITS.maxFileBytes) return null;
67
+
68
+ const lines = countNewlines(code);
69
+ if (lines > LIMITS.maxLines) return null;
70
+
71
+ if (isProbablyMinified(code)) return null;
72
+
73
+ try {
74
+ // include TypeScript + JSX support; \"unambiguous\" handles CJS/ESM
75
+ return parser.parse(code, {
76
+ sourceType: "unambiguous",
77
+ plugins: [
78
+ "typescript",
79
+ "jsx",
80
+ "decorators-legacy",
81
+ "classProperties",
82
+ "classPrivateProperties",
83
+ "classPrivateMethods",
84
+ "dynamicImport",
85
+ "optionalChaining",
86
+ "nullishCoalescingOperator",
87
+ "objectRestSpread",
88
+ "topLevelAwait",
89
+ ],
90
+ errorRecovery: true,
91
+ ranges: false,
92
+ tokens: false,
93
+ attachComment: true,
94
+ });
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ function _evictIfNeeded() {
101
+ while (_AST_CACHE.size > LIMITS.maxEntries || _TOTAL_BYTES > LIMITS.maxTotalBytes) {
102
+ const firstKey = _AST_CACHE.keys().next().value;
103
+ if (!firstKey) break;
104
+ const entry = _AST_CACHE.get(firstKey);
105
+ _AST_CACHE.delete(firstKey);
106
+ _TOTAL_BYTES -= entry?.bytes || 0;
107
+ }
108
+ }
109
+
110
+ function getAST(code, filePath = "<unknown>") {
111
+ // Caller should already filter, but keep extra guards here.
112
+ const bytes = Buffer.byteLength(code || "", "utf8");
113
+ if (!code || bytes > LIMITS.maxFileBytes) return null;
114
+ if (isProbablyMinified(code)) return null;
115
+
116
+ const key = String(filePath || "<unknown>");
117
+ const hash = sha1(code);
118
+
119
+ const existing = _AST_CACHE.get(key);
120
+ if (existing && existing.hash === hash && existing.ast) {
121
+ // LRU touch
122
+ _AST_CACHE.delete(key);
123
+ _AST_CACHE.set(key, existing);
124
+ return existing.ast;
125
+ }
126
+
127
+ const ast = parseCode(code, key);
128
+ if (!ast) return null;
129
+
130
+ const entry = {
131
+ ast,
132
+ hash,
133
+ bytes,
134
+ lines: countNewlines(code),
135
+ ts: Date.now(),
136
+ };
137
+
138
+ if (existing) _TOTAL_BYTES -= existing.bytes || 0;
139
+ _AST_CACHE.set(key, entry);
140
+ _TOTAL_BYTES += bytes;
141
+
142
+ _evictIfNeeded();
143
+ return ast;
144
+ }
145
+
146
+ function clearASTCache() {
147
+ _AST_CACHE.clear();
148
+ _TOTAL_BYTES = 0;
149
+ }
150
+
151
+ function getASTCacheStats() {
152
+ return {
153
+ entries: _AST_CACHE.size,
154
+ totalBytes: _TOTAL_BYTES,
155
+ limits: { ...LIMITS },
156
+ };
157
+ }
158
+
159
+ module.exports = {
160
+ getAST,
161
+ parseCode,
162
+ clearASTCache,
163
+ getASTCacheStats,
164
+ };
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Code Quality Engine
3
+ * Detects high cyclomatic complexity, long functions, deep nesting, and magic numbers.
4
+ */
5
+
6
+ const { getAST, parseCode } = require("./ast-cache");
7
+ const traverse = require("@babel/traverse").default;
8
+ const t = require("@babel/types");
9
+ const { shouldExcludeFile, isTestContext, hasIgnoreDirective } = require("./file-filter");
10
+
11
+ const MAX_COMPLEXITY = 30; // Increased from 18 - many legitimate functions have complexity 20-25
12
+ const MAX_FUNCTION_LENGTH = 200; // Increased from 120 - allow longer functions
13
+ const MAX_NESTING_DEPTH = 7; // Increased from 5 - many legitimate nested structures
14
+
15
+ function snippetForLine(lines, line) {
16
+ return lines[line - 1] ? lines[line - 1].trim() : "";
17
+ }
18
+
19
+ function isMagicNumberLiteral(node) {
20
+ if (!t.isNumericLiteral(node)) return false;
21
+ const v = node.value;
22
+
23
+ // commonly acceptable constants - expanded list
24
+ const allowed = new Set([
25
+ 0, 1, -1, 2, -2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
26
+ 20, 24, 30, 32, 60, 64, 100, 128, 256, 512, 1000, 1024, 2000, 3000,
27
+ 3600, 86400, // common time constants
28
+ ]);
29
+ if (allowed.has(v)) return false;
30
+
31
+ // Skip small integers (likely array indices, counts, etc.)
32
+ if (Math.abs(v) < 20 && Number.isInteger(v)) return false;
33
+
34
+ // Skip common HTTP status codes
35
+ if (v >= 200 && v <= 599 && Number.isInteger(v)) return false;
36
+
37
+ // Skip common port numbers
38
+ if (v >= 3000 && v <= 9999 && Number.isInteger(v)) return false;
39
+
40
+ if (!Number.isFinite(v)) return false;
41
+ return true;
42
+ }
43
+
44
+ function calculateCyclomaticComplexity(fnPath) {
45
+ let complexity = 1;
46
+
47
+ fnPath.traverse({
48
+ // don't count nested functions
49
+ Function(inner) {
50
+ if (inner !== fnPath) inner.skip();
51
+ },
52
+ IfStatement() {
53
+ complexity++;
54
+ },
55
+ ForStatement() {
56
+ complexity++;
57
+ },
58
+ ForInStatement() {
59
+ complexity++;
60
+ },
61
+ ForOfStatement() {
62
+ complexity++;
63
+ },
64
+ WhileStatement() {
65
+ complexity++;
66
+ },
67
+ DoWhileStatement() {
68
+ complexity++;
69
+ },
70
+ ConditionalExpression() {
71
+ complexity++;
72
+ },
73
+ LogicalExpression(p) {
74
+ if (p.node.operator === "&&" || p.node.operator === "||") complexity++;
75
+ },
76
+ SwitchCase() {
77
+ complexity++;
78
+ },
79
+ CatchClause() {
80
+ complexity++;
81
+ },
82
+ });
83
+
84
+ return complexity;
85
+ }
86
+
87
+ function measureMaxNesting(fnPath) {
88
+ let maxDepth = 0;
89
+
90
+ function walk(node, depth) {
91
+ if (!node) return;
92
+ maxDepth = Math.max(maxDepth, depth);
93
+
94
+ // stop nesting analysis at nested functions/classes
95
+ if (t.isFunction(node) || t.isArrowFunctionExpression(node) || t.isClass(node)) return;
96
+
97
+ const next = [];
98
+
99
+ if (t.isBlockStatement(node)) next.push(...node.body);
100
+ else if (t.isIfStatement(node)) {
101
+ next.push(node.consequent);
102
+ if (node.alternate) next.push(node.alternate);
103
+ } else if (
104
+ t.isForStatement(node) ||
105
+ t.isForInStatement(node) ||
106
+ t.isForOfStatement(node) ||
107
+ t.isWhileStatement(node) ||
108
+ t.isDoWhileStatement(node)
109
+ ) {
110
+ next.push(node.body);
111
+ } else if (t.isTryStatement(node)) {
112
+ next.push(node.block);
113
+ if (node.handler) next.push(node.handler.body);
114
+ if (node.finalizer) next.push(node.finalizer);
115
+ } else if (t.isSwitchStatement(node)) {
116
+ next.push(...node.cases);
117
+ } else if (t.isSwitchCase(node)) {
118
+ next.push(...node.consequent);
119
+ }
120
+
121
+ for (const n of next) walk(n, depth + 1);
122
+ }
123
+
124
+ const body = fnPath.node.body;
125
+ walk(body, 0);
126
+ return maxDepth;
127
+ }
128
+
129
+ function getFunctionName(fnPath) {
130
+ const n = fnPath.node;
131
+
132
+ if (t.isFunctionDeclaration(n) && n.id?.name) return n.id.name;
133
+
134
+ // const foo = () => {}
135
+ if (
136
+ (t.isFunctionExpression(n) || t.isArrowFunctionExpression(n)) &&
137
+ t.isVariableDeclarator(fnPath.parent) &&
138
+ t.isIdentifier(fnPath.parent.id)
139
+ ) {
140
+ return fnPath.parent.id.name;
141
+ }
142
+
143
+ if (t.isObjectMethod(n) && t.isIdentifier(n.key)) return n.key.name;
144
+ if (t.isClassMethod(n) && t.isIdentifier(n.key)) return n.key.name;
145
+
146
+ return "<anonymous>";
147
+ }
148
+
149
+ function analyzeCodeQuality(code, filePath) {
150
+ const findings = [];
151
+
152
+ if (shouldExcludeFile(filePath)) return findings;
153
+ if (isTestContext(code, filePath)) return findings;
154
+ if (hasIgnoreDirective(code, "code-quality")) return findings;
155
+
156
+ const ast = getAST(code, filePath);
157
+ if (!ast) return findings;
158
+
159
+ const lines = code.split("\n");
160
+ const seenMagic = new Set();
161
+
162
+ function analyzeFunction(fnPath) {
163
+ if (!fnPath.node.loc) return;
164
+
165
+ const name = getFunctionName(fnPath);
166
+
167
+ // length (lines)
168
+ const startLine = fnPath.node.loc.start.line;
169
+ const endLine = fnPath.node.loc.end.line;
170
+ const length = endLine - startLine + 1;
171
+
172
+ if (length > MAX_FUNCTION_LENGTH) {
173
+ findings.push({
174
+ type: "long_function",
175
+ severity: "WARN",
176
+ category: "CodeQuality",
177
+ file: filePath,
178
+ line: startLine,
179
+ column: fnPath.node.loc.start.column,
180
+ title: `Long function: ${name}`,
181
+ message: `Function is ${length} lines long. Consider extracting helpers.`,
182
+ codeSnippet: snippetForLine(lines, startLine),
183
+ confidence: "med",
184
+ });
185
+ }
186
+
187
+ // complexity
188
+ const complexity = calculateCyclomaticComplexity(fnPath);
189
+ if (complexity > MAX_COMPLEXITY) {
190
+ findings.push({
191
+ type: "high_complexity",
192
+ severity: "WARN",
193
+ category: "CodeQuality",
194
+ file: filePath,
195
+ line: startLine,
196
+ column: fnPath.node.loc.start.column,
197
+ title: `High complexity: ${name}`,
198
+ message: `Cyclomatic complexity is ${complexity} (limit ${MAX_COMPLEXITY}).`,
199
+ codeSnippet: snippetForLine(lines, startLine),
200
+ confidence: "med",
201
+ });
202
+ }
203
+
204
+ // nesting
205
+ const nesting = measureMaxNesting(fnPath);
206
+ if (nesting > MAX_NESTING_DEPTH) {
207
+ findings.push({
208
+ type: "deep_nesting",
209
+ severity: "WARN",
210
+ category: "CodeQuality",
211
+ file: filePath,
212
+ line: startLine,
213
+ column: fnPath.node.loc.start.column,
214
+ title: `Deep nesting: ${name}`,
215
+ message: `Max nesting depth is ${nesting} (limit ${MAX_NESTING_DEPTH}).`,
216
+ codeSnippet: snippetForLine(lines, startLine),
217
+ confidence: "low",
218
+ });
219
+ }
220
+ }
221
+
222
+ traverse(ast, {
223
+ FunctionDeclaration(path) {
224
+ analyzeFunction(path);
225
+ },
226
+ FunctionExpression(path) {
227
+ analyzeFunction(path);
228
+ },
229
+ ArrowFunctionExpression(path) {
230
+ analyzeFunction(path);
231
+ },
232
+
233
+ NumericLiteral(path) {
234
+ if (!path.node.loc) return;
235
+ if (!isMagicNumberLiteral(path.node)) return;
236
+
237
+ // reduce noise in obvious non-business contexts
238
+ if (path.parentPath.isUnaryExpression() && path.parent.operator === "-") return;
239
+ if (path.parentPath.isObjectProperty() && path.parent.key === path.node) return;
240
+ if (path.parentPath.isMemberExpression() && path.parent.computed && path.parent.property === path.node) return;
241
+ if (path.parentPath.isArrayExpression()) return; // array indices are fine
242
+ if (path.parentPath.isCallExpression()) return; // function arguments might be legitimate
243
+ if (path.parentPath.isBinaryExpression()) {
244
+ // Skip common math operations with small numbers
245
+ const op = path.parent.operator;
246
+ if (['+', '-', '*', '/', '%'].includes(op)) return;
247
+ }
248
+
249
+ // skip enums / TS literal types if present
250
+ if (path.findParent((p) => p.isTSEnumDeclaration?.() || p.isTSLiteralType?.())) return;
251
+
252
+ // Skip if inside a test context
253
+ if (path.findParent((p) => {
254
+ const fn = p.getFunctionParent?.();
255
+ if (!fn) return false;
256
+ const name = getFunctionName(fn);
257
+ return /test|spec|it|describe/i.test(name);
258
+ })) return;
259
+
260
+ const line = path.node.loc.start.line;
261
+ const col = path.node.loc.start.column;
262
+ const key = `${line}:${col}`;
263
+ if (seenMagic.has(key)) return;
264
+ seenMagic.add(key);
265
+
266
+ // Only flag larger numbers or decimals as magic numbers
267
+ const v = path.node.value;
268
+ if (Math.abs(v) < 100 && Number.isInteger(v)) return; // Skip small integers entirely
269
+
270
+ findings.push({
271
+ type: "magic_number",
272
+ severity: "INFO",
273
+ category: "CodeQuality",
274
+ file: filePath,
275
+ line,
276
+ column: col,
277
+ title: "Magic number",
278
+ message: `Numeric literal ${path.node.value} may be clearer as a named constant.`,
279
+ codeSnippet: snippetForLine(lines, line),
280
+ confidence: "low",
281
+ });
282
+ },
283
+ });
284
+
285
+ return findings;
286
+ }
287
+
288
+ module.exports = {
289
+ analyzeCodeQuality,
290
+ parseCode,
291
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Console Logs Engine
3
+ * Finds console.* usage that should not ship to production.
4
+ */
5
+
6
+ const { getAST, parseCode } = require("./ast-cache");
7
+ const traverse = require("@babel/traverse").default;
8
+ const t = require("@babel/types");
9
+ const { shouldExcludeFile, isTestContext, hasIgnoreDirective } = require("./file-filter");
10
+
11
+ function hasNoConsoleDisable(lines, lineIndex) {
12
+ const l = (lines[lineIndex] || "").toLowerCase();
13
+ const prev = (lines[lineIndex - 1] || "").toLowerCase();
14
+ return (
15
+ l.includes("eslint-disable-next-line no-console") ||
16
+ prev.includes("eslint-disable-next-line no-console") ||
17
+ l.includes("eslint-disable no-console") ||
18
+ prev.includes("eslint-disable no-console") ||
19
+ l.includes("vibecheck-allow-console") ||
20
+ prev.includes("vibecheck-allow-console")
21
+ );
22
+ }
23
+
24
+ function analyzeConsoleLogs(code, filePath) {
25
+ const findings = [];
26
+
27
+ if (shouldExcludeFile(filePath)) return findings;
28
+ if (isTestContext(code, filePath)) return findings;
29
+ if (hasIgnoreDirective(code, "console-logs")) return findings;
30
+
31
+ const ast = getAST(code, filePath);
32
+ if (!ast) return findings;
33
+
34
+ const lines = code.split("\n");
35
+
36
+ traverse(ast, {
37
+ CallExpression(path) {
38
+ const callee = path.node.callee;
39
+ if (!t.isMemberExpression(callee)) return;
40
+ if (!t.isIdentifier(callee.object, { name: "console" })) return;
41
+ if (!t.isIdentifier(callee.property)) return;
42
+
43
+ const method = callee.property.name;
44
+ const loc = path.node.loc?.start;
45
+ if (!loc) return;
46
+
47
+ if (hasNoConsoleDisable(lines, loc.line - 1)) return;
48
+
49
+ // classify severity
50
+ let severity = "WARN";
51
+ let confidence = "high";
52
+ if (method === "error" || method === "warn") {
53
+ severity = "INFO"; // these can be acceptable, but often should be routed to logger
54
+ confidence = "med";
55
+ } else if (method === "debug" || method === "trace") {
56
+ severity = "WARN";
57
+ confidence = "med";
58
+ } else if (method === "log") {
59
+ severity = "WARN";
60
+ }
61
+
62
+ findings.push({
63
+ type: "console_log",
64
+ severity,
65
+ category: "CodeQuality",
66
+ file: filePath,
67
+ line: loc.line,
68
+ column: loc.column,
69
+ title: `Console.${method} usage`,
70
+ message: `Consider removing console.${method} or replacing with structured logging.`,
71
+ codeSnippet: lines[loc.line - 1]?.trim(),
72
+ confidence,
73
+ });
74
+ },
75
+ });
76
+
77
+ return findings;
78
+ }
79
+
80
+ module.exports = {
81
+ analyzeConsoleLogs,
82
+ parseCode,
83
+ };