@vibecheckai/cli 3.4.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 (166) hide show
  1. package/bin/registry.js +243 -152
  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 +544 -19
  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 +103 -11
  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 +47 -0
  107. package/bin/runners/lib/security/owasp-scanner.js +939 -0
  108. package/bin/runners/lib/terminal-ui.js +113 -1
  109. package/bin/runners/lib/unified-cli-output.js +603 -430
  110. package/bin/runners/lib/validators/contract-validator.js +283 -0
  111. package/bin/runners/lib/validators/dead-export-detector.js +279 -0
  112. package/bin/runners/lib/validators/dep-audit.js +245 -0
  113. package/bin/runners/lib/validators/env-validator.js +319 -0
  114. package/bin/runners/lib/validators/index.js +120 -0
  115. package/bin/runners/lib/validators/license-checker.js +252 -0
  116. package/bin/runners/lib/validators/route-validator.js +290 -0
  117. package/bin/runners/runAIAgent.js +5 -10
  118. package/bin/runners/runAgent.js +3 -0
  119. package/bin/runners/runApprove.js +1233 -1200
  120. package/bin/runners/runAuth.js +22 -1
  121. package/bin/runners/runAuthority.js +528 -0
  122. package/bin/runners/runCheckpoint.js +4 -24
  123. package/bin/runners/runClassify.js +862 -859
  124. package/bin/runners/runConductor.js +772 -0
  125. package/bin/runners/runContainer.js +366 -0
  126. package/bin/runners/runContext.js +3 -0
  127. package/bin/runners/runDoctor.js +28 -41
  128. package/bin/runners/runEasy.js +410 -0
  129. package/bin/runners/runFirewall.js +3 -0
  130. package/bin/runners/runFirewallHook.js +3 -0
  131. package/bin/runners/runFix.js +76 -66
  132. package/bin/runners/runGuard.js +411 -18
  133. package/bin/runners/runIaC.js +372 -0
  134. package/bin/runners/runInit.js +10 -60
  135. package/bin/runners/runMcp.js +11 -12
  136. package/bin/runners/runPolish.js +240 -64
  137. package/bin/runners/runPromptFirewall.js +5 -12
  138. package/bin/runners/runProve.js +20 -55
  139. package/bin/runners/runReality.js +68 -59
  140. package/bin/runners/runReport.js +31 -5
  141. package/bin/runners/runRuntime.js +5 -8
  142. package/bin/runners/runScan.js +194 -1286
  143. package/bin/runners/runShip.js +695 -47
  144. package/bin/runners/runTruth.js +3 -0
  145. package/bin/runners/runValidate.js +7 -11
  146. package/bin/runners/runVibe.js +791 -0
  147. package/bin/runners/runWatch.js +14 -23
  148. package/bin/vibecheck.js +175 -56
  149. package/mcp-server/index.js +190 -14
  150. package/mcp-server/package.json +1 -1
  151. package/mcp-server/tools-v3.js +397 -64
  152. package/mcp-server/tools.js +495 -0
  153. package/package.json +1 -1
  154. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  155. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  156. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  157. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  158. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  159. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  160. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  161. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  162. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  163. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  164. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  165. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  166. package/mcp-server/index-v1.js +0 -698
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Error Handling Engine
3
+ * Detects:
4
+ * - Missing error handling
5
+ * - Swallowed errors (empty catch)
6
+ * - Generic error messages
7
+ * - Error re-throwing without context
8
+ * - Missing error boundaries (React)
9
+ * - Inconsistent error responses (API)
10
+ * - Missing validation error handling
11
+ * - Untyped error catches
12
+ */
13
+
14
+ const { getAST } = require("./ast-cache");
15
+ const traverse = require("@babel/traverse").default;
16
+ const t = require("@babel/types");
17
+ const { shouldExcludeFile, isTestContext, hasIgnoreDirective } = require("./file-filter");
18
+
19
+ function snippetForLine(lines, line) {
20
+ return lines[line - 1] ? lines[line - 1].trim() : "";
21
+ }
22
+
23
+ /**
24
+ * Good error handling patterns
25
+ */
26
+ const GOOD_ERROR_PATTERNS = {
27
+ logging: [
28
+ /console\.error/,
29
+ /logger\.error/,
30
+ /log\.error/,
31
+ /Sentry\.captureException/,
32
+ /captureException/,
33
+ /reportError/,
34
+ /trackError/,
35
+ /Bugsnag/,
36
+ /Rollbar/,
37
+ ],
38
+ rethrowing: [
39
+ /throw\s+/,
40
+ /throw\s+new\s+/,
41
+ ],
42
+ returning: [
43
+ /return\s+.*error/i,
44
+ /return\s+.*Error/,
45
+ /return\s+{.*error/i,
46
+ ],
47
+ handling: [
48
+ /if\s*\(/,
49
+ /switch\s*\(/,
50
+ /instanceof/,
51
+ ],
52
+ };
53
+
54
+ /**
55
+ * Generic error messages that should be more specific
56
+ */
57
+ const GENERIC_ERROR_MESSAGES = [
58
+ /['"]something went wrong['"]/i,
59
+ /['"]an error occurred['"]/i,
60
+ /['"]error['"]\s*$/i,
61
+ /['"]unknown error['"]/i,
62
+ /['"]oops['"]/i,
63
+ /['"]failed['"]\s*$/i,
64
+ ];
65
+
66
+ /**
67
+ * Check if catch block has meaningful handling
68
+ */
69
+ function hasMeaningfulHandling(catchBlock, code) {
70
+ if (!catchBlock || !t.isBlockStatement(catchBlock)) return false;
71
+
72
+ // Empty catch block
73
+ if (catchBlock.body.length === 0) return false;
74
+
75
+ const blockCode = code.substring(catchBlock.start, catchBlock.end);
76
+
77
+ // Check for good patterns
78
+ for (const patterns of Object.values(GOOD_ERROR_PATTERNS)) {
79
+ if (patterns.some(p => p.test(blockCode))) {
80
+ return true;
81
+ }
82
+ }
83
+
84
+ // Check if has any statements (not just comments)
85
+ const hasStatements = catchBlock.body.some(stmt => !t.isEmptyStatement(stmt));
86
+
87
+ return hasStatements;
88
+ }
89
+
90
+ /**
91
+ * Check if error message is generic
92
+ */
93
+ function isGenericErrorMessage(message) {
94
+ return GENERIC_ERROR_MESSAGES.some(p => p.test(message));
95
+ }
96
+
97
+ /**
98
+ * Analyze error handling patterns
99
+ */
100
+ function analyzeErrorHandling(code, filePath) {
101
+ const findings = [];
102
+
103
+ if (shouldExcludeFile(filePath)) return findings;
104
+ if (isTestContext(code, filePath)) return findings;
105
+ if (hasIgnoreDirective(code, "error-handling")) return findings;
106
+
107
+ const ast = getAST(code, filePath);
108
+ if (!ast) return findings;
109
+
110
+ const lines = code.split("\n");
111
+ const normalizedPath = filePath.replace(/\\/g, "/");
112
+ const isAPIRoute = normalizedPath.includes("/api/") || normalizedPath.includes("/routes/");
113
+
114
+ // Track error response patterns
115
+ const errorResponsePatterns = new Set();
116
+
117
+ traverse(ast, {
118
+ // Analyze catch blocks
119
+ CatchClause(path) {
120
+ const node = path.node;
121
+ const loc = node.loc?.start;
122
+ if (!loc) return;
123
+
124
+ const catchBody = node.body;
125
+ const param = node.param;
126
+
127
+ // Check for empty catch
128
+ if (!hasMeaningfulHandling(catchBody, code)) {
129
+ // Check for intentional comment
130
+ const blockCode = code.substring(catchBody.start, catchBody.end);
131
+ const hasIntentionalComment = /intentional|expected|ignore|safe|ok/i.test(blockCode);
132
+
133
+ if (!hasIntentionalComment) {
134
+ findings.push({
135
+ type: "empty_catch",
136
+ severity: "WARN",
137
+ category: "ErrorHandling",
138
+ file: filePath,
139
+ line: loc.line,
140
+ column: loc.column,
141
+ title: "Empty or minimal catch block",
142
+ message: "Errors are being swallowed without logging or handling.",
143
+ codeSnippet: snippetForLine(lines, loc.line),
144
+ confidence: "high",
145
+ fixHint: "Log the error or re-throw with context",
146
+ });
147
+ }
148
+ }
149
+
150
+ // Check for untyped catch parameter (TypeScript)
151
+ if (param && t.isIdentifier(param) && filePath.endsWith(".ts")) {
152
+ // In TypeScript, catch param is 'unknown' by default in strict mode
153
+ // but many codebases use 'any' or no annotation
154
+ const hasTypeAnnotation = param.typeAnnotation;
155
+
156
+ if (!hasTypeAnnotation) {
157
+ findings.push({
158
+ type: "untyped_catch",
159
+ severity: "INFO",
160
+ category: "ErrorHandling",
161
+ file: filePath,
162
+ line: loc.line,
163
+ column: loc.column,
164
+ title: "Catch parameter not typed",
165
+ message: "Consider typing catch parameter as 'unknown' and checking type before use.",
166
+ codeSnippet: snippetForLine(lines, loc.line),
167
+ confidence: "low",
168
+ fixHint: "catch (error: unknown) { if (error instanceof Error) { ... } }",
169
+ });
170
+ }
171
+ }
172
+
173
+ // Check for console.log instead of console.error
174
+ traverse(catchBody, {
175
+ CallExpression(innerPath) {
176
+ const callee = innerPath.node.callee;
177
+ if (t.isMemberExpression(callee) &&
178
+ t.isIdentifier(callee.object, { name: "console" }) &&
179
+ t.isIdentifier(callee.property, { name: "log" })) {
180
+
181
+ const innerLoc = innerPath.node.loc?.start;
182
+ if (innerLoc) {
183
+ findings.push({
184
+ type: "console_log_in_catch",
185
+ severity: "INFO",
186
+ category: "ErrorHandling",
187
+ file: filePath,
188
+ line: innerLoc.line,
189
+ column: innerLoc.column,
190
+ title: "console.log in catch block",
191
+ message: "Use console.error for error logging to distinguish from regular logs.",
192
+ codeSnippet: snippetForLine(lines, innerLoc.line),
193
+ confidence: "med",
194
+ fixHint: "Change to console.error(error)",
195
+ });
196
+ }
197
+ }
198
+ },
199
+ noScope: true,
200
+ }, path.scope);
201
+
202
+ // Check for error being thrown without additional context
203
+ traverse(catchBody, {
204
+ ThrowStatement(innerPath) {
205
+ const argument = innerPath.node.argument;
206
+
207
+ // Just rethrowing the same error
208
+ if (t.isIdentifier(argument) && param && t.isIdentifier(param) &&
209
+ argument.name === param.name) {
210
+
211
+ const innerLoc = innerPath.node.loc?.start;
212
+ if (innerLoc) {
213
+ findings.push({
214
+ type: "rethrow_without_context",
215
+ severity: "INFO",
216
+ category: "ErrorHandling",
217
+ file: filePath,
218
+ line: innerLoc.line,
219
+ column: innerLoc.column,
220
+ title: "Error rethrown without context",
221
+ message: "Consider wrapping with additional context for better debugging.",
222
+ codeSnippet: snippetForLine(lines, innerLoc.line),
223
+ confidence: "low",
224
+ fixHint: "throw new Error(`Context: ${error.message}`, { cause: error })",
225
+ });
226
+ }
227
+ }
228
+ },
229
+ noScope: true,
230
+ }, path.scope);
231
+ },
232
+
233
+ // Check for try without catch or finally
234
+ TryStatement(path) {
235
+ const node = path.node;
236
+ const loc = node.loc?.start;
237
+ if (!loc) return;
238
+
239
+ if (!node.handler && !node.finalizer) {
240
+ findings.push({
241
+ type: "try_without_catch",
242
+ severity: "WARN",
243
+ category: "ErrorHandling",
244
+ file: filePath,
245
+ line: loc.line,
246
+ column: loc.column,
247
+ title: "try without catch or finally",
248
+ message: "try statement has no error handling.",
249
+ codeSnippet: snippetForLine(lines, loc.line),
250
+ confidence: "high",
251
+ fixHint: "Add catch block to handle errors",
252
+ });
253
+ }
254
+ },
255
+
256
+ // Check for generic error messages
257
+ NewExpression(path) {
258
+ const node = path.node;
259
+ const loc = node.loc?.start;
260
+ if (!loc) return;
261
+
262
+ if (t.isIdentifier(node.callee, { name: "Error" }) ||
263
+ (t.isIdentifier(node.callee) && node.callee.name.endsWith("Error"))) {
264
+
265
+ const args = node.arguments;
266
+ if (args.length > 0 && t.isStringLiteral(args[0])) {
267
+ const message = args[0].value;
268
+
269
+ if (isGenericErrorMessage(message)) {
270
+ findings.push({
271
+ type: "generic_error_message",
272
+ severity: "INFO",
273
+ category: "ErrorHandling",
274
+ file: filePath,
275
+ line: loc.line,
276
+ column: loc.column,
277
+ title: "Generic error message",
278
+ message: `Error message "${message}" is not descriptive. Include specific context.`,
279
+ codeSnippet: snippetForLine(lines, loc.line),
280
+ confidence: "med",
281
+ fixHint: "Include what failed and why: 'Failed to fetch user: invalid ID'",
282
+ });
283
+ }
284
+ }
285
+ }
286
+ },
287
+
288
+ // Check API error responses for consistency
289
+ CallExpression(path) {
290
+ const node = path.node;
291
+ const callee = node.callee;
292
+ const loc = node.loc?.start;
293
+ if (!loc) return;
294
+
295
+ // Check for NextResponse.json() or res.json() error patterns
296
+ if (isAPIRoute) {
297
+ let isErrorResponse = false;
298
+ let responseShape = null;
299
+
300
+ // NextResponse.json({ error: ... }, { status: 4xx })
301
+ if (t.isMemberExpression(callee) &&
302
+ t.isIdentifier(callee.object, { name: "NextResponse" }) &&
303
+ t.isIdentifier(callee.property, { name: "json" })) {
304
+
305
+ if (node.arguments.length >= 2) {
306
+ const options = node.arguments[1];
307
+ if (t.isObjectExpression(options)) {
308
+ const statusProp = options.properties.find(p =>
309
+ t.isObjectProperty(p) &&
310
+ t.isIdentifier(p.key, { name: "status" }) &&
311
+ t.isNumericLiteral(p.value)
312
+ );
313
+
314
+ if (statusProp && statusProp.value.value >= 400) {
315
+ isErrorResponse = true;
316
+
317
+ // Get response shape
318
+ const body = node.arguments[0];
319
+ if (t.isObjectExpression(body)) {
320
+ responseShape = body.properties
321
+ .filter(p => t.isObjectProperty(p) && t.isIdentifier(p.key))
322
+ .map(p => p.key.name)
323
+ .sort()
324
+ .join(",");
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ // res.status(4xx).json({ error: ... })
332
+ if (t.isMemberExpression(callee) &&
333
+ t.isIdentifier(callee.property, { name: "json" }) &&
334
+ t.isCallExpression(callee.object)) {
335
+
336
+ const statusCall = callee.object;
337
+ if (t.isMemberExpression(statusCall.callee) &&
338
+ t.isIdentifier(statusCall.callee.property, { name: "status" }) &&
339
+ statusCall.arguments.length > 0 &&
340
+ t.isNumericLiteral(statusCall.arguments[0]) &&
341
+ statusCall.arguments[0].value >= 400) {
342
+
343
+ isErrorResponse = true;
344
+
345
+ // Get response shape
346
+ const body = node.arguments[0];
347
+ if (t.isObjectExpression(body)) {
348
+ responseShape = body.properties
349
+ .filter(p => t.isObjectProperty(p) && t.isIdentifier(p.key))
350
+ .map(p => p.key.name)
351
+ .sort()
352
+ .join(",");
353
+ }
354
+ }
355
+ }
356
+
357
+ if (isErrorResponse && responseShape) {
358
+ errorResponsePatterns.add(responseShape);
359
+ }
360
+ }
361
+ },
362
+
363
+ // Check for missing .catch() on promises
364
+ MemberExpression(path) {
365
+ const node = path.node;
366
+ const loc = node.loc?.start;
367
+ if (!loc) return;
368
+
369
+ // Check for .then() without .catch()
370
+ if (t.isIdentifier(node.property, { name: "then" })) {
371
+ const parent = path.parentPath;
372
+
373
+ if (parent.isCallExpression() && parent.node.callee === node) {
374
+ // Check if there's a .catch() after
375
+ const grandparent = parent.parentPath;
376
+
377
+ if (!grandparent.isMemberExpression() ||
378
+ !t.isIdentifier(grandparent.node.property, { name: "catch" })) {
379
+
380
+ // Check if the .then() callback handles errors
381
+ const thenArgs = parent.node.arguments;
382
+ const hasErrorHandler = thenArgs.length >= 2; // Second arg is error handler
383
+
384
+ if (!hasErrorHandler) {
385
+ // Check if result is returned or awaited (will propagate)
386
+ const returnParent = parent.findParent(p => p.isReturnStatement());
387
+ const awaitParent = parent.findParent(p => p.isAwaitExpression());
388
+
389
+ if (!returnParent && !awaitParent) {
390
+ findings.push({
391
+ type: "then_without_catch",
392
+ severity: "INFO",
393
+ category: "ErrorHandling",
394
+ file: filePath,
395
+ line: loc.line,
396
+ column: loc.column,
397
+ title: ".then() without .catch()",
398
+ message: "Promise may have unhandled rejections.",
399
+ codeSnippet: snippetForLine(lines, loc.line),
400
+ confidence: "low",
401
+ fixHint: "Add .catch() or use async/await with try/catch",
402
+ });
403
+ }
404
+ }
405
+ }
406
+ }
407
+ }
408
+ },
409
+ });
410
+
411
+ // Check for inconsistent error response patterns
412
+ if (isAPIRoute && errorResponsePatterns.size > 1) {
413
+ findings.push({
414
+ type: "inconsistent_error_response",
415
+ severity: "INFO",
416
+ category: "ErrorHandling",
417
+ file: filePath,
418
+ line: 1,
419
+ column: 0,
420
+ title: "Inconsistent error response shapes",
421
+ message: `Found ${errorResponsePatterns.size} different error response shapes. Standardize error format.`,
422
+ codeSnippet: "",
423
+ confidence: "low",
424
+ fixHint: "Use consistent shape: { error: { message, code, details? } }",
425
+ });
426
+ }
427
+
428
+ return findings;
429
+ }
430
+
431
+ module.exports = {
432
+ analyzeErrorHandling,
433
+ hasMeaningfulHandling,
434
+ isGenericErrorMessage,
435
+ GOOD_ERROR_PATTERNS,
436
+ GENERIC_ERROR_MESSAGES,
437
+ };