code-auditor-mcp 3.1.0 → 3.4.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 (360) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +665 -1
  3. package/README.md +85 -11
  4. package/dist/__test_tmp__/spec18_test.d.ts +2 -0
  5. package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
  6. package/dist/__test_tmp__/spec18_test.js +5 -0
  7. package/dist/__test_tmp__/spec18_test.js.map +1 -0
  8. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
  9. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
  11. package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
  12. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
  13. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
  15. package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
  16. package/dist/analyzers/analyzerUtils.js +2 -2
  17. package/dist/analyzers/analyzerUtils.js.map +1 -1
  18. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
  19. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  20. package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -4
  21. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  22. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
  23. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -5
  25. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  26. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  27. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +0 -3
  28. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  29. package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
  30. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  31. package/dist/analyzers/cross-language/SchemaValidator.js +2 -5
  32. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  33. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
  34. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
  35. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
  36. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
  37. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  38. package/dist/analyzers/documentationAnalyzer.js +8 -4
  39. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  40. package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
  41. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
  42. package/dist/analyzers/orm/adapterRegistry.js +73 -0
  43. package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
  44. package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
  45. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
  46. package/dist/analyzers/orm/drizzleAdapter.js +166 -0
  47. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
  48. package/dist/analyzers/orm/index.d.ts +14 -0
  49. package/dist/analyzers/orm/index.d.ts.map +1 -0
  50. package/dist/analyzers/orm/index.js +24 -0
  51. package/dist/analyzers/orm/index.js.map +1 -0
  52. package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
  53. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
  54. package/dist/analyzers/orm/prismaAdapter.js +170 -0
  55. package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
  56. package/dist/analyzers/orm/types.d.ts +63 -0
  57. package/dist/analyzers/orm/types.d.ts.map +1 -0
  58. package/dist/analyzers/orm/types.js +10 -0
  59. package/dist/analyzers/orm/types.js.map +1 -0
  60. package/dist/analyzers/provenance.d.ts +133 -0
  61. package/dist/analyzers/provenance.d.ts.map +1 -0
  62. package/dist/analyzers/provenance.js +1041 -0
  63. package/dist/analyzers/provenance.js.map +1 -0
  64. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  65. package/dist/analyzers/reactAnalyzer.js +117 -1
  66. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  67. package/dist/analyzers/ruleRegistry.d.ts +38 -0
  68. package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
  69. package/dist/analyzers/ruleRegistry.js +123 -0
  70. package/dist/analyzers/ruleRegistry.js.map +1 -0
  71. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
  72. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
  73. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
  74. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
  75. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
  76. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  77. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
  78. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  79. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
  80. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  81. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -103
  82. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  83. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  84. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
  85. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  86. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  87. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
  88. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  89. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
  90. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  91. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
  92. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  93. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
  94. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
  95. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
  96. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
  97. package/dist/auditRunner.d.ts.map +1 -1
  98. package/dist/auditRunner.js +483 -3
  99. package/dist/auditRunner.js.map +1 -1
  100. package/dist/baseline.d.ts +98 -0
  101. package/dist/baseline.d.ts.map +1 -0
  102. package/dist/baseline.js +184 -0
  103. package/dist/baseline.js.map +1 -0
  104. package/dist/churn/churnExtractor.d.ts +32 -0
  105. package/dist/churn/churnExtractor.d.ts.map +1 -0
  106. package/dist/churn/churnExtractor.js +261 -0
  107. package/dist/churn/churnExtractor.js.map +1 -0
  108. package/dist/cli.js +1215 -11
  109. package/dist/cli.js.map +1 -1
  110. package/dist/codeIndexDB-enhanced.js +1 -1
  111. package/dist/codeIndexDB-enhanced.js.map +1 -1
  112. package/dist/codeIndexDB.d.ts +101 -0
  113. package/dist/codeIndexDB.d.ts.map +1 -1
  114. package/dist/codeIndexDB.js +661 -5
  115. package/dist/codeIndexDB.js.map +1 -1
  116. package/dist/config/configLoader.d.ts.map +1 -1
  117. package/dist/config/configLoader.js +95 -1
  118. package/dist/config/configLoader.js.map +1 -1
  119. package/dist/config/defaults.d.ts +72 -1
  120. package/dist/config/defaults.d.ts.map +1 -1
  121. package/dist/config/defaults.js +146 -15
  122. package/dist/config/defaults.js.map +1 -1
  123. package/dist/config/pathProfiles.d.ts +38 -0
  124. package/dist/config/pathProfiles.d.ts.map +1 -0
  125. package/dist/config/pathProfiles.js +47 -0
  126. package/dist/config/pathProfiles.js.map +1 -0
  127. package/dist/conventions/conventionMiner.d.ts +63 -0
  128. package/dist/conventions/conventionMiner.d.ts.map +1 -0
  129. package/dist/conventions/conventionMiner.js +696 -0
  130. package/dist/conventions/conventionMiner.js.map +1 -0
  131. package/dist/coverage/coverageService.d.ts +49 -0
  132. package/dist/coverage/coverageService.d.ts.map +1 -0
  133. package/dist/coverage/coverageService.js +117 -0
  134. package/dist/coverage/coverageService.js.map +1 -0
  135. package/dist/coverage/istanbulParser.d.ts +84 -0
  136. package/dist/coverage/istanbulParser.d.ts.map +1 -0
  137. package/dist/coverage/istanbulParser.js +120 -0
  138. package/dist/coverage/istanbulParser.js.map +1 -0
  139. package/dist/coverage/lcovParser.d.ts +40 -0
  140. package/dist/coverage/lcovParser.d.ts.map +1 -0
  141. package/dist/coverage/lcovParser.js +168 -0
  142. package/dist/coverage/lcovParser.js.map +1 -0
  143. package/dist/fingerprint.d.ts +17 -0
  144. package/dist/fingerprint.d.ts.map +1 -1
  145. package/dist/fingerprint.js +42 -0
  146. package/dist/fingerprint.js.map +1 -1
  147. package/dist/grammars/tree-sitter-css.wasm +0 -0
  148. package/dist/graph/blastRadius.d.ts +27 -0
  149. package/dist/graph/blastRadius.d.ts.map +1 -0
  150. package/dist/graph/blastRadius.js +150 -0
  151. package/dist/graph/blastRadius.js.map +1 -0
  152. package/dist/graph/callGraph.d.ts +78 -0
  153. package/dist/graph/callGraph.d.ts.map +1 -0
  154. package/dist/graph/callGraph.js +484 -0
  155. package/dist/graph/callGraph.js.map +1 -0
  156. package/dist/graph/importGraph.d.ts +91 -0
  157. package/dist/graph/importGraph.d.ts.map +1 -0
  158. package/dist/graph/importGraph.js +602 -0
  159. package/dist/graph/importGraph.js.map +1 -0
  160. package/dist/graph/outputFormatter.d.ts +55 -0
  161. package/dist/graph/outputFormatter.d.ts.map +1 -0
  162. package/dist/graph/outputFormatter.js +283 -0
  163. package/dist/graph/outputFormatter.js.map +1 -0
  164. package/dist/hooks/codex.d.ts +36 -1
  165. package/dist/hooks/codex.d.ts.map +1 -1
  166. package/dist/hooks/codex.js +88 -54
  167. package/dist/hooks/codex.js.map +1 -1
  168. package/dist/hotspots/hotspotScorer.d.ts +20 -0
  169. package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
  170. package/dist/hotspots/hotspotScorer.js +202 -0
  171. package/dist/hotspots/hotspotScorer.js.map +1 -0
  172. package/dist/invariants/invariant-rules.schema.json +42 -1
  173. package/dist/invariants/ruleEngine.d.ts +4 -1
  174. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  175. package/dist/invariants/ruleEngine.js +141 -5
  176. package/dist/invariants/ruleEngine.js.map +1 -1
  177. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  178. package/dist/invariants/ruleValidator.js +62 -1
  179. package/dist/invariants/ruleValidator.js.map +1 -1
  180. package/dist/invariants/types.d.ts +23 -2
  181. package/dist/invariants/types.d.ts.map +1 -1
  182. package/dist/languages/UniversalAnalyzer.d.ts +1 -1
  183. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  184. package/dist/languages/UniversalAnalyzer.js +59 -24
  185. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  186. package/dist/languages/index.d.ts +1 -0
  187. package/dist/languages/index.d.ts.map +1 -1
  188. package/dist/languages/index.js +3 -0
  189. package/dist/languages/index.js.map +1 -1
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
  191. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
  192. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
  193. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
  194. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  195. package/dist/languages/tree-sitter/parser.js +2 -0
  196. package/dist/languages/tree-sitter/parser.js.map +1 -1
  197. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  198. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
  199. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  200. package/dist/ledger.d.ts +128 -0
  201. package/dist/ledger.d.ts.map +1 -0
  202. package/dist/ledger.js +345 -0
  203. package/dist/ledger.js.map +1 -0
  204. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  205. package/dist/mcp-tools/projectTasks.js +9 -14
  206. package/dist/mcp-tools/projectTasks.js.map +1 -1
  207. package/dist/mcp.js +0 -0
  208. package/dist/mcpAuditJobs.d.ts.map +1 -1
  209. package/dist/mcpAuditJobs.js +3 -0
  210. package/dist/mcpAuditJobs.js.map +1 -1
  211. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  212. package/dist/reporting/csvReportGenerator.js +7 -1
  213. package/dist/reporting/csvReportGenerator.js.map +1 -1
  214. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  215. package/dist/reporting/htmlReportGenerator.js +5 -1
  216. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  217. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  218. package/dist/reporting/jsonReportGenerator.js +10 -2
  219. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  220. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  221. package/dist/reporting/sarifReportGenerator.js +17 -17
  222. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  223. package/dist/scripts/runBench.d.ts +94 -0
  224. package/dist/scripts/runBench.d.ts.map +1 -0
  225. package/dist/scripts/runBench.js +1559 -0
  226. package/dist/scripts/runBench.js.map +1 -0
  227. package/dist/search/QueryParser.d.ts.map +1 -1
  228. package/dist/search/QueryParser.js +51 -4
  229. package/dist/search/QueryParser.js.map +1 -1
  230. package/dist/services/CodeMapGenerator.d.ts +33 -0
  231. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  232. package/dist/services/CodeMapGenerator.js +404 -0
  233. package/dist/services/CodeMapGenerator.js.map +1 -1
  234. package/dist/styles/normalizer.d.ts +37 -0
  235. package/dist/styles/normalizer.d.ts.map +1 -0
  236. package/dist/styles/normalizer.js +301 -0
  237. package/dist/styles/normalizer.js.map +1 -0
  238. package/dist/styles/styleExtractor.d.ts +38 -0
  239. package/dist/styles/styleExtractor.d.ts.map +1 -0
  240. package/dist/styles/styleExtractor.js +630 -0
  241. package/dist/styles/styleExtractor.js.map +1 -0
  242. package/dist/styles/styleIndexer.d.ts +35 -0
  243. package/dist/styles/styleIndexer.d.ts.map +1 -0
  244. package/dist/styles/styleIndexer.js +266 -0
  245. package/dist/styles/styleIndexer.js.map +1 -0
  246. package/dist/styles/tailwindConfigLoader.d.ts +38 -0
  247. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
  248. package/dist/styles/tailwindConfigLoader.js +374 -0
  249. package/dist/styles/tailwindConfigLoader.js.map +1 -0
  250. package/dist/styles/tailwindExpander.d.ts +38 -0
  251. package/dist/styles/tailwindExpander.d.ts.map +1 -0
  252. package/dist/styles/tailwindExpander.js +637 -0
  253. package/dist/styles/tailwindExpander.js.map +1 -0
  254. package/dist/styles/types.d.ts +92 -0
  255. package/dist/styles/types.d.ts.map +1 -0
  256. package/dist/styles/types.js +10 -0
  257. package/dist/styles/types.js.map +1 -0
  258. package/dist/symbols.d.ts +21 -0
  259. package/dist/symbols.d.ts.map +1 -0
  260. package/dist/symbols.js +31 -0
  261. package/dist/symbols.js.map +1 -0
  262. package/dist/types.d.ts +326 -3
  263. package/dist/types.d.ts.map +1 -1
  264. package/dist/types.js.map +1 -1
  265. package/package.json +9 -5
  266. package/plugin/.claude-plugin/plugin.json +1 -1
  267. package/plugin/scripts/hook-audit.sh +5 -1
  268. package/plugin/skills/code-auditor/SKILL.md +51 -0
  269. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
  270. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
  271. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
  272. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
  273. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
  274. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
  275. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
  276. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
  277. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
  278. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
  279. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
  280. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
  281. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
  282. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
  283. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
  284. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
  285. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
  286. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
  287. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
  288. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
  289. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
  290. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
  291. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
  292. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
  293. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
  294. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
  295. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
  296. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
  297. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
  298. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
  299. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
  300. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
  301. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
  302. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
  303. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
  304. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
  305. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
  306. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
  307. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
  308. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
  309. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
  310. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
  311. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
  312. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
  313. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
  314. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
  315. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
  316. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
  317. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
  318. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
  319. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
  320. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
  321. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
  322. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
  323. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
  324. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
  325. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
  326. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
  327. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
  328. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
  329. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
  330. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
  331. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
  332. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
  333. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
  334. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
  335. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
  336. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
  337. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
  338. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
  339. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
  340. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
  341. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
  342. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
  343. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
  344. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
  345. package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
  346. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
  347. package/dist/analyzers/dataAccessAnalyzer.js +0 -474
  348. package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
  349. package/dist/analyzers/dryAnalyzer.d.ts +0 -31
  350. package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
  351. package/dist/analyzers/dryAnalyzer.js +0 -734
  352. package/dist/analyzers/dryAnalyzer.js.map +0 -1
  353. package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
  354. package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
  355. package/dist/analyzers/schemaAnalyzer.js +0 -564
  356. package/dist/analyzers/schemaAnalyzer.js.map +0 -1
  357. package/dist/analyzers/solidAnalyzer.d.ts +0 -33
  358. package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
  359. package/dist/analyzers/solidAnalyzer.js +0 -847
  360. package/dist/analyzers/solidAnalyzer.js.map +0 -1
