@vibecheckai/cli 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/bin/registry.js +389 -269
  2. package/bin/runners/cli-utils.js +2 -33
  3. package/bin/runners/context/generators/cursor.js +49 -2
  4. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +849 -0
  5. package/bin/runners/lib/analyzers.js +599 -142
  6. package/bin/runners/lib/audit-logger.js +532 -0
  7. package/bin/runners/lib/authority/authorities/architecture.js +364 -0
  8. package/bin/runners/lib/authority/authorities/compliance.js +341 -0
  9. package/bin/runners/lib/authority/authorities/human.js +343 -0
  10. package/bin/runners/lib/authority/authorities/quality.js +420 -0
  11. package/bin/runners/lib/authority/authorities/security.js +228 -0
  12. package/bin/runners/lib/authority/index.js +293 -0
  13. package/bin/runners/lib/authority-badge.js +425 -425
  14. package/bin/runners/lib/bundle/bundle-intelligence.js +846 -0
  15. package/bin/runners/lib/cli-charts.js +368 -0
  16. package/bin/runners/lib/cli-config-display.js +405 -0
  17. package/bin/runners/lib/cli-demo.js +275 -0
  18. package/bin/runners/lib/cli-errors.js +438 -0
  19. package/bin/runners/lib/cli-help-formatter.js +439 -0
  20. package/bin/runners/lib/cli-interactive-menu.js +509 -0
  21. package/bin/runners/lib/cli-prompts.js +441 -0
  22. package/bin/runners/lib/cli-scan-cards.js +362 -0
  23. package/bin/runners/lib/compliance-reporter.js +710 -0
  24. package/bin/runners/lib/conductor/index.js +671 -0
  25. package/bin/runners/lib/easy/README.md +123 -0
  26. package/bin/runners/lib/easy/index.js +140 -0
  27. package/bin/runners/lib/easy/interactive-wizard.js +788 -0
  28. package/bin/runners/lib/easy/one-click-firewall.js +564 -0
  29. package/bin/runners/lib/easy/zero-config-reality.js +714 -0
  30. package/bin/runners/lib/engines/accessibility-engine.js +218 -18
  31. package/bin/runners/lib/engines/api-consistency-engine.js +335 -30
  32. package/bin/runners/lib/engines/async-patterns-engine.js +444 -0
  33. package/bin/runners/lib/engines/bundle-size-engine.js +433 -0
  34. package/bin/runners/lib/engines/confidence-scoring.js +276 -0
  35. package/bin/runners/lib/engines/context-detection.js +264 -0
  36. package/bin/runners/lib/engines/cross-file-analysis-engine.js +292 -27
  37. package/bin/runners/lib/engines/database-patterns-engine.js +429 -0
  38. package/bin/runners/lib/engines/duplicate-code-engine.js +354 -0
  39. package/bin/runners/lib/engines/empty-catch-engine.js +127 -17
  40. package/bin/runners/lib/engines/env-variables-engine.js +458 -0
  41. package/bin/runners/lib/engines/error-handling-engine.js +437 -0
  42. package/bin/runners/lib/engines/false-positive-prevention.js +630 -0
  43. package/bin/runners/lib/engines/framework-adapters/index.js +607 -0
  44. package/bin/runners/lib/engines/framework-detection.js +508 -0
  45. package/bin/runners/lib/engines/import-order-engine.js +429 -0
  46. package/bin/runners/lib/engines/mock-data-engine.js +53 -10
  47. package/bin/runners/lib/engines/naming-conventions-engine.js +544 -0
  48. package/bin/runners/lib/engines/noise-reduction-engine.js +452 -0
  49. package/bin/runners/lib/engines/orchestrator.js +334 -0
  50. package/bin/runners/lib/engines/performance-issues-engine.js +176 -36
  51. package/bin/runners/lib/engines/react-patterns-engine.js +457 -0
  52. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +382 -54
  53. package/bin/runners/lib/engines/type-aware-engine.js +263 -39
  54. package/bin/runners/lib/engines/vibecheck-engines/index.js +122 -13
  55. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +806 -0
  56. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +373 -73
  57. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +577 -0
  58. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +543 -0
  59. package/bin/runners/lib/engines/vibecheck-engines.js +514 -0
  60. package/bin/runners/lib/enhanced-features/index.js +305 -0
  61. package/bin/runners/lib/enhanced-output.js +631 -0
  62. package/bin/runners/lib/enterprise.js +300 -0
  63. package/bin/runners/lib/entitlements-v2.js +161 -478
  64. package/bin/runners/lib/firewall/command-validator.js +351 -0
  65. package/bin/runners/lib/firewall/config.js +341 -0
  66. package/bin/runners/lib/firewall/content-validator.js +519 -0
  67. package/bin/runners/lib/firewall/index.js +101 -0
  68. package/bin/runners/lib/firewall/path-validator.js +256 -0
  69. package/bin/runners/lib/html-proof-report.js +350 -700
  70. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +817 -0
  71. package/bin/runners/lib/mcp-utils.js +425 -0
  72. package/bin/runners/lib/missions/plan.js +46 -6
  73. package/bin/runners/lib/missions/templates.js +232 -0
  74. package/bin/runners/lib/output/index.js +1022 -0
  75. package/bin/runners/lib/policy-engine.js +652 -0
  76. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +333 -0
  77. package/bin/runners/lib/polish/autofix/async-handlers.js +273 -0
  78. package/bin/runners/lib/polish/autofix/dead-code.js +280 -0
  79. package/bin/runners/lib/polish/autofix/imports-optimizer.js +344 -0
  80. package/bin/runners/lib/polish/autofix/index.js +200 -0
  81. package/bin/runners/lib/polish/autofix/remove-consoles.js +209 -0
  82. package/bin/runners/lib/polish/autofix/strengthen-types.js +245 -0
  83. package/bin/runners/lib/polish/backend-checks.js +148 -0
  84. package/bin/runners/lib/polish/documentation-checks.js +111 -0
  85. package/bin/runners/lib/polish/frontend-checks.js +168 -0
  86. package/bin/runners/lib/polish/index.js +71 -0
  87. package/bin/runners/lib/polish/infrastructure-checks.js +131 -0
  88. package/bin/runners/lib/polish/library-detection.js +175 -0
  89. package/bin/runners/lib/polish/performance-checks.js +100 -0
  90. package/bin/runners/lib/polish/security-checks.js +148 -0
  91. package/bin/runners/lib/polish/utils.js +203 -0
  92. package/bin/runners/lib/prompt-builder.js +540 -0
  93. package/bin/runners/lib/proof-certificate.js +634 -0
  94. package/bin/runners/lib/reality/accessibility-audit.js +946 -0
  95. package/bin/runners/lib/reality/api-contract-validator.js +1012 -0
  96. package/bin/runners/lib/reality/chaos-engineering.js +1084 -0
  97. package/bin/runners/lib/reality/performance-tracker.js +1077 -0
  98. package/bin/runners/lib/reality/scenario-generator.js +1404 -0
  99. package/bin/runners/lib/reality/visual-regression.js +852 -0
  100. package/bin/runners/lib/reality-profiler.js +717 -0
  101. package/bin/runners/lib/replay/flight-recorder-viewer.js +1160 -0
  102. package/bin/runners/lib/review/ai-code-review.js +832 -0
  103. package/bin/runners/lib/rules/custom-rule-engine.js +985 -0
  104. package/bin/runners/lib/sbom-generator.js +641 -0
  105. package/bin/runners/lib/scan-output-enhanced.js +512 -0
  106. package/bin/runners/lib/scan-output.js +65 -19
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/ship-output.js +18 -25
  109. package/bin/runners/lib/terminal-ui.js +113 -1
  110. package/bin/runners/lib/unified-cli-output.js +603 -430
  111. package/bin/runners/lib/upsell.js +90 -338
  112. package/bin/runners/lib/validators/contract-validator.js +283 -0
  113. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  114. package/bin/runners/lib/validators/dep-audit.js +245 -0
  115. package/bin/runners/lib/validators/env-validator.js +319 -0
  116. package/bin/runners/lib/validators/index.js +120 -0
  117. package/bin/runners/lib/validators/license-checker.js +252 -0
  118. package/bin/runners/lib/validators/route-validator.js +290 -0
  119. package/bin/runners/runAIAgent.js +5 -10
  120. package/bin/runners/runAgent.js +3 -0
  121. package/bin/runners/runApprove.js +1233 -1200
  122. package/bin/runners/runAuth.js +22 -1
  123. package/bin/runners/runAuthority.js +528 -0
  124. package/bin/runners/runCheckpoint.js +4 -24
  125. package/bin/runners/runClassify.js +862 -859
  126. package/bin/runners/runConductor.js +772 -0
  127. package/bin/runners/runContainer.js +366 -0
  128. package/bin/runners/runContext.js +3 -0
  129. package/bin/runners/runDoctor.js +28 -41
  130. package/bin/runners/runEasy.js +410 -0
  131. package/bin/runners/runFirewall.js +3 -0
  132. package/bin/runners/runFirewallHook.js +3 -0
  133. package/bin/runners/runFix.js +76 -66
  134. package/bin/runners/runGuard.js +411 -18
  135. package/bin/runners/runIaC.js +372 -0
  136. package/bin/runners/runInit.js +10 -60
  137. package/bin/runners/runMcp.js +11 -12
  138. package/bin/runners/runPolish.js +240 -64
  139. package/bin/runners/runPromptFirewall.js +5 -12
  140. package/bin/runners/runProve.js +20 -55
  141. package/bin/runners/runReality.js +68 -59
  142. package/bin/runners/runReport.js +31 -5
  143. package/bin/runners/runRuntime.js +5 -8
  144. package/bin/runners/runScan.js +194 -1273
  145. package/bin/runners/runShip.js +695 -47
  146. package/bin/runners/runTruth.js +3 -0
  147. package/bin/runners/runValidate.js +7 -11
  148. package/bin/runners/runVibe.js +791 -0
  149. package/bin/runners/runWatch.js +14 -23
  150. package/bin/vibecheck.js +179 -65
  151. package/mcp-server/index.js +202 -636
  152. package/mcp-server/lib/api-client.cjs +7 -299
  153. package/mcp-server/package.json +1 -1
  154. package/mcp-server/tier-auth.js +175 -574
  155. package/mcp-server/tools-v3.js +800 -505
  156. package/mcp-server/tools.js +495 -0
  157. package/package.json +1 -1
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  166. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  167. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  168. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  169. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  170. package/mcp-server/index-v1.js +0 -698
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Duplicate Code Detection Engine
3
+ * Detects:
4
+ * - Exact duplicate code blocks
5
+ * - Similar code patterns (near-duplicates)
6
+ * - Copy-paste code between files
7
+ * - Repeated utility functions
8
+ */
9
+
10
+ const { getAST } = require("./ast-cache");
11
+ const traverse = require("@babel/traverse").default;
12
+ const t = require("@babel/types");
13
+ const crypto = require("crypto");
14
+
15
+ /**
16
+ * Minimum lines for a duplicate to be reported
17
+ */
18
+ const MIN_DUPLICATE_LINES = 5;
19
+
20
+ /**
21
+ * Minimum tokens for a duplicate to be considered significant
22
+ */
23
+ const MIN_DUPLICATE_TOKENS = 20;
24
+
25
+ /**
26
+ * Similarity threshold for near-duplicates (0-1)
27
+ */
28
+ const SIMILARITY_THRESHOLD = 0.8;
29
+
30
+ /**
31
+ * Normalize code by removing variable names and literals
32
+ */
33
+ function normalizeCode(code) {
34
+ // Remove comments
35
+ code = code.replace(/\/\/.*$/gm, "");
36
+ code = code.replace(/\/\*[\s\S]*?\*\//g, "");
37
+
38
+ // Normalize whitespace
39
+ code = code.replace(/\s+/g, " ").trim();
40
+
41
+ // Remove string contents (keep quotes)
42
+ code = code.replace(/"[^"]*"/g, '""');
43
+ code = code.replace(/'[^']*'/g, "''");
44
+ code = code.replace(/`[^`]*`/g, "``");
45
+
46
+ // Normalize numbers
47
+ code = code.replace(/\b\d+\.?\d*\b/g, "0");
48
+
49
+ return code;
50
+ }
51
+
52
+ /**
53
+ * Tokenize code for comparison
54
+ */
55
+ function tokenize(code) {
56
+ const normalized = normalizeCode(code);
57
+
58
+ // Split into tokens
59
+ const tokens = normalized.split(/(\s+|[{}()\[\];,.])/g)
60
+ .filter(t => t.trim().length > 0);
61
+
62
+ return tokens;
63
+ }
64
+
65
+ /**
66
+ * Calculate hash of normalized code block
67
+ */
68
+ function hashCodeBlock(code) {
69
+ const normalized = normalizeCode(code);
70
+ return crypto.createHash("md5").update(normalized).digest("hex");
71
+ }
72
+
73
+ /**
74
+ * Calculate similarity between two token arrays using Jaccard similarity
75
+ */
76
+ function calculateSimilarity(tokens1, tokens2) {
77
+ if (tokens1.length === 0 || tokens2.length === 0) return 0;
78
+
79
+ const set1 = new Set(tokens1);
80
+ const set2 = new Set(tokens2);
81
+
82
+ let intersection = 0;
83
+ for (const token of set1) {
84
+ if (set2.has(token)) intersection++;
85
+ }
86
+
87
+ const union = set1.size + set2.size - intersection;
88
+ return intersection / union;
89
+ }
90
+
91
+ /**
92
+ * Extract significant code blocks from AST
93
+ */
94
+ function extractCodeBlocks(code, filePath) {
95
+ const ast = getAST(code, filePath);
96
+ if (!ast) return [];
97
+
98
+ const blocks = [];
99
+ const lines = code.split("\n");
100
+
101
+ traverse(ast, {
102
+ // Function declarations and expressions
103
+ FunctionDeclaration(path) {
104
+ extractBlock(path, blocks, code, lines, "function");
105
+ },
106
+ FunctionExpression(path) {
107
+ // Only capture if it's a standalone function (not inline callback)
108
+ if (path.parentPath.isVariableDeclarator()) {
109
+ extractBlock(path, blocks, code, lines, "function");
110
+ }
111
+ },
112
+ ArrowFunctionExpression(path) {
113
+ // Only capture if it's a standalone function
114
+ if (path.parentPath.isVariableDeclarator()) {
115
+ const body = path.node.body;
116
+ // Only capture functions with block body (not one-liners)
117
+ if (t.isBlockStatement(body)) {
118
+ extractBlock(path, blocks, code, lines, "arrow");
119
+ }
120
+ }
121
+ },
122
+
123
+ // Class methods
124
+ ClassMethod(path) {
125
+ extractBlock(path, blocks, code, lines, "method");
126
+ },
127
+
128
+ // If statements with significant bodies
129
+ IfStatement(path) {
130
+ const bodyLines = getBlockLineCount(path.node);
131
+ if (bodyLines >= MIN_DUPLICATE_LINES) {
132
+ extractBlock(path, blocks, code, lines, "if-block");
133
+ }
134
+ },
135
+
136
+ // Switch statements
137
+ SwitchStatement(path) {
138
+ const bodyLines = getBlockLineCount(path.node);
139
+ if (bodyLines >= MIN_DUPLICATE_LINES) {
140
+ extractBlock(path, blocks, code, lines, "switch");
141
+ }
142
+ },
143
+ });
144
+
145
+ return blocks;
146
+ }
147
+
148
+ /**
149
+ * Get line count of a block
150
+ */
151
+ function getBlockLineCount(node) {
152
+ if (!node.loc) return 0;
153
+ return node.loc.end.line - node.loc.start.line + 1;
154
+ }
155
+
156
+ /**
157
+ * Extract a code block with metadata
158
+ */
159
+ function extractBlock(path, blocks, code, lines, type) {
160
+ const node = path.node;
161
+ if (!node.loc) return;
162
+
163
+ const startLine = node.loc.start.line;
164
+ const endLine = node.loc.end.line;
165
+ const lineCount = endLine - startLine + 1;
166
+
167
+ // Skip small blocks
168
+ if (lineCount < MIN_DUPLICATE_LINES) return;
169
+
170
+ const blockCode = code.substring(node.start, node.end);
171
+ const tokens = tokenize(blockCode);
172
+
173
+ // Skip blocks with too few tokens
174
+ if (tokens.length < MIN_DUPLICATE_TOKENS) return;
175
+
176
+ // Get function/method name if available
177
+ let name = "<anonymous>";
178
+ if (node.id?.name) {
179
+ name = node.id.name;
180
+ } else if (node.key?.name) {
181
+ name = node.key.name;
182
+ } else if (path.parentPath?.isVariableDeclarator()) {
183
+ const declId = path.parentPath.node.id;
184
+ if (t.isIdentifier(declId)) {
185
+ name = declId.name;
186
+ }
187
+ }
188
+
189
+ blocks.push({
190
+ type,
191
+ name,
192
+ startLine,
193
+ endLine,
194
+ lineCount,
195
+ code: blockCode,
196
+ tokens,
197
+ hash: hashCodeBlock(blockCode),
198
+ snippet: lines[startLine - 1]?.trim() || "",
199
+ });
200
+ }
201
+
202
+ /**
203
+ * Find duplicates within a single file
204
+ */
205
+ function findDuplicatesInFile(code, filePath) {
206
+ const findings = [];
207
+ const blocks = extractCodeBlocks(code, filePath);
208
+
209
+ // Group by hash for exact duplicates
210
+ const hashGroups = new Map();
211
+ for (const block of blocks) {
212
+ if (!hashGroups.has(block.hash)) {
213
+ hashGroups.set(block.hash, []);
214
+ }
215
+ hashGroups.get(block.hash).push(block);
216
+ }
217
+
218
+ // Report exact duplicates
219
+ for (const [hash, group] of hashGroups) {
220
+ if (group.length > 1) {
221
+ const first = group[0];
222
+ const locations = group.map(b => `line ${b.startLine}`).join(", ");
223
+
224
+ findings.push({
225
+ type: "exact_duplicate",
226
+ severity: "WARN",
227
+ category: "CodeQuality",
228
+ file: filePath,
229
+ line: first.startLine,
230
+ column: 0,
231
+ title: `Duplicate code block (${first.lineCount} lines, ${group.length} occurrences)`,
232
+ message: `Identical code found at: ${locations}. Consider extracting to a shared function.`,
233
+ codeSnippet: first.snippet,
234
+ confidence: "high",
235
+ fixHint: "Extract duplicate code into a reusable function",
236
+ });
237
+ }
238
+ }
239
+
240
+ // Find near-duplicates (similar but not identical)
241
+ const reported = new Set();
242
+ for (let i = 0; i < blocks.length; i++) {
243
+ for (let j = i + 1; j < blocks.length; j++) {
244
+ const block1 = blocks[i];
245
+ const block2 = blocks[j];
246
+
247
+ // Skip if already matched as exact duplicate
248
+ if (block1.hash === block2.hash) continue;
249
+
250
+ // Skip if already reported
251
+ const pairKey = `${block1.startLine}-${block2.startLine}`;
252
+ if (reported.has(pairKey)) continue;
253
+
254
+ const similarity = calculateSimilarity(block1.tokens, block2.tokens);
255
+
256
+ if (similarity >= SIMILARITY_THRESHOLD) {
257
+ reported.add(pairKey);
258
+
259
+ findings.push({
260
+ type: "similar_code",
261
+ severity: "INFO",
262
+ category: "CodeQuality",
263
+ file: filePath,
264
+ line: block1.startLine,
265
+ column: 0,
266
+ title: `Similar code blocks (${Math.round(similarity * 100)}% similar)`,
267
+ message: `'${block1.name}' (line ${block1.startLine}) and '${block2.name}' (line ${block2.startLine}) have similar structure.`,
268
+ codeSnippet: block1.snippet,
269
+ confidence: "med",
270
+ fixHint: "Consider refactoring to share common logic",
271
+ });
272
+ }
273
+ }
274
+ }
275
+
276
+ return findings;
277
+ }
278
+
279
+ /**
280
+ * Find duplicates across multiple files
281
+ */
282
+ function findDuplicatesAcrossFiles(files, repoRoot) {
283
+ const fs = require("fs");
284
+ const path = require("path");
285
+ const findings = [];
286
+ const allBlocks = new Map(); // hash -> [{ file, block }]
287
+
288
+ // Collect blocks from all files
289
+ for (const fileAbs of files) {
290
+ try {
291
+ const code = fs.readFileSync(fileAbs, "utf8");
292
+ const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
293
+ const blocks = extractCodeBlocks(code, fileRel);
294
+
295
+ for (const block of blocks) {
296
+ if (!allBlocks.has(block.hash)) {
297
+ allBlocks.set(block.hash, []);
298
+ }
299
+ allBlocks.get(block.hash).push({ file: fileRel, block });
300
+ }
301
+ } catch (e) {
302
+ // Skip files that can't be read
303
+ continue;
304
+ }
305
+ }
306
+
307
+ // Find cross-file duplicates
308
+ for (const [hash, occurrences] of allBlocks) {
309
+ // Get unique files
310
+ const uniqueFiles = new Set(occurrences.map(o => o.file));
311
+
312
+ if (uniqueFiles.size > 1) {
313
+ const first = occurrences[0];
314
+ const fileList = Array.from(uniqueFiles).slice(0, 5).join(", ");
315
+ const moreFiles = uniqueFiles.size > 5 ? ` and ${uniqueFiles.size - 5} more` : "";
316
+
317
+ findings.push({
318
+ type: "cross_file_duplicate",
319
+ severity: "WARN",
320
+ category: "CodeQuality",
321
+ file: first.file,
322
+ line: first.block.startLine,
323
+ column: 0,
324
+ title: `Duplicate code across ${uniqueFiles.size} files`,
325
+ message: `Same code block (${first.block.lineCount} lines) found in: ${fileList}${moreFiles}`,
326
+ codeSnippet: first.block.snippet,
327
+ confidence: "high",
328
+ fixHint: "Extract to a shared utility module",
329
+ });
330
+ }
331
+ }
332
+
333
+ return findings;
334
+ }
335
+
336
+ /**
337
+ * Analyze duplicate code in a single file
338
+ */
339
+ function analyzeDuplicateCode(code, filePath) {
340
+ return findDuplicatesInFile(code, filePath);
341
+ }
342
+
343
+ module.exports = {
344
+ analyzeDuplicateCode,
345
+ findDuplicatesInFile,
346
+ findDuplicatesAcrossFiles,
347
+ extractCodeBlocks,
348
+ calculateSimilarity,
349
+ normalizeCode,
350
+ tokenize,
351
+ hashCodeBlock,
352
+ MIN_DUPLICATE_LINES,
353
+ SIMILARITY_THRESHOLD,
354
+ };
@@ -1,12 +1,95 @@
1
1
  /**
2
2
  * Empty Catch Block Detection Engine
3
3
  * Uses AST analysis to detect empty or effectively empty catch blocks
4
+ * Enhanced with:
5
+ * - Intentional suppression comment detection
6
+ * - Structured logging detection (winston, pino, bunyan)
7
+ * - Error reporting service detection (Sentry, Bugsnag, Rollbar)
8
+ * - Recovery/cleanup pattern detection
4
9
  */
5
10
 
6
11
  const { getAST, parseCode } = require("./ast-cache");
7
12
  const traverse = require("@babel/traverse").default;
8
13
  const t = require("@babel/types");
9
14
 
15
+ /**
16
+ * Comments that indicate intentional error suppression
17
+ */
18
+ const INTENTIONAL_SUPPRESSION_PATTERNS = [
19
+ /intentionally?\s*(?:ignored?|suppress(?:ed)?|silent|swallow(?:ed)?)/i,
20
+ /error\s*(?:is\s*)?(?:expected|ok|fine|acceptable)/i,
21
+ /safe\s*to\s*ignore/i,
22
+ /this\s*(?:error|exception)\s*(?:can|is)\s*(?:safely\s*)?(?:be\s*)?ignored/i,
23
+ /ignore\s*(?:this\s*)?(?:error|exception)/i,
24
+ /suppress(?:ed)?\s*(?:error|exception)/i,
25
+ /best\s*effort/i,
26
+ /fallback/i,
27
+ /graceful(?:ly)?/i,
28
+ /optional/i,
29
+ /non-critical/i,
30
+ /not\s*(?:a\s*)?(?:critical|fatal)/i,
31
+ ];
32
+
33
+ /**
34
+ * Structured logging calls (not console)
35
+ */
36
+ const STRUCTURED_LOGGER_PATTERNS = [
37
+ // Logger method calls
38
+ /^logger\./i,
39
+ /^log\./i,
40
+ /^winston\./i,
41
+ /^pino\./i,
42
+ /^bunyan\./i,
43
+ /^console\.(error|warn)/i,
44
+ // Error tracking services
45
+ /Sentry\.capture/i,
46
+ /Bugsnag\.notify/i,
47
+ /Rollbar\.error/i,
48
+ /trackError/i,
49
+ /reportError/i,
50
+ /captureException/i,
51
+ /logError/i,
52
+ ];
53
+
54
+ /**
55
+ * Check if code contains intentional suppression comment
56
+ */
57
+ function hasIntentionalSuppressionComment(code, startLine, endLine, lines) {
58
+ // Check comments in and around the catch block
59
+ for (let i = Math.max(0, startLine - 2); i <= Math.min(lines.length - 1, endLine + 1); i++) {
60
+ const line = lines[i];
61
+ // Check for intentional suppression patterns in comments
62
+ if (INTENTIONAL_SUPPRESSION_PATTERNS.some(pattern => pattern.test(line))) {
63
+ return true;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * Check if expression is a structured logging call
71
+ */
72
+ function isStructuredLoggingCall(expression, code) {
73
+ if (!t.isCallExpression(expression)) return false;
74
+
75
+ const callee = expression.callee;
76
+ let calleeStr = "";
77
+
78
+ // Build the callee string
79
+ if (t.isMemberExpression(callee)) {
80
+ if (t.isIdentifier(callee.object)) {
81
+ calleeStr = callee.object.name + ".";
82
+ }
83
+ if (t.isIdentifier(callee.property)) {
84
+ calleeStr += callee.property.name;
85
+ }
86
+ } else if (t.isIdentifier(callee)) {
87
+ calleeStr = callee.name;
88
+ }
89
+
90
+ return STRUCTURED_LOGGER_PATTERNS.some(pattern => pattern.test(calleeStr));
91
+ }
92
+
10
93
  /**
11
94
  * Check if a catch block is effectively empty
12
95
  */
@@ -83,59 +166,86 @@ function analyzeEmptyCatch(code, filePath) {
83
166
 
84
167
  if (!body) return;
85
168
 
169
+ const startLine = catchNode.loc.start.line;
170
+ const endLine = catchNode.loc.end.line;
171
+
172
+ // Check for intentional suppression comments first
173
+ if (hasIntentionalSuppressionComment(code, startLine - 1, endLine - 1, lines)) {
174
+ // Has intentional suppression comment - skip or low severity
175
+ return;
176
+ }
177
+
86
178
  // Check if completely empty
87
179
  if (isEmptyCatchBlock(body)) {
88
- const line = catchNode.loc.start.line;
89
180
  findings.push({
90
181
  type: "empty_catch",
91
182
  severity: "WARN",
92
183
  category: "EmptyCatch",
93
184
  file: filePath,
94
- line,
185
+ line: startLine,
95
186
  column: catchNode.loc.start.column,
96
187
  title: "Empty catch block",
97
188
  message: "Catch block contains no error handling code",
98
- codeSnippet: lines[line - 1]?.trim(),
189
+ codeSnippet: lines[startLine - 1]?.trim(),
99
190
  confidence: "high",
191
+ fixHint: "Add error handling, logging, or a comment explaining why the error is intentionally ignored",
100
192
  });
101
193
  return;
102
194
  }
103
195
 
104
- // Check if only has comments - but allow console.error/warn and return/throw
196
+ // Check if only has comments - but allow console.error/warn, structured logging, return/throw
105
197
  const statements = body.body || [];
106
198
  const hasRealHandling = statements.some(stmt => {
107
- // Allow return statements
199
+ // Allow return statements (with or without value)
108
200
  if (t.isReturnStatement(stmt)) return true;
109
- // Allow throw statements
201
+
202
+ // Allow throw statements (re-throwing or wrapping)
110
203
  if (t.isThrowStatement(stmt)) return true;
111
- // Allow console.error/warn (logging is acceptable error handling)
204
+
205
+ // Allow structured logging and error reporting
112
206
  if (t.isExpressionStatement(stmt) && t.isCallExpression(stmt.expression)) {
113
- const callee = stmt.expression.callee;
114
- if (t.isMemberExpression(callee) &&
115
- t.isIdentifier(callee.object, { name: "console" }) &&
116
- t.isIdentifier(callee.property)) {
117
- if (["error", "warn"].includes(callee.property.name)) {
118
- return true; // This is acceptable error handling
119
- }
207
+ if (isStructuredLoggingCall(stmt.expression, code)) {
208
+ return true;
120
209
  }
121
210
  }
211
+
212
+ // Allow await expressions (might be async error handling)
213
+ if (t.isExpressionStatement(stmt) && t.isAwaitExpression(stmt.expression)) {
214
+ return true;
215
+ }
216
+
217
+ // Allow variable assignments (might be setting error state)
218
+ if (t.isExpressionStatement(stmt) && t.isAssignmentExpression(stmt.expression)) {
219
+ return true;
220
+ }
221
+
222
+ // Allow variable declarations (const error = ...)
223
+ if (t.isVariableDeclaration(stmt)) {
224
+ return true;
225
+ }
226
+
227
+ // Allow if statements (conditional error handling)
228
+ if (t.isIfStatement(stmt)) {
229
+ return true;
230
+ }
231
+
122
232
  return false;
123
233
  });
124
234
 
125
235
  // Only flag if it truly has no handling
126
236
  if (!hasRealHandling && onlyHasComments(body, code)) {
127
- const line = catchNode.loc.start.line;
128
237
  findings.push({
129
238
  type: "comment_only_catch",
130
239
  severity: "WARN",
131
240
  category: "EmptyCatch",
132
241
  file: filePath,
133
- line,
242
+ line: startLine,
134
243
  column: catchNode.loc.start.column,
135
244
  title: "Catch block with only comments",
136
245
  message: "Catch block contains only comments, no actual error handling",
137
- codeSnippet: lines[line - 1]?.trim(),
246
+ codeSnippet: lines[startLine - 1]?.trim(),
138
247
  confidence: "med",
248
+ fixHint: "Add proper error handling or use a recognized suppression comment pattern",
139
249
  });
140
250
  }
141
251
  },