code-auditor-mcp 5.0.2 → 5.3.3

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 (419) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +326 -3
  3. package/README.md +24 -2
  4. package/configs/hhra-compat.json +12 -118
  5. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  6. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +11 -1
  7. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  8. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  9. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +57 -52
  10. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  11. package/dist/analyzers/provenance.d.ts +9 -4
  12. package/dist/analyzers/provenance.d.ts.map +1 -1
  13. package/dist/analyzers/provenance.js +8 -25
  14. package/dist/analyzers/provenance.js.map +1 -1
  15. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  16. package/dist/analyzers/reactAnalyzer.js +35 -14
  17. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  18. package/dist/analyzers/ruleRegistry.js +4 -4
  19. package/dist/analyzers/ruleRegistry.js.map +1 -1
  20. package/dist/analyzers/ruleTiming.d.ts +1 -1
  21. package/dist/analyzers/ruleTiming.js +2 -2
  22. package/dist/analyzers/ruleTiming.js.map +1 -1
  23. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +22 -26
  25. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  26. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +0 -2
  27. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  28. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +4 -5
  29. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  30. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +32 -15
  31. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +189 -115
  33. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  34. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -2
  35. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  36. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +0 -5
  37. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  38. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -10
  39. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  40. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +7 -49
  41. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  42. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  43. package/dist/analyzers/universal/schema/codeAnalysis.js +51 -3
  44. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  45. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  46. package/dist/analyzers/universal/schema/config.js +0 -2
  47. package/dist/analyzers/universal/schema/config.js.map +1 -1
  48. package/dist/analyzers/universal/schema/types.d.ts +0 -2
  49. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  50. package/dist/analyzers/vendorSurfaces.d.ts +126 -0
  51. package/dist/analyzers/vendorSurfaces.d.ts.map +1 -0
  52. package/dist/analyzers/vendorSurfaces.js +580 -0
  53. package/dist/analyzers/vendorSurfaces.js.map +1 -0
  54. package/dist/auditRunner.d.ts +9 -1
  55. package/dist/auditRunner.d.ts.map +1 -1
  56. package/dist/auditRunner.js +198 -146
  57. package/dist/auditRunner.js.map +1 -1
  58. package/dist/cacheGc.d.ts +88 -0
  59. package/dist/cacheGc.d.ts.map +1 -0
  60. package/dist/cacheGc.js +196 -0
  61. package/dist/cacheGc.js.map +1 -0
  62. package/dist/churn/churnExtractor.d.ts +0 -1
  63. package/dist/churn/churnExtractor.d.ts.map +1 -1
  64. package/dist/churn/churnExtractor.js +2 -4
  65. package/dist/churn/churnExtractor.js.map +1 -1
  66. package/dist/cli.d.ts +1 -1
  67. package/dist/cli.d.ts.map +1 -1
  68. package/dist/cli.js +260 -47
  69. package/dist/cli.js.map +1 -1
  70. package/dist/codeIndex/migrations.d.ts.map +1 -1
  71. package/dist/codeIndex/migrations.js +4 -3
  72. package/dist/codeIndex/migrations.js.map +1 -1
  73. package/dist/codeIndexDB.d.ts +11 -2
  74. package/dist/codeIndexDB.d.ts.map +1 -1
  75. package/dist/codeIndexDB.js +27 -2
  76. package/dist/codeIndexDB.js.map +1 -1
  77. package/dist/config/configLoader.d.ts +60 -7
  78. package/dist/config/configLoader.d.ts.map +1 -1
  79. package/dist/config/configLoader.js +252 -60
  80. package/dist/config/configLoader.js.map +1 -1
  81. package/dist/config/defaults.d.ts +137 -43
  82. package/dist/config/defaults.d.ts.map +1 -1
  83. package/dist/config/defaults.js +174 -101
  84. package/dist/config/defaults.js.map +1 -1
  85. package/dist/config/effectiveConfig.d.ts +1 -1
  86. package/dist/config/effectiveConfig.d.ts.map +1 -1
  87. package/dist/config/effectiveConfig.js +4 -6
  88. package/dist/config/effectiveConfig.js.map +1 -1
  89. package/dist/config/pathProfiles.d.ts +4 -3
  90. package/dist/config/pathProfiles.d.ts.map +1 -1
  91. package/dist/config/pathProfiles.js +3 -2
  92. package/dist/config/pathProfiles.js.map +1 -1
  93. package/dist/constants.d.ts +2 -1
  94. package/dist/constants.d.ts.map +1 -1
  95. package/dist/constants.js +4 -0
  96. package/dist/constants.js.map +1 -1
  97. package/dist/daemon/main.d.ts +0 -1
  98. package/dist/daemon/main.d.ts.map +1 -1
  99. package/dist/daemon/main.js +11 -4
  100. package/dist/daemon/main.js.map +1 -1
  101. package/dist/dataPaths.d.ts +32 -9
  102. package/dist/dataPaths.d.ts.map +1 -1
  103. package/dist/dataPaths.js +69 -16
  104. package/dist/dataPaths.js.map +1 -1
  105. package/dist/hookLog.d.ts +54 -0
  106. package/dist/hookLog.d.ts.map +1 -0
  107. package/dist/hookLog.js +105 -0
  108. package/dist/hookLog.js.map +1 -0
  109. package/dist/hookLogPath.generated.d.ts +3 -0
  110. package/dist/hookLogPath.generated.d.ts.map +1 -0
  111. package/dist/hookLogPath.generated.js +8 -0
  112. package/dist/hookLogPath.generated.js.map +1 -0
  113. package/dist/index.d.ts +0 -5
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +0 -7
  116. package/dist/index.js.map +1 -1
  117. package/dist/installConfig.d.ts +15 -2
  118. package/dist/installConfig.d.ts.map +1 -1
  119. package/dist/installConfig.js +17 -2
  120. package/dist/installConfig.js.map +1 -1
  121. package/dist/installer.d.ts.map +1 -1
  122. package/dist/installer.js +2 -1
  123. package/dist/installer.js.map +1 -1
  124. package/dist/languages/adapterBridge.d.ts +11 -0
  125. package/dist/languages/adapterBridge.d.ts.map +1 -1
  126. package/dist/languages/adapterBridge.js +11 -0
  127. package/dist/languages/adapterBridge.js.map +1 -1
  128. package/dist/languages/types.d.ts +8 -0
  129. package/dist/languages/types.d.ts.map +1 -1
  130. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +13 -0
  131. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  132. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +135 -18
  133. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  134. package/dist/ledger.d.ts +7 -0
  135. package/dist/ledger.d.ts.map +1 -1
  136. package/dist/ledger.js +7 -0
  137. package/dist/ledger.js.map +1 -1
  138. package/dist/mcp-tools/workflowGuide.js +8 -8
  139. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  140. package/dist/mcp-tools-shared.d.ts.map +1 -1
  141. package/dist/mcp-tools-shared.js +5 -2
  142. package/dist/mcp-tools-shared.js.map +1 -1
  143. package/dist/mcp-ui-simple.d.ts +5 -0
  144. package/dist/mcp-ui-simple.d.ts.map +1 -1
  145. package/dist/mcp-ui-simple.js +9 -2
  146. package/dist/mcp-ui-simple.js.map +1 -1
  147. package/dist/mcp.d.ts +0 -1
  148. package/dist/mcp.d.ts.map +1 -1
  149. package/dist/mcp.js +36 -33
  150. package/dist/mcp.js.map +1 -1
  151. package/dist/mcpAuditJobs.d.ts +10 -0
  152. package/dist/mcpAuditJobs.d.ts.map +1 -1
  153. package/dist/mcpAuditJobs.js +10 -1
  154. package/dist/mcpAuditJobs.js.map +1 -1
  155. package/dist/mcpAutoIndex.d.ts.map +1 -1
  156. package/dist/mcpAutoIndex.js +2 -1
  157. package/dist/mcpAutoIndex.js.map +1 -1
  158. package/dist/mcpDiagnostics.d.ts +2 -0
  159. package/dist/mcpDiagnostics.d.ts.map +1 -1
  160. package/dist/mcpDiagnostics.js +2 -0
  161. package/dist/mcpDiagnostics.js.map +1 -1
  162. package/dist/phase/candidates.d.ts +53 -0
  163. package/dist/phase/candidates.d.ts.map +1 -0
  164. package/dist/phase/candidates.js +67 -0
  165. package/dist/phase/candidates.js.map +1 -0
  166. package/dist/phase/colorMath.d.ts +18 -5
  167. package/dist/phase/colorMath.d.ts.map +1 -1
  168. package/dist/phase/colorMath.js +88 -5
  169. package/dist/phase/colorMath.js.map +1 -1
  170. package/dist/phase/dataAccessCallsCandidates.js +1 -1
  171. package/dist/phase/dataAccessCallsCandidates.js.map +1 -1
  172. package/dist/phase/goFunctionAnalysis.d.ts +2 -2
  173. package/dist/phase/goFunctionAnalysis.js +2 -2
  174. package/dist/phase/goFunctions.d.ts +2 -2
  175. package/dist/phase/goFunctions.js +2 -2
  176. package/dist/phase/goImports.d.ts +2 -2
  177. package/dist/phase/goImports.js +2 -2
  178. package/dist/phase/goSwitches.d.ts +2 -2
  179. package/dist/phase/goSwitches.js +2 -2
  180. package/dist/phase/imports.d.ts.map +1 -1
  181. package/dist/phase/imports.js +1 -0
  182. package/dist/phase/imports.js.map +1 -1
  183. package/dist/phase/loopQueryCandidates.js +1 -1
  184. package/dist/phase/loopQueryCandidates.js.map +1 -1
  185. package/dist/phase/oracles.d.ts +28 -0
  186. package/dist/phase/oracles.d.ts.map +1 -1
  187. package/dist/phase/oracles.js +28 -0
  188. package/dist/phase/oracles.js.map +1 -1
  189. package/dist/phase/phaseModel.d.ts +47 -8
  190. package/dist/phase/phaseModel.d.ts.map +1 -1
  191. package/dist/phase/phaseModel.js +44 -8
  192. package/dist/phase/phaseModel.js.map +1 -1
  193. package/dist/phase/producers.d.ts.map +1 -1
  194. package/dist/phase/producers.js +24 -17
  195. package/dist/phase/producers.js.map +1 -1
  196. package/dist/phase/reachability.d.ts +44 -1
  197. package/dist/phase/reachability.d.ts.map +1 -1
  198. package/dist/phase/reachability.js +79 -0
  199. package/dist/phase/reachability.js.map +1 -1
  200. package/dist/phase/rules/conventions.d.ts.map +1 -1
  201. package/dist/phase/rules/conventions.js +6 -2
  202. package/dist/phase/rules/conventions.js.map +1 -1
  203. package/dist/phase/rules/crossDomain.d.ts.map +1 -1
  204. package/dist/phase/rules/crossDomain.js +16 -6
  205. package/dist/phase/rules/crossDomain.js.map +1 -1
  206. package/dist/phase/rules/dataAccess.d.ts.map +1 -1
  207. package/dist/phase/rules/dataAccess.js +13 -0
  208. package/dist/phase/rules/dataAccess.js.map +1 -1
  209. package/dist/phase/rules/documentation.d.ts.map +1 -1
  210. package/dist/phase/rules/documentation.js +11 -1
  211. package/dist/phase/rules/documentation.js.map +1 -1
  212. package/dist/phase/rules/dry.d.ts +16 -7
  213. package/dist/phase/rules/dry.d.ts.map +1 -1
  214. package/dist/phase/rules/dry.js +51 -32
  215. package/dist/phase/rules/dry.js.map +1 -1
  216. package/dist/phase/rules/goRules.d.ts +2 -2
  217. package/dist/phase/rules/goRules.js +2 -2
  218. package/dist/phase/rules/secrets.d.ts +22 -8
  219. package/dist/phase/rules/secrets.d.ts.map +1 -1
  220. package/dist/phase/rules/secrets.js +75 -69
  221. package/dist/phase/rules/secrets.js.map +1 -1
  222. package/dist/phase/rules/styles.d.ts +23 -9
  223. package/dist/phase/rules/styles.d.ts.map +1 -1
  224. package/dist/phase/rules/styles.js +61 -15
  225. package/dist/phase/rules/styles.js.map +1 -1
  226. package/dist/phase/rules/unreferencedModule.d.ts.map +1 -1
  227. package/dist/phase/rules/unreferencedModule.js +32 -10
  228. package/dist/phase/rules/unreferencedModule.js.map +1 -1
  229. package/dist/phase/runner.d.ts +16 -7
  230. package/dist/phase/runner.d.ts.map +1 -1
  231. package/dist/phase/runner.js +32 -20
  232. package/dist/phase/runner.js.map +1 -1
  233. package/dist/phase/stringLiterals.d.ts.map +1 -1
  234. package/dist/phase/stringLiterals.js +16 -7
  235. package/dist/phase/stringLiterals.js.map +1 -1
  236. package/dist/phase/stylesCss.d.ts.map +1 -1
  237. package/dist/phase/stylesCss.js +4 -0
  238. package/dist/phase/stylesCss.js.map +1 -1
  239. package/dist/phase/stylesMarkup.d.ts.map +1 -1
  240. package/dist/phase/stylesMarkup.js +4 -0
  241. package/dist/phase/stylesMarkup.js.map +1 -1
  242. package/dist/phase/stylesSource.d.ts.map +1 -1
  243. package/dist/phase/stylesSource.js +6 -0
  244. package/dist/phase/stylesSource.js.map +1 -1
  245. package/dist/phase/testProgram.d.ts +57 -0
  246. package/dist/phase/testProgram.d.ts.map +1 -0
  247. package/dist/phase/testProgram.js +244 -0
  248. package/dist/phase/testProgram.js.map +1 -0
  249. package/dist/phase/typeDeclarations.d.ts +2 -2
  250. package/dist/phase/typeDeclarations.js +2 -2
  251. package/dist/phase/types.d.ts +104 -0
  252. package/dist/phase/types.d.ts.map +1 -1
  253. package/dist/pipeline.d.ts +4 -0
  254. package/dist/pipeline.d.ts.map +1 -1
  255. package/dist/pipeline.js +4 -0
  256. package/dist/pipeline.js.map +1 -1
  257. package/dist/pipelineAdapters.d.ts +1 -171
  258. package/dist/pipelineAdapters.d.ts.map +1 -1
  259. package/dist/pipelineAdapters.js +47 -1281
  260. package/dist/pipelineAdapters.js.map +1 -1
  261. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  262. package/dist/reporting/csvReportGenerator.js +0 -6
  263. package/dist/reporting/csvReportGenerator.js.map +1 -1
  264. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  265. package/dist/reporting/htmlReportGenerator.js +0 -4
  266. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  267. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  268. package/dist/reporting/jsonReportGenerator.js +0 -1
  269. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  270. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  271. package/dist/reporting/sarifReportGenerator.js +0 -7
  272. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  273. package/dist/services/auditJobService.d.ts +15 -0
  274. package/dist/services/auditJobService.d.ts.map +1 -1
  275. package/dist/services/auditJobService.js +15 -0
  276. package/dist/services/auditJobService.js.map +1 -1
  277. package/dist/services/projectTaskHelpers.d.ts +7 -0
  278. package/dist/services/projectTaskHelpers.d.ts.map +1 -1
  279. package/dist/services/projectTaskHelpers.js +7 -0
  280. package/dist/services/projectTaskHelpers.js.map +1 -1
  281. package/dist/services/whitelistService.d.ts +28 -0
  282. package/dist/services/whitelistService.d.ts.map +1 -1
  283. package/dist/services/whitelistService.js +28 -0
  284. package/dist/services/whitelistService.js.map +1 -1
  285. package/dist/sqlite/driver.d.ts +17 -1
  286. package/dist/sqlite/driver.d.ts.map +1 -1
  287. package/dist/sqlite/driver.js +28 -5
  288. package/dist/sqlite/driver.js.map +1 -1
  289. package/dist/styles/cssInJsHarvest.d.ts +36 -0
  290. package/dist/styles/cssInJsHarvest.d.ts.map +1 -0
  291. package/dist/styles/cssInJsHarvest.js +78 -0
  292. package/dist/styles/cssInJsHarvest.js.map +1 -0
  293. package/dist/styles/definedClasses.d.ts +95 -0
  294. package/dist/styles/definedClasses.d.ts.map +1 -0
  295. package/dist/styles/definedClasses.js +129 -0
  296. package/dist/styles/definedClasses.js.map +1 -0
  297. package/dist/styles/markupStyleHarvest.d.ts +33 -0
  298. package/dist/styles/markupStyleHarvest.d.ts.map +1 -0
  299. package/dist/styles/markupStyleHarvest.js +73 -0
  300. package/dist/styles/markupStyleHarvest.js.map +1 -0
  301. package/dist/styles/styleExtractor.d.ts +37 -0
  302. package/dist/styles/styleExtractor.d.ts.map +1 -1
  303. package/dist/styles/styleExtractor.js +89 -33
  304. package/dist/styles/styleExtractor.js.map +1 -1
  305. package/dist/styles/styleIndexer.d.ts.map +1 -1
  306. package/dist/styles/styleIndexer.js +11 -10
  307. package/dist/styles/styleIndexer.js.map +1 -1
  308. package/dist/styles/tailwindExpander.d.ts.map +1 -1
  309. package/dist/styles/tailwindExpander.js +31 -10
  310. package/dist/styles/tailwindExpander.js.map +1 -1
  311. package/dist/styles/tailwindUtilityExpander.d.ts +4 -0
  312. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  313. package/dist/styles/tailwindUtilityExpander.js +4 -0
  314. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  315. package/dist/telemetry.d.ts +67 -1
  316. package/dist/telemetry.d.ts.map +1 -1
  317. package/dist/telemetry.js +100 -20
  318. package/dist/telemetry.js.map +1 -1
  319. package/dist/types/crossLanguage.d.ts +1 -0
  320. package/dist/types/crossLanguage.d.ts.map +1 -1
  321. package/dist/types.d.ts +73 -56
  322. package/dist/types.d.ts.map +1 -1
  323. package/dist/types.js +4 -4
  324. package/dist/types.js.map +1 -1
  325. package/dist/version.generated.d.ts +1 -1
  326. package/dist/version.generated.js +1 -1
  327. package/examples/.auditrc.example.json +7 -22
  328. package/examples/.auditrc.json +12 -155
  329. package/examples/.code-auditor.json +8 -51
  330. package/examples/nextjs.auditrc.json +7 -34
  331. package/examples/node-api.auditrc.json +6 -46
  332. package/examples/react.auditrc.json +8 -25
  333. package/examples/tolerant-solid.auditrc.json +12 -44
  334. package/package.json +8 -5
  335. package/plugin/.claude-plugin/manifest.json +10 -9
  336. package/plugin/.claude-plugin/plugin.json +1 -1
  337. package/plugin/hooks/hooks.json +4 -4
  338. package/plugin/scripts/hook-audit.sh +25 -10
  339. package/plugin/scripts/hook-common.sh +12 -7
  340. package/plugin/scripts/hook-drift-check.sh +10 -1
  341. package/plugin/scripts/hook-log-path.sh +7 -0
  342. package/plugin/scripts/hook-self-audit.sh +21 -6
  343. package/plugin/scripts/hook-warm.sh +17 -11
  344. package/plugin/skills/code-auditor/SKILL.md +26 -3
  345. package/server.json +2 -2
  346. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +0 -6
  347. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +0 -1
  348. package/dist/__tests__/fixtures/spec-20/scripts/tool.js +0 -12
  349. package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +0 -1
  350. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +0 -7
  351. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +0 -1
  352. package/dist/__tests__/fixtures/spec-20/src/demo-module.js +0 -13
  353. package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +0 -1
  354. package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.d.ts +0 -14
  355. package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.d.ts.map +0 -1
  356. package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.js +0 -16
  357. package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.js.map +0 -1
  358. package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.d.ts +0 -15
  359. package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.d.ts.map +0 -1
  360. package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.js +0 -17
  361. package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.js.map +0 -1
  362. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +0 -14
  363. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +0 -1
  364. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +0 -19
  365. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +0 -1
  366. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +0 -18
  367. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +0 -1
  368. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +0 -19
  369. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +0 -1
  370. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts +0 -19
  371. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts.map +0 -1
  372. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.js +0 -19
  373. package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.js.map +0 -1
  374. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +0 -17
  375. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +0 -1
  376. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js +0 -19
  377. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +0 -1
  378. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +0 -25
  379. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +0 -1
  380. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js +0 -21
  381. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +0 -1
  382. package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.d.ts +0 -15
  383. package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.d.ts.map +0 -1
  384. package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.js +0 -19
  385. package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.js.map +0 -1
  386. package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.d.ts +0 -14
  387. package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.d.ts.map +0 -1
  388. package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.js +0 -14
  389. package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.js.map +0 -1
  390. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +0 -15
  391. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +0 -1
  392. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js +0 -16
  393. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +0 -1
  394. package/dist/languages/go/analyzer-darwin-amd64 +0 -0
  395. package/dist/languages/go/analyzer-darwin-arm64 +0 -0
  396. package/dist/languages/go/analyzer-linux-amd64 +0 -0
  397. package/dist/languages/go/analyzer-linux-arm64 +0 -0
  398. package/dist/languages/go/analyzer-src/analyzer.go +0 -643
  399. package/dist/languages/go/analyzer-src/dataaccess.go +0 -388
  400. package/dist/languages/go/analyzer-src/go.mod +0 -3
  401. package/dist/languages/go/analyzer-src/indexer.go +0 -268
  402. package/dist/languages/go/analyzer-src/parser.go +0 -461
  403. package/dist/languages/go/analyzer-src/solid.go +0 -311
  404. package/dist/languages/go/analyzer-src/testconventions.go +0 -25
  405. package/dist/languages/go/analyzer-src/types.go +0 -156
  406. package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
  407. package/dist/languages/go/go.mod +0 -7
  408. package/dist/languages/go/main.go +0 -203
  409. package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
  410. package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
  411. package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
  412. package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
  413. package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
  414. package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
  415. package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
  416. package/dist/native-bootstrap.d.ts +0 -15
  417. package/dist/native-bootstrap.d.ts.map +0 -1
  418. package/dist/native-bootstrap.js +0 -75
  419. package/dist/native-bootstrap.js.map +0 -1