@@ -10,6 +10,8 @@
10
10
  */
11
11
  import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
12
12
  import picomatch from 'picomatch';
13
+ import { buildProvenanceContext, isDBProvenanced, DB_CALL_METHODS, } from '../provenance.js';
14
+ import { OrmAdapterRegistry } from '../orm/index.js';
13
15
  export const DEFAULT_SCHEMA_CONFIG = {
14
16
  enableTableUsageTracking: true,
15
17
  checkMissingReferences: true,
@@ -34,6 +36,7 @@ export const DEFAULT_SCHEMA_CONFIG = {
34
36
  fileGateGlobs: ['**/*.sql', '**/migrations/**'],
35
37
  };
36
38
  import { promises as fs } from 'fs';
39
+ import { CodeIndexDB } from '../../codeIndexDB.js';
37
40
  export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
38
41
  name = 'schema';
39
42
  description = 'Analyzes code against database schemas and validates JSON schemas';
@@ -61,8 +64,20 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
61
64
  async analyzeAST(ast, adapter, config, sourceCode) {
62
65
  const violations = [];
63
66
  const finalConfig = { ...DEFAULT_SCHEMA_CONFIG, ...config };
64
- // R2.2 File gate: only analyze files with DB context
65
- if (!this.passesFileGate(ast.filePath, sourceCode, finalConfig)) {
67
+ // Spec 21: Build provenance context for this file (R1 — provenance-primary detection)
68
+ const detectionMode = config.detection?.mode ?? 'hybrid';
69
+ const p0 = performance.now();
70
+ const provenanceContext = buildProvenanceContext(ast, adapter, sourceCode, {
71
+ mode: detectionMode,
72
+ dbReceiverNames: finalConfig.dbReceiverNames ?? DEFAULT_SCHEMA_CONFIG.dbReceiverNames,
73
+ dbBindingNames: finalConfig.dbBindingNames ?? DEFAULT_SCHEMA_CONFIG.dbBindingNames,
74
+ dbCallMethods: finalConfig.dbCallMethods ?? DEFAULT_SCHEMA_CONFIG.dbCallMethods,
75
+ });
76
+ const timingAcc = config._provenanceTiming;
77
+ if (timingAcc)
78
+ timingAcc.totalMs += performance.now() - p0;
79
+ // R2.2 — File gate: only analyze files with DB context (Spec 21: provenance-based)
80
+ if (!this.passesFileGate(ast.filePath, sourceCode, finalConfig, provenanceContext)) {
66
81
  return violations;
67
82
  }
68
83
  // Get available schemas
@@ -77,12 +92,18 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
77
92
  }
78
93
  }
79
94
  if (finalConfig.requiredSchemas && finalConfig.requiredSchemas.length > 0 && schemas.length === 0) {
80
- violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'No database schemas loaded for analysis', 'warning', 'missing-schemas'));
95
+ violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'No database schemas loaded for analysis', 'warning', 'missing-schemas', undefined, 'top-level:missing-schemas'));
81
96
  return violations;
