@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,429 @@
1
+ /**
2
+ * Database Patterns Engine
3
+ * Detects:
4
+ * - N+1 query patterns
5
+ * - Missing transaction boundaries
6
+ * - Unbounded queries (no LIMIT)
7
+ * - Raw SQL injection risks (beyond basic detection)
8
+ * - Missing indexes (suggested)
9
+ * - Connection pool issues
10
+ * - ORM anti-patterns
11
+ * - Missing error handling on DB operations
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
+ * ORM detection patterns
25
+ */
26
+ const ORM_PATTERNS = {
27
+ prisma: {
28
+ import: /from\s+['"]@prisma\/client['"]/,
29
+ findMany: /\.findMany\s*\(/,
30
+ findFirst: /\.findFirst\s*\(/,
31
+ findUnique: /\.findUnique\s*\(/,
32
+ create: /\.create\s*\(/,
33
+ update: /\.update\s*\(/,
34
+ delete: /\.delete\s*\(/,
35
+ transaction: /\$transaction/,
36
+ rawQuery: /\$queryRaw|\$executeRaw/,
37
+ },
38
+ drizzle: {
39
+ import: /from\s+['"]drizzle-orm['"]/,
40
+ select: /\.select\s*\(/,
41
+ insert: /\.insert\s*\(/,
42
+ update: /\.update\s*\(/,
43
+ delete: /\.delete\s*\(/,
44
+ rawQuery: /sql`|sql\(/,
45
+ },
46
+ typeorm: {
47
+ import: /from\s+['"]typeorm['"]/,
48
+ find: /\.find\s*\(/,
49
+ findOne: /\.findOne\s*\(/,
50
+ save: /\.save\s*\(/,
51
+ remove: /\.remove\s*\(/,
52
+ transaction: /\.transaction\s*\(/,
53
+ rawQuery: /\.query\s*\(/,
54
+ },
55
+ sequelize: {
56
+ import: /from\s+['"]sequelize['"]/,
57
+ findAll: /\.findAll\s*\(/,
58
+ findOne: /\.findOne\s*\(/,
59
+ create: /\.create\s*\(/,
60
+ update: /\.update\s*\(/,
61
+ destroy: /\.destroy\s*\(/,
62
+ transaction: /\.transaction\s*\(/,
63
+ rawQuery: /\.query\s*\(/,
64
+ },
65
+ mongoose: {
66
+ import: /from\s+['"]mongoose['"]/,
67
+ find: /\.find\s*\(/,
68
+ findOne: /\.findOne\s*\(/,
69
+ findById: /\.findById\s*\(/,
70
+ save: /\.save\s*\(/,
71
+ aggregate: /\.aggregate\s*\(/,
72
+ },
73
+ knex: {
74
+ import: /from\s+['"]knex['"]/,
75
+ select: /\.select\s*\(/,
76
+ insert: /\.insert\s*\(/,
77
+ update: /\.update\s*\(/,
78
+ delete: /\.del\s*\(|\.delete\s*\(/,
79
+ transaction: /\.transaction\s*\(/,
80
+ rawQuery: /\.raw\s*\(/,
81
+ },
82
+ };
83
+
84
+ /**
85
+ * Detect which ORM is being used
86
+ */
87
+ function detectORM(code) {
88
+ for (const [name, patterns] of Object.entries(ORM_PATTERNS)) {
89
+ if (patterns.import.test(code)) {
90
+ return name;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Analyze database patterns
98
+ */
99
+ function analyzeDatabasePatterns(code, filePath) {
100
+ const findings = [];
101
+
102
+ if (shouldExcludeFile(filePath)) return findings;
103
+ if (isTestContext(code, filePath)) return findings;
104
+ if (hasIgnoreDirective(code, "database-patterns")) return findings;
105
+
106
+ const ast = getAST(code, filePath);
107
+ if (!ast) return findings;
108
+
109
+ const lines = code.split("\n");
110
+ const orm = detectORM(code);
111
+
112
+ // If no ORM detected, skip most checks
113
+ if (!orm) return findings;
114
+
115
+ // Track queries for N+1 detection
116
+ const queriesInLoops = [];
117
+ let inLoop = false;
118
+ let loopDepth = 0;
119
+
120
+ // Track if we're in a transaction
121
+ let inTransaction = false;
122
+
123
+ traverse(ast, {
124
+ // Track loop entry
125
+ ForStatement: {
126
+ enter() { inLoop = true; loopDepth++; },
127
+ exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
128
+ },
129
+ ForInStatement: {
130
+ enter() { inLoop = true; loopDepth++; },
131
+ exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
132
+ },
133
+ ForOfStatement: {
134
+ enter() { inLoop = true; loopDepth++; },
135
+ exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
136
+ },
137
+ WhileStatement: {
138
+ enter() { inLoop = true; loopDepth++; },
139
+ exit() { loopDepth--; if (loopDepth === 0) inLoop = false; },
140
+ },
141
+
142
+ // Check for map/forEach with DB calls (potential N+1)
143
+ CallExpression(path) {
144
+ const node = path.node;
145
+ const callee = node.callee;
146
+ const loc = node.loc?.start;
147
+ if (!loc) return;
148
+
149
+ // Check for array.map/forEach that might contain DB queries
150
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
151
+ const methodName = callee.property.name;
152
+
153
+ if (["map", "forEach", "filter", "reduce"].includes(methodName)) {
154
+ const callback = node.arguments[0];
155
+
156
+ if (callback && (t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback))) {
157
+ // Check if callback contains DB operations
158
+ let hasDBCall = false;
159
+ let dbCallLoc = null;
160
+
161
+ path.traverse({
162
+ CallExpression(innerPath) {
163
+ const innerNode = innerPath.node;
164
+ const innerCallee = innerNode.callee;
165
+
166
+ // Check for Prisma patterns
167
+ if (orm === "prisma" && t.isMemberExpression(innerCallee)) {
168
+ const prop = innerCallee.property;
169
+ if (t.isIdentifier(prop) &&
170
+ ["findUnique", "findFirst", "findMany", "create", "update", "delete"].includes(prop.name)) {
171
+ hasDBCall = true;
172
+ dbCallLoc = innerNode.loc?.start;
173
+ }
174
+ }
175
+
176
+ // Check for await inside the callback (common pattern)
177
+ if (t.isAwaitExpression(innerPath.parent)) {
178
+ // Check if awaiting a DB operation
179
+ const awaitedCode = code.substring(innerNode.start, innerNode.end);
180
+ const dbPatterns = [
181
+ /findUnique|findFirst|findMany|findOne|findById/,
182
+ /\.select\(|\.find\(|\.query\(/,
183
+ ];
184
+
185
+ if (dbPatterns.some(p => p.test(awaitedCode))) {
186
+ hasDBCall = true;
187
+ dbCallLoc = innerNode.loc?.start;
188
+ }
189
+ }
190
+ },
191
+ });
192
+
193
+ if (hasDBCall) {
194
+ findings.push({
195
+ type: "n_plus_1_query",
196
+ severity: "WARN",
197
+ category: "DatabasePatterns",
198
+ file: filePath,
199
+ line: dbCallLoc?.line || loc.line,
200
+ column: 0,
201
+ title: "Potential N+1 query pattern",
202
+ message: `Database query inside ${methodName}() callback. This executes a query for each item.`,
203
+ codeSnippet: snippetForLine(lines, dbCallLoc?.line || loc.line),
204
+ confidence: "high",
205
+ fixHint: orm === "prisma"
206
+ ? "Use include/select to fetch related data in one query, or use Prisma's createMany/updateMany"
207
+ : "Batch queries or use eager loading to reduce database roundtrips",
208
+ });
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ // Detect DB operations in loops
215
+ if (inLoop && t.isMemberExpression(callee)) {
216
+ const prop = callee.property;
217
+ const ormPatterns = ORM_PATTERNS[orm];
218
+
219
+ if (ormPatterns && t.isIdentifier(prop)) {
220
+ const isDbOp = Object.entries(ormPatterns)
221
+ .filter(([key]) => key !== "import" && key !== "transaction" && key !== "rawQuery")
222
+ .some(([_, pattern]) => pattern.test(`.${prop.name}(`));
223
+
224
+ if (isDbOp) {
225
+ findings.push({
226
+ type: "query_in_loop",
227
+ severity: "WARN",
228
+ category: "DatabasePatterns",
229
+ file: filePath,
230
+ line: loc.line,
231
+ column: loc.column,
232
+ title: "Database query inside loop",
233
+ message: `${prop.name}() called in a loop may cause performance issues.`,
234
+ codeSnippet: snippetForLine(lines, loc.line),
235
+ confidence: "high",
236
+ fixHint: "Batch operations or fetch all data before the loop",
237
+ });
238
+ }
239
+ }
240
+ }
241
+
242
+ // Check for findMany/findAll without take/limit
243
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
244
+ const methodName = callee.property.name;
245
+
246
+ if (["findMany", "findAll", "find"].includes(methodName)) {
247
+ const args = node.arguments;
248
+ let hasLimit = false;
249
+
250
+ if (args.length > 0 && t.isObjectExpression(args[0])) {
251
+ const props = args[0].properties;
252
+ hasLimit = props.some(p =>
253
+ t.isObjectProperty(p) &&
254
+ t.isIdentifier(p.key) &&
255
+ ["take", "limit", "first", "last"].includes(p.key.name)
256
+ );
257
+ }
258
+
259
+ if (!hasLimit) {
260
+ findings.push({
261
+ type: "unbounded_query",
262
+ severity: "INFO",
263
+ category: "DatabasePatterns",
264
+ file: filePath,
265
+ line: loc.line,
266
+ column: loc.column,
267
+ title: "Query without limit",
268
+ message: `${methodName}() without limit may return excessive data.`,
269
+ codeSnippet: snippetForLine(lines, loc.line),
270
+ confidence: "low",
271
+ fixHint: orm === "prisma"
272
+ ? "Add { take: 100 } or implement pagination"
273
+ : "Add a limit parameter or implement pagination",
274
+ });
275
+ }
276
+ }
277
+ }
278
+
279
+ // Check for missing transaction in multiple writes
280
+ // (This is a heuristic check)
281
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
282
+ const methodName = callee.property.name;
283
+ const writeMethods = ["create", "update", "delete", "upsert", "insert", "save", "remove", "destroy"];
284
+
285
+ if (writeMethods.includes(methodName) && !inTransaction) {
286
+ // Check if there are multiple write operations in the same function
287
+ const fnParent = path.getFunctionParent();
288
+ if (fnParent) {
289
+ let writeCount = 0;
290
+
291
+ fnParent.traverse({
292
+ CallExpression(innerPath) {
293
+ const innerCallee = innerPath.node.callee;
294
+ if (t.isMemberExpression(innerCallee) && t.isIdentifier(innerCallee.property)) {
295
+ if (writeMethods.includes(innerCallee.property.name)) {
296
+ writeCount++;
297
+ }
298
+ }
299
+ },
300
+ });
301
+
302
+ if (writeCount >= 2) {
303
+ // Check if function uses transaction
304
+ const fnCode = code.substring(fnParent.node.start, fnParent.node.end);
305
+ const hasTransaction = /\$transaction|\.transaction\s*\(|BEGIN|COMMIT/.test(fnCode);
306
+
307
+ if (!hasTransaction) {
308
+ findings.push({
309
+ type: "missing_transaction",
310
+ severity: "INFO",
311
+ category: "DatabasePatterns",
312
+ file: filePath,
313
+ line: loc.line,
314
+ column: loc.column,
315
+ title: "Multiple writes without transaction",
316
+ message: `Function has ${writeCount} write operations without explicit transaction.`,
317
+ codeSnippet: snippetForLine(lines, loc.line),
318
+ confidence: "low",
319
+ fixHint: orm === "prisma"
320
+ ? "Wrap in prisma.$transaction([...])"
321
+ : "Wrap operations in a transaction for atomicity",
322
+ });
323
+ }
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ // Check for raw queries (SQL injection risk)
330
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
331
+ const methodName = callee.property.name;
332
+ const rawMethods = ["query", "raw", "$queryRaw", "$executeRaw", "queryRaw", "executeRaw"];
333
+
334
+ if (rawMethods.includes(methodName)) {
335
+ const args = node.arguments;
336
+
337
+ // Check if using template literal (safer) vs string concatenation
338
+ if (args.length > 0) {
339
+ const queryArg = args[0];
340
+
341
+ // String concatenation or interpolation is risky
342
+ if (t.isBinaryExpression(queryArg, { operator: "+" }) ||
343
+ (t.isTemplateLiteral(queryArg) && queryArg.expressions.length > 0)) {
344
+
345
+ // Check if expressions are parameterized
346
+ let hasUnsafeInterpolation = false;
347
+
348
+ if (t.isTemplateLiteral(queryArg)) {
349
+ // Prisma's Prisma.sql`` is safe, but direct string template isn't
350
+ const parentCallee = path.parentPath?.node?.callee;
351
+ const isPrismaSql = t.isMemberExpression(parentCallee) &&
352
+ t.isIdentifier(parentCallee.object, { name: "Prisma" }) &&
353
+ t.isIdentifier(parentCallee.property, { name: "sql" });
354
+
355
+ if (!isPrismaSql) {
356
+ hasUnsafeInterpolation = true;
357
+ }
358
+ } else {
359
+ hasUnsafeInterpolation = true;
360
+ }
361
+
362
+ if (hasUnsafeInterpolation) {
363
+ findings.push({
364
+ type: "raw_query_interpolation",
365
+ severity: "WARN",
366
+ category: "DatabasePatterns",
367
+ file: filePath,
368
+ line: loc.line,
369
+ column: loc.column,
370
+ title: "Dynamic values in raw query",
371
+ message: "Raw query with string interpolation may be vulnerable to SQL injection.",
372
+ codeSnippet: snippetForLine(lines, loc.line),
373
+ confidence: "med",
374
+ fixHint: orm === "prisma"
375
+ ? "Use Prisma.sql`` tagged template or $queryRaw with Prisma.sql"
376
+ : "Use parameterized queries with placeholders ($1, ?, :name)",
377
+ });
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
383
+ },
384
+
385
+ // Check for missing error handling on DB operations
386
+ AwaitExpression(path) {
387
+ const node = path.node;
388
+ const argument = node.argument;
389
+ const loc = node.loc?.start;
390
+ if (!loc) return;
391
+
392
+ // Check if it's a DB operation
393
+ if (t.isCallExpression(argument) && t.isMemberExpression(argument.callee)) {
394
+ const prop = argument.callee.property;
395
+ const dbMethods = ["findMany", "findFirst", "findUnique", "findOne", "find", "findAll",
396
+ "create", "update", "delete", "save", "query", "execute"];
397
+
398
+ if (t.isIdentifier(prop) && dbMethods.includes(prop.name)) {
399
+ // Check if inside try-catch
400
+ const tryParent = path.findParent(p => p.isTryStatement());
401
+
402
+ if (!tryParent) {
403
+ findings.push({
404
+ type: "db_no_error_handling",
405
+ severity: "INFO",
406
+ category: "DatabasePatterns",
407
+ file: filePath,
408
+ line: loc.line,
409
+ column: loc.column,
410
+ title: "Database operation without error handling",
411
+ message: `${prop.name}() should be wrapped in try-catch for proper error handling.`,
412
+ codeSnippet: snippetForLine(lines, loc.line),
413
+ confidence: "low",
414
+ fixHint: "Wrap in try-catch to handle potential database errors",
415
+ });
416
+ }
417
+ }
418
+ }
419
+ },
420
+ });
421
+
422
+ return findings;
423
+ }
424
+
425
+ module.exports = {
426
+ analyzeDatabasePatterns,
427
+ detectORM,
428
+ ORM_PATTERNS,
429
+ };