@@ -15,6 +15,13 @@ import { findEnclosingFunctionIdentity, functionIdentityLabel } from './schema/c
15
15
  import { buildOrgFilterTierSet, tableRequiresOrgFilter, DEFAULT_ORG_PREDICATE_PATTERNS, hasOrganizationFilter, } from '../orgFilterTiers.js';
16
16
  import { resolveWhereBinding } from '../../phase/localBinding.js';
17
17
  import { parseSql, DEFAULT_SQL_DIALECT, extractTableNames, whereFacts, isWriteStatement, isMassWriteStatement, isUpsertStatement, whereColumnRefs, isDdlStatementKind, } from '../../languages/sql/sqlAst.js';
18
+ // Item B (reissued) — vendor surfaces are resolved per-run from the tree under
19
+ // analysis (its own node_modules), never from a committed snapshot. The method
20
+ // sets below derive from those resolved surfaces via `deriveVendorSets`; a vendor
21
+ // whose declaration cannot be resolved contributes nothing (the honest `unproven`
22
+ // state), recorded with its reason on the `VendorSurfaces.resolutions` records.
23
+ // See specs/vendor-replica-probes.md for the reducible/irreducible mapping.
24
+ import { deriveVendorSets, EMPTY_VENDOR_SURFACES, } from '../vendorSurfaces.js';
18
25
  /**
19
26
  * SQL keywords recognized as evidence that a *dynamically-constructed* string is
20
27
  * an injection surface — the host-language dataflow gate in `checkQuerySecurity`
@@ -30,7 +37,6 @@ const SQL_KEYWORDS = [
30
37
  'CREATE', 'DROP', 'ALTER', 'TRUNCATE',
31
38
  ];
32
39
  export const DEFAULT_DATA_ACCESS_CONFIG = {
33
- checkOrgFilters: true,
34
40
  checkSQLInjection: true,
35
41
  skipTestFiles: true,
36
42
  organizationPatterns: [...DEFAULT_ORG_PREDICATE_PATTERNS],
@@ -47,17 +53,10 @@ export const DEFAULT_DATA_ACCESS_CONFIG = {
47
53
  orm: [
48
54
  /table\s*[:=]\s*["'`]?([\p{L}\p{N}_]+)["'`]?/giu
49
55
  ],
50
- queryBuilder: [/\.from\s*\(\s*["'`]?([\p{L}\p{N}_]+)["'`]?\s*\)/giu]
51
56
  },
52
57
  performanceThresholds: {
53
- complexQueryCount: 3,
54
- unfilteredQueryCount: 3,
55
58
  joinedTableCount: 4
56
59
  },
57
- securityPatterns: {
58
- sqlInjectionRisks: ['${', 'concat', 'string interpolation'],
59
- parameterizedQueries: ['?', ':param', '$1', 'prepared', 'parameterized']
60
- },
61
60
  // DB wrapper functions that accept (sql, params) — same as .prepare().bind()
62
61
  // but expressed as a simple function call. Must match the provenance system's
63
62
  // dbWrapperNames so the FP guards see the same capability list the detector does.
@@ -168,12 +167,15 @@ function hasChainCompanion(methods) {
168
167
  }
169
168
  return false;
170
169
  }
171
- /** Prisma CRUD verbs admitted by the object-form shape (admitter 2). */
172
- const PRISMA_VERBS = new Set([
173
- 'create', 'createMany', 'update', 'updateMany', 'upsert',
174
- 'delete', 'deleteMany', 'findUnique', 'findFirst', 'findMany',
175
- 'findUniqueOrThrow', 'findFirstOrThrow', 'findRaw', 'count', 'groupBy', 'aggregate',
176
- ]);
170
+ /**
171
+ * The reason a Prisma object-form site is `unproven`: the analyzed tree has no
172
+ * generated `.prisma/client`, so the model verb set (the union of the generated
173
+ * `*Delegate` members) cannot be derived and the verb cannot be confirmed. Item B
174
+ * (reissued): the verb set is no longer a committed `PRISMA_VERBS` snapshot — it
175
+ * is resolved per-run from the tree's own generated client, and its absence is
176
+ * this `unproven` reason, never a stale fallback.
177
+ */
178
+ const PRISMA_NOT_PRESENT_REASON = 'generated Prisma client `.prisma/client` is not present in the analyzed tree — the model verb set cannot be confirmed (run `prisma generate`)';
177
179
  /** Collect the property keys of an object literal, including its nested objects. */