82
97
  }
83
98
  // R2.1 — AST-based table reference extraction (replaces legacy regex scan-all-strings)
84
- const tableRefs = this.findTableReferences(ast, adapter, sourceCode, finalConfig);
99
+ // Spec 21: Uses provenance context for DB-call pattern detection
100
+ const tableRefs = this.findTableReferences(ast, adapter, sourceCode, finalConfig, provenanceContext);
85
101
  const columnRefs = this.findColumnReferences(ast, adapter, sourceCode);
102
+ // Spec 15 R1 — Record schema usage for cross-domain lifecycle analysis.
103
+ // Idempotent per-file: clear stale entries before inserting fresh references.
104
+ if (finalConfig.enableTableUsageTracking) {
105
+ this.recordTableUsage(ast, adapter, ast.filePath, tableRefs);
106
+ }
86
107
  // Check for missing table references — R2.4: Levenshtein suggestions
87
108
  if (finalConfig.checkMissingReferences) {
88
109
  for (const ref of tableRefs) {
@@ -92,13 +113,13 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
92
113
  ? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
93
114
  : `Reference to unknown table '${ref.table}' (${ref.type})`;
94
115
  violations.push(this.createViolation(ast.filePath, ref.location, msg, 'suggestion', // R7
95
- 'unknown-table'));
116
+ 'unknown-table', undefined, ref.table));
96
117
  }