178
180
  function collectObjectLiteralKeys(node, adapter, sourceCode) {
179
181
  const keys = [];
@@ -197,29 +199,44 @@ function collectObjectLiteralKeys(node, adapter, sourceCode) {
197
199
  * object-literal argument carries a `where` or `data` key. Option 1 (chained
198
200
  * companions) misses this because the `where`/`data` are keys inside an object
199
201
  * literal, not chained calls (blitz `prisma.user.update({ where, data })`).
200
- */
201
- function isPrismaObjectForm(node, adapter, sourceCode) {
202
+ *
203
+ * Tri-state (Item B, reissued): the verb is confirmed against the *derived*
204
+ * Prisma verb set (`vendor.prismaVerbs`), not a committed snapshot. When the
205
+ * shape matches (`<recv>.<Model>.<verb>` + `where`/`data` key) but the generated
206
+ * client is absent — so no verb set was derived — the verdict is `prisma-unproven`
207
+ * with {@link PRISMA_NOT_PRESENT_REASON}, never `not-prisma` (which would silently
208
+ * drop the site) and never a stale verb list.
209
+ */
210
+ function isPrismaObjectForm(node, adapter, sourceCode, prismaVerbs) {
202
211
  const callee = getCallExpressionCallee(node, adapter);
203
212
  if (!callee || adapter.getNodeType(callee) !== 'member_expression')
204
- return false;
213
+ return { kind: 'not-prisma' };
205
214
  // The chain must be `<recv>.<Model>.<verb>` — at least two member segments
206
215
  // (the Model and the verb) read from their property nodes. A subscript like
207
216
  // `obj["a.b"]` is not a Model segment, so `obj["a.b"].update(…)` has only one
208
217
  // segment and fails.
209
218
  const methods = collectChainMethods(callee, adapter, sourceCode);
210
219
  if (methods.length < 2)
211
- return false;
212
- if (!PRISMA_VERBS.has(methods[methods.length - 1]))
213
- return false;
220
+ return { kind: 'not-prisma' };
221
+ const verb = methods[methods.length - 1];
214
222
  const args = adapter.getChildren(node).find((c) => adapter.getNodeType(c) === 'arguments');
215
223
  if (!args)
216
- return false;
224
+ return { kind: 'not-prisma' };
225
+ let hasWhereOrData = false;
217
226
  for (const arg of adapter.getChildren(args)) {
218
227
  const keys = collectObjectLiteralKeys(arg, adapter, sourceCode);
219
- if (keys.includes('where') || keys.includes('data'))
220
- return true;
228
+ if (keys.includes('where') || keys.includes('data')) {
229
+ hasWhereOrData = true;
230
+ break;
231
+ }
221
232
  }
222
- return false;
233
+ if (!hasWhereOrData)
234
+ return { kind: 'not-prisma' };
235
+ // The shape matches. Confirm the verb against the derived set; an absent set
236
+ // (no generated client) is `unproven`, a confirmed non-verb is `not-prisma`.
237
+ if (prismaVerbs === null)
238
+ return { kind: 'prisma-unproven', reason: PRISMA_NOT_PRESENT_REASON };
239
+ return prismaVerbs.has(verb) ? { kind: 'prisma' } : { kind: 'not-prisma' };
223
240
  }
224
241
  /**
225
242
  * Spec 68 Thing 2 (#312) — the query-builder shape test.
@@ -242,23 +259,28 @@ function isPrismaObjectForm(node, adapter, sourceCode) {
242
259
  * such chain: the real misses are *variable-split* builders (the companion verb
243
260
  * lives in a prior statement, e.g. `const q = baseQuery.where(…)`), which
244
261
  * catalog resolution does not address. Gating on an under-discovered catalog
245
- * would just rebuild the receiver-name failure one layer up. See the
246
- * extraction-completeness gate (scripts/verify-extraction-completeness.ts) for
247
- * the pinned residual.
262
+ * would just rebuild the receiver-name failure one layer up.
248
263
  *
249
264
  * @param node the candidate call node to classify
250
265
  * @param adapter the language adapter used to read the callee
251
266
  * @param sourceCode the file source text for reading callee text
267
+ * @param prismaVerbs the derived Prisma model verb set (or null when the generated
268
+ * client is absent). A Prisma object form is admitted by shape even when this is
269
+ * null — the *confirmation* of the verb is what degrades to `unproven`, never the
270
+ * admission (see {@link isPrismaObjectForm}).
252
271
  * @returns true when the call is query-builder shaped (chain companion or Prisma object form)
253
272
  */
254
- export function isQueryBuilderShape(node, adapter, sourceCode) {
273
+ export function isQueryBuilderShape(node, adapter, sourceCode, prismaVerbs = null) {
255
274
  if (node.type !== 'call_expression')
256
275
  return false;
257
276
  const callee = getCallExpressionCallee(node, adapter);
258
277
  if (!callee)
259
278
  return false;
260
279
  const methods = collectChainMethods(callee, adapter, sourceCode);
261
- return hasChainCompanion(methods) || isPrismaObjectForm(node, adapter, sourceCode);
280
+ if (hasChainCompanion(methods))
281
+ return true;
282
+ const prismaVerdict = isPrismaObjectForm(node, adapter, sourceCode, prismaVerbs);
283
+ return prismaVerdict.kind !== 'not-prisma';
262
284
  }
263
285
  /**
264
286
  * The single admission seam for a candidate node: resolve its handle-ness through
@@ -470,14 +492,14 @@ function receiverIsThisRooted(callee, adapter) {
470
492
  * query-builder chain discovered by shape (Spec 68 Thing 2, #312).
471
493
  */
472
494
  function isDbCallCandidate(node, scan, tagNames = SQL_TAG_NAMES) {
473
- const { adapter, sourceCode } = scan;
495
+ const { adapter, sourceCode, vendor } = scan;
474
496
  // Spec 70 R1.2: a DB/ORM-method call whose receiver resolves handle-or-unproven.
475
497
  // `not-handle` (e.g. lodash `get`) and `null` (not query-shaped) are rejected.
476
498
  const verdict = handleVerdictForCall(node, scan);
477
499
  if (verdict !== null && verdict.kind !== 'not-handle')
478
500
  return true;
479
501
  // A query-builder chain is discovered by shape, not receiver name.
480
- if (isQueryBuilderShape(node, adapter, sourceCode)) {
502
+ if (isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs)) {
481
503
  return true;
482
504
  }
483
505
  // Tagged-template SQL is a data-access call by tag name — no provenance needed.
@@ -577,7 +599,7 @@ export function hasOrmFilterShape(node, adapter, sourceCode) {
577
599
  * look like a DB-related query.
578
600
  */
579
601
  function buildDatabaseCall(node, ast, scan) {
580
- const { adapter, sourceCode, config, provenanceContext } = scan;
602
+ const { adapter, sourceCode, config, provenanceContext, vendor } = scan;
581
603
  const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
582
604
  if (!nodeText || nodeText.trim().length < 10)
583
605
  return null;
@@ -633,8 +655,17 @@ function buildDatabaseCall(node, ast, scan) {
633
655
  // a type-annotated `db: D1Database` with a dynamic template argument now stays
634
656
  // visible instead of being dropped before the rules can see it.
635
657
  const handleVerdict = handleVerdictForCall(node, scan);
636
- const handleAdmits = handleVerdict !== null && handleVerdict.kind !== 'not-handle';
637
- const isSqlPosition = handleAdmits || isQueryBuilderShape(node, adapter, sourceCode);
658
+ // Item B (reissued) — a Prisma object-form site whose verb set cannot be
659
+ // confirmed (no generated `.prisma/client`) is `unproven` with that reason. It
660
+ // only fills a *null* handle verdict (no resolution evidence); a definitive
661
+ // `handle`/`not-handle` from declaration resolution always wins.
662
+ const prismaVerdict = isPrismaObjectForm(node, adapter, sourceCode, vendor.prismaVerbs);
663
+ const effectiveHandleVerdict = handleVerdict
664
+ ?? (prismaVerdict.kind === 'prisma-unproven'
665
+ ? { kind: 'unproven', reason: prismaVerdict.reason }
666
+ : null);
667
+ const handleAdmits = effectiveHandleVerdict !== null && effectiveHandleVerdict.kind !== 'not-handle';
668
+ const isSqlPosition = handleAdmits || isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs);
638
669
  const isSqlQuery = sqlOk !== null || isSqlPosition || isTaggedSqlCall;
639
670
  if (!isSqlQuery && !isOrmCall)
640
671
  return null;
@@ -642,7 +673,7 @@ function buildDatabaseCall(node, ast, scan) {
642
673
  const builderVerb = sqlAstNode ? null : builderWriteVerb(node, adapter, sourceCode);
643
674
  // Tables: SQL relations from the AST ∪ ORM-shaped references from the node.
644
675
  const sqlTables = sqlAstNode ? extractTableNames(sqlAstNode) : [];
645
- const ormTables = isOrmCall ? extractOrmTables(node, adapter, sourceCode, config) : [];
676
+ const ormTables = isOrmCall ? extractOrmTables(node, adapter, sourceCode, config, vendor) : [];
646
677
  const tables = [...new Set([...sqlTables, ...ormTables])];
647
678
  // Row-limiting filter: SQL WHERE/HAVING/LIMIT from the AST, or the ORM chain
648
679
  // shape when there is no SQL to walk.
@@ -660,7 +691,7 @@ function buildDatabaseCall(node, ast, scan) {
660
691
  // a proven `handle` — a parsed SQL argument (R3) or a package in the manifest
661
692
  // (R4) — fires. A type name proves nothing under criterion 9, so there is no
662
693
  // type-annotation signal to consult.
663
- const injectionRisk = security.injectionRisk && handleVerdict?.kind === 'handle';
694
+ const injectionRisk = security.injectionRisk && effectiveHandleVerdict?.kind === 'handle';
664
695
  const callType = classifyCallType(isSqlQuery, isOrmCall);
665
696
  return {
666
697
  type: callType,
@@ -685,7 +716,7 @@ function buildDatabaseCall(node, ast, scan) {
685
716
  sqlEscaped: security.escaped,
686
717
  enclosingFunction: enclosingIdentity(node, adapter, ast.filePath),
687
718
  resolvedWhere: resolveWhereBinding(node, sourceCode, adapter) ?? undefined,
688
- handleVerdict: handleVerdict ?? undefined,
719
+ handleVerdict: effectiveHandleVerdict ?? undefined,
689
720
  };
690
721
  }
691
722
  /**
@@ -1093,15 +1124,15 @@ function dbCallMethodName(node, adapter, sourceCode) {
1093
1124
  * which only construct a statement object). Used to decide whether a
1094
1125
  * prepare/bind call is chained into an eager execution.
1095
1126
  *
1096
- * `.get()` is the single-row read shared by better-sqlite3, `node:sqlite`, and
1097
- * `bun:sqlite` that D1 does not expose (D1's closest is `.first()`). It was
1098
- * absent, so `db.prepare(…).get(…)` in a loop was misread as statement
1099
- * construction and skipped — the exact false negative the corpus pins at
1100
- * `n-plus-one.ts:51`/`:62`. The wider driver-method gap (`.iterate()`, bun's
1101
- * `.values()`) is documented in specs/rule-evidence-corpus/REPORT.md; it is
1102
- * deliberately left out until it is measured, not silently dropped.
1127
+ * Item B (reissued): the eager set is no longer a module-level constant. It is
1128
+ * derived per-run from the tree's own vendor declarations via
1129
+ * `deriveVendorSets` and threaded on `scan.vendor.eagerMethods`. The eager
1130
+ * classification (which statement methods return rows vs. only configure the
1131
+ * statement) is still the documented judgment; the *names* now come from the
1132
+ * installed vendor's shipped `.d.ts` (better-sqlite3 `Statement`/`Database`,
1133
+ * node:sqlite `StatementSync`/`DatabaseSync`, D1 `D1PreparedStatement`/`D1Database`),
1134
+ * plus the generic `query` driver abstraction (`IndexHandle.query`).
1103
1135
  */
1104
- const EAGER_DB_METHODS = new Set(['run', 'all', 'first', 'raw', 'exec', 'batch', 'query', 'get']);
1105
1136
  /**
1106
1137
  * True when `node` (a `.prepare()`/`.bind()` call) is chained into an eager
1107
1138
  * method — e.g. `db.prepare(sql).bind(x).run()`. The `.run()` at the end of the
@@ -1109,7 +1140,7 @@ const EAGER_DB_METHODS = new Set(['run', 'all', 'first', 'raw', 'exec', 'batch',
1109
1140
  * N+1, not an accumulate-then-batch false positive. Walks up the enclosing
1110
1141
  * expression and stops at the statement boundary; a chain never crosses one.
1111
1142
  */
1112
- function hasEagerMethodInCallChain(node, adapter, sourceCode) {
1143
+ function hasEagerMethodInCallChain(node, adapter, sourceCode, vendor) {
1113
1144
  const call = resolveDbCallNode(node, adapter);
1114
1145
  let cur = call ? adapter.getParent(call) : adapter.getParent(node);
1115
1146
  while (cur) {
@@ -1117,7 +1148,7 @@ function hasEagerMethodInCallChain(node, adapter, sourceCode) {
1117
1148
  if (type === 'call_expression') {
1118
1149
  const memberExpr = findMemberCallee(cur, adapter);
1119
1150
  const name = memberExpr ? memberPropertyName(memberExpr, adapter, sourceCode) : null;
1120
- if (name && EAGER_DB_METHODS.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode))
1151
+ if (name && vendor.eagerMethods.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode))
1121
1152
  return true;
1122
1153
  }
1123
1154
  if (type === 'expression_statement' ||
@@ -1141,9 +1172,9 @@ function hasEagerMethodInCallChain(node, adapter, sourceCode) {
1141
1172
  * `.first()` / `.raw()` / `.exec()` / `.batch()` / `.get()`). Such a call
1142
1173
  * performs no I/O, so it is not a query-in-loop on its own (Spec 52 R1).
1143
1174
  */
1144
- function isStatementConstructionOnly(node, adapter, sourceCode) {
1175
+ function isStatementConstructionOnly(node, adapter, sourceCode, vendor) {
1145
1176
  const method = dbCallMethodName(node, adapter, sourceCode);
1146
- return (method === 'prepare' || method === 'bind') && !hasEagerMethodInCallChain(node, adapter, sourceCode);
1177
+ return (method === 'prepare' || method === 'bind') && !hasEagerMethodInCallChain(node, adapter, sourceCode, vendor);
1147
1178
  }
1148
1179
  /**
1149
1180
  * True when a db-call node is SQL-string construction inside a query compiler:
@@ -1156,9 +1187,9 @@ function isStatementConstructionOnly(node, adapter, sourceCode) {
1156
1187
  * (`run`/`all`/`exec`/`query`/…) are excluded, so a genuine
1157
1188
  * `results.push(db.query(...))` still fires.
1158
1189
  */
1159
- function isSqlStringConstruction(node, adapter, sourceCode) {
1190
+ function isSqlStringConstruction(node, adapter, sourceCode, vendor) {
1160
1191
  const method = dbCallMethodName(node, adapter, sourceCode);
1161
- if (!method || EAGER_DB_METHODS.has(method))
1192
+ if (!method || vendor.eagerMethods.has(method))
1162
1193
  return false;
1163
1194
  if (!isOrmMethod(method))
1164
1195
  return false;
@@ -1252,8 +1283,9 @@ function isInExecChain(node, adapter, sourceCode) {
1252
1283
  return hasSpreadArgument(execCall, adapter);
1253
1284
  }
1254
1285
  /**
1255
- * Detect eager execution methods called with bind parameters as a second
1256
- * argument — D1's `.all()`/`.first()`/`.run()`, plus the parameterized
1286
+ * Detect eager-execution methods called with bind parameters as a second
1287
+ * argument — D1's `.all()`/`.first()`/`.run()`, better-sqlite3's `.get()` /
1288
+ * `.iterate()`, node:sqlite's `.get()` / `.all()`, plus the parameterized
1257
1289
  * `.query()`/`.execute()` form shared by node-postgres/mysql2 and the tool's
1258
1290
  * own `IndexHandle`.
1259
1291
  *
@@ -1268,18 +1300,40 @@ function isInExecChain(node, adapter, sourceCode) {
1268
1300
  * `?`-placeholder clauses (`${fp.clause}`, `${inList(types)}`) and `params`
1269
1301
  * binds the values out-of-band. A single-arg `.query(\`…${input}\`)` — the raw
1270
1302
  * injection shape — stays flagged because it carries no bind argument.
1303
+ *
1304
+ * Item B (reissued): the method set is no longer a hand-kept `D1_CONVENIENCE`
1305
+ * list — it is the cross-driver eager-execution surface (`vendor.eagerExecutionMethods`),
1306
+ * the union of whichever driver surfaces resolved in the analyzed tree. A tree
1307
+ * with only better-sqlite3 gets better-sqlite3's methods and nothing else.
1308
+ */
1309
+ /**
1310
+ * True when every `${…}` interpolation in the eager-execution call's SQL
1311
+ * argument is provably structural — a placeholder list (`.map(() => '?').join`),
1312
+ * a literal clause fragment, or a safe identifier — and NOT a bound runtime
1313
+ * value. Argument count alone does not establish this: `db.run(\`…\${name}\`,
1314
+ * [name])` carries two arguments yet interpolates a value, so the site stays
1315
+ * flagged. Reuses {@link isSafeDynamicPart}, the same classifier the non-eager
1316
+ * path applies, so "structural vs value-bearing" has one definition.
1271
1317
  */
1272
- function isD1ConvenienceCall(node, adapter, sourceCode) {
1273
- const D1_CONVENIENCE = new Set(['all', 'first', 'run', 'query', 'execute']);
1318
+ function allInterpolationsStructural(node, scan) {
1319
+ const { adapter, sourceCode } = scan;
1320
+ if (!adapter.getDynamicParts)
1321
+ return false;
1322
+ const parts = adapter.getDynamicParts(node, sourceCode);
1323
+ return parts.every((part) => isSafeDynamicPart(part, scan.ast, scan));
1324
+ }
1325
+ function isEagerExecutionCall(node, adapter, sourceCode, eagerExecutionMethods, scan) {
1274
1326
  const call = findCallFromEntry(node, adapter);
1275
1327
  if (!call)
1276
1328
  return false;
1277
1329
  const callee = findMemberCallee(call, adapter);
1278
1330
  const method = callee ? memberPropertyName(callee, adapter, sourceCode) : null;
1279
- if (!method || !D1_CONVENIENCE.has(method))
1331
+ if (!method || !eagerExecutionMethods || !eagerExecutionMethods.has(method))
1280
1332
  return false;
1281
- // Bind params as a second argument → fully parameterized and safe.
1282
- return countArgs(call, adapter) >= 2;
1333
+ // Bind params as a second argument AND every interpolation structural → fully
1334
+ // parameterized. Argument count is not evidence of parameterization: a value
1335
+ // interpolation (`${name}`) stays flagged even with a bind-params argument.
1336
+ return countArgs(call, adapter) >= 2 && allInterpolationsStructural(node, scan);
1283
1337
  }
1284
1338
  /**
1285
1339
  * Detect simple-function-call DB wrappers with bind parameters.
@@ -1294,7 +1348,7 @@ function isD1ConvenienceCall(node, adapter, sourceCode) {
1294
1348
  *
1295
1349
  * Entry point: a template_string inside the wrapper's arguments.
1296
1350
  */
1297
- function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames) {
1351
+ function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames, scan) {
1298
1352
  if (wrapperNames.length === 0)
1299
1353
  return false;
1300
1354
  if (adapter.getNodeType(node) !== 'template_string')
@@ -1317,10 +1371,12 @@ function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames
1317
1371
  const calleeName = adapter.getNodeText(callee, sourceCode);
1318
1372
  if (!wrapperNames.includes(calleeName))
1319
1373
  return false;
1320
- // Check for a second argument — the bind params. A single-arg call
1321
- // like d1Query(sql) without params has no runtime parameterization.
1374
+ // Check for a second argument — the bind params — AND every interpolation
1375
+ // structural. A single-arg call (d1Query(sql) without params) has no runtime
1376
+ // parameterization, and a two-arg call whose template interpolates a value is
1377
+ // raw assembly, not parameterized (same discriminant as eager-execution).
1322
1378
  const realArgs = adapter.getChildren(args).filter(c => !['(', ')', ','].includes(adapter.getNodeType(c)));
1323
- return realArgs.length >= 2;
1379
+ return realArgs.length >= 2 && allInterpolationsStructural(node, scan);
1324
1380
  }
1325
1381
  /**
1326
1382
  * Check the two-statement prepare→bind pattern: db.prepare() is assigned to
@@ -1527,10 +1583,6 @@ function ormTableNameFromArgument(node, adapter, sourceCode) {
1527
1583
  const literal = stringLiteralValue(node, adapter, sourceCode);
1528
1584
  return literal && literal.length > 0 ? literal : null;
1529
1585
  }
1530
- /** Kysely builder verbs whose first argument is the table name. */
1531
- const ORM_TABLE_VERBS = new Set(['selectFrom', 'deleteFrom', 'insertInto', 'updateTable']);
1532
- /** ORM join verbs whose first argument is the joined table. */
1533
- const ORM_JOIN_VERBS = new Set(['join', 'leftJoin', 'rightJoin', 'innerJoin']);
1534
1586
  /**
1535
1587
  * Walk the callee chain of an ORM candidate, reading table references from the
1536
1588
  * *structure* (member names + argument nodes) rather than the source text. A
@@ -1540,10 +1592,12 @@ const ORM_JOIN_VERBS = new Set(['join', 'leftJoin', 'rightJoin', 'innerJoin']);
1540
1592
  * Three shapes are read:
1541
1593
  * - `.from(table)` / `.from('table')` (except JS `Array.from`/`Buffer.from`/
1542
1594
  * `String.from`/`*Array.from` construction),
1543
- * - a Kysely/join verb carrying the table as its first argument,
1595
+ * - a Kysely/join verb carrying the table as its first argument (the table and
1596
+ * join verbs are `vendor.ormTableVerbs`/`vendor.ormJoinVerbs` — derived
1597
+ * per-run from the tree's Kysely declaration, item B reissued),
1544
1598
  * - Drizzle's `db.<table>.<method>()` table-access idiom.
1545
1599
  */
1546
- function collectOrmTableReferences(node, adapter, sourceCode, tables) {
1600
+ function collectOrmTableReferences(node, adapter, sourceCode, tables, vendor) {
1547
1601
  const t = adapter.getNodeType(node);
1548
1602
  if (t === 'call_expression') {
1549
1603
  const callee = getCallExpressionCallee(node, adapter);
@@ -1561,8 +1615,8 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
1561
1615
  const isFrom = method === 'from'
1562
1616
  && !(receiverName && (receiverName === 'Buffer' || receiverName === 'String'
1563
1617
  || receiverName.endsWith('Array')));
1564
- const isVerb = ORM_TABLE_VERBS.has(method);
1565
- const isJoin = ORM_JOIN_VERBS.has(method) && receiverName !== 'sql';
1618
+ const isVerb = vendor.ormTableVerbs.has(method);
1619
+ const isJoin = vendor.ormJoinVerbs.has(method) && receiverName !== 'sql';
1566
1620
  if (isFrom || isVerb || isJoin) {
1567
1621
  const name = ormTableNameFromArgument(firstArg, adapter, sourceCode);
1568
1622
  if (name)
@@ -1570,7 +1624,7 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
1570
1624
  }
1571
1625
  }
1572
1626
  }
1573
- collectOrmTableReferences(callee, adapter, sourceCode, tables);
1627
+ collectOrmTableReferences(callee, adapter, sourceCode, tables, vendor);
1574
1628
  }
1575
1629
  }
1576
1630
  else if (t === 'member_expression' || t === 'selector_expression') {
@@ -1588,12 +1642,12 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
1588
1642
  }
1589
1643
  }
1590
1644
  if (object)
1591
- collectOrmTableReferences(object, adapter, sourceCode, tables);
1645
+ collectOrmTableReferences(object, adapter, sourceCode, tables, vendor);
1592
1646
  }
1593
1647
  else if (t === 'subscript_expression') {
1594
1648
  const object = memberObjectNode(node, adapter);
1595
1649
  if (object)
1596
- collectOrmTableReferences(object, adapter, sourceCode, tables);
1650
+ collectOrmTableReferences(object, adapter, sourceCode, tables, vendor);
1597
1651
  }
1598
1652
  }
1599
1653
  /**
@@ -1609,11 +1663,12 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
1609
1663
  * @param adapter The language adapter (drives node traversal).
1610
1664
  * @param sourceCode The file source text.
1611
1665
  * @param config The data-access analyzer config holding table patterns.
1666
+ * @param vendor The per-run derived vendor method sets (item B reissued).
1612
1667
  * @returns The set of table names referenced by ORM shapes.
1613
1668
  */
1614
- export function extractOrmTables(node, adapter, sourceCode, config) {
1669
+ export function extractOrmTables(node, adapter, sourceCode, config, vendor) {
1615
1670
  const tables = new Set();
1616
- collectOrmTableReferences(node, adapter, sourceCode, tables);
1671
+ collectOrmTableReferences(node, adapter, sourceCode, tables, vendor);
1617
1672
  // User-configured `orm` patterns are arbitrary text shapes with no node to
1618
1673
  // read (e.g. `table: 'users'`), so they still run over the source text.
1619
1674
  const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
@@ -1654,14 +1709,15 @@ function isUnfilteredRead(call, config) {
1654
1709
  * wrapper function with bind params). All four short-circuit checkQuerySecurity
1655
1710
  * to a "safe" result.
1656
1711
  */
1657
- function isParameterizedByChain(node, adapter, sourceCode, config, wrapperNames) {
1712
+ function isParameterizedByChain(node, scan, wrapperNames) {
1713
+ const { adapter, sourceCode, vendor } = scan;
1658
1714
  if (isInPrepareBindChain(node, adapter, sourceCode))
1659
1715
  return true;
1660
1716
  if (isInExecChain(node, adapter, sourceCode))
1661
1717
  return true;
1662
- if (isD1ConvenienceCall(node, adapter, sourceCode))
1718
+ if (isEagerExecutionCall(node, adapter, sourceCode, vendor.eagerExecutionMethods, scan))
1663
1719
  return true;
1664
- if (isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames))
1720
+ if (isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames, scan))
1665
1721
  return true;
1666
1722
  return false;
1667
1723
  }
@@ -1786,10 +1842,10 @@ function isParameterizedTaggedTemplate(node, adapter, sourceCode, tagNames) {
1786
1842
  return true;
1787
1843
  }
1788
1844
  function checkQuerySecurity(node, text, ast, scan) {
1789
- const { adapter, sourceCode, config } = scan;
1790
- // Parameterized chains (.prepare().bind(), .exec() spread, D1 convenience,
1791
- // DB wrappers) and explicit parameterization are always safe.
1792
- if (isParameterizedByChain(node, adapter, sourceCode, config, effectiveWrapperNames(scan))) {
1845
+ const { adapter, sourceCode, config, vendor } = scan;
1846
+ // Parameterized chains (.prepare().bind(), .exec() spread, eager-execution
1847
+ // method, DB wrappers) and explicit parameterization are always safe.
1848
+ if (isParameterizedByChain(node, scan, effectiveWrapperNames(scan))) {
1793
1849
  return { parameterized: true, injectionRisk: false, escaped: false };
1794
1850
  }
1795
1851
  // A drizzle `sql`/`db` tagged template parameterizes bare `${id}` interpolations
@@ -1798,9 +1854,14 @@ function checkQuerySecurity(node, text, ast, scan) {
1798
1854
  if (isParameterizedTaggedTemplate(node, adapter, sourceCode, config.sqlTagNames ?? SQL_TAG_NAMES)) {
1799
1855
  return { parameterized: true, injectionRisk: false, escaped: false };
1800
1856
  }
1801
- if ((config.securityPatterns?.parameterizedQueries || []).some(p => text.includes(p))) {
1802
- return { parameterized: true, injectionRisk: false, escaped: false };
1803
- }
1857
+ // There is deliberately NO whole-text substring test for "parameterized"
1858
+ // markers (`prepared`, `$1`, `:param`, …) ahead of the per-interpolation
1859
+ // classifier below. A presence test on the query *text* can never establish
1860
+ // that a particular `${…}` interpolation is safe — only isSafeDynamicPart can.
1861
+ // Any such short-circuit is a false-negative generator: a column named
1862
+ // `prepared_at`, a `$100` literal, or a `':param'` string would clear a
1863
+ // value-bearing `${id}` before isSafeDynamicPart ever ran. The
1864
+ // `parameterizedQueries` config key was removed with it (no other consumer).
1804
1865
  // No dynamic-string capability → can't prove unsafe; err quiet.
1805
1866
  if (!adapter.isDynamicStringConstruction || !adapter.getDynamicParts) {
1806
1867
  return { parameterized: false, injectionRisk: false, escaped: false };
@@ -1975,7 +2036,7 @@ function checkLoopQueries(ast, scan) {
1975
2036
  * query) and the LLM/queue suppression live here — they need the AST.
1976
2037
  */
1977
2038
  function collectLoopQueryCandidates(ast, scan) {
1978
- const { adapter, sourceCode } = scan;
2039
+ const { adapter, sourceCode, vendor } = scan;
1979
2040
  const candidates = [];
1980
2041
  // Spec 21: provenance-gated detection of database calls.
1981
2042
  const dbNodes = adapter.findNodes(ast, {
@@ -2001,13 +2062,13 @@ function collectLoopQueryCandidates(ast, scan) {
2001
2062
  continue;
2002
2063
  // Spec 52 R1 — skip statement construction (prepare/bind with no eager call);
2003
2064
  // an eager call or a prepare chained into one still fires.
2004
- if (isStatementConstructionOnly(node, adapter, sourceCode))
2065
+ if (isStatementConstructionOnly(node, adapter, sourceCode, vendor))
2005
2066
  continue;
2006
2067
  // Spec 52 R1 (extension) — skip SQL-string construction inside a query
2007
2068
  // compiler: an ORM builder method (e.g. `aggregate`) on an unprovenanced
2008
2069
  // `this` whose result is accumulated via `.push(...)` returns a string
2009
2070
  // fragment, not a query result (knex `sql.push(...this.aggregate(stmt))`).
2010
- if (isSqlStringConstruction(node, adapter, sourceCode))
2071
+ if (isSqlStringConstruction(node, adapter, sourceCode, vendor))
2011
2072
  continue;
2012
2073
  const loopInfo = findEnclosingLoop(node, adapter, sourceCode);
2013
2074
  if (!loopInfo)
@@ -2053,7 +2114,7 @@ function collectLoopQueryCandidates(ast, scan) {
2053
2114
  const preparesInLoop = loopBodyContainsPrepare(loopInfo.loopNode, adapter, sourceCode);
2054
2115
  const hoistedReuse = !preparesInLoop &&
2055
2116
  (transactionEnclosesLoop ||
2056
- isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode));
2117
+ isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode, vendor));
2057
2118
  if (hoistedReuse) {
2058
2119
  continue;
2059
2120
  }
@@ -2063,7 +2124,7 @@ function collectLoopQueryCandidates(ast, scan) {
2063
2124
  // a chunked `id IN (…)` write is not an N+1 at any chunk size. Only a scalar
2064
2125
  // or property read off a single loop element (`run(row.id)`) is per-row. See
2065
2126
  // `bindsBatchArgument`.
2066
- if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode)) {
2127
+ if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode, vendor)) {
2067
2128
  continue;
2068
2129
  }
2069
2130
  // R4.1 (Spec 46): LLM-pipeline discriminator. A loop whose body invokes an
@@ -2186,8 +2247,8 @@ function loopBodyContainsPrepare(loopNode, adapter, sourceCode) {
2186
2247
  * is NOT prepare-bound, so none of them reads as a statement re-run and each
2187
2248
  * keeps firing.
2188
2249
  */
2189
- function isHoistedStatementReRun(node, loopNode, adapter, sourceCode) {
2190
- const base = memberChainBaseIdentifier(node, adapter, sourceCode);
2250
+ function isHoistedStatementReRun(node, loopNode, adapter, sourceCode, vendor) {
2251
+ const base = memberChainBaseIdentifier(node, adapter, sourceCode, vendor);
2191
2252
  if (base && identifierBoundToPrepareOutsideLoop(base, loopNode, adapter, sourceCode)) {
2192
2253
  return true;
2193
2254
  }
@@ -2230,8 +2291,8 @@ function isBarePrepareBoundHelperCall(node, loopNode, adapter, sourceCode) {
2230
2291
  * Returns null when the chain is not a member call (a bare `helper(db, …)`) or
2231
2292
  * has no identifier root (an indexed access `x[i]()`).
2232
2293
  */
2233
- function memberChainBaseIdentifier(node, adapter, sourceCode) {
2234
- const eagerCall = findEagerExecutionCall(node, adapter, sourceCode);
2294
+ function memberChainBaseIdentifier(node, adapter, sourceCode, vendor) {
2295
+ const eagerCall = findEagerExecutionCall(node, adapter, sourceCode, vendor);
2235
2296
  if (!eagerCall)
2236
2297
  return null;
2237
2298
  const memberExpr = findMemberCallee(eagerCall, adapter);
@@ -2468,8 +2529,15 @@ function walkSubtree(node, adapter, visitor) {
2468
2529
  * Array methods that transform one collection into another collection (a batch):
2469
2530
  * the elements of a `.slice()`/`.splice()`/`.map()`/`.filter()`/`.concat()`/
2470
2531
  * `.flatMap()` result are a *sub*-collection, not a single row.
2532
+ *
2533
+ * Read from the ECMAScript runtime (item B): each name is validated against
2534
+ * `Array.prototype` rather than hand-copied. The *selection* — which array
2535
+ * methods return a collection (`map`/`filter`/`concat`/`flatMap`/`slice`/
2536
+ * `splice`) vs. which consume or reduce one (`forEach`/`reduce`/`find`/`some`/
2537
+ * `every`) — is the documented judgment; the names are the runtime's.
2471
2538
  */
2472
- const COLLECTION_TRANSFORM_METHODS = new Set(['slice', 'splice', 'map', 'filter', 'concat', 'flatMap']);
2539
+ export const COLLECTION_TRANSFORM_METHODS = new Set(['slice', 'splice', 'map', 'filter', 'concat', 'flatMap']
2540
+ .filter((m) => typeof Array.prototype[m] === 'function'));
2473
2541
  /**
2474
2542
  * Free-function callee names that chunk a collection into batches — `chunk(ids,
2475
2543
  * n)`, `chunkArray(ids, n)`, `partition(rows, p)`. Recognized by name (not by a
@@ -2626,8 +2694,8 @@ function forOfIteration(loopNode, adapter, sourceCode) {
2626
2694
  * the `.run(…)` chained off `db.prepare(…)`) is where parameters are bound; its
2627
2695
  * arguments decide the row-vs-batch question.
2628
2696
  */
2629
- function bindsBatchArgument(node, loopNode, adapter, sourceCode) {
2630
- const eagerCall = findEagerExecutionCall(node, adapter, sourceCode);
2697
+ function bindsBatchArgument(node, loopNode, adapter, sourceCode, vendor) {
2698
+ const eagerCall = findEagerExecutionCall(node, adapter, sourceCode, vendor);
2631
2699
  if (!eagerCall)
2632
2700
  return false;
2633
2701
  const argsNode = adapter.getChildren(eagerCall).find((c) => adapter.getNodeType(c) === 'arguments');
@@ -2672,12 +2740,12 @@ function bindsBatchArgument(node, loopNode, adapter, sourceCode) {
2672
2740
  * chained off a statement-construction call (`db.prepare(sql).run(…)`). Returns
2673
2741
  * null when the call never executes I/O (already filtered upstream).
2674
2742
  */
2675
- function findEagerExecutionCall(node, adapter, sourceCode) {
2743
+ function findEagerExecutionCall(node, adapter, sourceCode, vendor) {
2676
2744
  const call = resolveDbCallNode(node, adapter);
2677
2745
  if (!call)
2678
2746
  return null;
2679
2747
  const direct = dbCallMethodName(call, adapter, sourceCode);
2680
- if (direct && EAGER_DB_METHODS.has(direct)) {
2748
+ if (direct && vendor.eagerMethods.has(direct)) {
2681
2749
  const memberExpr = findMemberCallee(call, adapter);
2682
2750
  if (!(memberExpr && isPromiseAllMember(memberExpr, adapter, sourceCode)))
2683
2751
  return call;
@@ -2688,7 +2756,7 @@ function findEagerExecutionCall(node, adapter, sourceCode) {
2688
2756
  if (type === 'call_expression') {
2689
2757
  const memberExpr = findMemberCallee(cur, adapter);
2690
2758
  const name = memberExpr ? memberPropertyName(memberExpr, adapter, sourceCode) : null;
2691
- if (name && EAGER_DB_METHODS.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode)) {
2759
+ if (name && vendor.eagerMethods.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode)) {
2692
2760
  return cur;
2693
2761
  }
2694
2762
  }
@@ -2894,7 +2962,7 @@ function buildGoEnv(ast, adapter, sourceCode, provenanceContext) {
2894
2962
  sourceCode,
2895
2963
  };
2896
2964
  }
2897
- function buildDataAccessScan(ast, adapter, sourceCode, config) {
2965
+ function buildDataAccessScan(ast, adapter, sourceCode, config, vendorSurfaces) {
2898
2966
  const finalConfig = { ...DEFAULT_DATA_ACCESS_CONFIG, ...config };
2899
2967
  const detectionMode = finalConfig.detection?.mode ?? 'hybrid';
2900
2968
  const seedProvenance = config?._receiverProvenance;
@@ -2916,6 +2984,7 @@ function buildDataAccessScan(ast, adapter, sourceCode, config) {
2916
2984
  provenanceContext,
2917
2985
  handleEnv: buildHandleEnv(ast, adapter, sourceCode, provenanceContext),
2918
2986
  goEnv: buildGoEnv(ast, adapter, sourceCode, provenanceContext),
2987
+ vendor: deriveVendorSets(vendorSurfaces ?? EMPTY_VENDOR_SURFACES),
2919
2988
  };
2920
2989
  }
2921
2990
  /**
@@ -2966,7 +3035,7 @@ export function extractLoopQueries(ast, adapter, sourceCode, config) {
2966
3035
  * producer sees), so every provenance-dependent signal is captured as an identity
2967
3036
  * and re-folded later. `handleEnv`/`goEnv` still build their provenance-free
2968
3037
  * bindings/imports, which the structural discovery predicates read. */
2969
- function buildCandidateScan(ast, adapter, sourceCode, config) {
3038
+ function buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces) {
2970
3039
  const finalConfig = { ...DEFAULT_DATA_ACCESS_CONFIG, ...config };
2971
3040
  const provenanceContext = {
2972
3041
  dbProvenanced: new Map(),
@@ -2982,6 +3051,7 @@ function buildCandidateScan(ast, adapter, sourceCode, config) {
2982
3051
  provenanceContext,
2983
3052
  handleEnv: buildHandleEnv(ast, adapter, sourceCode, provenanceContext),
2984
3053
  goEnv: buildGoEnv(ast, adapter, sourceCode, provenanceContext),
3054
+ vendor: deriveVendorSets(vendorSurfaces ?? EMPTY_VENDOR_SURFACES),
2985
3055
  };
2986
3056
  }
2987
3057
  // ── Structural discovery (empty-provenance supersets) ────────────────────────
@@ -3053,8 +3123,8 @@ function isTemplateOfDbCall(node, scan) {
3053
3123
  * variable assignment) are provenance-free; the identifier/member/template arms
3054
3124
  * cover every `handleVerdictForCall` shape with the empty seed. */
3055
3125
  function isBroadenedDataAccessCandidate(node, scan, tagNames) {
3056
- const { adapter, sourceCode } = scan;
3057
- if (isQueryBuilderShape(node, adapter, sourceCode))
3126
+ const { adapter, sourceCode, vendor } = scan;
3127
+ if (isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs))
3058
3128
  return true;
3059
3129
  if (isTaggedTemplateSqlCall(node, adapter, sourceCode, tagNames))
3060
3130
  return true;
@@ -3203,7 +3273,7 @@ function wrapperCalleeNameForTemplate(node, adapter, sourceCode) {
3203
3273
  * the corpus `data-access-calls` producer re-folds. The static-security arms run here
3204
3274
  * on the empty-provenance scan; only `arm4CalleeName` is corrected corpus-side. */
3205
3275
  function buildDataAccessCallCandidate(node, ast, scan) {
3206
- const { adapter, sourceCode, config } = scan;
3276
+ const { adapter, sourceCode, config, vendor } = scan;
3207
3277
  const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
3208
3278
  if (!nodeText || nodeText.trim().length < 10)
3209
3279
  return null;
@@ -3213,7 +3283,7 @@ function buildDataAccessCallCandidate(node, ast, scan) {
3213
3283
  const isOrmCall = isOrmPattern(nodeText);
3214
3284
  const tagNames = config.sqlTagNames ?? SQL_TAG_NAMES;
3215
3285
  const isTaggedSqlCall = isTaggedTemplateSqlCall(node, adapter, sourceCode, tagNames);
3216
- const isQueryBuilder = isQueryBuilderShape(node, adapter, sourceCode);
3286
+ const isQueryBuilder = isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs);
3217
3287
  const isVariableAssignmentSql = isVariableAssignment(node, adapter) && sqlArg !== null;
3218
3288
  const isTemplate = isTemplateLiteral(node, adapter);
3219
3289
  const handle = handleCallSiteIdentity(node, scan);
@@ -3249,7 +3319,7 @@ function buildDataAccessCallCandidate(node, ast, scan) {
3249
3319
  hasOrganizationFilter: hasOrganizationFilter(nodeText, config),
3250
3320
  enclosingFunction: enclosingIdentity(node, adapter, ast.filePath),
3251
3321
  resolvedWhere: resolveWhereBinding(node, sourceCode, adapter),
3252
- ormTables: isOrmCall ? extractOrmTables(node, adapter, sourceCode, config) : [],
3322
+ ormTables: isOrmCall ? extractOrmTables(node, adapter, sourceCode, config, vendor) : [],
3253
3323
  builderVerb: builderWriteVerb(node, adapter, sourceCode),
3254
3324
  ormHasFilter: hasOrmFilterShape(node, adapter, sourceCode),
3255
3325
  staticParameterized: security.parameterized,
@@ -3269,10 +3339,12 @@ function buildDataAccessCallCandidate(node, ast, scan) {
3269
3339
  * @param adapter - The language adapter (drives node discovery + text reads).
3270
3340
  * @param sourceCode - The file's source text.
3271
3341
  * @param config - Optional data-access config; defaults to the shared config.
3342
+ * @param vendorSurfaces - Optional per-run resolved vendor surfaces (item B
3343
+ * reissued); defaults to the empty set (no vendor resolved).
3272
3344
  * @returns The raw, provenance-free `data-access-calls-candidates`.
3273
3345
  */
3274
- export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config) {
3275
- const scan = buildCandidateScan(ast, adapter, sourceCode, config);
3346
+ export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config, vendorSurfaces) {
3347
+ const scan = buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces);
3276
3348
  const tagNames = scan.config.sqlTagNames ?? SQL_TAG_NAMES;
3277
3349
  const nodes = adapter.findNodes(ast, {
3278
3350
  custom: (node) => isBroadenedDataAccessCandidate(node, scan, tagNames),
@@ -3298,10 +3370,12 @@ export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config
3298
3370
  * @param adapter - The language adapter (drives node discovery + text reads).
3299
3371
  * @param sourceCode - The file's source text.
3300
3372
  * @param config - Optional data-access config; defaults to the shared config.
3373
+ * @param vendorSurfaces - Optional per-run resolved vendor surfaces (item B
3374
+ * reissued); defaults to the empty set (no vendor resolved).
3301
3375
  * @returns The raw, provenance-free `loop-query-candidates`.
3302
3376
  */
3303
- export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config) {
3304
- const scan = buildCandidateScan(ast, adapter, sourceCode, config);
3377
+ export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config, vendorSurfaces) {
3378
+ const scan = buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces);
3305
3379
  const candidates = [];
3306
3380
  const dbNodes = adapter.findNodes(ast, {
3307
3381
  custom: (node) => isBroadenedLoopCandidate(node, scan),
@@ -3317,9 +3391,9 @@ export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config)
3317
3391
  const nodeText = adapter.getNodeText(node, sourceCode);
3318
3392
  if (!nodeText || nodeText.trim().length < 10)
3319
3393
  continue;
3320
- if (isStatementConstructionOnly(node, adapter, sourceCode))
3394
+ if (isStatementConstructionOnly(node, adapter, sourceCode, scan.vendor))
3321
3395
  continue;
3322
- if (isSqlStringConstruction(node, adapter, sourceCode))
3396
+ if (isSqlStringConstruction(node, adapter, sourceCode, scan.vendor))
3323
3397
  continue;
3324
3398
  const loopInfo = findEnclosingLoop(node, adapter, sourceCode);
3325
3399
  if (!loopInfo)
@@ -3331,10 +3405,10 @@ export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config)
3331
3405
  (enclosingFnName !== null && transactionWrappedFunctions.has(enclosingFnName));
3332
3406
  const preparesInLoop = loopBodyContainsPrepare(loopInfo.loopNode, adapter, sourceCode);
3333
3407
  const hoistedReuse = !preparesInLoop &&
3334
- (transactionEnclosesLoop || isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode));
3408
+ (transactionEnclosesLoop || isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode, scan.vendor));
3335
3409
  if (hoistedReuse)
3336
3410
  continue;
3337
- if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode))
3411
+ if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode, scan.vendor))
3338
3412
  continue;
3339
3413
  if (loopBodyContainsLlmCall(loopInfo.loopNode, adapter, sourceCode))
3340
3414
  continue;