97
118
  }
98
119
  for (const ref of columnRefs) {
99
120
  const columnSet = tableColumns.get(ref.table);
100
121
  if (columnSet && !columnSet.has(ref.column)) {
101
- violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown column '${ref.column}' in table '${ref.table}'`, 'warning', 'unknown-column'));
122
+ violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown column '${ref.column}' in table '${ref.table}'`, 'warning', 'unknown-column', undefined, `${ref.table}.${ref.column}`));
102
123
  }
103
124
  }
104
125
  }
@@ -121,7 +142,7 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
121
142
  * Pre-filter: only analyze files that show DB usage.
122
143
  * Checks: .sql/migration glob, D1/SQL imports, env-binding patterns, DB calls.
123
144
  */
124
- passesFileGate(filePath, sourceCode, config) {
145
+ passesFileGate(filePath, sourceCode, config, provenanceContext) {
125
146
  // Always pass .sql files and migration directories
126
147
  const gateGlobs = config.fileGateGlobs ?? ['**/*.sql', '**/migrations/**'];
127
148
  for (const glob of gateGlobs) {
@@ -129,38 +150,49 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
129
150
  return true;
130
151
  }
131
152
  }
132
- // Check for D1 or SQL API imports
133
- const importPatterns = [
134
- /import\s+.*\b(D1Database|D1PreparedStatement|D1Result)\b/,
135
- /import\s+.*from\s+['"].*d1['"]/,
136
- /import\s+.*from\s+['"].*pg['"]/,
137
- /import\s+.*from\s+['"].*mysql['"]/,
138
- /import\s+.*from\s+['"].*sqlite['"]/,
139
- /import\s+.*from\s+['"].*knex['"]/,
140
- /import\s+.*from\s+['"].*drizzle['"]/,
141
- /import\s+.*from\s+['"].*prisma['"]/,
142
- ];
143
- for (const pat of importPatterns) {
144
- if (pat.test(sourceCode))
145
- return true;
146
- }
147
- // Check for env-binding patterns (e.g., env.DB in Cloudflare Workers)
148
- const bindingNames = config.dbBindingNames ?? ['env.DB'];
149
- for (const binding of bindingNames) {
150
- if (sourceCode.includes(binding))
153
+ // Spec 21: Provenance-first DB detection if any identifier is DB-provenanced,
154
+ // this file passes the gate. This replaces the regex patterns for import/environment/
155
+ // receiver.method checks in hybrid and provenance modes.
156
+ if (provenanceContext && provenanceContext.mode !== 'names') {
157
+ if (provenanceContext.dbProvenanced.size > 0) {
151
158
  return true;
159
+ }
152
160
  }
153
- // Check for DB call patterns (receiver.method)
154
- const receivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
155
- const methods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first'];
156
- for (const receiver of receivers) {
157
- for (const method of methods) {
158
- const pattern = new RegExp(`\\b${escapeRegex(receiver)}\\.${escapeRegex(method)}\\s*\\(`);
159
- if (pattern.test(sourceCode))
161
+ // Legacy name-based detection used in 'names' mode or when no provenance context
162
+ if (!provenanceContext || provenanceContext.mode === 'names') {
163
+ // Check for D1 or SQL API imports
164
+ const importPatterns = [
165
+ /import\s+.*\b(D1Database|D1PreparedStatement|D1Result)\b/,
166
+ /import\s+.*from\s+['"].*d1['"]/,
167
+ /import\s+.*from\s+['"].*pg['"]/,
168
+ /import\s+.*from\s+['"].*mysql['"]/,
169
+ /import\s+.*from\s+['"].*sqlite['"]/,
170
+ /import\s+.*from\s+['"].*knex['"]/,
171
+ /import\s+.*from\s+['"].*drizzle['"]/,
172
+ /import\s+.*from\s+['"].*prisma['"]/,
173
+ ];
174
+ for (const pat of importPatterns) {
175
+ if (pat.test(sourceCode))
160
176
  return true;
161
177
  }
178
+ // Check for env-binding patterns (e.g., env.DB in Cloudflare Workers)
179
+ const bindingNames = config.dbBindingNames ?? ['env.DB'];
180
+ for (const binding of bindingNames) {
181
+ if (sourceCode.includes(binding))
182
+ return true;
183
+ }
184
+ // Check for DB call patterns (receiver.method)
185
+ const receivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
186
+ const methods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first'];
187
+ for (const receiver of receivers) {
188
+ for (const method of methods) {
189
+ const pattern = new RegExp(`\\b${escapeRegex(receiver)}\\.${escapeRegex(method)}\\s*\\(`);
190
+ if (pattern.test(sourceCode))
191
+ return true;
192
+ }
193
+ }
162
194
  }
163
- // Check for SQL tagged template literals
195
+ // Check for SQL tagged template literals (syntax feature, not naming convention)
164
196
  const sqlTags = config.sqlTagNames ?? ['sql', 'db'];
165
197
  for (const tag of sqlTags) {
166
198
  const pattern = new RegExp(`\\b${escapeRegex(tag)}\`\\s*SELECT|\\b${escapeRegex(tag)}\`\\s*INSERT|\\b${escapeRegex(tag)}\`\\s*UPDATE|\\b${escapeRegex(tag)}\`\\s*DELETE|\\b${escapeRegex(tag)}\`\\s*CREATE`, 'i');
@@ -177,12 +209,11 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
177
209
  * R2.1: Only tagged template SQL and DB-call patterns produce candidates.
178
210
  * R2.3: Template expressions (${var}) resolved to wildcards.
179
211
  */
180
- findTableReferences(ast, adapter, sourceCode, config) {
212
+ findTableReferences(ast, adapter, sourceCode, config, provenanceContext) {
181
213
  const references = [];
182
214
  const sqlTags = config.sqlTagNames ?? ['sql', 'db'];
183
- const dbMethods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first', 'query'];
184
- const dbReceivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
185
215
  // (1) Tagged template SQL — e.g. sql`SELECT * FROM heroes`
216
+ // This is a syntax feature, not a naming convention — keep the sqlTagNames gate.
186
217
  const taggedTemplates = adapter.findNodes(ast, {
187
218
  custom: (node) => {
188
219
  if (node.type !== 'call_expression')
@@ -204,14 +235,21 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
204
235
  references.push(...tableRefs);
205
236
  }
206
237
  // (2) DB-call patterns — e.g. db.exec("SELECT * FROM heroes")
238
+ // Spec 21: Replace name-based isDbMemberCall with provenance-based isDBProvenanced.
207
239
  const dbCalls = adapter.findNodes(ast, {
208
240
  custom: (node) => {
209
241
  if (node.type !== 'call_expression')
210
242
  return false;
243
+ // Spec 21: Use provenance when available, fall back to name-based check
244
+ if (provenanceContext && provenanceContext.mode !== 'names') {
245
+ return isDBProvenanced(node, adapter, sourceCode, provenanceContext, DB_CALL_METHODS);
246
+ }
247
+ // Legacy name-based check for names mode / no context
211
248
  const callee = this.getCallee(node, adapter, sourceCode);
212
249
  if (!callee)
213
250
  return false;
214
- // Check if callee is a member expression like db.exec or db.prepare
251
+ const dbMethods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first', 'query'];
252
+ const dbReceivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
215
253
  return this.isDbMemberCall(node, callee, dbMethods, dbReceivers, adapter, sourceCode);
216
254
  },
217
255
  });
@@ -230,8 +268,67 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
230
268
  const fileRefs = this.parseSqlTables(sourceCode, { line: 1, column: 1 }, sourceCode);
231
269
  references.push(...fileRefs);
232
270
  }
271
+ // (4) Spec 15 R2 — ORM-aware extraction (Drizzle + Prisma)
272
+ // Run ORM adapter extraction for files that match a registered adapter.
273
+ // This complements raw-SQL extraction by picking up ORM-specific patterns
274
+ // like db.select().from(users) and prisma.user.findMany().
275
+ const ormRegistry = OrmAdapterRegistry.getInstance();
276
+ const ormAdapter = ormRegistry.getAdapterForFile(ast.filePath);
277
+ if (ormAdapter) {
278
+ try {
279
+ const ormRefs = ormAdapter.extractTableReferences(ast, adapter, sourceCode);
280
+ for (const ormRef of ormRefs) {
281
+ references.push({
282
+ table: ormRef.table,
283
+ type: ormRef.type,
284
+ location: ormRef.location,
285
+ context: ormRef.context,
286
+ });
287
+ }
288
+ }
289
+ catch {
290
+ // ORM extraction is best-effort — failures don't block raw-SQL extraction.
291
+ }
292
+ }
233
293
  return references;
234
294
  }
295
+ /**
296
+ * Spec 15 R1 — Record extracted table references to schema_usage for
297
+ * cross-domain lifecycle analysis (written-never-read, read-never-written,
298
+ * transaction-boundary risk).
299
+ *
300
+ * Idempotent per-file: stale entries are cleared before fresh references
301
+ * are inserted. For .sql/migration files, uses "schema-file" as the
302
+ * function name since there's no AST function context.
303
+ */
304
+ recordTableUsage(ast, adapter, filePath, references) {
305
+ try {
306
+ const db = CodeIndexDB.getInstance();
307
+ db.clearSchemaUsageForFile(filePath);
308
+ for (const ref of references) {
309
+ // Find enclosing function from the AST position
310
+ const node = this.findClosestNodeAt(ast.root, ref.location, adapter);
311
+ const functionName = node
312
+ ? this.findEnclosingFunctionName(node, adapter)
313
+ : ast.filePath.endsWith('.sql') || ast.filePath.includes('/migrations/')
314
+ ? 'schema-file'
315
+ : 'top-level';
316
+ const usage = {
317
+ tableName: ref.table,
318
+ filePath,
319
+ functionName,
320
+ usageType: ref.type,
321
+ line: ref.location.line,
322
+ column: ref.location.column,
323
+ rawQuery: ref.context,
324
+ };
325
+ db.recordSchemaUsage(usage);
326
+ }
327
+ }
328
+ catch {
329
+ // Schema recording is best-effort — failures don't block analysis.
330
+ }
331
+ }
235
332
  /**
236
333
  * Parse SQL table names from a SQL text string.
237
334
  * R2.3: Template expressions (${...}) resolve portions to wildcards.
@@ -241,14 +338,16 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
241
338
  // R2.3: Strip template expressions — `${prefix}_builds` → `_builds`
242
339
  // (the prefix is replaced with empty, the suffix remains for matching)
243
340
  const cleaned = this.resolveTemplateExpressions(sqlText);
244
- // SQL patterns anchored to SQL keywords (not arbitrary substrings)
341
+ // SQL patterns anchored to SQL keywords (not arbitrary substrings).
342
+ // Uses Unicode-aware \p{L} so non-Latin table names (日, 注文, пользователи)
343
+ // are correctly matched — \w is ASCII-only. Spec 21 R5.
245
344
  const sqlPatterns = [
246
- { regex: /\bFROM\s+([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'select' },
247
- { regex: /\bJOIN\s+([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'select' },
248
- { regex: /\bINSERT\s+INTO\s+([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'insert' },
249
- { regex: /\bUPDATE\s+([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'update' },
250
- { regex: /\bDELETE\s+FROM\s+([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'delete' },
251
- { regex: /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"']?)([a-zA-Z_]\w*)\1\b/gi, type: 'create' },
345
+ { regex: /\bFROM\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'select' },
346
+ { regex: /\bJOIN\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'select' },
347
+ { regex: /\bINSERT\s+INTO\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'insert' },
348
+ { regex: /\bUPDATE\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'update' },
349
+ { regex: /\bDELETE\s+FROM\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'delete' },
350
+ { regex: /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'create' },
252
351
  ];
253
352
  for (const { regex, type } of sqlPatterns) {
254
353
  let match;
@@ -334,9 +433,9 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
334
433
  findColumnReferences(ast, adapter, sourceCode) {
335
434
  const references = [];
336
435
  const patterns = [
337
- /(\w+)\.(\w+)\s*[=<>]/gi,
338
- /SELECT\s+.*?(\w+)\.(\w+)/gi,
339
- /WHERE\s+.*?(\w+)\.(\w+)/gi,
436
+ /([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)\s*[=<>]/giu,
437
+ /SELECT\s+.*?([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)/giu,
438
+ /WHERE\s+.*?([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)/giu,
340
439
  ];
341
440
  for (const pattern of patterns) {
342
441
  let match;
@@ -363,11 +462,11 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
363
462
  const violations = [];
364
463
  for (const ref of references) {
365
464
  if (/[A-Z]/.test(ref.table) && !ref.table.endsWith('Table')) {
366
- violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' should use snake_case convention`, 'suggestion', 'naming-convention'));
465
+ violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' should use snake_case convention`, 'suggestion', 'naming-convention', undefined, ref.table));
367
466
  }
368
467
  const reserved = ['user', 'order', 'group', 'table', 'column', 'index'];
369
468
  if (reserved.includes(ref.table.toLowerCase())) {
370
- violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' is a reserved word. Consider using a different name.`, 'warning', 'reserved-word'));
469
+ violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' is a reserved word. Consider using a different name.`, 'warning', 'reserved-word', undefined, ref.table));
371
470
  }
372
471
  }
373
472
  return violations;
@@ -385,30 +484,43 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
385
484
  const funcText = adapter.getNodeText(funcNode, sourceCode);
386
485
  const queryCount = this.countQueries(funcText);
387
486
  if (queryCount > (config.maxQueriesPerFunction || 5)) {
388
- violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' has ${queryCount} queries, exceeding the maximum of ${config.maxQueriesPerFunction}`, 'warning', 'too-many-queries'));
487
+ violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' has ${queryCount} queries, exceeding the maximum of ${config.maxQueriesPerFunction}`, 'warning', 'too-many-queries', undefined, func.name));
389
488
  }
390
489
  }
391
- // N+1 query check only flag if we have actual DB calls, not grep
392
- if (sourceCode.includes('.map') && (sourceCode.includes('.query') || sourceCode.includes('SELECT'))) {
393
- violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Potential N+1 query pattern detected. Consider using a join or batch query.', 'warning', 'n-plus-one'));
394
- }
490
+ // N+1 query detection is handled by the data-access analyzer (loop-query rule).
491
+ // The two were consolidated in Spec-19 Corrective Batch Item 3 — see CHANGELOG.
395
492
  return violations;
396
493
  }
397
494
  // ---------------------------------------------------------------------------
398
495
  // SQL injection — R7: severity changed from critical to warning
399
496
  // ---------------------------------------------------------------------------
497
+ // ---------------------------------------------------------------------------
498
+ // SQL injection — per-call-site with enclosing-function + ordinal symbols
499
+ // ---------------------------------------------------------------------------
400
500
  checkSQLInjection(ast, adapter, sourceCode) {
401
501
  const violations = [];
502
+ const symbolOrdinals = new Map();
503
+ // Use regex with global flag to find individual call sites
402
504
  const dangerousPatterns = [
403
505
  /query\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`/g,
404
506
  /query\s*\(\s*['"][^'"]*['"]?\s*\+/g,
405
507
  /execute\s*\(\s*['"][^'"]*['"]?\s*\+/g,
406
508
  ];
407
509
  for (const pattern of dangerousPatterns) {
408
- if (pattern.test(sourceCode)) {
409
- violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Potential SQL injection vulnerability. Use parameterized queries.', 'warning', // R7: was 'critical'
410
- 'sql-injection'));
411
- break;
510
+ // Clone regex to reset state (global regexes track lastIndex)
511
+ const re = new RegExp(pattern.source, pattern.flags);
512
+ let match;
513
+ while ((match = re.exec(sourceCode)) !== null) {
514
+ const location = this.offsetToLocation(sourceCode, match.index, { line: 1, column: 1 });
515
+ // Find enclosing function from the AST at this position
516
+ const node = this.findClosestNodeAt(ast.root, location, adapter);
517
+ const enclosingFn = node ? this.findEnclosingFunctionName(node, adapter) : 'top-level';
518
+ const baseSymbol = `${enclosingFn}:sql-injection`;
519
+ const ordinal = (symbolOrdinals.get(baseSymbol) ?? 0) + 1;
520
+ symbolOrdinals.set(baseSymbol, ordinal);
521
+ const symbol = ordinal > 1 ? `${baseSymbol}:${ordinal}` : baseSymbol;
522
+ violations.push(this.createViolation(ast.filePath, location, 'Potential SQL injection vulnerability. Use parameterized queries.', 'suggestion', // Spec 11 R4 blanket demotion: all survivors → suggestion
523
+ 'sql-injection', undefined, symbol));
412
524
  }
413
525
  }
414
526
  return violations;
@@ -599,6 +711,85 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
599
711
  }
600
712
  return null;
601
713
  }
714
+ /**
715
+ * Find the nearest AST node at a source location — walks the tree looking
716
+ * for the deepest node that contains the given line/column.
717
+ */
718
+ findClosestNodeAt(root, location, adapter) {
719
+ let best = null;
720
+ let bestDepth = -1;
721
+ const walk = (node, depth) => {
722
+ const start = node.location.start;
723
+ const end = node.location.end;
724
+ // Check if node contains the location
725
+ if ((start.line < location.line ||
726
+ (start.line === location.line && start.column <= location.column)) &&
727
+ (end.line > location.line ||
728
+ (end.line === location.line && end.column >= location.column))) {
729
+ if (depth > bestDepth) {
730
+ best = node;
731
+ bestDepth = depth;
732
+ }
733
+ if (node.children) {
734
+ for (const child of node.children) {
735
+ walk(child, depth + 1);
736
+ }
737
+ }
738
+ }
739
+ };
740
+ walk(root, 0);
741
+ return best;
742
+ }
743
+ /**
744
+ * Walk up the AST from a node to find the enclosing function or method name.
745
+ * Matches the same scheme as UniversalDataAccessAnalyzer.findEnclosingFunctionName.
746
+ */
747
+ findEnclosingFunctionName(node, adapter) {
748
+ let current = node;
749
+ while (current) {
750
+ const type = adapter.getNodeType(current);
751
+ if (type === 'arrow_function' ||
752
+ type === 'function_declaration' ||
753
+ type === 'function_expression' ||
754
+ type === 'generator_function_declaration' ||
755
+ type === 'generator_function_expression' ||
756
+ type === 'method_definition') {
757
+ const name = this.getNodeName(current, adapter);
758
+ if (name)
759
+ return name;
760
+ }
761
+ if (adapter.isMethod(current)) {
762
+ const name = this.getNodeName(current, adapter);
763
+ if (name)
764
+ return name;
765
+ }
766
+ current = adapter.getParent(current);
767
+ }
768
+ return 'top-level';
769
+ }
770
+ /**
771
+ * Extract a human-readable name from an AST node.
772
+ * Matches the same scheme as UniversalDataAccessAnalyzer.getNodeName.
773
+ */
774
+ getNodeName(node, adapter) {
775
+ if (node.name && typeof node.name === 'string') {
776
+ return node.name;
777
+ }
778
+ if (node.text && typeof node.text === 'string') {
779
+ return node.text;
780
+ }
781
+ if (node.children) {
782
+ for (const child of node.children) {
783
+ const childType = adapter.getNodeType(child);
784
+ if (childType === 'identifier' || childType === 'property_identifier') {
785
+ const name = this.getNodeName(child, adapter);
786
+ if (name)
787
+ return name;
788
+ }
789
+ }
790
+ }
791
+ return '';
792
+ }
602
793
  // ---------------------------------------------------------------------------
603
794
  // JSON Schema validation (unchanged from original)
604
795
  // ---------------------------------------------------------------------------