code-auditor-mcp 4.1.1 → 5.0.1

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 (1082) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +338 -0
  3. package/README.md +4 -4
  4. package/configs/hhra-compat.json +0 -8
  5. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +1 -1
  6. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +1 -1
  7. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +7 -1
  8. package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +1 -1
  9. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +2 -8
  10. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
  11. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +4 -4
  12. package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +1 -1
  13. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +1 -1
  14. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +1 -1
  15. package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +1 -1
  16. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +2 -2
  17. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +1 -1
  18. package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +1 -1
  19. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +1 -1
  20. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +1 -1
  21. package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +1 -1
  22. package/dist/analyzers/applicability.d.ts +42 -0
  23. package/dist/analyzers/applicability.d.ts.map +1 -1
  24. package/dist/analyzers/applicability.js +74 -0
  25. package/dist/analyzers/applicability.js.map +1 -1
  26. package/dist/analyzers/cross-language/APIContractAnalyzer.js +18 -24
  27. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  28. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +30 -23
  29. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  30. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +113 -76
  31. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  32. package/dist/analyzers/cross-language/SchemaValidator.d.ts +2 -0
  33. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  34. package/dist/analyzers/cross-language/SchemaValidator.js +8 -3
  35. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  36. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  37. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +210 -148
  38. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  39. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  40. package/dist/analyzers/documentationAnalyzer.js +23 -28
  41. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  42. package/dist/analyzers/handleIdentification.d.ts +356 -0
  43. package/dist/analyzers/handleIdentification.d.ts.map +1 -0
  44. package/dist/analyzers/handleIdentification.js +424 -0
  45. package/dist/analyzers/handleIdentification.js.map +1 -0
  46. package/dist/analyzers/manifestStaleness.d.ts +64 -0
  47. package/dist/analyzers/manifestStaleness.d.ts.map +1 -0
  48. package/dist/analyzers/manifestStaleness.js +115 -0
  49. package/dist/analyzers/manifestStaleness.js.map +1 -0
  50. package/dist/analyzers/orgFilterTiers.d.ts +73 -1
  51. package/dist/analyzers/orgFilterTiers.d.ts.map +1 -1
  52. package/dist/analyzers/orgFilterTiers.js +166 -1
  53. package/dist/analyzers/orgFilterTiers.js.map +1 -1
  54. package/dist/analyzers/provenance.d.ts +251 -57
  55. package/dist/analyzers/provenance.d.ts.map +1 -1
  56. package/dist/analyzers/provenance.js +1109 -429
  57. package/dist/analyzers/provenance.js.map +1 -1
  58. package/dist/analyzers/reactAnalyzer.d.ts +2 -0
  59. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  60. package/dist/analyzers/reactAnalyzer.js +2 -0
  61. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  62. package/dist/analyzers/receiverResolution.d.ts +183 -0
  63. package/dist/analyzers/receiverResolution.d.ts.map +1 -0
  64. package/dist/analyzers/receiverResolution.js +540 -0
  65. package/dist/analyzers/receiverResolution.js.map +1 -0
  66. package/dist/analyzers/receiverRoot.d.ts +328 -0
  67. package/dist/analyzers/receiverRoot.d.ts.map +1 -0
  68. package/dist/analyzers/receiverRoot.js +990 -0
  69. package/dist/analyzers/receiverRoot.js.map +1 -0
  70. package/dist/analyzers/ruleRegistry.d.ts +12 -68
  71. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  72. package/dist/analyzers/ruleRegistry.js +32 -342
  73. package/dist/analyzers/ruleRegistry.js.map +1 -1
  74. package/dist/analyzers/ruleTiming.d.ts +10 -0
  75. package/dist/analyzers/ruleTiming.d.ts.map +1 -1
  76. package/dist/analyzers/ruleTiming.js +20 -0
  77. package/dist/analyzers/ruleTiming.js.map +1 -1
  78. package/dist/analyzers/sqlLiteral.d.ts +76 -0
  79. package/dist/analyzers/sqlLiteral.d.ts.map +1 -0
  80. package/dist/analyzers/sqlLiteral.js +419 -0
  81. package/dist/analyzers/sqlLiteral.js.map +1 -0
  82. package/dist/analyzers/tsEcosystem.d.ts +115 -0
  83. package/dist/analyzers/tsEcosystem.d.ts.map +1 -0
  84. package/dist/analyzers/tsEcosystem.js +231 -0
  85. package/dist/analyzers/tsEcosystem.js.map +1 -0
  86. package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
  87. package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
  88. package/dist/analyzers/tsExpressionDescriptor.js +374 -0
  89. package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
  90. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
  91. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  92. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
  93. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  94. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  95. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +23 -35
  96. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  97. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
  98. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  99. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
  100. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  101. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  102. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +2 -18
  103. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  104. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
  105. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  106. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
  107. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  108. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
  109. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  110. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
  111. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  112. package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
  113. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
  114. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
  115. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
  116. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
  117. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
  118. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
  119. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
  120. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  121. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
  122. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  123. package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
  124. package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
  125. package/dist/analyzers/universal/functionConcerns.js +4 -1
  126. package/dist/analyzers/universal/functionConcerns.js.map +1 -1
  127. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
  128. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  129. package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
  130. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  131. package/dist/analyzers/universal/schema/config.d.ts +3 -14
  132. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  133. package/dist/analyzers/universal/schema/config.js +3 -16
  134. package/dist/analyzers/universal/schema/config.js.map +1 -1
  135. package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
  136. package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
  137. package/dist/analyzers/universal/schema/discovery.js +36 -20
  138. package/dist/analyzers/universal/schema/discovery.js.map +1 -1
  139. package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
  140. package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
  141. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  142. package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
  143. package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
  144. package/dist/analyzers/universal/schema/migrations.js +443 -186
  145. package/dist/analyzers/universal/schema/migrations.js.map +1 -1
  146. package/dist/analyzers/universal/schema/types.d.ts +17 -3
  147. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  148. package/dist/analyzers/universal/schema/violations.js +1 -1
  149. package/dist/analyzers/universal/schema/violations.js.map +1 -1
  150. package/dist/analyzers/universal/styleScale.d.ts +52 -0
  151. package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
  152. package/dist/analyzers/universal/styleScale.js +95 -0
  153. package/dist/analyzers/universal/styleScale.js.map +1 -0
  154. package/dist/applyDataDirEnv.js +1 -1
  155. package/dist/applyDataDirEnv.js.map +1 -1
  156. package/dist/auditRouter.d.ts +26 -30
  157. package/dist/auditRouter.d.ts.map +1 -1
  158. package/dist/auditRouter.js +373 -143
  159. package/dist/auditRouter.js.map +1 -1
  160. package/dist/auditRunner.d.ts +64 -1
  161. package/dist/auditRunner.d.ts.map +1 -1
  162. package/dist/auditRunner.js +1538 -1033
  163. package/dist/auditRunner.js.map +1 -1
  164. package/dist/baseline.d.ts +30 -3
  165. package/dist/baseline.d.ts.map +1 -1
  166. package/dist/baseline.js +70 -9
  167. package/dist/baseline.js.map +1 -1
  168. package/dist/cli.js +969 -821
  169. package/dist/cli.js.map +1 -1
  170. package/dist/codeIndex/analyzerConfig.d.ts +60 -0
  171. package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
  172. package/dist/codeIndex/analyzerConfig.js +98 -0
  173. package/dist/codeIndex/analyzerConfig.js.map +1 -0
  174. package/dist/codeIndex/auditResults.d.ts +39 -0
  175. package/dist/codeIndex/auditResults.d.ts.map +1 -0
  176. package/dist/codeIndex/auditResults.js +99 -0
  177. package/dist/codeIndex/auditResults.js.map +1 -0
  178. package/dist/codeIndex/codeMap.d.ts +59 -0
  179. package/dist/codeIndex/codeMap.d.ts.map +1 -0
  180. package/dist/codeIndex/codeMap.js +72 -0
  181. package/dist/codeIndex/codeMap.js.map +1 -0
  182. package/dist/codeIndex/conventions.d.ts +33 -0
  183. package/dist/codeIndex/conventions.d.ts.map +1 -0
  184. package/dist/codeIndex/conventions.js +84 -0
  185. package/dist/codeIndex/conventions.js.map +1 -0
  186. package/dist/codeIndex/coverage.d.ts +78 -0
  187. package/dist/codeIndex/coverage.d.ts.map +1 -0
  188. package/dist/codeIndex/coverage.js +164 -0
  189. package/dist/codeIndex/coverage.js.map +1 -0
  190. package/dist/codeIndex/dependencyGraph.d.ts +97 -0
  191. package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
  192. package/dist/codeIndex/dependencyGraph.js +241 -0
  193. package/dist/codeIndex/dependencyGraph.js.map +1 -0
  194. package/dist/codeIndex/functionIndex.d.ts +111 -0
  195. package/dist/codeIndex/functionIndex.d.ts.map +1 -0
  196. package/dist/codeIndex/functionIndex.js +306 -0
  197. package/dist/codeIndex/functionIndex.js.map +1 -0
  198. package/dist/codeIndex/inferredReceivers.d.ts +32 -0
  199. package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
  200. package/dist/codeIndex/inferredReceivers.js +41 -0
  201. package/dist/codeIndex/inferredReceivers.js.map +1 -0
  202. package/dist/codeIndex/meta.d.ts +30 -0
  203. package/dist/codeIndex/meta.d.ts.map +1 -0
  204. package/dist/codeIndex/meta.js +37 -0
  205. package/dist/codeIndex/meta.js.map +1 -0
  206. package/dist/codeIndex/migrations.d.ts +96 -0
  207. package/dist/codeIndex/migrations.d.ts.map +1 -0
  208. package/dist/codeIndex/migrations.js +1302 -0
  209. package/dist/codeIndex/migrations.js.map +1 -0
  210. package/dist/codeIndex/projectTasks.d.ts +54 -0
  211. package/dist/codeIndex/projectTasks.d.ts.map +1 -0
  212. package/dist/codeIndex/projectTasks.js +81 -0
  213. package/dist/codeIndex/projectTasks.js.map +1 -0
  214. package/dist/codeIndex/provenance.d.ts +47 -0
  215. package/dist/codeIndex/provenance.d.ts.map +1 -0
  216. package/dist/codeIndex/provenance.js +48 -0
  217. package/dist/codeIndex/provenance.js.map +1 -0
  218. package/dist/codeIndex/rawSql.d.ts +54 -0
  219. package/dist/codeIndex/rawSql.d.ts.map +1 -0
  220. package/dist/codeIndex/rawSql.js +71 -0
  221. package/dist/codeIndex/rawSql.js.map +1 -0
  222. package/dist/codeIndex/schema.d.ts +99 -0
  223. package/dist/codeIndex/schema.d.ts.map +1 -0
  224. package/dist/codeIndex/schema.js +174 -0
  225. package/dist/codeIndex/schema.js.map +1 -0
  226. package/dist/codeIndex/search.d.ts +40 -0
  227. package/dist/codeIndex/search.d.ts.map +1 -0
  228. package/dist/codeIndex/search.js +316 -0
  229. package/dist/codeIndex/search.js.map +1 -0
  230. package/dist/codeIndex/shared.d.ts +27 -0
  231. package/dist/codeIndex/shared.d.ts.map +1 -0
  232. package/dist/codeIndex/shared.js +46 -0
  233. package/dist/codeIndex/shared.js.map +1 -0
  234. package/dist/codeIndex/sqliteCollection.d.ts +81 -0
  235. package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
  236. package/dist/codeIndex/sqliteCollection.js +250 -0
  237. package/dist/codeIndex/sqliteCollection.js.map +1 -0
  238. package/dist/codeIndex/whitelist.d.ts +59 -0
  239. package/dist/codeIndex/whitelist.d.ts.map +1 -0
  240. package/dist/codeIndex/whitelist.js +188 -0
  241. package/dist/codeIndex/whitelist.js.map +1 -0
  242. package/dist/codeIndexDB.d.ts +132 -271
  243. package/dist/codeIndexDB.d.ts.map +1 -1
  244. package/dist/codeIndexDB.js +365 -2729
  245. package/dist/codeIndexDB.js.map +1 -1
  246. package/dist/codeIndexService.d.ts +87 -12
  247. package/dist/codeIndexService.d.ts.map +1 -1
  248. package/dist/codeIndexService.js +110 -34
  249. package/dist/codeIndexService.js.map +1 -1
  250. package/dist/componentScanner.d.ts +35 -0
  251. package/dist/componentScanner.d.ts.map +1 -1
  252. package/dist/componentScanner.js +114 -80
  253. package/dist/componentScanner.js.map +1 -1
  254. package/dist/config/configGeneratePath.d.ts +4 -0
  255. package/dist/config/configGeneratePath.d.ts.map +1 -1
  256. package/dist/config/configGeneratePath.js +4 -0
  257. package/dist/config/configGeneratePath.js.map +1 -1
  258. package/dist/config/configLoader.d.ts +15 -0
  259. package/dist/config/configLoader.d.ts.map +1 -1
  260. package/dist/config/configLoader.js +15 -16
  261. package/dist/config/configLoader.js.map +1 -1
  262. package/dist/config/defaults.d.ts +11 -7
  263. package/dist/config/defaults.d.ts.map +1 -1
  264. package/dist/config/defaults.js +16 -14
  265. package/dist/config/defaults.js.map +1 -1
  266. package/dist/config/effectiveConfig.d.ts +17 -2
  267. package/dist/config/effectiveConfig.d.ts.map +1 -1
  268. package/dist/config/effectiveConfig.js +32 -25
  269. package/dist/config/effectiveConfig.js.map +1 -1
  270. package/dist/config/lintConfigReader.d.ts +6 -0
  271. package/dist/config/lintConfigReader.d.ts.map +1 -1
  272. package/dist/config/lintConfigReader.js +6 -0
  273. package/dist/config/lintConfigReader.js.map +1 -1
  274. package/dist/config/staticObjectExtract.d.ts +4 -0
  275. package/dist/config/staticObjectExtract.d.ts.map +1 -1
  276. package/dist/config/staticObjectExtract.js +7 -3
  277. package/dist/config/staticObjectExtract.js.map +1 -1
  278. package/dist/config/thresholdRationales.d.ts +2 -0
  279. package/dist/config/thresholdRationales.d.ts.map +1 -1
  280. package/dist/config/thresholdRationales.js +5 -2
  281. package/dist/config/thresholdRationales.js.map +1 -1
  282. package/dist/constants.d.ts +1 -1
  283. package/dist/conventions/conventionMiner.d.ts +105 -2
  284. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  285. package/dist/conventions/conventionMiner.js +334 -259
  286. package/dist/conventions/conventionMiner.js.map +1 -1
  287. package/dist/coverage/coverageService.js +4 -4
  288. package/dist/coverage/coverageService.js.map +1 -1
  289. package/dist/daemon/args.d.ts +3 -0
  290. package/dist/daemon/args.d.ts.map +1 -1
  291. package/dist/daemon/args.js +3 -0
  292. package/dist/daemon/args.js.map +1 -1
  293. package/dist/daemon/core.d.ts +33 -27
  294. package/dist/daemon/core.d.ts.map +1 -1
  295. package/dist/daemon/core.js +198 -162
  296. package/dist/daemon/core.js.map +1 -1
  297. package/dist/daemon/lspServer.d.ts +8 -0
  298. package/dist/daemon/lspServer.d.ts.map +1 -1
  299. package/dist/daemon/lspServer.js +8 -0
  300. package/dist/daemon/lspServer.js.map +1 -1
  301. package/dist/daemon/main.d.ts +2 -0
  302. package/dist/daemon/main.d.ts.map +1 -1
  303. package/dist/daemon/main.js +2 -0
  304. package/dist/daemon/main.js.map +1 -1
  305. package/dist/daemon/resolve.d.ts +18 -3
  306. package/dist/daemon/resolve.d.ts.map +1 -1
  307. package/dist/daemon/resolve.js +25 -4
  308. package/dist/daemon/resolve.js.map +1 -1
  309. package/dist/daemon/socketClient.d.ts +10 -1
  310. package/dist/daemon/socketClient.d.ts.map +1 -1
  311. package/dist/daemon/socketClient.js +10 -1
  312. package/dist/daemon/socketClient.js.map +1 -1
  313. package/dist/daemon/socketServer.d.ts +13 -0
  314. package/dist/daemon/socketServer.d.ts.map +1 -1
  315. package/dist/daemon/socketServer.js +13 -0
  316. package/dist/daemon/socketServer.js.map +1 -1
  317. package/dist/dataPaths.d.ts +17 -8
  318. package/dist/dataPaths.d.ts.map +1 -1
  319. package/dist/dataPaths.js +16 -44
  320. package/dist/dataPaths.js.map +1 -1
  321. package/dist/dismissals.d.ts +22 -1
  322. package/dist/dismissals.d.ts.map +1 -1
  323. package/dist/dismissals.js +22 -1
  324. package/dist/dismissals.js.map +1 -1
  325. package/dist/enforcement/gate.d.ts +1 -0
  326. package/dist/enforcement/gate.d.ts.map +1 -1
  327. package/dist/enforcement/gate.js +1 -0
  328. package/dist/enforcement/gate.js.map +1 -1
  329. package/dist/fingerprint.d.ts +6 -0
  330. package/dist/fingerprint.d.ts.map +1 -1
  331. package/dist/fingerprint.js +46 -1
  332. package/dist/fingerprint.js.map +1 -1
  333. package/dist/functionScanner.d.ts +30 -1
  334. package/dist/functionScanner.d.ts.map +1 -1
  335. package/dist/functionScanner.js +258 -256
  336. package/dist/functionScanner.js.map +1 -1
  337. package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
  338. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  339. package/dist/generators/AWSQConfigGenerator.js +23 -0
  340. package/dist/generators/AWSQConfigGenerator.js.map +1 -1
  341. package/dist/generators/BaseConfigGenerator.d.ts +7 -0
  342. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  343. package/dist/generators/BaseConfigGenerator.js +7 -0
  344. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  345. package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
  346. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  347. package/dist/generators/ClaudeConfigGenerator.js +23 -0
  348. package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
  349. package/dist/generators/ClineConfigGenerator.d.ts +23 -0
  350. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  351. package/dist/generators/ClineConfigGenerator.js +23 -0
  352. package/dist/generators/ClineConfigGenerator.js.map +1 -1
  353. package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
  354. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  355. package/dist/generators/CodeiumConfigGenerator.js +23 -0
  356. package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
  357. package/dist/generators/CodexConfigGenerator.d.ts +23 -0
  358. package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
  359. package/dist/generators/CodexConfigGenerator.js +23 -0
  360. package/dist/generators/CodexConfigGenerator.js.map +1 -1
  361. package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
  362. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  363. package/dist/generators/ConfigGeneratorFactory.js +9 -0
  364. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  365. package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
  366. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  367. package/dist/generators/ContinueConfigGenerator.js +23 -0
  368. package/dist/generators/ContinueConfigGenerator.js.map +1 -1
  369. package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
  370. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  371. package/dist/generators/CopilotConfigGenerator.js +23 -0
  372. package/dist/generators/CopilotConfigGenerator.js.map +1 -1
  373. package/dist/generators/CursorConfigGenerator.d.ts +23 -0
  374. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  375. package/dist/generators/CursorConfigGenerator.js +23 -0
  376. package/dist/generators/CursorConfigGenerator.js.map +1 -1
  377. package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
  378. package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
  379. package/dist/generators/GeminiConfigGenerator.js +23 -0
  380. package/dist/generators/GeminiConfigGenerator.js.map +1 -1
  381. package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
  382. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  383. package/dist/generators/VSCodeConfigGenerator.js +23 -0
  384. package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
  385. package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
  386. package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
  387. package/dist/generators/ZCodeConfigGenerator.js +23 -0
  388. package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
  389. package/dist/graph/blastRadius.d.ts.map +1 -1
  390. package/dist/graph/blastRadius.js +12 -7
  391. package/dist/graph/blastRadius.js.map +1 -1
  392. package/dist/graph/callGraph.d.ts +31 -0
  393. package/dist/graph/callGraph.d.ts.map +1 -1
  394. package/dist/graph/callGraph.js +62 -32
  395. package/dist/graph/callGraph.js.map +1 -1
  396. package/dist/graph/importClassification.d.ts +28 -0
  397. package/dist/graph/importClassification.d.ts.map +1 -1
  398. package/dist/graph/importClassification.js +57 -0
  399. package/dist/graph/importClassification.js.map +1 -1
  400. package/dist/graph/importGraph.d.ts +38 -0
  401. package/dist/graph/importGraph.d.ts.map +1 -1
  402. package/dist/graph/importGraph.js +38 -0
  403. package/dist/graph/importGraph.js.map +1 -1
  404. package/dist/graph/outputFormatter.d.ts +12 -4
  405. package/dist/graph/outputFormatter.d.ts.map +1 -1
  406. package/dist/graph/outputFormatter.js +12 -62
  407. package/dist/graph/outputFormatter.js.map +1 -1
  408. package/dist/hooks/codex.d.ts +8 -1
  409. package/dist/hooks/codex.d.ts.map +1 -1
  410. package/dist/hooks/codex.js +7 -0
  411. package/dist/hooks/codex.js.map +1 -1
  412. package/dist/hooks/core.d.ts +6 -0
  413. package/dist/hooks/core.d.ts.map +1 -1
  414. package/dist/hooks/core.js +6 -1
  415. package/dist/hooks/core.js.map +1 -1
  416. package/dist/hooks/cursor.d.ts +11 -1
  417. package/dist/hooks/cursor.d.ts.map +1 -1
  418. package/dist/hooks/cursor.js +10 -0
  419. package/dist/hooks/cursor.js.map +1 -1
  420. package/dist/index.d.ts +4 -0
  421. package/dist/index.d.ts.map +1 -1
  422. package/dist/index.js +4 -0
  423. package/dist/index.js.map +1 -1
  424. package/dist/installConfig.d.ts +23 -5
  425. package/dist/installConfig.d.ts.map +1 -1
  426. package/dist/installConfig.js +23 -5
  427. package/dist/installConfig.js.map +1 -1
  428. package/dist/installer.d.ts +6 -0
  429. package/dist/installer.d.ts.map +1 -1
  430. package/dist/installer.js +6 -0
  431. package/dist/installer.js.map +1 -1
  432. package/dist/invariants/ruleEngine.d.ts +3 -0
  433. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  434. package/dist/invariants/ruleEngine.js +156 -119
  435. package/dist/invariants/ruleEngine.js.map +1 -1
  436. package/dist/invariants/ruleValidator.d.ts +3 -0
  437. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  438. package/dist/invariants/ruleValidator.js +185 -178
  439. package/dist/invariants/ruleValidator.js.map +1 -1
  440. package/dist/languages/LanguageOrchestrator.d.ts +4 -1
  441. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  442. package/dist/languages/LanguageOrchestrator.js +8 -19
  443. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  444. package/dist/languages/RuntimeManager.d.ts +8 -0
  445. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  446. package/dist/languages/RuntimeManager.js +55 -5
  447. package/dist/languages/RuntimeManager.js.map +1 -1
  448. package/dist/languages/UniversalAnalyzer.js +1 -1
  449. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  450. package/dist/languages/adapterBridge.d.ts +4 -0
  451. package/dist/languages/adapterBridge.d.ts.map +1 -1
  452. package/dist/languages/adapterBridge.js +37 -3
  453. package/dist/languages/adapterBridge.js.map +1 -1
  454. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  455. package/dist/languages/go/GoAdapter.js +11 -14
  456. package/dist/languages/go/GoAdapter.js.map +1 -1
  457. package/dist/languages/go/analyzer-darwin-amd64 +0 -0
  458. package/dist/languages/go/analyzer-darwin-arm64 +0 -0
  459. package/dist/languages/go/analyzer-linux-amd64 +0 -0
  460. package/dist/languages/go/analyzer-linux-arm64 +0 -0
  461. package/dist/languages/go/analyzer-src/analyzer.go +2 -0
  462. package/dist/languages/go/analyzer-src/dataaccess.go +388 -0
  463. package/dist/languages/go/analyzer-src/types.go +8 -0
  464. package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
  465. package/dist/languages/go/database-packages.json +8 -0
  466. package/dist/languages/go/goResolution.d.ts +229 -0
  467. package/dist/languages/go/goResolution.d.ts.map +1 -0
  468. package/dist/languages/go/goResolution.js +799 -0
  469. package/dist/languages/go/goResolution.js.map +1 -0
  470. package/dist/languages/go/stdlib-packages.json +353 -0
  471. package/dist/languages/go/tenantInputs.d.ts +37 -0
  472. package/dist/languages/go/tenantInputs.d.ts.map +1 -0
  473. package/dist/languages/go/tenantInputs.js +121 -0
  474. package/dist/languages/go/tenantInputs.js.map +1 -0
  475. package/dist/languages/index.d.ts +1 -0
  476. package/dist/languages/index.d.ts.map +1 -1
  477. package/dist/languages/index.js +5 -0
  478. package/dist/languages/index.js.map +1 -1
  479. package/dist/languages/json/JsonAdapter.d.ts +177 -0
  480. package/dist/languages/json/JsonAdapter.d.ts.map +1 -0
  481. package/dist/languages/json/JsonAdapter.js +531 -0
  482. package/dist/languages/json/JsonAdapter.js.map +1 -0
  483. package/dist/languages/locationIndex.d.ts +41 -0
  484. package/dist/languages/locationIndex.d.ts.map +1 -0
  485. package/dist/languages/locationIndex.js +42 -0
  486. package/dist/languages/locationIndex.js.map +1 -0
  487. package/dist/languages/sql/dialectDetection.d.ts +57 -0
  488. package/dist/languages/sql/dialectDetection.d.ts.map +1 -0
  489. package/dist/languages/sql/dialectDetection.js +135 -0
  490. package/dist/languages/sql/dialectDetection.js.map +1 -0
  491. package/dist/languages/sql/sqlAst.d.ts +494 -0
  492. package/dist/languages/sql/sqlAst.d.ts.map +1 -0
  493. package/dist/languages/sql/sqlAst.js +1817 -0
  494. package/dist/languages/sql/sqlAst.js.map +1 -0
  495. package/dist/languages/testConventions.d.ts +12 -1
  496. package/dist/languages/testConventions.d.ts.map +1 -1
  497. package/dist/languages/testConventions.js +13 -1
  498. package/dist/languages/testConventions.js.map +1 -1
  499. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +1 -1
  500. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  501. package/dist/languages/tree-sitter/converter.js +2 -2
  502. package/dist/languages/tree-sitter/converter.js.map +1 -1
  503. package/dist/languages/tree-sitter/parser.d.ts +3 -4
  504. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  505. package/dist/languages/tree-sitter/parser.js +10 -4
  506. package/dist/languages/tree-sitter/parser.js.map +1 -1
  507. package/dist/languages/tree-sitter/rawNode.d.ts +3 -0
  508. package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -1
  509. package/dist/languages/tree-sitter/rawNode.js +3 -0
  510. package/dist/languages/tree-sitter/rawNode.js.map +1 -1
  511. package/dist/languages/types.d.ts +7 -0
  512. package/dist/languages/types.d.ts.map +1 -1
  513. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +36 -0
  514. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  515. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +168 -45
  516. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  517. package/dist/languages/typescript/database-packages.json +19 -0
  518. package/dist/ledger.d.ts +118 -2
  519. package/dist/ledger.d.ts.map +1 -1
  520. package/dist/ledger.js +167 -45
  521. package/dist/ledger.js.map +1 -1
  522. package/dist/legacyIndexNotice.d.ts +32 -0
  523. package/dist/legacyIndexNotice.d.ts.map +1 -0
  524. package/dist/legacyIndexNotice.js +89 -0
  525. package/dist/legacyIndexNotice.js.map +1 -0
  526. package/dist/mcp-tools/discoveryQueries.d.ts +55 -0
  527. package/dist/mcp-tools/discoveryQueries.d.ts.map +1 -0
  528. package/dist/mcp-tools/discoveryQueries.js +157 -0
  529. package/dist/mcp-tools/discoveryQueries.js.map +1 -0
  530. package/dist/mcp-tools/projectTasks.d.ts +9 -123
  531. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  532. package/dist/mcp-tools/projectTasks.js +180 -170
  533. package/dist/mcp-tools/projectTasks.js.map +1 -1
  534. package/dist/mcp-tools/whitelistTools.d.ts +20 -0
  535. package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
  536. package/dist/mcp-tools/whitelistTools.js +20 -0
  537. package/dist/mcp-tools/whitelistTools.js.map +1 -1
  538. package/dist/mcp-tools/workflowGuide.d.ts +9 -0
  539. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  540. package/dist/mcp-tools/workflowGuide.js +13 -5
  541. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  542. package/dist/mcp-tools-shared.d.ts +78 -0
  543. package/dist/mcp-tools-shared.d.ts.map +1 -1
  544. package/dist/mcp-tools-shared.js +203 -340
  545. package/dist/mcp-tools-shared.js.map +1 -1
  546. package/dist/mcp-ui-simple.d.ts +2 -0
  547. package/dist/mcp-ui-simple.d.ts.map +1 -1
  548. package/dist/mcp-ui-simple.js +12 -5
  549. package/dist/mcp-ui-simple.js.map +1 -1
  550. package/dist/mcp.d.ts +6 -0
  551. package/dist/mcp.d.ts.map +1 -1
  552. package/dist/mcp.js +827 -850
  553. package/dist/mcp.js.map +1 -1
  554. package/dist/mcpAuditJobs.d.ts +29 -33
  555. package/dist/mcpAuditJobs.d.ts.map +1 -1
  556. package/dist/mcpAuditJobs.js +102 -791
  557. package/dist/mcpAuditJobs.js.map +1 -1
  558. package/dist/mcpAutoIndex.d.ts +5 -0
  559. package/dist/mcpAutoIndex.d.ts.map +1 -1
  560. package/dist/mcpAutoIndex.js +7 -2
  561. package/dist/mcpAutoIndex.js.map +1 -1
  562. package/dist/mcpDiagnostics.d.ts +7 -0
  563. package/dist/mcpDiagnostics.d.ts.map +1 -1
  564. package/dist/mcpDiagnostics.js +34 -9
  565. package/dist/mcpDiagnostics.js.map +1 -1
  566. package/dist/mcpToolErrors.d.ts +16 -0
  567. package/dist/mcpToolErrors.d.ts.map +1 -1
  568. package/dist/mcpToolErrors.js +16 -0
  569. package/dist/mcpToolErrors.js.map +1 -1
  570. package/dist/nextFile.d.ts +6 -0
  571. package/dist/nextFile.d.ts.map +1 -1
  572. package/dist/nextFile.js +6 -0
  573. package/dist/nextFile.js.map +1 -1
  574. package/dist/nextFileIncremental.d.ts +44 -4
  575. package/dist/nextFileIncremental.d.ts.map +1 -1
  576. package/dist/nextFileIncremental.js +47 -7
  577. package/dist/nextFileIncremental.js.map +1 -1
  578. package/dist/phase/batchFunctions.d.ts +38 -0
  579. package/dist/phase/batchFunctions.d.ts.map +1 -0
  580. package/dist/phase/batchFunctions.js +64 -0
  581. package/dist/phase/batchFunctions.js.map +1 -0
  582. package/dist/phase/checks.d.ts +39 -0
  583. package/dist/phase/checks.d.ts.map +1 -0
  584. package/dist/phase/checks.js +48 -0
  585. package/dist/phase/checks.js.map +1 -0
  586. package/dist/phase/codeBlocks.d.ts +25 -0
  587. package/dist/phase/codeBlocks.d.ts.map +1 -0
  588. package/dist/phase/codeBlocks.js +268 -0
  589. package/dist/phase/codeBlocks.js.map +1 -0
  590. package/dist/phase/colorMath.d.ts +41 -0
  591. package/dist/phase/colorMath.d.ts.map +1 -0
  592. package/dist/phase/colorMath.js +109 -0
  593. package/dist/phase/colorMath.js.map +1 -0
  594. package/dist/phase/config.d.ts +29 -0
  595. package/dist/phase/config.d.ts.map +1 -0
  596. package/dist/phase/config.js +54 -0
  597. package/dist/phase/config.js.map +1 -0
  598. package/dist/phase/consumed.d.ts +47 -0
  599. package/dist/phase/consumed.d.ts.map +1 -0
  600. package/dist/phase/consumed.js +22 -0
  601. package/dist/phase/consumed.js.map +1 -0
  602. package/dist/phase/conventionMining.d.ts +59 -0
  603. package/dist/phase/conventionMining.d.ts.map +1 -0
  604. package/dist/phase/conventionMining.js +152 -0
  605. package/dist/phase/conventionMining.js.map +1 -0
  606. package/dist/phase/coverage.d.ts +65 -0
  607. package/dist/phase/coverage.d.ts.map +1 -0
  608. package/dist/phase/coverage.js +121 -0
  609. package/dist/phase/coverage.js.map +1 -0
  610. package/dist/phase/dag.d.ts +49 -0
  611. package/dist/phase/dag.d.ts.map +1 -0
  612. package/dist/phase/dag.js +120 -0
  613. package/dist/phase/dag.js.map +1 -0
  614. package/dist/phase/dataAccessCalls.d.ts +33 -0
  615. package/dist/phase/dataAccessCalls.d.ts.map +1 -0
  616. package/dist/phase/dataAccessCalls.js +42 -0
  617. package/dist/phase/dataAccessCalls.js.map +1 -0
  618. package/dist/phase/dataAccessCallsCandidates.d.ts +25 -0
  619. package/dist/phase/dataAccessCallsCandidates.d.ts.map +1 -0
  620. package/dist/phase/dataAccessCallsCandidates.js +27 -0
  621. package/dist/phase/dataAccessCallsCandidates.js.map +1 -0
  622. package/dist/phase/dynamicSql.d.ts +18 -0
  623. package/dist/phase/dynamicSql.d.ts.map +1 -0
  624. package/dist/phase/dynamicSql.js +20 -0
  625. package/dist/phase/dynamicSql.js.map +1 -0
  626. package/dist/phase/exportForm.d.ts +23 -0
  627. package/dist/phase/exportForm.d.ts.map +1 -0
  628. package/dist/phase/exportForm.js +29 -0
  629. package/dist/phase/exportForm.js.map +1 -0
  630. package/dist/phase/exportSymbols.d.ts +21 -0
  631. package/dist/phase/exportSymbols.d.ts.map +1 -0
  632. package/dist/phase/exportSymbols.js +28 -0
  633. package/dist/phase/exportSymbols.js.map +1 -0
  634. package/dist/phase/fileHeader.d.ts +15 -0
  635. package/dist/phase/fileHeader.d.ts.map +1 -0
  636. package/dist/phase/fileHeader.js +33 -0
  637. package/dist/phase/fileHeader.js.map +1 -0
  638. package/dist/phase/fileImports.d.ts +39 -0
  639. package/dist/phase/fileImports.d.ts.map +1 -0
  640. package/dist/phase/fileImports.js +138 -0
  641. package/dist/phase/fileImports.js.map +1 -0
  642. package/dist/phase/fileSymbols.d.ts +25 -0
  643. package/dist/phase/fileSymbols.d.ts.map +1 -0
  644. package/dist/phase/fileSymbols.js +302 -0
  645. package/dist/phase/fileSymbols.js.map +1 -0
  646. package/dist/phase/functionBodies.d.ts +28 -0
  647. package/dist/phase/functionBodies.d.ts.map +1 -0
  648. package/dist/phase/functionBodies.js +67 -0
  649. package/dist/phase/functionBodies.js.map +1 -0
  650. package/dist/phase/functionIndex.d.ts +28 -0
  651. package/dist/phase/functionIndex.d.ts.map +1 -0
  652. package/dist/phase/functionIndex.js +152 -0
  653. package/dist/phase/functionIndex.js.map +1 -0
  654. package/dist/phase/goFunctionAnalysis.d.ts +47 -0
  655. package/dist/phase/goFunctionAnalysis.d.ts.map +1 -0
  656. package/dist/phase/goFunctionAnalysis.js +291 -0
  657. package/dist/phase/goFunctionAnalysis.js.map +1 -0
  658. package/dist/phase/goFunctions.d.ts +38 -0
  659. package/dist/phase/goFunctions.d.ts.map +1 -0
  660. package/dist/phase/goFunctions.js +154 -0
  661. package/dist/phase/goFunctions.js.map +1 -0
  662. package/dist/phase/goImports.d.ts +29 -0
  663. package/dist/phase/goImports.d.ts.map +1 -0
  664. package/dist/phase/goImports.js +68 -0
  665. package/dist/phase/goImports.js.map +1 -0
  666. package/dist/phase/goPackageBindings.d.ts +22 -0
  667. package/dist/phase/goPackageBindings.d.ts.map +1 -0
  668. package/dist/phase/goPackageBindings.js +30 -0
  669. package/dist/phase/goPackageBindings.js.map +1 -0
  670. package/dist/phase/goSwitches.d.ts +28 -0
  671. package/dist/phase/goSwitches.d.ts.map +1 -0
  672. package/dist/phase/goSwitches.js +52 -0
  673. package/dist/phase/goSwitches.js.map +1 -0
  674. package/dist/phase/importForm.d.ts +20 -0
  675. package/dist/phase/importForm.d.ts.map +1 -0
  676. package/dist/phase/importForm.js +27 -0
  677. package/dist/phase/importForm.js.map +1 -0
  678. package/dist/phase/importSpecifiers.d.ts +20 -0
  679. package/dist/phase/importSpecifiers.d.ts.map +1 -0
  680. package/dist/phase/importSpecifiers.js +30 -0
  681. package/dist/phase/importSpecifiers.js.map +1 -0
  682. package/dist/phase/imports.d.ts +18 -0
  683. package/dist/phase/imports.d.ts.map +1 -0
  684. package/dist/phase/imports.js +25 -0
  685. package/dist/phase/imports.js.map +1 -0
  686. package/dist/phase/jsonDocument.d.ts +26 -0
  687. package/dist/phase/jsonDocument.d.ts.map +1 -0
  688. package/dist/phase/jsonDocument.js +36 -0
  689. package/dist/phase/jsonDocument.js.map +1 -0
  690. package/dist/phase/localBinding.d.ts +33 -0
  691. package/dist/phase/localBinding.d.ts.map +1 -0
  692. package/dist/phase/localBinding.js +208 -0
  693. package/dist/phase/localBinding.js.map +1 -0
  694. package/dist/phase/loopQueries.d.ts +31 -0
  695. package/dist/phase/loopQueries.d.ts.map +1 -0
  696. package/dist/phase/loopQueries.js +44 -0
  697. package/dist/phase/loopQueries.js.map +1 -0
  698. package/dist/phase/loopQueryCandidates.d.ts +26 -0
  699. package/dist/phase/loopQueryCandidates.d.ts.map +1 -0
  700. package/dist/phase/loopQueryCandidates.js +28 -0
  701. package/dist/phase/loopQueryCandidates.js.map +1 -0
  702. package/dist/phase/oracles.d.ts +216 -0
  703. package/dist/phase/oracles.d.ts.map +1 -0
  704. package/dist/phase/oracles.js +484 -0
  705. package/dist/phase/oracles.js.map +1 -0
  706. package/dist/phase/phaseModel.d.ts +190 -0
  707. package/dist/phase/phaseModel.d.ts.map +1 -0
  708. package/dist/phase/phaseModel.js +468 -0
  709. package/dist/phase/phaseModel.js.map +1 -0
  710. package/dist/phase/producers.d.ts +450 -0
  711. package/dist/phase/producers.d.ts.map +1 -0
  712. package/dist/phase/producers.js +1027 -0
  713. package/dist/phase/producers.js.map +1 -0
  714. package/dist/phase/querySiteCandidates.d.ts +27 -0
  715. package/dist/phase/querySiteCandidates.d.ts.map +1 -0
  716. package/dist/phase/querySiteCandidates.js +66 -0
  717. package/dist/phase/querySiteCandidates.js.map +1 -0
  718. package/dist/phase/querySites.d.ts +30 -0
  719. package/dist/phase/querySites.d.ts.map +1 -0
  720. package/dist/phase/querySites.js +93 -0
  721. package/dist/phase/querySites.js.map +1 -0
  722. package/dist/phase/reachability.d.ts +61 -0
  723. package/dist/phase/reachability.d.ts.map +1 -0
  724. package/dist/phase/reachability.js +104 -0
  725. package/dist/phase/reachability.js.map +1 -0
  726. package/dist/phase/reactComponents.d.ts +33 -0
  727. package/dist/phase/reactComponents.d.ts.map +1 -0
  728. package/dist/phase/reactComponents.js +47 -0
  729. package/dist/phase/reactComponents.js.map +1 -0
  730. package/dist/phase/receiverActivity.d.ts +35 -0
  731. package/dist/phase/receiverActivity.d.ts.map +1 -0
  732. package/dist/phase/receiverActivity.js +66 -0
  733. package/dist/phase/receiverActivity.js.map +1 -0
  734. package/dist/phase/receiverConsumers.d.ts +200 -0
  735. package/dist/phase/receiverConsumers.d.ts.map +1 -0
  736. package/dist/phase/receiverConsumers.js +1025 -0
  737. package/dist/phase/receiverConsumers.js.map +1 -0
  738. package/dist/phase/receiverProvenance.d.ts +97 -0
  739. package/dist/phase/receiverProvenance.d.ts.map +1 -0
  740. package/dist/phase/receiverProvenance.js +337 -0
  741. package/dist/phase/receiverProvenance.js.map +1 -0
  742. package/dist/phase/routing.d.ts +47 -0
  743. package/dist/phase/routing.d.ts.map +1 -0
  744. package/dist/phase/routing.js +62 -0
  745. package/dist/phase/routing.js.map +1 -0
  746. package/dist/phase/rules/conventions.d.ts +61 -0
  747. package/dist/phase/rules/conventions.d.ts.map +1 -0
  748. package/dist/phase/rules/conventions.js +403 -0
  749. package/dist/phase/rules/conventions.js.map +1 -0
  750. package/dist/phase/rules/crossDomain.d.ts +102 -0
  751. package/dist/phase/rules/crossDomain.d.ts.map +1 -0
  752. package/dist/phase/rules/crossDomain.js +918 -0
  753. package/dist/phase/rules/crossDomain.js.map +1 -0
  754. package/dist/phase/rules/dataAccess.d.ts +62 -0
  755. package/dist/phase/rules/dataAccess.d.ts.map +1 -0
  756. package/dist/phase/rules/dataAccess.js +480 -0
  757. package/dist/phase/rules/dataAccess.js.map +1 -0
  758. package/dist/phase/rules/dependencyGraph.d.ts +44 -0
  759. package/dist/phase/rules/dependencyGraph.d.ts.map +1 -0
  760. package/dist/phase/rules/dependencyGraph.js +260 -0
  761. package/dist/phase/rules/dependencyGraph.js.map +1 -0
  762. package/dist/phase/rules/documentation.d.ts +45 -0
  763. package/dist/phase/rules/documentation.d.ts.map +1 -0
  764. package/dist/phase/rules/documentation.js +340 -0
  765. package/dist/phase/rules/documentation.js.map +1 -0
  766. package/dist/phase/rules/dry.d.ts +109 -0
  767. package/dist/phase/rules/dry.d.ts.map +1 -0
  768. package/dist/phase/rules/dry.js +803 -0
  769. package/dist/phase/rules/dry.js.map +1 -0
  770. package/dist/phase/rules/fileDocumentation.d.ts +36 -0
  771. package/dist/phase/rules/fileDocumentation.d.ts.map +1 -0
  772. package/dist/phase/rules/fileDocumentation.js +134 -0
  773. package/dist/phase/rules/fileDocumentation.js.map +1 -0
  774. package/dist/phase/rules/functionBodies.d.ts +33 -0
  775. package/dist/phase/rules/functionBodies.d.ts.map +1 -0
  776. package/dist/phase/rules/functionBodies.js +71 -0
  777. package/dist/phase/rules/functionBodies.js.map +1 -0
  778. package/dist/phase/rules/goRules.d.ts +49 -0
  779. package/dist/phase/rules/goRules.d.ts.map +1 -0
  780. package/dist/phase/rules/goRules.js +209 -0
  781. package/dist/phase/rules/goRules.js.map +1 -0
  782. package/dist/phase/rules/querySites.d.ts +29 -0
  783. package/dist/phase/rules/querySites.d.ts.map +1 -0
  784. package/dist/phase/rules/querySites.js +94 -0
  785. package/dist/phase/rules/querySites.js.map +1 -0
  786. package/dist/phase/rules/react.d.ts +38 -0
  787. package/dist/phase/rules/react.d.ts.map +1 -0
  788. package/dist/phase/rules/react.js +202 -0
  789. package/dist/phase/rules/react.js.map +1 -0
  790. package/dist/phase/rules/registry.d.ts +170 -0
  791. package/dist/phase/rules/registry.d.ts.map +1 -0
  792. package/dist/phase/rules/registry.js +103 -0
  793. package/dist/phase/rules/registry.js.map +1 -0
  794. package/dist/phase/rules/schema.d.ts +79 -0
  795. package/dist/phase/rules/schema.d.ts.map +1 -0
  796. package/dist/phase/rules/schema.js +273 -0
  797. package/dist/phase/rules/schema.js.map +1 -0
  798. package/dist/phase/rules/schemaJson.d.ts +32 -0
  799. package/dist/phase/rules/schemaJson.d.ts.map +1 -0
  800. package/dist/phase/rules/schemaJson.js +81 -0
  801. package/dist/phase/rules/schemaJson.js.map +1 -0
  802. package/dist/phase/rules/schemaValidator.d.ts +38 -0
  803. package/dist/phase/rules/schemaValidator.d.ts.map +1 -0
  804. package/dist/phase/rules/schemaValidator.js +107 -0
  805. package/dist/phase/rules/schemaValidator.js.map +1 -0
  806. package/dist/phase/rules/secrets.d.ts +34 -0
  807. package/dist/phase/rules/secrets.d.ts.map +1 -0
  808. package/dist/phase/rules/secrets.js +208 -0
  809. package/dist/phase/rules/secrets.js.map +1 -0
  810. package/dist/phase/rules/security.d.ts +30 -0
  811. package/dist/phase/rules/security.d.ts.map +1 -0
  812. package/dist/phase/rules/security.js +85 -0
  813. package/dist/phase/rules/security.js.map +1 -0
  814. package/dist/phase/rules/securityDefects.d.ts +37 -0
  815. package/dist/phase/rules/securityDefects.d.ts.map +1 -0
  816. package/dist/phase/rules/securityDefects.js +167 -0
  817. package/dist/phase/rules/securityDefects.js.map +1 -0
  818. package/dist/phase/rules/solid.d.ts +61 -0
  819. package/dist/phase/rules/solid.d.ts.map +1 -0
  820. package/dist/phase/rules/solid.js +562 -0
  821. package/dist/phase/rules/solid.js.map +1 -0
  822. package/dist/phase/rules/styles.d.ts +77 -0
  823. package/dist/phase/rules/styles.d.ts.map +1 -0
  824. package/dist/phase/rules/styles.js +868 -0
  825. package/dist/phase/rules/styles.js.map +1 -0
  826. package/dist/phase/rules/unreferencedModule.d.ts +26 -0
  827. package/dist/phase/rules/unreferencedModule.d.ts.map +1 -0
  828. package/dist/phase/rules/unreferencedModule.js +61 -0
  829. package/dist/phase/rules/unreferencedModule.js.map +1 -0
  830. package/dist/phase/runner.d.ts +549 -0
  831. package/dist/phase/runner.d.ts.map +1 -0
  832. package/dist/phase/runner.js +1098 -0
  833. package/dist/phase/runner.js.map +1 -0
  834. package/dist/phase/schemaCode.d.ts +34 -0
  835. package/dist/phase/schemaCode.d.ts.map +1 -0
  836. package/dist/phase/schemaCode.js +53 -0
  837. package/dist/phase/schemaCode.js.map +1 -0
  838. package/dist/phase/schemaObjects.d.ts +37 -0
  839. package/dist/phase/schemaObjects.d.ts.map +1 -0
  840. package/dist/phase/schemaObjects.js +139 -0
  841. package/dist/phase/schemaObjects.js.map +1 -0
  842. package/dist/phase/schemaUsage.d.ts +30 -0
  843. package/dist/phase/schemaUsage.d.ts.map +1 -0
  844. package/dist/phase/schemaUsage.js +99 -0
  845. package/dist/phase/schemaUsage.js.map +1 -0
  846. package/dist/phase/schemaUsageCandidates.d.ts +32 -0
  847. package/dist/phase/schemaUsageCandidates.d.ts.map +1 -0
  848. package/dist/phase/schemaUsageCandidates.js +165 -0
  849. package/dist/phase/schemaUsageCandidates.js.map +1 -0
  850. package/dist/phase/schemaValidations.d.ts +34 -0
  851. package/dist/phase/schemaValidations.d.ts.map +1 -0
  852. package/dist/phase/schemaValidations.js +53 -0
  853. package/dist/phase/schemaValidations.js.map +1 -0
  854. package/dist/phase/secretCandidates.d.ts +28 -0
  855. package/dist/phase/secretCandidates.d.ts.map +1 -0
  856. package/dist/phase/secretCandidates.js +132 -0
  857. package/dist/phase/secretCandidates.js.map +1 -0
  858. package/dist/phase/securityCandidates.d.ts +29 -0
  859. package/dist/phase/securityCandidates.d.ts.map +1 -0
  860. package/dist/phase/securityCandidates.js +469 -0
  861. package/dist/phase/securityCandidates.js.map +1 -0
  862. package/dist/phase/seeded-defects.d.ts +18 -0
  863. package/dist/phase/seeded-defects.d.ts.map +1 -0
  864. package/dist/phase/seeded-defects.js +61 -0
  865. package/dist/phase/seeded-defects.js.map +1 -0
  866. package/dist/phase/stringLiterals.d.ts +24 -0
  867. package/dist/phase/stringLiterals.d.ts.map +1 -0
  868. package/dist/phase/stringLiterals.js +35 -0
  869. package/dist/phase/stringLiterals.js.map +1 -0
  870. package/dist/phase/stylesCss.d.ts +25 -0
  871. package/dist/phase/stylesCss.d.ts.map +1 -0
  872. package/dist/phase/stylesCss.js +34 -0
  873. package/dist/phase/stylesCss.js.map +1 -0
  874. package/dist/phase/stylesMarkup.d.ts +31 -0
  875. package/dist/phase/stylesMarkup.d.ts.map +1 -0
  876. package/dist/phase/stylesMarkup.js +48 -0
  877. package/dist/phase/stylesMarkup.js.map +1 -0
  878. package/dist/phase/stylesSource.d.ts +36 -0
  879. package/dist/phase/stylesSource.d.ts.map +1 -0
  880. package/dist/phase/stylesSource.js +50 -0
  881. package/dist/phase/stylesSource.js.map +1 -0
  882. package/dist/phase/typeDeclarations.d.ts +33 -0
  883. package/dist/phase/typeDeclarations.d.ts.map +1 -0
  884. package/dist/phase/typeDeclarations.js +76 -0
  885. package/dist/phase/typeDeclarations.js.map +1 -0
  886. package/dist/phase/types.d.ts +2191 -0
  887. package/dist/phase/types.d.ts.map +1 -0
  888. package/dist/phase/types.js +27 -0
  889. package/dist/phase/types.js.map +1 -0
  890. package/dist/phase/withinFileProvenance.d.ts +50 -0
  891. package/dist/phase/withinFileProvenance.d.ts.map +1 -0
  892. package/dist/phase/withinFileProvenance.js +112 -0
  893. package/dist/phase/withinFileProvenance.js.map +1 -0
  894. package/dist/pipeline.d.ts +37 -30
  895. package/dist/pipeline.d.ts.map +1 -1
  896. package/dist/pipeline.js +131 -326
  897. package/dist/pipeline.js.map +1 -1
  898. package/dist/pipelineAdapters.d.ts +104 -21
  899. package/dist/pipelineAdapters.d.ts.map +1 -1
  900. package/dist/pipelineAdapters.js +1144 -984
  901. package/dist/pipelineAdapters.js.map +1 -1
  902. package/dist/pipelineTypes.d.ts +21 -0
  903. package/dist/pipelineTypes.d.ts.map +1 -1
  904. package/dist/pipelineTypes.js +21 -10
  905. package/dist/pipelineTypes.js.map +1 -1
  906. package/dist/presets/presets.d.ts +9 -2
  907. package/dist/presets/presets.d.ts.map +1 -1
  908. package/dist/presets/presets.js +15 -26
  909. package/dist/presets/presets.js.map +1 -1
  910. package/dist/reporting/csvReportGenerator.d.ts +14 -3
  911. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  912. package/dist/reporting/csvReportGenerator.js +14 -3
  913. package/dist/reporting/csvReportGenerator.js.map +1 -1
  914. package/dist/reporting/htmlReportGenerator.d.ts +5 -1
  915. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  916. package/dist/reporting/htmlReportGenerator.js +5 -1
  917. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  918. package/dist/reporting/jsonReportGenerator.d.ts +9 -2
  919. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  920. package/dist/reporting/jsonReportGenerator.js +19 -5
  921. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  922. package/dist/reporting/reportGenerator.d.ts +9 -2
  923. package/dist/reporting/reportGenerator.d.ts.map +1 -1
  924. package/dist/reporting/reportGenerator.js +9 -2
  925. package/dist/reporting/reportGenerator.js.map +1 -1
  926. package/dist/reporting/sarifReportGenerator.d.ts +14 -0
  927. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  928. package/dist/reporting/sarifReportGenerator.js +14 -0
  929. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  930. package/dist/reporting/sizeDistribution.d.ts +19 -2
  931. package/dist/reporting/sizeDistribution.d.ts.map +1 -1
  932. package/dist/reporting/sizeDistribution.js +23 -5
  933. package/dist/reporting/sizeDistribution.js.map +1 -1
  934. package/dist/ruleAliases.d.ts +6 -8
  935. package/dist/ruleAliases.d.ts.map +1 -1
  936. package/dist/ruleAliases.js +29 -4
  937. package/dist/ruleAliases.js.map +1 -1
  938. package/dist/search/QueryParser.d.ts +1 -0
  939. package/dist/search/QueryParser.d.ts.map +1 -1
  940. package/dist/search/QueryParser.js +32 -91
  941. package/dist/search/QueryParser.js.map +1 -1
  942. package/dist/services/CodeMapGenerator.d.ts +19 -20
  943. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  944. package/dist/services/CodeMapGenerator.js +214 -194
  945. package/dist/services/CodeMapGenerator.js.map +1 -1
  946. package/dist/services/ProjectTaskRepository.d.ts +65 -3
  947. package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
  948. package/dist/services/ProjectTaskRepository.js +94 -25
  949. package/dist/services/ProjectTaskRepository.js.map +1 -1
  950. package/dist/services/auditJobService.d.ts +17 -1
  951. package/dist/services/auditJobService.d.ts.map +1 -1
  952. package/dist/services/auditJobService.js +16 -0
  953. package/dist/services/auditJobService.js.map +1 -1
  954. package/dist/services/fileAccounting.d.ts +43 -5
  955. package/dist/services/fileAccounting.d.ts.map +1 -1
  956. package/dist/services/fileAccounting.js +48 -9
  957. package/dist/services/fileAccounting.js.map +1 -1
  958. package/dist/services/projectTaskHelpers.d.ts +45 -0
  959. package/dist/services/projectTaskHelpers.d.ts.map +1 -1
  960. package/dist/services/projectTaskHelpers.js +45 -0
  961. package/dist/services/projectTaskHelpers.js.map +1 -1
  962. package/dist/services/whitelistService.d.ts +40 -5
  963. package/dist/services/whitelistService.d.ts.map +1 -1
  964. package/dist/services/whitelistService.js +87 -46
  965. package/dist/services/whitelistService.js.map +1 -1
  966. package/dist/sqlite/betterSqlite3.d.ts +35 -1
  967. package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
  968. package/dist/sqlite/betterSqlite3.js +35 -1
  969. package/dist/sqlite/betterSqlite3.js.map +1 -1
  970. package/dist/sqlite/driver.d.ts +23 -0
  971. package/dist/sqlite/driver.d.ts.map +1 -1
  972. package/dist/sqlite/driver.js +23 -0
  973. package/dist/sqlite/driver.js.map +1 -1
  974. package/dist/sqlite/nodeSqlite.d.ts +35 -1
  975. package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
  976. package/dist/sqlite/nodeSqlite.js +35 -1
  977. package/dist/sqlite/nodeSqlite.js.map +1 -1
  978. package/dist/structuralSignature.d.ts +9 -0
  979. package/dist/structuralSignature.d.ts.map +1 -1
  980. package/dist/structuralSignature.js +9 -0
  981. package/dist/structuralSignature.js.map +1 -1
  982. package/dist/styles/cssAstExtractor.d.ts +15 -0
  983. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  984. package/dist/styles/cssAstExtractor.js +15 -0
  985. package/dist/styles/cssAstExtractor.js.map +1 -1
  986. package/dist/styles/normalizer.d.ts +6 -0
  987. package/dist/styles/normalizer.d.ts.map +1 -1
  988. package/dist/styles/normalizer.js +6 -0
  989. package/dist/styles/normalizer.js.map +1 -1
  990. package/dist/styles/styleExtractor.d.ts +29 -2
  991. package/dist/styles/styleExtractor.d.ts.map +1 -1
  992. package/dist/styles/styleExtractor.js +67 -48
  993. package/dist/styles/styleExtractor.js.map +1 -1
  994. package/dist/styles/styleIndexer.d.ts +8 -0
  995. package/dist/styles/styleIndexer.d.ts.map +1 -1
  996. package/dist/styles/styleIndexer.js +135 -138
  997. package/dist/styles/styleIndexer.js.map +1 -1
  998. package/dist/styles/tailwindConfigLoader.d.ts +12 -0
  999. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
  1000. package/dist/styles/tailwindConfigLoader.js +12 -0
  1001. package/dist/styles/tailwindConfigLoader.js.map +1 -1
  1002. package/dist/styles/tailwindExpander.d.ts +3 -0
  1003. package/dist/styles/tailwindExpander.d.ts.map +1 -1
  1004. package/dist/styles/tailwindExpander.js +8 -4
  1005. package/dist/styles/tailwindExpander.js.map +1 -1
  1006. package/dist/styles/tailwindProbe.d.ts +9 -0
  1007. package/dist/styles/tailwindProbe.d.ts.map +1 -1
  1008. package/dist/styles/tailwindProbe.js +181 -166
  1009. package/dist/styles/tailwindProbe.js.map +1 -1
  1010. package/dist/styles/tailwindUtilityExpander.d.ts +13 -0
  1011. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  1012. package/dist/styles/tailwindUtilityExpander.js +13 -0
  1013. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  1014. package/dist/symbols.d.ts +13 -11
  1015. package/dist/symbols.d.ts.map +1 -1
  1016. package/dist/symbols.js +14 -21
  1017. package/dist/symbols.js.map +1 -1
  1018. package/dist/telemetry.d.ts +31 -3
  1019. package/dist/telemetry.d.ts.map +1 -1
  1020. package/dist/telemetry.js +31 -3
  1021. package/dist/telemetry.js.map +1 -1
  1022. package/dist/tool-registry.d.ts +37 -1
  1023. package/dist/tool-registry.d.ts.map +1 -1
  1024. package/dist/tool-registry.js +37 -1
  1025. package/dist/tool-registry.js.map +1 -1
  1026. package/dist/types.d.ts +101 -71
  1027. package/dist/types.d.ts.map +1 -1
  1028. package/dist/types.js +7 -16
  1029. package/dist/types.js.map +1 -1
  1030. package/dist/ui/InteractivePrompts.d.ts +12 -0
  1031. package/dist/ui/InteractivePrompts.d.ts.map +1 -1
  1032. package/dist/ui/InteractivePrompts.js +12 -0
  1033. package/dist/ui/InteractivePrompts.js.map +1 -1
  1034. package/dist/utils/astParser.d.ts +15 -0
  1035. package/dist/utils/astParser.d.ts.map +1 -1
  1036. package/dist/utils/astParser.js +15 -0
  1037. package/dist/utils/astParser.js.map +1 -1
  1038. package/dist/utils/astUtils.d.ts +60 -0
  1039. package/dist/utils/astUtils.d.ts.map +1 -1
  1040. package/dist/utils/astUtils.js +350 -362
  1041. package/dist/utils/astUtils.js.map +1 -1
  1042. package/dist/utils/contentHash.d.ts +22 -0
  1043. package/dist/utils/contentHash.d.ts.map +1 -0
  1044. package/dist/utils/contentHash.js +26 -0
  1045. package/dist/utils/contentHash.js.map +1 -0
  1046. package/dist/utils/dependencyExtractor.d.ts +28 -0
  1047. package/dist/utils/dependencyExtractor.d.ts.map +1 -1
  1048. package/dist/utils/dependencyExtractor.js +28 -1
  1049. package/dist/utils/dependencyExtractor.js.map +1 -1
  1050. package/dist/utils/errorMessage.d.ts +10 -0
  1051. package/dist/utils/errorMessage.d.ts.map +1 -0
  1052. package/dist/utils/errorMessage.js +12 -0
  1053. package/dist/utils/errorMessage.js.map +1 -0
  1054. package/dist/utils/fileDiscovery.d.ts +61 -2
  1055. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  1056. package/dist/utils/fileDiscovery.js +131 -23
  1057. package/dist/utils/fileDiscovery.js.map +1 -1
  1058. package/dist/utils/reactDetection.d.ts +34 -5
  1059. package/dist/utils/reactDetection.d.ts.map +1 -1
  1060. package/dist/utils/reactDetection.js +73 -52
  1061. package/dist/utils/reactDetection.js.map +1 -1
  1062. package/dist/version.generated.d.ts +1 -1
  1063. package/dist/version.generated.js +1 -1
  1064. package/dist/workers/auditWorker.js +7 -3
  1065. package/dist/workers/auditWorker.js.map +1 -1
  1066. package/examples/.auditrc.example.json +0 -8
  1067. package/examples/.auditrc.json +0 -11
  1068. package/examples/.code-auditor.json +0 -2
  1069. package/examples/nextjs.auditrc.json +0 -8
  1070. package/examples/node-api.auditrc.json +0 -7
  1071. package/examples/react.auditrc.json +0 -6
  1072. package/examples/tolerant-solid.auditrc.json +0 -5
  1073. package/package.json +11 -5
  1074. package/plugin/.claude-plugin/manifest.json +16 -0
  1075. package/plugin/.claude-plugin/plugin.json +1 -1
  1076. package/plugin/hooks/hooks.json +6 -2
  1077. package/plugin/scripts/hook-audit.sh +41 -14
  1078. package/plugin/scripts/hook-common.sh +50 -5
  1079. package/plugin/scripts/hook-drift-check.sh +72 -0
  1080. package/plugin/scripts/hook-self-audit.sh +17 -12
  1081. package/plugin/skills/code-auditor/SKILL.md +1 -1
  1082. package/server.json +2 -2
@@ -7,8 +7,6 @@
7
7
  *
8
8
  * Key patterns:
9
9
  * - Per-file (solid, data-access, doc, schema): wrap analyzeAST()
10
- * - DRY: wraps analyzeAST(), accumulates dryPairs internally →
11
- * factory returns { visitor, getDryPairs } for post-pipeline extraction
12
10
  * - React: wraps scanFile() per file, accumulates scan results →
13
11
  * factory returns { visitor, finalizeCrossComponent }
14
12
  * - DB-based (styles, conventions, cross-domain): reducers call
@@ -17,21 +15,21 @@
17
15
  */
18
16
  import { createHash } from 'crypto';
19
17
  import path from 'path';
20
- import { RULE_REGISTRY } from './analyzers/ruleRegistry.js';
18
+ import { MIGRATED_RULES } from './phase/rules/registry.js';
21
19
  import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
20
+ import { computeContentHash } from './utils/contentHash.js';
22
21
  import { walkAST, isExported, getNodeText, getFieldNode, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
23
22
  import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
24
23
  import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
25
24
  import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
26
- import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
25
+ import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
27
26
  import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
27
+ import { buildBindingEnv } from './analyzers/receiverRoot.js';
28
28
  import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
29
29
  import { buildOrgFilterTierSet, tableRequiresOrgFilter, } from './analyzers/orgFilterTiers.js';
30
30
  // ── Rule ID helpers ──────────────────────────────────────────────────────────
31
31
  function getRuleIdsFor(name) {
32
- return Object.entries(RULE_REGISTRY)
33
- .filter(([, entry]) => entry.analyzer === name)
34
- .map(([id]) => id);
32
+ return MIGRATED_RULES.filter((r) => r.analyzer === name).map((r) => r.id);
35
33
  }
36
34
  /** Join a list of names as prose: "a", "a and b", "a, b and c". */
37
35
  function joinEnglish(names) {
@@ -50,91 +48,62 @@ function lazySingleton(loader) {
50
48
  return promise;
51
49
  };
52
50
  }
53
- export function createSolidVisitor() {
54
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSOLIDAnalyzer.js').then((m) => new m.UniversalSOLIDAnalyzer()));
55
- const visitor = {
56
- name: 'solid',
57
- stage: 'visitor',
58
- getRuleIds: () => getRuleIdsFor('solid'),
59
- async visit(ast, adapter, context, sourceCode) {
60
- const a = await getAnalyzer();
61
- const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
62
- return {
63
- violations,
64
- facts: {},
65
- };
66
- },
67
- defaultConfig: {},
68
- description: 'Detects violations of SOLID principles',
69
- category: 'architecture',
70
- };
51
+ function createAnalyzerAstVisitor(spec) {
71
52
  return {
72
- visitor,
73
- getSizeSamples: async () => {
74
- try {
75
- const a = await getAnalyzer();
76
- return a.sizeSamples ?? [];
77
- }
78
- catch {
79
- return [];
80
- }
81
- },
82
- };
83
- }
84
- export function createDryVisitor() {
85
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDRYAnalyzer.js').then((m) => new m.UniversalDRYAnalyzer()));
86
- const visitor = {
87
- name: 'dry',
53
+ name: spec.name,
88
54
  stage: 'visitor',
89
- getRuleIds: () => getRuleIdsFor('dry'),
55
+ getRuleIds: () => getRuleIdsFor(spec.name),
90
56
  async visit(ast, adapter, context, sourceCode) {
91
- const a = await getAnalyzer();
57
+ const a = await spec.getAnalyzer();
92
58
  const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
93
59
  return { violations, facts: {} };
94
60
  },
95
61
  defaultConfig: {},
96
- description: 'Detects code duplication across the codebase',
97
- category: 'maintainability',
98
- };
99
- return {
100
- visitor,
101
- getDryPairs: async () => {
102
- try {
103
- const a = await getAnalyzer();
104
- return a.dryPairs ?? [];
105
- }
106
- catch {
107
- return [];
108
- }
109
- },
62
+ description: spec.description,
63
+ category: spec.category,
110
64
  };
111
65
  }
112
- // ── Data-Access visitor ──────────────────────────────────────────────────────
113
- export function createDataAccessVisitor() {
114
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDataAccessAnalyzer.js').then((m) => new m.UniversalDataAccessAnalyzer()));
66
+ /** Read the analyzer's accumulated samples/pairs, swallowing a load failure. */
67
+ async function readAccumulated(getAnalyzer, pick) {
68
+ try {
69
+ const a = await getAnalyzer();
70
+ return pick(a) ?? [];
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
76
+ /** A bundle visitor: an analyzeAST visitor plus a getter for the analyzer's
77
+ * accumulated samples/pairs. The bundle creators (SOLID, DRY) share one lazy
78
+ * analyzer instance between the visitor and the getter. */
79
+ function createAnalyzerBundle(spec) {
80
+ const getAnalyzer = lazySingleton(spec.loader);
115
81
  return {
116
- name: 'data-access',
117
- stage: 'visitor',
118
- getRuleIds: () => getRuleIdsFor('data-access'),
119
- async visit(ast, adapter, context, sourceCode) {
120
- const a = await getAnalyzer();
121
- // Spec 62 Amendment B — emit the extracted query facts (per-file) for the
122
- // Stage-4 missing-org-filter reducer to join against the tenant tiers.
123
- // The violations returned here exclude missing-org-filter (moved to the
124
- // derived reducer); the calls carry resolved table + filter presence +
125
- // DB provenance + the query-site location.
126
- const { violations, calls } = await a.analyzeWithFacts(ast, adapter, context.config, sourceCode);
127
- const facts = {};
128
- if (calls.length > 0) {
129
- facts[context.filePath] = { calls };
130
- }
131
- return { violations, facts };
132
- },
133
- defaultConfig: {},
134
- description: 'Analyzes database access patterns and data layer interactions',
135
- category: 'security',
82
+ visitor: createAnalyzerAstVisitor({
83
+ name: spec.name,
84
+ getAnalyzer,
85
+ description: spec.description,
86
+ category: spec.category,
87
+ }),
88
+ getAccumulated: () => readAccumulated(getAnalyzer, spec.pick),
136
89
  };
137
90
  }
91
+ /**
92
+ * Create the SOLID stage-2 visitor, which lazily loads the SOLID analyzer and
93
+ * also exposes its accumulated size samples.
94
+ *
95
+ * @returns The SOLID visitor bundle.
96
+ */
97
+ export function createSolidVisitor() {
98
+ const { visitor, getAccumulated } = createAnalyzerBundle({
99
+ name: 'solid',
100
+ loader: () => import('./analyzers/universal/UniversalSOLIDAnalyzer.js').then((m) => new m.UniversalSOLIDAnalyzer()),
101
+ description: 'Detects violations of SOLID principles',
102
+ category: 'architecture',
103
+ pick: (a) => a.sizeSamples,
104
+ });
105
+ return { visitor, getSizeSamples: getAccumulated };
106
+ }
138
107
  // ── Data-Access org-filter reducer (Spec 62 Amendment B) ─────────────────────
139
108
  /**
140
109
  * Stage-4 derived reducer for `missing-org-filter`. It joins the data-access
@@ -148,6 +117,8 @@ export function createDataAccessVisitor() {
148
117
  *
149
118
  * Firing and applicability both derive from {@link buildOrgFilterTierSet}, so
150
119
  * they read the identical tier set (Tier 1 config / Tier 2 schema / Tier 3 DDL).
120
+ *
121
+ * @returns The data-access org-filter stage-4 reducer.
151
122
  */
152
123
  export function createOrgFilterReducer() {
153
124
  return {
@@ -155,9 +126,8 @@ export function createOrgFilterReducer() {
155
126
  stage: 'derivedReducer',
156
127
  // Hard dependency on the data-access visitor's per-query facts. The schema
157
128
  // table catalog (`allFacts['schema'].tableColumns`) is read opportunistically
158
- // so an MCP-default run (which omits `schema`) degrades to Tier 1–2
159
- // (config-only) instead of throwing — see buildOrgFilterTierSet.
160
- consumes: ['data-access'],
129
+ // so a run without `schema` facts degrades to Tier 1–2 (config-only) instead
130
+ // of throwing — see buildOrgFilterTierSet.
161
131
  getRuleIds: () => getRuleIdsFor('data-access-org-filter'),
162
132
  async reduce(allFacts, context) {
163
133
  const violations = [];
@@ -192,7 +162,7 @@ export function createOrgFilterReducer() {
192
162
  },
193
163
  rule: 'missing-org-filter',
194
164
  analyzer: 'data-access',
195
- functionName: symbol,
165
+ symbol,
196
166
  });
197
167
  }
198
168
  }
@@ -203,41 +173,28 @@ export function createOrgFilterReducer() {
203
173
  category: 'security',
204
174
  };
205
175
  }
206
- // ── Documentation visitor ────────────────────────────────────────────────────
207
- export function createDocumentationVisitor() {
208
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalDocumentationAnalyzer.js').then((m) => new m.UniversalDocumentationAnalyzer()));
209
- return {
210
- name: 'documentation',
211
- stage: 'visitor',
212
- getRuleIds: () => getRuleIdsFor('documentation'),
213
- async visit(ast, adapter, context, sourceCode) {
214
- const a = await getAnalyzer();
215
- const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
216
- return { violations, facts: {} };
217
- },
218
- defaultConfig: {},
219
- description: 'Checks documentation completeness',
220
- category: 'style',
221
- };
222
- }
223
176
  // ── Secrets visitor ──────────────────────────────────────────────────────────
177
+ /**
178
+ * Create the secrets stage-2 visitor, which detects hardcoded credentials,
179
+ * API keys, and tokens.
180
+ *
181
+ * @returns The secrets stage-2 visitor.
182
+ */
224
183
  export function createSecretsVisitor() {
225
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecretsAnalyzer.js').then((m) => new m.UniversalSecretsAnalyzer()));
226
- return {
184
+ return createAnalyzerAstVisitor({
227
185
  name: 'secrets',
228
- stage: 'visitor',
229
- getRuleIds: () => getRuleIdsFor('secrets'),
230
- async visit(ast, adapter, context, sourceCode) {
231
- const a = await getAnalyzer();
232
- const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
233
- return { violations, facts: {} };
234
- },
235
- defaultConfig: {},
186
+ getAnalyzer: lazySingleton(() => import('./analyzers/universal/UniversalSecretsAnalyzer.js').then((m) => new m.UniversalSecretsAnalyzer())),
236
187
  description: 'Detects hardcoded credentials, API keys, and tokens',
237
188
  category: 'security',
238
- };
189
+ });
239
190
  }
240
191
  // ── Security visitor (Spec 61 R6) ────────────────────────────────────────────
192
+ /**
193
+ * Create the security stage-2 visitor, which detects command injection,
194
+ * dynamic require of project paths, and unescaped HTML interpolation.
195
+ *
196
+ * @returns The security stage-2 visitor.
197
+ */
241
198
  export function createSecurityVisitor() {
242
199
  const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSecurityAnalyzer.js').then((m) => new m.UniversalSecurityAnalyzer()));
243
200
  return {
@@ -247,23 +204,276 @@ export function createSecurityVisitor() {
247
204
  async visit(ast, adapter, context, sourceCode) {
248
205
  const a = await getAnalyzer();
249
206
  const violations = await a.analyzeAST(ast, adapter, context.config, sourceCode);
250
- return { violations, facts: {} };
207
+ // Emit a fact per trigger construct the file contained. These fold
208
+ // corpus-wide (only keys seen in ≥1 file survive the spread-merge) and
209
+ // feed securityInputApplicability, so a rule whose construct is absent
210
+ // reads `notApplicable` instead of `clean`.
211
+ const saw = a.inputPresence;
212
+ const facts = {};
213
+ if (saw.shellProcess)
214
+ facts.shellProcessSeen = true;
215
+ if (saw.dynamicRequire)
216
+ facts.dynamicRequireSeen = true;
217
+ if (saw.htmlSink)
218
+ facts.htmlSinkSeen = true;
219
+ return { violations, facts };
251
220
  },
252
221
  defaultConfig: {},
253
222
  description: 'Detects command injection, dynamic require of project paths, and unescaped HTML interpolation',
254
223
  category: 'security',
255
224
  };
256
225
  }
257
- // ── Function-Index visitor (infrastructure) ──────────────────────────────────
258
- // Always-on Stage 2 visitor that populates the `functions` table with
259
- // per-file definitions so the conventions and cross-domain reducers have data
260
- // to mine — even on a cold run with no prior `index sync`.
261
- // The function_calls table is rebuilt post-pipeline after functions rows have
262
- // their auto-increment IDs assigned.
263
- function computeContentHash(body, signature) {
264
- const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
265
- return createHash('sha256').update(normalized).digest('hex');
226
+ /**
227
+ * Collect function/method/component entries from an AST in three passes:
228
+ * (1) named function declarations and methods, (2) arrow functions assigned
229
+ * to variables, (3) React component detection. Returns the deduplicated list
230
+ * of `FunctionIndexEntry` rows destined for the `functions` index table.
231
+ */
232
+ function collectFunctionEntries(root, sourceCode, importMap, filePath) {
233
+ const fnEntries = [];
234
+ // Pass 1 — named function declarations and methods
235
+ walkAST(root, (node) => {
236
+ if (node.type === 'function_declaration') {
237
+ const nameNode = node.children?.find((c) => c.type === 'identifier');
238
+ if (!nameNode)
239
+ return;
240
+ const name = getNodeText(nameNode, sourceCode);
241
+ if (!name)
242
+ return;
243
+ const { line } = getLineAndColumn(node);
244
+ const endLine = node.location.end.line;
245
+ const body = getFunctionBody(node, sourceCode);
246
+ const calls = extractFunctionCalls(node, sourceCode, importMap);
247
+ const callNames = [...new Set(calls.map((c) => c.callee))];
248
+ fnEntries.push({
249
+ name,
250
+ line,
251
+ endLine,
252
+ entityType: 'function',
253
+ componentType: null,
254
+ isExported: isExported(node),
255
+ complexity: calculateComplexity(node),
256
+ body,
257
+ functionCalls: callNames,
258
+ });
259
+ }
260
+ // Class methods
261
+ if (node.type === 'method_definition') {
262
+ const nameNode = node.children?.find((c) => c.type === 'identifier');
263
+ if (!nameNode)
264
+ return;
265
+ const methodName = getNodeText(nameNode, sourceCode);
266
+ if (!methodName)
267
+ return;
268
+ // Walk up to find class name
269
+ let parent = node.parent;
270
+ let className = 'AnonymousClass';
271
+ while (parent) {
272
+ if (parent.type === 'class_declaration') {
273
+ const cn = parent.children?.find((c) => c.type === 'identifier');
274
+ if (cn)
275
+ className = getNodeText(cn, sourceCode);
276
+ break;
277
+ }
278
+ parent = parent.parent;
279
+ }
280
+ const { line } = getLineAndColumn(node);
281
+ const endLine = node.location.end.line;
282
+ const body = getFunctionBody(node, sourceCode);
283
+ const calls = extractFunctionCalls(node, sourceCode, importMap);
284
+ const callNames = [...new Set(calls.map((c) => c.callee))];
285
+ fnEntries.push({
286
+ name: `${className}.${methodName}`,
287
+ line,
288
+ endLine,
289
+ entityType: 'method',
290
+ componentType: null,
291
+ isExported: isExported(node),
292
+ complexity: calculateComplexity(node),
293
+ body,
294
+ functionCalls: callNames,
295
+ });
296
+ }
297
+ });
298
+ // Pass 2 — arrow functions assigned to variables
299
+ walkAST(root, (node) => {
300
+ if (node.type !== 'variable_declarator')
301
+ return;
302
+ const nameNode = node.children?.find((c) => c.type === 'identifier');
303
+ const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
304
+ if (!nameNode || !arrowFunc)
305
+ return;
306
+ const name = getNodeText(nameNode, sourceCode);
307
+ if (!name)
308
+ return;
309
+ // Don't duplicate if already covered as function_declaration
310
+ // (shouldn't happen — function_declaration is a different node type)
311
+ const { line } = getLineAndColumn(arrowFunc);
312
+ const endLine = arrowFunc.location.end.line;
313
+ const body = getFunctionBody(arrowFunc, sourceCode);
314
+ const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
315
+ const callNames = [...new Set(calls.map((c) => c.callee))];
316
+ fnEntries.push({
317
+ name,
318
+ line,
319
+ endLine,
320
+ entityType: 'function',
321
+ componentType: null,
322
+ isExported: isExported(node),
323
+ complexity: calculateComplexity(arrowFunc),
324
+ body,
325
+ functionCalls: callNames,
326
+ });
327
+ });
328
+ // Pass 3 — React component detection
329
+ // Detect JSX-returning functions to set entity_type='component' and
330
+ // component_type, matching what functionScanner's deepSync produces.
331
+ // The convention miner's classifyExportKind() uses these columns to
332
+ // partition naming conventions.
333
+ const hasReactImport = [...importMap.values()].some((v) => v.modulePath === 'react');
334
+ if (filePath.endsWith('.tsx') ||
335
+ filePath.endsWith('.jsx') ||
336
+ (filePath.endsWith('.js') && hasReactImport)) {
337
+ walkAST(root, (node) => {
338
+ if (!isReactComponent(node, sourceCode))
339
+ return;
340
+ const ct = detectComponentType(node, sourceCode);
341
+ if (!ct)
342
+ return;
343
+ const cName = getComponentName(node, sourceCode);
344
+ if (!cName || cName === 'AnonymousComponent')
345
+ return;
346
+ const existing = fnEntries.find((f) => f.name === cName);
347
+ if (existing) {
348
+ // Upgrade existing function_declaration or arrow-function entry
349
+ existing.entityType = 'component';
350
+ existing.componentType = ct;
351
+ }
352
+ else {
353
+ // New entry — class component, function_expression, or memo/forwardRef
354
+ // wrapper not already captured by passes 1 or 2.
355
+ const { line } = getLineAndColumn(node);
356
+ const endLine = node.location.end.line;
357
+ const body = getFunctionBody(node, sourceCode);
358
+ fnEntries.push({
359
+ name: cName,
360
+ line,
361
+ endLine,
362
+ entityType: 'component',
363
+ componentType: ct,
364
+ isExported: isExported(node),
365
+ complexity: calculateComplexity(node),
366
+ body,
367
+ functionCalls: [],
368
+ });
369
+ }
370
+ });
371
+ }
372
+ return fnEntries;
373
+ }
374
+ /**
375
+ * Extract static/dynamic imports and exports for a single file, emitting
376
+ * classified `import_specifiers` rows (Spec 60.1) into `indexFacts`. Returns
377
+ * the import/export facts for the file's stage-2 fact map.
378
+ */
379
+ function collectImportExportFacts(opts) {
380
+ const { ast, adapter, sourceCode, filePath, context, isTsJs, indexFacts } = opts;
381
+ const langAdapter = adapter;
382
+ const langAst = ast;
383
+ // Static imports via the adapter's canonical extractImports()
384
+ const staticImportInfos = langAdapter.extractImports(langAst);
385
+ const staticImports = staticImportInfos.map((imp) => ({
386
+ moduleSpecifier: imp.source,
387
+ isStatic: true,
388
+ isDynamic: false,
389
+ isRequire: false,
390
+ line: imp.location.start.line,
391
+ }));
392
+ // Spec 60.1 — classify each static import specifier and emit one row per
393
+ // occurrence to `import_specifiers`. The corpus file set is the stage-1
394
+ // discovery list, threaded here as `_infra.files` (merged into
395
+ // `context.config` by pipeline.ts). Existence is checked against that
396
+ // in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
397
+ // `paths` patterns are threaded via `_infra` and passed to the classifier.
398
+ if (isTsJs) {
399
+ // Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
400
+ // (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
401
+ // the audit's analysis list (already pruned by includePaths/excludePaths),
402
+ // which drops `.json`/other non-analyzed extensions and would mis-classify
403
+ // a real `./invariant-rules.schema.json` as `internal-broken`.
404
+ const corpusFiles = new Set((context.config.corpusFiles) ??
405
+ (context.config.files) ??
406
+ []);
407
+ const virtualModules = context.config.importVirtualModules
408
+ ?? DEFAULT_VIRTUAL_MODULES;
409
+ const tsconfigAliases = context.config.tsconfigAliases;
410
+ for (const imp of staticImportInfos) {
411
+ const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
412
+ virtualModules,
413
+ aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
414
+ pathMappings: tsconfigAliases?.paths ?? {},
415
+ baseUrl: tsconfigAliases?.baseUrl,
416
+ projectRoot: context.projectRoot,
417
+ });
418
+ indexFacts.push({
419
+ table: 'import_specifiers',
420
+ data: {
421
+ file_path: filePath,
422
+ specifier: imp.source,
423
+ classification,
424
+ resolved_path: resolvedPath ?? null,
425
+ line: imp.location.start.line,
426
+ },
427
+ conflictKey: 'file_path, specifier, line',
428
+ });
429
+ }
430
+ }
431
+ // Dynamic import() and require() — first attempt with pure NodePattern
432
+ // Plan note: import keyword is an anonymous tree-sitter node, so
433
+ // hasChild cannot see it. We use `custom` with raw node access.
434
+ const dynamicCallNodes = langAdapter.findNodes(langAst, {
435
+ type: 'call_expression',
436
+ custom: (node) => {
437
+ const fn = node.children?.[0];
438
+ return (fn?.type === 'import') ||
439
+ (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
440
+ },
441
+ });
442
+ const dynamicImports = [];
443
+ for (const node of dynamicCallNodes) {
444
+ const fn = node.children?.[0];
445
+ const isImport = fn?.type === 'import';
446
+ const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
447
+ // Walk the AST children to find the string argument
448
+ const argsNode = node.children?.find((c) => c.type === 'arguments');
449
+ const stringNode = argsNode?.children?.find((c) => c.type === 'string');
450
+ if (stringNode) {
451
+ const text = getNodeText(stringNode, sourceCode);
452
+ if (text.length >= 2) {
453
+ dynamicImports.push({
454
+ moduleSpecifier: text.slice(1, -1), // strip quotes
455
+ isStatic: false,
456
+ isDynamic: isImport,
457
+ isRequire,
458
+ line: node.location.start.line,
459
+ });
460
+ }
461
+ }
462
+ }
463
+ // Exports via the adapter's canonical extractExports()
464
+ // Returns ExportInfo[] with isDefault — used by both invariants and conventions (B2)
465
+ const exportInfos = langAdapter.extractExports(langAst);
466
+ return {
467
+ imports: [...staticImports, ...dynamicImports],
468
+ exports: exportInfos,
469
+ };
266
470
  }
471
+ /**
472
+ * Create the function-index stage-2 visitor, which populates the `functions`
473
+ * and `import_specifiers` index tables for the downstream reducers.
474
+ *
475
+ * @returns The function-index stage-2 visitor.
476
+ */
267
477
  export function createFunctionIndexVisitor() {
268
478
  return {
269
479
  name: 'function-index',
@@ -275,9 +485,12 @@ export function createFunctionIndexVisitor() {
275
485
  if (!filePath)
276
486
  return { violations: [], facts: {} };
277
487
  const indexFacts = [];
278
- // Only index TS/JS files
488
+ // Only index TS/JS files. The registry now maps `.json`/`.css` to their
489
+ // adapter language ids rather than `unknown`, so gate on the two languages
490
+ // this visitor indexes instead of on `unknown` (which would newly admit
491
+ // JSON/CSS ASTs to `buildImportMap`/`collectFunctionEntries`).
279
492
  const lang = getLanguageFromPath(filePath);
280
- if (lang === 'unknown')
493
+ if (lang !== 'typescript' && lang !== 'javascript')
281
494
  return { violations: [], facts: {} };
282
495
  // Clear existing entries for this file
283
496
  indexFacts.push({
@@ -296,148 +509,12 @@ export function createFunctionIndexVisitor() {
296
509
  }
297
510
  // Build import map once per file for resolving call targets
298
511
  const importMap = buildImportMap(root, sourceCode);
299
- // Collect function-like nodes in two passes:
300
- // 1) function_declaration + method_definition nodes
301
- // 2) arrow functions assigned to variables (variable_declarator children)
302
- const fnEntries = [];
303
- // Pass 1 — named function declarations and methods
304
- walkAST(root, (node) => {
305
- if (node.type === 'function_declaration') {
306
- const nameNode = node.children?.find((c) => c.type === 'identifier');
307
- if (!nameNode)
308
- return;
309
- const name = getNodeText(nameNode, sourceCode);
310
- if (!name)
311
- return;
312
- const { line } = getLineAndColumn(node);
313
- const endLine = node.location.end.line;
314
- const body = getFunctionBody(node, sourceCode);
315
- const calls = extractFunctionCalls(node, sourceCode, importMap);
316
- const callNames = [...new Set(calls.map((c) => c.callee))];
317
- fnEntries.push({
318
- name,
319
- line,
320
- endLine,
321
- entityType: 'function',
322
- componentType: null,
323
- isExported: isExported(node),
324
- complexity: calculateComplexity(node),
325
- body,
326
- functionCalls: callNames,
327
- });
328
- }
329
- // Class methods
330
- if (node.type === 'method_definition') {
331
- const nameNode = node.children?.find((c) => c.type === 'identifier');
332
- if (!nameNode)
333
- return;
334
- const methodName = getNodeText(nameNode, sourceCode);
335
- if (!methodName)
336
- return;
337
- // Walk up to find class name
338
- let parent = node.parent;
339
- let className = 'AnonymousClass';
340
- while (parent) {
341
- if (parent.type === 'class_declaration') {
342
- const cn = parent.children?.find((c) => c.type === 'identifier');
343
- if (cn)
344
- className = getNodeText(cn, sourceCode);
345
- break;
346
- }
347
- parent = parent.parent;
348
- }
349
- const { line } = getLineAndColumn(node);
350
- const endLine = node.location.end.line;
351
- const body = getFunctionBody(node, sourceCode);
352
- const calls = extractFunctionCalls(node, sourceCode, importMap);
353
- const callNames = [...new Set(calls.map((c) => c.callee))];
354
- fnEntries.push({
355
- name: `${className}.${methodName}`,
356
- line,
357
- endLine,
358
- entityType: 'method',
359
- componentType: null,
360
- isExported: isExported(node),
361
- complexity: calculateComplexity(node),
362
- body,
363
- functionCalls: callNames,
364
- });
365
- }
366
- });
367
- // Pass 2 — arrow functions assigned to variables
368
- walkAST(root, (node) => {
369
- if (node.type !== 'variable_declarator')
370
- return;
371
- const nameNode = node.children?.find((c) => c.type === 'identifier');
372
- const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
373
- if (!nameNode || !arrowFunc)
374
- return;
375
- const name = getNodeText(nameNode, sourceCode);
376
- if (!name)
377
- return;
378
- // Don't duplicate if already covered as function_declaration
379
- // (shouldn't happen — function_declaration is a different node type)
380
- const { line } = getLineAndColumn(arrowFunc);
381
- const endLine = arrowFunc.location.end.line;
382
- const body = getFunctionBody(arrowFunc, sourceCode);
383
- const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
384
- const callNames = [...new Set(calls.map((c) => c.callee))];
385
- fnEntries.push({
386
- name,
387
- line,
388
- endLine,
389
- entityType: 'function',
390
- componentType: null,
391
- isExported: isExported(node),
392
- complexity: calculateComplexity(arrowFunc),
393
- body,
394
- functionCalls: callNames,
395
- });
396
- });
397
- // Pass 3 — React component detection
398
- // Detect JSX-returning functions to set entity_type='component' and
399
- // component_type, matching what functionScanner's deepSync produces.
400
- // The convention miner's classifyExportKind() uses these columns to
401
- // partition naming conventions.
402
- const hasReactImport = [...importMap.values()].some((v) => v.modulePath === 'react');
403
- if (filePath.endsWith('.tsx') ||
404
- filePath.endsWith('.jsx') ||
405
- (filePath.endsWith('.js') && hasReactImport)) {
406
- walkAST(root, (node) => {
407
- if (!isReactComponent(node, sourceCode))
408
- return;
409
- const ct = detectComponentType(node, sourceCode);
410
- if (!ct)
411
- return;
412
- const cName = getComponentName(node, sourceCode);
413
- if (!cName || cName === 'AnonymousComponent')
414
- return;
415
- const existing = fnEntries.find((f) => f.name === cName);
416
- if (existing) {
417
- // Upgrade existing function_declaration or arrow-function entry
418
- existing.entityType = 'component';
419
- existing.componentType = ct;
420
- }
421
- else {
422
- // New entry — class component, function_expression, or memo/forwardRef
423
- // wrapper not already captured by passes 1 or 2.
424
- const { line } = getLineAndColumn(node);
425
- const endLine = node.location.end.line;
426
- const body = getFunctionBody(node, sourceCode);
427
- fnEntries.push({
428
- name: cName,
429
- line,
430
- endLine,
431
- entityType: 'component',
432
- componentType: ct,
433
- isExported: isExported(node),
434
- complexity: calculateComplexity(node),
435
- body,
436
- functionCalls: [],
437
- });
438
- }
439
- });
440
- }
512
+ const fnEntries = collectFunctionEntries(root, sourceCode, importMap, filePath);
513
+ // File-level content hash, shared by every function row this file emits.
514
+ // detectChangedFunctions stores the same value (from its own read of the
515
+ // file) so a later `changed` run can skip the re-scan when the file's
516
+ // whole content is unchanged.
517
+ const fileHash = computeContentHash(sourceCode);
441
518
  // Build IndexFactsEntry for each function
442
519
  for (const fn of fnEntries) {
443
520
  const metadata = {
@@ -446,7 +523,7 @@ export function createFunctionIndexVisitor() {
446
523
  complexity: fn.complexity,
447
524
  functionCalls: fn.functionCalls,
448
525
  };
449
- const contentHash = computeContentHash(fn.body, '');
526
+ const contentHash = computeContentHash(fn.body);
450
527
  const now = new Date().toISOString();
451
528
  indexFacts.push({
452
529
  table: 'functions',
@@ -459,7 +536,6 @@ export function createFunctionIndexVisitor() {
459
536
  language: lang,
460
537
  entity_type: fn.entityType,
461
538
  component_type: fn.componentType,
462
- signature: '',
463
539
  return_type: null,
464
540
  complexity: fn.complexity,
465
541
  is_exported: fn.isExported ? 1 : 0,
@@ -468,105 +544,27 @@ export function createFunctionIndexVisitor() {
468
544
  parameters: null,
469
545
  body: fn.body ?? null,
470
546
  content_hash: contentHash,
547
+ file_hash: fileHash,
471
548
  last_modified: now,
472
549
  metadata_json: JSON.stringify(metadata),
473
550
  },
474
551
  conflictKey: 'name, file_path, line_number',
475
552
  });
476
553
  }
477
- // ── Extract imports and exports from AST for downstream consumers ──
478
- // B1: Replaces regex-based extractImports()/extractExportedSymbols() in
479
- // ruleEngine.ts. Also consumed by conventions (B2: detectExportForm).
480
- const langAdapter = _adapter;
481
- const langAst = ast;
482
- // Static imports via the adapter's canonical extractImports()
483
- const staticImportInfos = langAdapter.extractImports(langAst);
484
- const staticImports = staticImportInfos.map((imp) => ({
485
- moduleSpecifier: imp.source,
486
- isStatic: true,
487
- isDynamic: false,
488
- isRequire: false,
489
- line: imp.location.start.line,
490
- }));
491
- // Spec 60.1 — classify each static import specifier and emit one row per
492
- // occurrence to `import_specifiers`. The corpus file set is the stage-1
493
- // discovery list, threaded here as `_infra.files` (merged into
494
- // `context.config` by pipeline.ts). Existence is checked against that
495
- // in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
496
- // `paths` patterns are threaded via `_infra` and passed to the classifier.
497
- if (isTsJs) {
498
- // Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
499
- // (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
500
- // the audit's analysis list (already pruned by includePaths/excludePaths),
501
- // which drops `.json`/other non-analyzed extensions and would mis-classify
502
- // a real `./invariant-rules.schema.json` as `internal-broken`.
503
- const corpusFiles = new Set((context.config.corpusFiles) ??
504
- (context.config.files) ??
505
- []);
506
- const virtualModules = context.config.importVirtualModules
507
- ?? DEFAULT_VIRTUAL_MODULES;
508
- const tsconfigAliases = context.config.tsconfigAliases;
509
- for (const imp of staticImportInfos) {
510
- const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
511
- virtualModules,
512
- aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
513
- pathMappings: tsconfigAliases?.paths ?? {},
514
- baseUrl: tsconfigAliases?.baseUrl,
515
- projectRoot: context.projectRoot,
516
- });
517
- indexFacts.push({
518
- table: 'import_specifiers',
519
- data: {
520
- file_path: filePath,
521
- specifier: imp.source,
522
- classification,
523
- resolved_path: resolvedPath ?? null,
524
- line: imp.location.start.line,
525
- },
526
- conflictKey: 'file_path, specifier, line',
527
- });
528
- }
529
- }
530
- // Dynamic import() and require() — first attempt with pure NodePattern
531
- // Plan note: import keyword is an anonymous tree-sitter node, so
532
- // hasChild cannot see it. We use `custom` with raw node access.
533
- const dynamicCallNodes = langAdapter.findNodes(langAst, {
534
- type: 'call_expression',
535
- custom: (node) => {
536
- const fn = node.children?.[0];
537
- return (fn?.type === 'import') ||
538
- (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
539
- },
554
+ const { imports: importFacts, exports: exportInfos } = collectImportExportFacts({
555
+ ast: ast,
556
+ adapter: _adapter,
557
+ sourceCode,
558
+ filePath,
559
+ context,
560
+ isTsJs,
561
+ indexFacts,
540
562
  });
541
- const dynamicImports = [];
542
- for (const node of dynamicCallNodes) {
543
- const fn = node.children?.[0];
544
- const isImport = fn?.type === 'import';
545
- const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
546
- // Walk the AST children to find the string argument
547
- const argsNode = node.children?.find((c) => c.type === 'arguments');
548
- const stringNode = argsNode?.children?.find((c) => c.type === 'string');
549
- if (stringNode) {
550
- const text = getNodeText(stringNode, sourceCode);
551
- if (text.length >= 2) {
552
- dynamicImports.push({
553
- moduleSpecifier: text.slice(1, -1), // strip quotes
554
- isStatic: false,
555
- isDynamic: isImport,
556
- isRequire,
557
- line: node.location.start.line,
558
- });
559
- }
560
- }
561
- }
562
- // Exports via the adapter's canonical extractExports()
563
- // Returns ExportInfo[] with isDefault — used by both invariants and conventions (B2)
564
- const exportInfos = langAdapter.extractExports(langAst);
565
563
  return {
566
564
  violations: [],
567
565
  facts: {
568
566
  [filePath]: {
569
- imports: [...staticImports, ...dynamicImports],
567
+ imports: importFacts,
570
568
  exports: exportInfos,
571
569
  },
572
570
  },
@@ -578,6 +576,12 @@ export function createFunctionIndexVisitor() {
578
576
  category: 'infrastructure',
579
577
  };
580
578
  }
579
+ /**
580
+ * Create the React stage-2 visitor, which scans React components and exposes
581
+ * cross-component checks to run after stage 2 completes.
582
+ *
583
+ * @returns The React visitor bundle.
584
+ */
581
585
  export function createReactVisitor() {
582
586
  const scanResults = [];
583
587
  const visitor = {
@@ -659,6 +663,8 @@ export function createReactVisitor() {
659
663
  * Styles CSS visitor — extracts declarations, tokens, and class usage from
660
664
  * tree-sitter-css and tree-sitter-scss parsed ASTs. Replaces the regex-based
661
665
  * CSS/SCSS extraction in styleExtractor.ts for .css and .scss files.
666
+ *
667
+ * @returns The styles-css stage-2 visitor.
662
668
  */
663
669
  export function createStylesCssVisitor() {
664
670
  return {
@@ -699,6 +705,8 @@ export function createStylesCssVisitor() {
699
705
  * emitting a fact (with a content hash) even when it yields no declarations.
700
706
  * The reducer uses that fact to (a) drop stale rows for files that *lost* their
701
707
  * styles and (b) skip re-writing files whose stored rows are already current.
708
+ *
709
+ * @returns The styles-source stage-2 visitor.
702
710
  */
703
711
  export function createStylesSourceVisitor() {
704
712
  // Spec 61 R3.4 — a project tailwind config that exists but cannot be read
@@ -744,6 +752,44 @@ export function createStylesSourceVisitor() {
744
752
  category: 'style',
745
753
  };
746
754
  }
755
+ // Style-table SQL, hoisted so the two inserters (source CSS-in-JS facts and
756
+ // compiled-CSS facts) and each one's prepare-once fast path + `indexHandle.run`
757
+ // fallback cannot drift on a statement string.
758
+ const SQL_STYLE_GET_HASH = 'SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1';
759
+ const SQL_STYLE_DEL_DECL = 'DELETE FROM style_declarations WHERE file_path = ?';
760
+ const SQL_STYLE_DEL_USAGE = 'DELETE FROM style_class_usage WHERE file_path = ?';
761
+ const SQL_STYLE_DEL_TOKEN = 'DELETE FROM style_tokens WHERE file_path = ?';
762
+ const SQL_STYLE_DEL_CLASS = 'DELETE FROM style_defined_classes WHERE file_path = ?';
763
+ const SQL_STYLE_INS_DECL = 'INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
764
+ const SQL_STYLE_INS_CLASS = 'INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)';
765
+ const SQL_STYLE_INS_USAGE = 'INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)';
766
+ const SQL_STYLE_INS_TOKEN = 'INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)';
767
+ /** Run a style write through its prepared statement. The index handle always
768
+ * exposes a raw better-sqlite3 DB (`pipelineIndexHandle` in auditRunner is the
769
+ * only construction and always sets `rawDb`), so the prepared statements are
770
+ * always available — the former `else indexHandle.run(sql, params)` per-call
771
+ * fallback was unreachable and has been removed. */
772
+ function runStyleWrite(stmts, stmtKey, params) {
773
+ stmts[stmtKey].run(...params);
774
+ }
775
+ /** Read a file's stored style content hash through its prepared statement. */
776
+ function getStyleHash(stmts, filePath) {
777
+ return stmts.getHash.get(filePath);
778
+ }
779
+ /** Prepare the statements shared by both style inserters (source CSS-in-JS and
780
+ * compiled CSS). Each site layers its own extras (`getHash` for the source path,
781
+ * `insTok` for the compiled-CSS path) on top of this common set. */
782
+ function prepareCommonStyleStatements(rawDb) {
783
+ return {
784
+ delDecl: rawDb.prepare(SQL_STYLE_DEL_DECL),
785
+ delUsage: rawDb.prepare(SQL_STYLE_DEL_USAGE),
786
+ delTok: rawDb.prepare(SQL_STYLE_DEL_TOKEN),
787
+ delClass: rawDb.prepare(SQL_STYLE_DEL_CLASS),
788
+ insDecl: rawDb.prepare(SQL_STYLE_INS_DECL),
789
+ insClass: rawDb.prepare(SQL_STYLE_INS_CLASS),
790
+ insUsage: rawDb.prepare(SQL_STYLE_INS_USAGE),
791
+ };
792
+ }
747
793
  /**
748
794
  * Insert TS/JS CSS-in-JS facts (from the styles-source visitor) into the
749
795
  * style_* tables. Mirrors the TS/JS branch the style indexer used to run
@@ -764,19 +810,10 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
764
810
  if (entries.length === 0)
765
811
  return false;
766
812
  const rawDb = indexHandle.rawDb;
767
- const stmts = rawDb
768
- ? {
769
- getHash: rawDb.prepare('SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1'),
770
- delDecl: rawDb.prepare('DELETE FROM style_declarations WHERE file_path = ?'),
771
- delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
772
- delTok: rawDb.prepare('DELETE FROM style_tokens WHERE file_path = ?'),
773
- delUnread: rawDb.prepare('DELETE FROM style_unread_sources WHERE file_path = ?'),
774
- delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
775
- insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
776
- insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
777
- insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
778
- }
779
- : null;
813
+ const stmts = {
814
+ ...prepareCommonStyleStatements(rawDb),
815
+ getHash: rawDb.prepare(SQL_STYLE_GET_HASH),
816
+ };
780
817
  let contributed = false;
781
818
  const insertOne = (filePath, facts) => {
782
819
  if (facts.declarations.length > 0 || facts.classUsage.length > 0)
@@ -784,59 +821,32 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
784
821
  // Full-run content-hash skip: leave already-current rows untouched. Scoped
785
822
  // runs always re-write, matching the old style indexer's `if (!scoped)` guard.
786
823
  if (!isScoped) {
787
- const stored = stmts
788
- ? stmts.getHash.get(filePath)
789
- : indexHandle.query('SELECT content_hash FROM style_declarations WHERE file_path = ? LIMIT 1', [filePath])[0];
824
+ const stored = getStyleHash(stmts, filePath);
790
825
  if (stored?.content_hash === facts.contentHash)
791
826
  return;
792
827
  }
793
- // Delete stale rows — all five tables, mirroring the old deleteFileEntries
794
- // so a file that *lost* its styles also loses its stale unread-sources row.
795
- if (stmts) {
796
- stmts.delDecl.run(filePath);
797
- stmts.delUsage.run(filePath);
798
- stmts.delTok.run(filePath);
799
- stmts.delUnread.run(filePath);
800
- stmts.delClass.run(filePath);
801
- }
802
- else {
803
- indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
804
- indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
805
- indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
806
- indexHandle.run('DELETE FROM style_unread_sources WHERE file_path = ?', [filePath]);
807
- indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
808
- }
828
+ // Delete stale rows — all four tables, mirroring the old deleteFileEntries
829
+ // so a file that *lost* its styles also loses its stale rows.
830
+ runStyleWrite(stmts, 'delDecl', [filePath]);
831
+ runStyleWrite(stmts, 'delUsage', [filePath]);
832
+ runStyleWrite(stmts, 'delTok', [filePath]);
833
+ runStyleWrite(stmts, 'delClass', [filePath]);
809
834
  // Insert declarations (and their defined-class catalog entries).
810
835
  for (const decl of facts.declarations) {
811
- if (stmts) {
812
- stmts.insDecl.run(decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash);
813
- }
814
- else {
815
- indexHandle.run('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash]);
816
- }
836
+ runStyleWrite(stmts, 'insDecl', [decl.property, decl.rawValue, decl.normalizedValue ? JSON.stringify(decl.normalizedValue) : null, decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, facts.contentHash]);
817
837
  if (decl.context) {
818
838
  for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
819
- if (stmts)
820
- stmts.insClass.run(m[1], decl.filePath);
821
- else
822
- indexHandle.run('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)', [m[1], decl.filePath]);
839
+ runStyleWrite(stmts, 'insClass', [m[1], decl.filePath]);
823
840
  }
824
841
  }
825
842
  }
826
843
  // Insert class usage.
827
844
  for (const cu of facts.classUsage) {
828
- if (stmts)
829
- stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
830
- else
831
- indexHandle.run('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
845
+ runStyleWrite(stmts, 'insUsage', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
832
846
  }
833
847
  };
834
- if (stmts)
835
- rawDb.transaction(() => { for (const [fp, f] of entries)
836
- insertOne(fp, f); })();
837
- else
838
- for (const [fp, f] of entries)
839
- insertOne(fp, f);
848
+ rawDb.transaction(() => { for (const [fp, f] of entries)
849
+ insertOne(fp, f); })();
840
850
  return contributed;
841
851
  }
842
852
  /**
@@ -847,6 +857,8 @@ function insertSourceStyleFacts(indexHandle, sourceFacts, isScoped) {
847
857
  * Spec 26 Phase 2: CSS facts from the styles-css visitor are inserted into
848
858
  * the DB before the analyzer runs. For .css files, the AST extraction replaces
849
859
  * the regex-based styleIndexer path (which now skips .css files).
860
+ *
861
+ * @returns The styles stage-3 reducer.
850
862
  */
851
863
  export function createStylesReducer() {
852
864
  return {
@@ -888,77 +900,44 @@ export function createStylesReducer() {
888
900
  // statement — measured at 345ms for 5 files / ~13k declarations on
889
901
  // recall-protocol. Prepare-once + one transaction collapses that.
890
902
  const rawDb = context.indexHandle.rawDb;
891
- const stmts = rawDb
892
- ? {
893
- delDecl: rawDb.prepare('DELETE FROM style_declarations WHERE file_path = ?'),
894
- delTok: rawDb.prepare('DELETE FROM style_tokens WHERE file_path = ?'),
895
- delUsage: rawDb.prepare('DELETE FROM style_class_usage WHERE file_path = ?'),
896
- delClass: rawDb.prepare('DELETE FROM style_defined_classes WHERE file_path = ?'),
897
- insDecl: rawDb.prepare('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'),
898
- insClass: rawDb.prepare('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)'),
899
- insTok: rawDb.prepare('INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)'),
900
- insUsage: rawDb.prepare('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)'),
901
- }
902
- : null;
903
+ const stmts = {
904
+ ...prepareCommonStyleStatements(rawDb),
905
+ insTok: rawDb.prepare(SQL_STYLE_INS_TOKEN),
906
+ };
903
907
  const insertAll = () => {
904
908
  for (const [filePath, facts] of Object.entries(cssFacts)) {
905
909
  // Delete old entries for this file (replaces the styleIndexer path)
906
- if (stmts) {
907
- stmts.delDecl.run(filePath);
908
- stmts.delTok.run(filePath);
909
- stmts.delUsage.run(filePath);
910
- stmts.delClass.run(filePath);
911
- }
912
- else {
913
- indexHandle.run('DELETE FROM style_declarations WHERE file_path = ?', [filePath]);
914
- indexHandle.run('DELETE FROM style_tokens WHERE file_path = ?', [filePath]);
915
- indexHandle.run('DELETE FROM style_class_usage WHERE file_path = ?', [filePath]);
916
- indexHandle.run('DELETE FROM style_defined_classes WHERE file_path = ?', [filePath]);
917
- }
910
+ runStyleWrite(stmts, 'delDecl', [filePath]);
911
+ runStyleWrite(stmts, 'delTok', [filePath]);
912
+ runStyleWrite(stmts, 'delUsage', [filePath]);
913
+ runStyleWrite(stmts, 'delClass', [filePath]);
918
914
  // Compute content hash for the file
919
915
  const contentStr = JSON.stringify({ declarations: facts.declarations.length, tokens: facts.tokens.length, classUsage: facts.classUsage.length });
920
916
  const contentHash = createHash('sha256').update(contentStr).digest('hex').slice(0, 16);
921
917
  // Insert declarations
922
918
  for (const decl of facts.declarations) {
923
- if (stmts) {
924
- stmts.insDecl.run(decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash);
925
- }
926
- else {
927
- indexHandle.run('INSERT INTO style_declarations (property, raw_value, normalized_value, mechanism, file_path, line, context, variant_context, token_ref, content_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash]);
928
- }
919
+ runStyleWrite(stmts, 'insDecl', [decl.property, decl.rawValue, JSON.stringify(decl.normalizedValue), decl.mechanism, decl.filePath, decl.line, decl.context, decl.variantContext, decl.tokenRef, contentHash]);
929
920
  // Populate the defined-class catalog (Spec 45) from any class
930
921
  // selectors in the rule's context so styles/undefined-class can
931
922
  // resolve names via `style_defined_classes` instead of a
932
923
  // full-corpus regex scan over every declaration.
933
924
  if (decl.context) {
934
925
  for (const m of decl.context.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
935
- if (stmts)
936
- stmts.insClass.run(m[1], decl.filePath);
937
- else
938
- indexHandle.run('INSERT OR IGNORE INTO style_defined_classes (class_name, file_path) VALUES (?, ?)', [m[1], decl.filePath]);
926
+ runStyleWrite(stmts, 'insClass', [m[1], decl.filePath]);
939
927
  }
940
928
  }
941
929
  }
942
930
  // Insert tokens
943
931
  for (const tok of facts.tokens) {
944
- if (stmts)
945
- stmts.insTok.run(tok.name, tok.value, tok.filePath, tok.mechanism);
946
- else
947
- indexHandle.run('INSERT INTO style_tokens (name, value, file_path, mechanism) VALUES (?, ?, ?, ?)', [tok.name, tok.value, tok.filePath, tok.mechanism]);
932
+ runStyleWrite(stmts, 'insTok', [tok.name, tok.value, tok.filePath, tok.mechanism]);
948
933
  }
949
934
  // Insert class usage
950
935
  for (const cu of facts.classUsage) {
951
- if (stmts)
952
- stmts.insUsage.run(cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0);
953
- else
954
- indexHandle.run('INSERT INTO style_class_usage (class_name, file_path, line, mechanism, unresolvable) VALUES (?, ?, ?, ?, ?)', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
936
+ runStyleWrite(stmts, 'insUsage', [cu.className, cu.filePath, cu.line, cu.mechanism, cu.unresolvable ? 1 : 0]);
955
937
  }
956
938
  }
957
939
  };
958
- if (rawDb)
959
- rawDb.transaction(insertAll)();
960
- else
961
- insertAll();
940
+ rawDb.transaction(insertAll)();
962
941
  }
963
942
  // Short-circuit on non-style-bearing scoped runs — the source and CSS
964
943
  // facts above are already written (so stale rows are dropped); only the
@@ -990,10 +969,32 @@ export function createStylesReducer() {
990
969
  };
991
970
  }
992
971
  // ── Conventions reducer (stage 3) ────────────────────────────────────────────
972
+ /**
973
+ * Builds the shared analyzer-config bag for reducers that query the code
974
+ * index: the reducer's own namespaced config spread over the index handle,
975
+ * project root, in-scope file set, and scope flag. Reducers that need extra
976
+ * inputs (`readSource`, `exportsMap`) spread this and layer them on top.
977
+ *
978
+ * Scoped/diff audits thread `_infra.files` (absolute) as `scopedFiles` so the
979
+ * analyzer scopes its function/file queries to only the changed files;
980
+ * `functions.file_path` is also absolute, so the IN clause matches directly.
981
+ */
982
+ function makeIndexAnalyzerConfig(context) {
983
+ const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
984
+ return {
985
+ ...context.config,
986
+ indexHandle: context.indexHandle,
987
+ projectRoot: context.projectRoot,
988
+ scopedFiles,
989
+ isScoped: context.isScoped,
990
+ };
991
+ }
993
992
  /**
994
993
  * Conventions reducer — queries conventions table and flags deviations
995
994
  * (usage-pair, import-form, error-handling, export-shape, naming).
996
995
  * Uses analyzer.analyze([]) with rawDb injected.
996
+ *
997
+ * @returns The conventions stage-3 reducer.
997
998
  */
998
999
  export function createConventionsReducer() {
999
1000
  return {
@@ -1014,12 +1015,7 @@ export function createConventionsReducer() {
1014
1015
  .filter(([, data]) => data?.exports?.length)
1015
1016
  .map(([filePath, data]) => [filePath, data.exports]))
1016
1017
  : undefined;
1017
- // Scoped/diff audits: thread the in-scope file set so the analyzer scopes
1018
- // its function/file queries to only the changed files. `_infra.files` are
1019
- // absolute; `functions.file_path` is also absolute (function-index visitor
1020
- // stores `context.filePath`), so the IN clause matches directly.
1021
- const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
1022
- const config = { ...context.config, indexHandle: context.indexHandle, projectRoot: context.projectRoot, readSource: context.readSource, exportsMap, scopedFiles, isScoped: context.isScoped };
1018
+ const config = { ...makeIndexAnalyzerConfig(context), readSource: context.readSource, exportsMap };
1023
1019
  const result = await analyzer.analyze([], config);
1024
1020
  // Count the INPUT the conventions analyzer reads (the function index the
1025
1021
  // function-index visitor populated), not the derived `conventions` table it
@@ -1028,7 +1024,12 @@ export function createConventionsReducer() {
1028
1024
  // analyzer — counting it as "facts consumed" misreports a present input as
1029
1025
  // notApplicable. (Spec 39 R2/R3 bug B)
1030
1026
  const factsConsumed = context.indexHandle.count('functions');
1031
- return { violations: result.violations ?? [], facts: {}, factsConsumed };
1027
+ return {
1028
+ violations: result.violations ?? [],
1029
+ facts: {},
1030
+ factsConsumed,
1031
+ ...(result.diagnostics && result.diagnostics.length > 0 && { diagnostics: result.diagnostics }),
1032
+ };
1032
1033
  }
1033
1034
  catch {
1034
1035
  return { violations: [], facts: {} };
@@ -1044,12 +1045,13 @@ export function createConventionsReducer() {
1044
1045
  * Cross-domain reducer — runs written-never-read, read-never-written,
1045
1046
  * transaction-boundary risk, no-validator-reachable, and uncovered-risk detectors.
1046
1047
  * Uses analyzer.analyze([]) with rawDb injected.
1048
+ *
1049
+ * @returns The cross-domain stage-4 reducer.
1047
1050
  */
1048
1051
  export function createCrossDomainReducer() {
1049
1052
  return {
1050
1053
  name: 'cross-domain',
1051
1054
  stage: 'derivedReducer',
1052
- consumes: [],
1053
1055
  getRuleIds: () => getRuleIdsFor('cross-domain'),
1054
1056
  async reduce(_allFacts, context) {
1055
1057
  if (!context.indexHandle)
@@ -1057,11 +1059,7 @@ export function createCrossDomainReducer() {
1057
1059
  try {
1058
1060
  const { CrossDomainAnalyzer } = await import('./analyzers/crossDomain/CrossDomainAnalyzer.js');
1059
1061
  const analyzer = new CrossDomainAnalyzer();
1060
- // Thread the in-scope file set (absolute, from _infra.files) so detectors
1061
- // anchor to changed files on diff audits; full audits keep the root-LIKE
1062
- // scope (resolveFileScope falls back when isScoped is false).
1063
- const scopedFiles = context.isScoped ? (context.config.files ?? []) : undefined;
1064
- const config = { ...context.config, indexHandle: context.indexHandle, projectRoot: context.projectRoot, scopedFiles, isScoped: context.isScoped };
1062
+ const config = makeIndexAnalyzerConfig(context);
1065
1063
  const result = await analyzer.analyze([], config);
1066
1064
  // Count DB rows consumed across primary cross-domain tables
1067
1065
  let factsConsumed = 0;
@@ -1095,7 +1093,7 @@ export function createCrossDomainReducer() {
1095
1093
  // DependencyGraphBuilder) are corpus-wide: they compare schemas/endpoints/cycles
1096
1094
  // across files and languages. They run as Stage-4 derived reducers over a single
1097
1095
  // shared entity-extraction visitor, so each analyzer's rule IDs map to its own
1098
- // result row (coverage resolves by RULE_REGISTRY analyzer name — one reducer per
1096
+ // result row (coverage resolves by each rule's analyzer — one reducer per
1099
1097
  // analyzer, not one reducer fanning out to three names).
1100
1098
  //
1101
1099
  // Scope-awareness ("Stage 4, and measure the gate"): the corpus-wide graph/cycle
@@ -1180,10 +1178,11 @@ function clCollectFileReferences(node, sourceCode) {
1180
1178
  }
1181
1179
  else if (t === 'identifier') {
1182
1180
  // A bare identifier used as a value — an argument to a call
1183
- // (`arr.map(singularize)`) or an array element (`[missedClose, …]`) — is a
1184
- // function reference. Declaration names (`name` fields) and member
1185
- // accesses (`property_identifier`) are separate node types and are not
1186
- // captured here.
1181
+ // (`arr.map(singularize)`), an array element (`[missedClose, …]`), or an
1182
+ // object-literal value (`{ hostname: isValidHostname }`) — is a function
1183
+ // reference. Declaration names (`name` fields) and member accesses
1184
+ // (`property_identifier`) are separate node types and are not captured
1185
+ // here.
1187
1186
  const parent = n.parent;
1188
1187
  if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
1189
1188
  const text = getNodeText(n, sourceCode).trim();
@@ -1191,13 +1190,34 @@ function clCollectFileReferences(node, sourceCode) {
1191
1190
  names.add(text);
1192
1191
  }
1193
1192
  }
1193
+ else if (t === 'shorthand_property_identifier') {
1194
+ // `{ isValidHostname }` — the shorthand key doubles as a reference to the
1195
+ // in-scope function of the same name. A function wired up only through an
1196
+ // object literal (`FORMAT_VALIDATORS`) has no call edge, so without this
1197
+ // it reads orphaned even though it is genuinely referenced.
1198
+ const text = getNodeText(n, sourceCode).trim();
1199
+ if (text && /^[A-Za-z_$][\w$]*$/.test(text))
1200
+ names.add(text);
1201
+ }
1202
+ else if (t === 'pair') {
1203
+ // `{ key: value }` — the *value* may be a bare identifier referencing a
1204
+ // function (`{ hostname: isValidHostname }`); the key is a property name,
1205
+ // not a reference, and must not be added.
1206
+ const value = getFieldNode(n, 'value');
1207
+ if (value?.type === 'identifier') {
1208
+ const text = getNodeText(value, sourceCode).trim();
1209
+ if (text && /^[A-Za-z_$][\w$]*$/.test(text))
1210
+ names.add(text);
1211
+ }
1212
+ }
1194
1213
  for (const child of n.children ?? [])
1195
1214
  walk(child);
1196
1215
  };
1197
1216
  walk(node);
1198
1217
  return [...names];
1199
1218
  }
1200
- function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
1219
+ function clMakeFunction(opts) {
1220
+ const { filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false } = opts;
1201
1221
  return {
1202
1222
  id: clEntityId(filePath, 'function', name, line),
1203
1223
  name,
@@ -1245,7 +1265,7 @@ function clDynamicImport(node, sourceCode) {
1245
1265
  // not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
1246
1266
  // to an edge just like `import('@babel/plugin-syntax-jsx')`. Only an
1247
1267
  // interpolated template (`` import(`./${name}.js`) ``) is genuinely computed.
1248
- const templateNode = args?.children?.find((c) => c.type === 'template_string' || c.type === 'template_literal');
1268
+ const templateNode = args?.children?.find((c) => c.type === 'template_string');
1249
1269
  if (templateNode) {
1250
1270
  const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
1251
1271
  if (!hasSubstitution) {
@@ -1375,7 +1395,16 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1375
1395
  }
1376
1396
  }
1377
1397
  const line = node.location.start.line;
1378
- const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractTSParams(node, sourceCode), clCollectCallees(node, sourceCode), isExported(node), calculateComplexity(node), node.type === 'method_definition');
1398
+ const entity = clMakeFunction({
1399
+ filePath, lang, name: displayName, line,
1400
+ signature: clSignatureText(node, sourceCode) || getNodeText(node, sourceCode),
1401
+ purpose: clBodyText(node, sourceCode),
1402
+ params: clExtractTSParams(node, sourceCode),
1403
+ callees: clCollectCallees(node, sourceCode),
1404
+ exported: isExported(node),
1405
+ complexity: calculateComplexity(node),
1406
+ isMethod: node.type === 'method_definition',
1407
+ });
1379
1408
  entity.metadata.fileReferences = fileReferences;
1380
1409
  out.push(entity);
1381
1410
  return;
@@ -1389,7 +1418,15 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
1389
1418
  if (!name)
1390
1419
  return;
1391
1420
  const line = node.location.start.line;
1392
- const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode), clBodyText(arrow, sourceCode), [], clCollectCallees(arrow, sourceCode), isExported(node), calculateComplexity(node));
1421
+ const entity = clMakeFunction({
1422
+ filePath, lang, name, line,
1423
+ signature: clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode),
1424
+ purpose: clBodyText(arrow, sourceCode),
1425
+ params: [],
1426
+ callees: clCollectCallees(arrow, sourceCode),
1427
+ exported: isExported(node),
1428
+ complexity: calculateComplexity(node),
1429
+ });
1393
1430
  entity.metadata.fileReferences = fileReferences;
1394
1431
  out.push(entity);
1395
1432
  return;
@@ -1449,18 +1486,24 @@ function clExtractGoParams(node, sourceCode) {
1449
1486
  }
1450
1487
  function clExtractGoStructFields(structNode, sourceCode) {
1451
1488
  const fields = [];
1452
- for (const f of structNode.children ?? []) {
1453
- if (f.type !== 'field_declaration')
1454
- continue;
1455
- const nameNode = getFieldNode(f, 'name');
1456
- const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1457
- if (!name)
1458
- continue;
1459
- const typeNode = getFieldNode(f, 'type');
1460
- const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1461
- const tagNode = getFieldNode(f, 'tag');
1462
- const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
1463
- fields.push({ name, type, isExported: clIsExportedGo(name), tag });
1489
+ // A struct's fields are not direct children of `struct_type` — tree-sitter-go
1490
+ // wraps them in a `field_declaration_list`. Iterate one level in so the
1491
+ // per-field name/type/tag extraction actually sees them.
1492
+ for (const child of structNode.children ?? []) {
1493
+ const decls = child.type === 'field_declaration_list' ? (child.children ?? []) : [child];
1494
+ for (const f of decls) {
1495
+ if (f.type !== 'field_declaration')
1496
+ continue;
1497
+ const nameNode = getFieldNode(f, 'name');
1498
+ const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
1499
+ if (!name)
1500
+ continue;
1501
+ const typeNode = getFieldNode(f, 'type');
1502
+ const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
1503
+ const tagNode = getFieldNode(f, 'tag');
1504
+ const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
1505
+ fields.push({ name, type, isExported: clIsExportedGo(name), tag });
1506
+ }
1464
1507
  }
1465
1508
  return fields;
1466
1509
  }
@@ -1475,7 +1518,15 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1475
1518
  const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
1476
1519
  const displayName = receiverType ? `${receiverType}.${name}` : name;
1477
1520
  const line = node.location.start.line;
1478
- const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractGoParams(node, sourceCode), clCollectCallees(node, sourceCode), clIsExportedGo(name), calculateComplexity(node));
1521
+ const entity = clMakeFunction({
1522
+ filePath, lang: 'go', name: displayName, line,
1523
+ signature: clSignatureText(node, sourceCode) || getNodeText(node, sourceCode),
1524
+ purpose: clBodyText(node, sourceCode),
1525
+ params: clExtractGoParams(node, sourceCode),
1526
+ callees: clCollectCallees(node, sourceCode),
1527
+ exported: clIsExportedGo(name),
1528
+ complexity: calculateComplexity(node),
1529
+ });
1479
1530
  entity.metadata.fileReferences = fileReferences;
1480
1531
  out.push(entity);
1481
1532
  return;
@@ -1534,6 +1585,38 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
1534
1585
  });
1535
1586
  }
1536
1587
  // ── Shared entity visitor (Stage 2) ──────────────────────────────────────────
1588
+ /**
1589
+ * Spec 68 §3.2 — the `cross-language-entities` FileProcessor extraction.
1590
+ *
1591
+ * The entity half of {@link createCrossLanguageEntityVisitor}, factored out so
1592
+ * the phase-model producer can call it directly without the Stage-2 `visit`
1593
+ * plumbing (violations/facts/indexFacts). Emits the same `CrossLanguageEntity[]`
1594
+ * the visitor feeds the cross-language analyzers; the per-file imports/exports
1595
+ * half of the visitor is the reachability/coverage concern (§8), not this fact.
1596
+ *
1597
+ * @param ast - The parsed file to extract entities from.
1598
+ * @param filePath - The file path stamping each extracted entity.
1599
+ * @param sourceCode - Source text used for names and signatures.
1600
+ * @param lang - The resolved language (`go`, `typescript`, or `javascript`).
1601
+ * @returns The cross-language entities extracted from the file.
1602
+ */
1603
+ export function extractCrossLanguageEntities(ast, filePath, sourceCode, lang) {
1604
+ const root = ast.root;
1605
+ const entities = [];
1606
+ if (lang === 'go') {
1607
+ clExtractGoEntities(root, filePath, sourceCode, entities);
1608
+ }
1609
+ else {
1610
+ clExtractTSEntities(root, filePath, sourceCode, lang, entities);
1611
+ }
1612
+ return entities;
1613
+ }
1614
+ /**
1615
+ * Create the cross-language-entities stage-2 visitor, which extracts entities
1616
+ * and import/export info for the cross-language analyzers.
1617
+ *
1618
+ * @returns The cross-language-entities stage-2 visitor.
1619
+ */
1537
1620
  export function createCrossLanguageEntityVisitor() {
1538
1621
  return {
1539
1622
  name: 'cross-language-entities',
@@ -1545,7 +1628,7 @@ export function createCrossLanguageEntityVisitor() {
1545
1628
  if (!filePath)
1546
1629
  return { violations: [], facts: {} };
1547
1630
  const lang = getLanguageFromPath(filePath);
1548
- if (lang === 'unknown')
1631
+ if (lang !== 'typescript' && lang !== 'javascript' && lang !== 'go')
1549
1632
  return { violations: [], facts: {} };
1550
1633
  const root = ast.root;
1551
1634
  const entities = [];
@@ -1686,6 +1769,11 @@ function clComputeReachability(fileFacts, options) {
1686
1769
  * nothing *and* not a framework entry point. `deadPreException` is the
1687
1770
  * empty-importers count before the entry-point exception, so the drop
1688
1771
  * (`deadPreException - untestedDead`) attributes the entry points exempted.
1772
+ *
1773
+ * @param fileFacts - Per-file import/export info keyed by project-relative path.
1774
+ * @param importersOf - Map of file to the set of files that import it.
1775
+ * @param packageEntrySet - Framework/package entry points exempted from the dead bucket.
1776
+ * @returns The test-coverage classification report.
1689
1777
  */
1690
1778
  export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
1691
1779
  const result = {
@@ -1750,6 +1838,9 @@ export function classifyTestCoverage(fileFacts, importersOf, packageEntrySet) {
1750
1838
  * the same file name appearing in several sibling directories (e.g. two
1751
1839
  * `ErrorState.tsx`, three `services.ts` under separate `reports` folders). Only
1752
1840
  * basenames with ≥2 files become clusters; a singleton dead file is not a duplicate.
1841
+ *
1842
+ * @param deadFiles - Unreferenced module paths to cluster by basename.
1843
+ * @returns Dead-module clusters (only basenames with two or more files).
1753
1844
  */
1754
1845
  export function clusterDeadModules(deadFiles) {
1755
1846
  const byBasename = new Map();
@@ -1789,6 +1880,15 @@ function clBuildReferences(entities) {
1789
1880
  const eDir = e.file.split('/').slice(0, -1).join('/');
1790
1881
  for (const callee of callees) {
1791
1882
  const name = (callee.split('.').pop() ?? callee).toLowerCase();
1883
+ // A free function that calls itself by bare name is recursing. Resolving
1884
+ // that name to a *different* same-named entity (a second `dfs` helper in
1885
+ // the same file) fabricates a spurious mutual-recursion cycle between two
1886
+ // unrelated functions and reports it as a circular dependency. Recursion
1887
+ // is a self-reference, not a module cycle — drop it rather than resolve
1888
+ // it sideways. (Methods are named `Class.method`, so a bare callee never
1889
+ // matches their own name and this only affects free functions.)
1890
+ if (name === e.name.toLowerCase())
1891
+ continue;
1792
1892
  const others = (byName.get(name) ?? []).filter((t) => t.id !== e.id);
1793
1893
  if (others.length === 0)
1794
1894
  continue;
@@ -1825,11 +1925,16 @@ function clBuildReferences(entities) {
1825
1925
  return refs;
1826
1926
  }
1827
1927
  // ── schema-validator reducer (Stage 4) ───────────────────────────────────────
1928
+ /**
1929
+ * Create the schema-validator stage-4 reducer, which compares schemas across
1930
+ * language boundaries (TS interfaces vs Go structs).
1931
+ *
1932
+ * @returns The schema-validator stage-4 reducer.
1933
+ */
1828
1934
  export function createSchemaValidatorReducer() {
1829
1935
  return {
1830
1936
  name: 'schema-validator',
1831
1937
  stage: 'derivedReducer',
1832
- consumes: ['cross-language-entities'],
1833
1938
  getRuleIds: () => getRuleIdsFor('schema-validator'),
1834
1939
  async reduce(allFacts, context) {
1835
1940
  if (context.isScoped) {
@@ -1881,11 +1986,16 @@ export function createSchemaValidatorReducer() {
1881
1986
  };
1882
1987
  }
1883
1988
  // ── api-contract reducer (Stage 4) ───────────────────────────────────────────
1989
+ /**
1990
+ * Create the api-contract stage-4 reducer, which compares frontend API calls
1991
+ * against backend endpoints across languages.
1992
+ *
1993
+ * @returns The api-contract stage-4 reducer.
1994
+ */
1884
1995
  export function createAPIContractReducer() {
1885
1996
  return {
1886
1997
  name: 'api-contract',
1887
1998
  stage: 'derivedReducer',
1888
- consumes: ['cross-language-entities'],
1889
1999
  getRuleIds: () => getRuleIdsFor('api-contract'),
1890
2000
  async reduce(allFacts, context) {
1891
2001
  if (context.isScoped) {
@@ -1917,11 +2027,39 @@ export function createAPIContractReducer() {
1917
2027
  };
1918
2028
  }
1919
2029
  // ── dependency-graph reducer (Stage 4) ───────────────────────────────────────
2030
+ /**
2031
+ * Persist a `graph_cache` edge set in one transaction: delete the graph's
2032
+ * existing rows, then insert `rows` as (node_key, neighbor_key, weight).
2033
+ * Advisory — a failure here only degrades the post-pipeline hotspot ranking,
2034
+ * so it is swallowed rather than surfaced as an audit error.
2035
+ */
2036
+ function persistGraphEdges(rawDb, graphType, rows) {
2037
+ try {
2038
+ const db = rawDb;
2039
+ const del = db.prepare(`DELETE FROM graph_cache WHERE graph_type = '${graphType}'`);
2040
+ const ins = db.prepare(`INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('${graphType}', ?, ?, ?)`);
2041
+ const tx = db.transaction(() => {
2042
+ del.run();
2043
+ for (const [nodeKey, neighborKey, weight] of rows) {
2044
+ ins.run(nodeKey, neighborKey, weight);
2045
+ }
2046
+ });
2047
+ tx();
2048
+ }
2049
+ catch {
2050
+ // Advisory persistence — ranking falls back to a neutral default without it.
2051
+ }
2052
+ }
2053
+ /**
2054
+ * Create the dependency-graph stage-4 reducer, which detects dependency cycles,
2055
+ * hubs, and orphaned nodes.
2056
+ *
2057
+ * @returns The dependency-graph stage-4 reducer.
2058
+ */
1920
2059
  export function createDependencyGraphReducer() {
1921
2060
  return {
1922
2061
  name: 'dependency-graph',
1923
2062
  stage: 'derivedReducer',
1924
- consumes: ['cross-language-entities'],
1925
2063
  getRuleIds: () => getRuleIdsFor('dependency-graph'),
1926
2064
  async reduce(allFacts, context) {
1927
2065
  if (context.isScoped) {
@@ -1962,7 +2100,7 @@ export function createDependencyGraphReducer() {
1962
2100
  type: issue.type, // dependency-graph rules match on field: 'type'
1963
2101
  analyzer: 'dependency-graph',
1964
2102
  category: 'cross-language-dependency',
1965
- functionName: orphan.name,
2103
+ symbol: orphan.name,
1966
2104
  details: { orphanId: id },
1967
2105
  });
1968
2106
  }
@@ -2001,45 +2139,21 @@ export function createDependencyGraphReducer() {
2001
2139
  // Persist reachability to graph_cache so the post-pipeline reorder
2002
2140
  // (auditRunner hotspot block) can rank every finding by live/dead.
2003
2141
  if (context.indexHandle?.rawDb) {
2004
- try {
2005
- const db = context.indexHandle.rawDb;
2006
- const del = db.prepare("DELETE FROM graph_cache WHERE graph_type = 'reachability'");
2007
- const ins = db.prepare("INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('reachability', ?, '', ?)");
2008
- const tx = db.transaction(() => {
2009
- del.run();
2010
- for (const [fp, score] of reachability) {
2011
- ins.run(fp, score);
2012
- }
2013
- });
2014
- tx();
2015
- }
2016
- catch {
2017
- // Reachability persistence is advisory — ranking falls back to a
2018
- // neutral default without it.
2019
- }
2142
+ const reachabilityRows = [...reachability].map(([fp, score]) => [fp, '', score]);
2143
+ persistGraphEdges(context.indexHandle.rawDb, 'reachability', reachabilityRows);
2020
2144
  }
2021
2145
  // Spec 60 R1 — persist the file-level import edges (reverse adjacency:
2022
2146
  // module → its direct importers) so "what tests exercise this module"
2023
2147
  // is answerable downstream from `importersOf`, not the symbol-level
2024
2148
  // `function_dependencies` table.
2025
2149
  if (context.indexHandle?.rawDb) {
2026
- try {
2027
- const db = context.indexHandle.rawDb;
2028
- const del = db.prepare("DELETE FROM graph_cache WHERE graph_type = 'importers'");
2029
- const ins = db.prepare("INSERT OR REPLACE INTO graph_cache (graph_type, node_key, neighbor_key, weight) VALUES ('importers', ?, ?, 1)");
2030
- const tx = db.transaction(() => {
2031
- del.run();
2032
- for (const [fp, importers] of importersOf) {
2033
- for (const importer of importers) {
2034
- ins.run(fp, importer);
2035
- }
2036
- }
2037
- });
2038
- tx();
2039
- }
2040
- catch {
2041
- // Importer-edge persistence is advisory, like reachability.
2150
+ const importerRows = [];
2151
+ for (const [fp, importers] of importersOf) {
2152
+ for (const importer of importers) {
2153
+ importerRows.push([fp, importer, 1]);
2154
+ }
2042
2155
  }
2156
+ persistGraphEdges(context.indexHandle.rawDb, 'importers', importerRows);
2043
2157
  }
2044
2158
  // Flag files that export symbols yet are imported by nothing and are
2045
2159
  // not framework entry points — a dead module. This is the signal the
@@ -2097,7 +2211,6 @@ export function createDependencyGraphReducer() {
2097
2211
  const HEALTH_SEVERITY = {
2098
2212
  'break-cycles': 'severe',
2099
2213
  'reduce-coupling': 'high',
2100
- 'split-responsibilities': 'high',
2101
2214
  'review-orphans': 'severe',
2102
2215
  };
2103
2216
  for (const s of health.suggestions) {
@@ -2135,6 +2248,8 @@ export function createDependencyGraphReducer() {
2135
2248
  * Invariants reducer — enforces user-defined invariant rules from .codeauditor.json.
2136
2249
  * Receives the full file list via context.files and runs the rule engine across
2137
2250
  * all files at once (call-constraint rules inherently need cross-file scope).
2251
+ *
2252
+ * @returns The invariants stage-3 reducer.
2138
2253
  */
2139
2254
  export function createInvariantsReducer() {
2140
2255
  return {
@@ -2218,6 +2333,8 @@ async function _getProvenanceModule() {
2218
2333
  * Schema SQL visitor (.sql files) — extracts ordered DDL operations for the
2219
2334
  * Stage 3 reducer to replay into the known-tables catalog. Emits the parsed
2220
2335
  * ops, not raw source, so the reducer retains only state transitions.
2336
+ *
2337
+ * @returns The schema-sql stage-2 visitor.
2221
2338
  */
2222
2339
  export function createSchemaSqlVisitor() {
2223
2340
  const getMigrationExtractor = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js'));
@@ -2228,7 +2345,8 @@ export function createSchemaSqlVisitor() {
2228
2345
  getRuleIds: () => [],
2229
2346
  async visit(_ast, _adapter, context, sourceCode) {
2230
2347
  const { extractMigrationOpsFromFile } = await getMigrationExtractor();
2231
- const { ops, columns, tableColumns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode);
2348
+ const dialect = context.config.sqlDialect;
2349
+ const { ops, columns, tableColumns, skipped, bytes } = await extractMigrationOpsFromFile(context.filePath, sourceCode, dialect ?? null);
2232
2350
  return {
2233
2351
  violations: [],
2234
2352
  facts: {
@@ -2246,181 +2364,177 @@ export function createSchemaSqlVisitor() {
2246
2364
  category: 'database',
2247
2365
  };
2248
2366
  }
2367
+ /** The shared lazy `UniversalSchemaAnalyzer` for the schema-code visitor. Hoisted
2368
+ * to module scope so the factory below stays a thin descriptor and the analyzer
2369
+ * is constructed once, not per factory call. */
2370
+ const getSchemaCodeAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
2371
+ analyzer: new m.UniversalSchemaAnalyzer(),
2372
+ defaults: m.DEFAULT_SCHEMA_CONFIG,
2373
+ parseMigrationOps: m.parseMigrationOps,
2374
+ extractDdlColumnNames: m.extractDdlColumnNames,
2375
+ extractDdlTableColumns: m.extractDdlTableColumns,
2376
+ })));
2377
+ /** The schema-code visitor's per-file `visit` body, extracted from
2378
+ * `createSchemaCodeVisitor` (which exceeded the 200-line `function-length`
2379
+ * budget) so the factory stays a thin descriptor over the shared analyzer. */
2380
+ async function runSchemaCodeVisit(ast, adapter, context, sourceCode) {
2381
+ const { defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getSchemaCodeAnalyzer();
2382
+ const pm = await _getProvenanceModule();
2383
+ const violations = [];
2384
+ // Pipeline config for this analyzer (moved before table extraction, needed
2385
+ // by the table-source registry and provenance context).
2386
+ const schemaConfig = (context.config ?? {});
2387
+ // Spec 70 R1 — the corpus's named dialect (or null) threads to the DDL
2388
+ // extractors so DO-local DDL parses instead of regex. A null dialect is
2389
+ // cannot-fire (no DDL facts).
2390
+ const dialect = schemaConfig.sqlDialect;
2391
+ // Spec 29 R2: Extract ORM table names via declarative table-source registry.
2392
+ // Adding an ORM is now a config entry, not code. Falls back to Drizzle
2393
+ // entries when no user-specified tableSources are configured.
2394
+ const ormTables = [];
2395
+ const tableProvenances = [];
2396
+ const tableSources = schemaConfig.tableSources ?? [
2397
+ { kind: 'callee', name: 'pgTable', arg: 0, description: 'Drizzle PostgreSQL table' },
2398
+ { kind: 'callee', name: 'mysqlTable', arg: 0, description: 'Drizzle MySQL table' },
2399
+ { kind: 'callee', name: 'sqliteTable', arg: 0, description: 'Drizzle SQLite table' },
2400
+ ];
2401
+ if (tableSources.length > 0) {
2402
+ const registered = extractTablesFromRegistry(tableSources, {
2403
+ ast: ast,
2404
+ adapter: adapter,
2405
+ sourceCode,
2406
+ filePath: context.filePath,
2407
+ });
2408
+ for (const { table, source } of registered) {
2409
+ ormTables.push(table);
2410
+ tableProvenances.push({ table, source });
2411
+ }
2412
+ }
2413
+ // Extract DDL from sql.exec(...) string literals inside Durable Object classes.
2414
+ // These are CREATE TABLE / DROP TABLE statements at runtime that migration
2415
+ // discovery never sees. Extracting them completes the authoritative catalog
2416
+ // so unknown-table doesn't false-positive on DO-local tables.
2417
+ const doDDL = [];
2418
+ const doTemplateDDL = /`([^`]*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^`]+)`/gis;
2419
+ const doStringDDL = /(["'])((?:\s*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^"']+))\1/gis;
2420
+ let ddlMatch;
2421
+ while ((ddlMatch = doTemplateDDL.exec(sourceCode)) !== null) {
2422
+ const sql = ddlMatch[1].trim();
2423
+ if (sql)
2424
+ doDDL.push(sql);
2425
+ }
2426
+ while ((ddlMatch = doStringDDL.exec(sourceCode)) !== null) {
2427
+ const sql = ddlMatch[2].trim();
2428
+ if (sql)
2429
+ doDDL.push(sql);
2430
+ }
2431
+ const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
2432
+ const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql, dialect ?? null) : [];
2433
+ const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql, dialect ?? null) : {};
2434
+ // Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
2435
+ const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
2436
+ const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
2437
+ mode: detectionMode,
2438
+ dbBindingNames: schemaConfig.dbBindingNames ?? defaults.dbBindingNames,
2439
+ dbWrapperNames: schemaConfig.dbWrapperNames ?? defaults.dbWrapperNames,
2440
+ // Spec 70 criterion 8 — thread the corpus's named dialect so R3 can prove
2441
+ // a receiver by its parsed SQL argument (the criterion-9 type-annotation
2442
+ // tests are gone). Mirrors buildSchemaProvenanceContext in the analyzer.
2443
+ sqlDialect: dialect ?? null,
2444
+ });
2445
+ // Spec 70 criterion 2 — the declaration-resolution environment `identifyHandle`
2446
+ // reads for handle admission (mirrors buildSchemaHandleEnv). Absent for Go,
2447
+ // which has no local binding env and resolves cross-file.
2448
+ const handleEnv = adapter.name === 'go'
2449
+ ? undefined
2450
+ : {
2451
+ provenance: provenanceContext.dbProvenanced,
2452
+ bindings: buildBindingEnv(ast, adapter, sourceCode),
2453
+ adapter: adapter,
2454
+ sourceCode,
2455
+ };
2456
+ // File gate — skip files without DB usage
2457
+ if (!passesFileGate(context.filePath, sourceCode, schemaConfig, provenanceContext)) {
2458
+ const facts = { [context.filePath]: { tableRefs: [], ormTables, tableProvenance: tableProvenances } };
2459
+ if (doDDL.length > 0) {
2460
+ facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql, dialect ?? null);
2461
+ if (doDDLColumns.length > 0)
2462
+ facts[context.filePath].ddlColumns = doDDLColumns;
2463
+ if (Object.keys(doDDLTableColumns).length > 0)
2464
+ facts[context.filePath].ddlTableColumns = doDDLTableColumns;
2465
+ }
2466
+ return { violations: [], facts };
2467
+ }
2468
+ // Build known-tables set from schemas config (pre-pipeline + DB-loaded schemas)
2469
+ const schemas = schemaConfig.schemas ?? [];
2470
+ const knownTablesArr = schemaConfig.knownTables ?? [];
2471
+ const allTables = new Set();
2472
+ for (const t of knownTablesArr)
2473
+ allTables.add(t);
2474
+ for (const schema of schemas) {
2475
+ for (const table of (schema.tables ?? [])) {
2476
+ allTables.add(table.name);
2477
+ }
2478
+ }
2479
+ // Find table references (per-file, uses allTables for short-id false-positive
2480
+ // filtering). The `unresolved`/`unparseable` halves are no longer consumed
2481
+ // here: both the `unresolved-query` (Spec 58 R1 / 70 1b) and `unparseable`
2482
+ // (Spec 70 R2) coverage diagnostics are re-derived corpus-side
2483
+ // (classifyUnresolvedQuerySites / classifyUnparseableSql →
2484
+ // buildReceiverDiagnostics), so this visitor stops emitting them.
2485
+ const { references: tableRefs } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables, handleEnv });
2486
+ // Check naming conventions
2487
+ if (schemaConfig.checkNamingConventions !== false) {
2488
+ violations.push(...checkNamingConventions(tableRefs, context.filePath));
2489
+ }
2490
+ // Check query patterns
2491
+ if (schemaConfig.validateQueryPatterns !== false) {
2492
+ violations.push(...checkQueryPatterns(ast, adapter, sourceCode, schemaConfig));
2493
+ }
2494
+ // Check SQL injection
2495
+ violations.push(...checkSQLInjection(ast, adapter, sourceCode));
2496
+ // Emit facts for the Stage 3 reducer. Fluent-builder references
2497
+ // (`origin: 'query-builder'`) are excluded from the unknown-table check:
2498
+ // a scratch/test table name carried by `db('t').select(...)` is not a
2499
+ // schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
2500
+ const fileFacts = {
2501
+ tableRefs: tableRefs
2502
+ .filter((r) => r.origin !== 'query-builder')
2503
+ .map((r) => ({
2504
+ table: r.table,
2505
+ type: r.type,
2506
+ line: r.location.line,
2507
+ column: r.location.column,
2508
+ context: r.context,
2509
+ })),
2510
+ ormTables,
2511
+ tableProvenance: tableProvenances,
2512
+ };
2513
+ if (doDDL.length > 0) {
2514
+ fileFacts.ddlOps = parseMigrationOps(doDDLSql, dialect ?? null);
2515
+ if (doDDLColumns.length > 0)
2516
+ fileFacts.ddlColumns = doDDLColumns;
2517
+ if (Object.keys(doDDLTableColumns).length > 0)
2518
+ fileFacts.ddlTableColumns = doDDLTableColumns;
2519
+ }
2520
+ return {
2521
+ violations,
2522
+ facts: { [context.filePath]: fileFacts },
2523
+ };
2524
+ }
2249
2525
  /**
2250
2526
  * Schema code visitor (.ts/.tsx/.js/.jsx) — per-file analysis for naming conventions,
2251
2527
  * query patterns, SQL injection, table references, and ORM table extraction.
2528
+ *
2529
+ * @returns The schema-code stage-2 visitor.
2252
2530
  */
2253
2531
  export function createSchemaCodeVisitor() {
2254
- const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
2255
- analyzer: new m.UniversalSchemaAnalyzer(),
2256
- defaults: m.DEFAULT_SCHEMA_CONFIG,
2257
- parseMigrationOps: m.parseMigrationOps,
2258
- extractDdlColumnNames: m.extractDdlColumnNames,
2259
- extractDdlTableColumns: m.extractDdlTableColumns,
2260
- })));
2261
2532
  return {
2262
2533
  name: 'schema-code',
2263
2534
  stage: 'visitor',
2264
2535
  extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],
2265
2536
  getRuleIds: () => getRuleIdsFor('schema'),
2266
- async visit(ast, adapter, context, sourceCode) {
2267
- const { analyzer: a, defaults, parseMigrationOps, extractDdlColumnNames, extractDdlTableColumns } = await getAnalyzer();
2268
- const pm = await _getProvenanceModule();
2269
- const violations = [];
2270
- const diagnostics = [];
2271
- const indexFacts = [];
2272
- // Pipeline config for this analyzer (moved before table extraction, needed
2273
- // by the table-source registry and provenance context).
2274
- const schemaConfig = (context.config ?? {});
2275
- // Spec 29 R2: Extract ORM table names via declarative table-source registry.
2276
- // Adding an ORM is now a config entry, not code. Falls back to Drizzle
2277
- // entries when no user-specified tableSources are configured.
2278
- const ormTables = [];
2279
- const tableProvenances = [];
2280
- const tableSources = schemaConfig.tableSources ?? [
2281
- { kind: 'callee', name: 'pgTable', arg: 0, description: 'Drizzle PostgreSQL table' },
2282
- { kind: 'callee', name: 'mysqlTable', arg: 0, description: 'Drizzle MySQL table' },
2283
- { kind: 'callee', name: 'sqliteTable', arg: 0, description: 'Drizzle SQLite table' },
2284
- ];
2285
- if (tableSources.length > 0) {
2286
- const registered = extractTablesFromRegistry(tableSources, {
2287
- ast: ast,
2288
- adapter: adapter,
2289
- sourceCode,
2290
- filePath: context.filePath,
2291
- });
2292
- for (const { table, source } of registered) {
2293
- ormTables.push(table);
2294
- tableProvenances.push({ table, source });
2295
- }
2296
- }
2297
- // Extract DDL from sql.exec(...) string literals inside Durable Object classes.
2298
- // These are CREATE TABLE / DROP TABLE statements at runtime that migration
2299
- // discovery never sees. Extracting them completes the authoritative catalog
2300
- // so unknown-table doesn't false-positive on DO-local tables.
2301
- const doDDL = [];
2302
- const doTemplateDDL = /`([^`]*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^`]+)`/gis;
2303
- const doStringDDL = /(["'])((?:\s*(?:CREATE|DROP|ALTER)\s+(?:TABLE|VIRTUAL\s+TABLE)\s+[^"']+))\1/gis;
2304
- let ddlMatch;
2305
- while ((ddlMatch = doTemplateDDL.exec(sourceCode)) !== null) {
2306
- const sql = ddlMatch[1].trim();
2307
- if (sql)
2308
- doDDL.push(sql);
2309
- }
2310
- while ((ddlMatch = doStringDDL.exec(sourceCode)) !== null) {
2311
- const sql = ddlMatch[2].trim();
2312
- if (sql)
2313
- doDDL.push(sql);
2314
- }
2315
- const doDDLSql = doDDL.length > 0 ? doDDL.join(';\n') : null;
2316
- const doDDLColumns = doDDLSql ? extractDdlColumnNames(doDDLSql) : [];
2317
- const doDDLTableColumns = doDDLSql ? extractDdlTableColumns(doDDLSql) : {};
2318
- // Build provenance context for this file — defaults from DEFAULT_SCHEMA_CONFIG
2319
- const detectionMode = schemaConfig.detection?.mode ?? 'hybrid';
2320
- const provenanceContext = pm.buildProvenanceContext(ast, adapter, sourceCode, {
2321
- mode: detectionMode,
2322
- dbReceiverNames: schemaConfig.dbReceiverNames ?? defaults.dbReceiverNames,
2323
- dbBindingNames: schemaConfig.dbBindingNames ?? defaults.dbBindingNames,
2324
- dbCallMethods: schemaConfig.dbCallMethods ?? defaults.dbCallMethods,
2325
- dbWrapperNames: schemaConfig.dbWrapperNames ?? defaults.dbWrapperNames,
2326
- });
2327
- // File gate — skip files without DB usage
2328
- if (!passesFileGate(context.filePath, sourceCode, schemaConfig, provenanceContext)) {
2329
- const facts = { [context.filePath]: { tableRefs: [], ormTables, tableProvenance: tableProvenances } };
2330
- if (doDDL.length > 0) {
2331
- facts[context.filePath].ddlOps = parseMigrationOps(doDDLSql);
2332
- if (doDDLColumns.length > 0)
2333
- facts[context.filePath].ddlColumns = doDDLColumns;
2334
- if (Object.keys(doDDLTableColumns).length > 0)
2335
- facts[context.filePath].ddlTableColumns = doDDLTableColumns;
2336
- }
2337
- return { violations: [], facts };
2338
- }
2339
- // Build known-tables set from schemas config (pre-pipeline + DB-loaded schemas)
2340
- const schemas = schemaConfig.schemas ?? [];
2341
- const knownTablesArr = schemaConfig.knownTables ?? [];
2342
- const allTables = new Set();
2343
- for (const t of knownTablesArr)
2344
- allTables.add(t);
2345
- for (const schema of schemas) {
2346
- for (const table of (schema.tables ?? [])) {
2347
- allTables.add(table.name);
2348
- }
2349
- }
2350
- // Find table references (per-file, uses allTables for short-id false-positive filtering)
2351
- const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
2352
- // Record schema usage → emit as indexFacts via the shared instance
2353
- a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
2354
- const pending = a.getPendingSchemaRecords();
2355
- // Emit clear-by-file + per-usage index facts
2356
- if (pending.clearFiles.length > 0) {
2357
- for (const filePath of pending.clearFiles) {
2358
- indexFacts.push({ table: 'schema_usage', data: { _action: 'clear-by-file', file_path: filePath } });
2359
- }
2360
- }
2361
- for (const usage of pending.usages) {
2362
- indexFacts.push({
2363
- table: 'schema_usage',
2364
- data: {
2365
- file_path: usage.filePath,
2366
- table_name: usage.tableName,
2367
- function_name: usage.functionName,
2368
- function_start_line: usage.functionStartLine,
2369
- function_start_column: usage.functionStartColumn,
2370
- usage_type: usage.usageType,
2371
- line: usage.line,
2372
- column: usage.column,
2373
- raw_query: usage.rawQuery,
2374
- origin: usage.origin ?? null,
2375
- },
2376
- });
2377
- }
2378
- // Check naming conventions
2379
- if (schemaConfig.checkNamingConventions !== false) {
2380
- violations.push(...checkNamingConventions(tableRefs, context.filePath));
2381
- }
2382
- // Spec 58 R1 — DB-call SQL held in an unresolvable identifier is a coverage
2383
- // diagnostic (the analyzer can't see the SQL), not a finding (the code isn't wrong).
2384
- if (schemaConfig.reportUnresolvedQueries !== false) {
2385
- diagnostics.push(...checkUnresolvedQueries(unresolved, context.filePath));
2386
- }
2387
- // Check query patterns
2388
- if (schemaConfig.validateQueryPatterns !== false) {
2389
- violations.push(...checkQueryPatterns(ast, adapter, sourceCode, schemaConfig));
2390
- }
2391
- // Check SQL injection
2392
- violations.push(...checkSQLInjection(ast, adapter, sourceCode));
2393
- // Emit facts for the Stage 3 reducer. Fluent-builder references
2394
- // (`origin: 'query-builder'`) are excluded from the unknown-table check:
2395
- // a scratch/test table name carried by `db('t').select(...)` is not a
2396
- // schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
2397
- const fileFacts = {
2398
- tableRefs: tableRefs
2399
- .filter((r) => r.origin !== 'query-builder')
2400
- .map((r) => ({
2401
- table: r.table,
2402
- type: r.type,
2403
- line: r.location.line,
2404
- column: r.location.column,
2405
- context: r.context,
2406
- })),
2407
- ormTables,
2408
- tableProvenance: tableProvenances,
2409
- };
2410
- if (doDDL.length > 0) {
2411
- fileFacts.ddlOps = parseMigrationOps(doDDLSql);
2412
- if (doDDLColumns.length > 0)
2413
- fileFacts.ddlColumns = doDDLColumns;
2414
- if (Object.keys(doDDLTableColumns).length > 0)
2415
- fileFacts.ddlTableColumns = doDDLTableColumns;
2416
- }
2417
- return {
2418
- violations,
2419
- facts: { [context.filePath]: fileFacts },
2420
- indexFacts: indexFacts.length > 0 ? indexFacts : undefined,
2421
- ...(diagnostics.length > 0 && { diagnostics }),
2422
- };
2423
- },
2537
+ visit: runSchemaCodeVisit,
2424
2538
  defaultConfig: {},
2425
2539
  description: 'Per-file schema analysis: naming conventions, query patterns, SQL injection, table references',
2426
2540
  category: 'database',
@@ -2428,6 +2542,8 @@ export function createSchemaCodeVisitor() {
2428
2542
  }
2429
2543
  /**
2430
2544
  * Schema Prisma visitor (.prisma files) — extracts model names for known-table catalog.
2545
+ *
2546
+ * @returns The schema-prisma stage-2 visitor.
2431
2547
  */
2432
2548
  export function createSchemaPrismaVisitor() {
2433
2549
  return {
@@ -2470,6 +2586,8 @@ export function createSchemaPrismaVisitor() {
2470
2586
  }
2471
2587
  /**
2472
2588
  * Schema JSON visitor (.json files) — parses JSON for the Stage 3 reducer to validate.
2589
+ *
2590
+ * @returns The schema-json stage-2 visitor.
2473
2591
  */
2474
2592
  export function createSchemaJsonVisitor() {
2475
2593
  return {
@@ -2491,11 +2609,329 @@ export function createSchemaJsonVisitor() {
2491
2609
  category: 'database',
2492
2610
  };
2493
2611
  }
2612
+ /**
2613
+ * Flatten `allFacts` (keyed by visitor name, each value `{ [filePath]: facts }`)
2614
+ * into per-file `[filePath, fact]` pairs.
2615
+ */
2616
+ function* iteratePerFileFacts(allFacts) {
2617
+ for (const [, visitorFacts] of Object.entries(allFacts)) {
2618
+ if (typeof visitorFacts === 'object' && visitorFacts !== null) {
2619
+ for (const [filePath, fact] of Object.entries(visitorFacts)) {
2620
+ if (typeof fact === 'object' && fact !== null) {
2621
+ yield [filePath, fact];
2622
+ }
2623
+ }
2624
+ }
2625
+ }
2626
+ }
2627
+ /**
2628
+ * Build the known-tables catalog (and derived column sets) from the schema-sql,
2629
+ * schema-code, and schema-prisma facts. Also replays SQL migrations to compute
2630
+ * drop provenance for stale-table-reference detection.
2631
+ */
2632
+ function buildSchemaCatalog(allFacts) {
2633
+ const knownTables = new Set();
2634
+ const tableProvenances = new Map(); // table name → origins
2635
+ // 1a. SQL DDL replay — collect all DDL facts, sort by numeric prefix, replay
2636
+ const sqlFiles = [];
2637
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2638
+ if ('ddlOps' in fact) {
2639
+ sqlFiles.push({ filePath, ops: fact.ddlOps });
2640
+ }
2641
+ }
2642
+ // Sort by numeric prefix in basename: "009_something" < "0010_rename"
2643
+ // Falls back to localeCompare for non-numeric-prefixed names.
2644
+ const numericPrefix = (p) => {
2645
+ const base = p.split('/').pop() ?? p;
2646
+ const m = base.match(/^(\d+)/);
2647
+ return m ? parseInt(m[1], 10) : 0;
2648
+ };
2649
+ sqlFiles.sort((a, b) => {
2650
+ const na = numericPrefix(a.filePath);
2651
+ const nb = numericPrefix(b.filePath);
2652
+ if (na !== nb)
2653
+ return na - nb;
2654
+ return a.filePath.localeCompare(b.filePath);
2655
+ });
2656
+ // Drop provenance — the last migration that removed a table, and the tables
2657
+ // that same migration introduced. Lets unknown-table detection distinguish
2658
+ // "never existed" from "existed and was dropped": the latter is a stale code
2659
+ // reference, not a typo. `dropProvenance` ends up holding exactly the tables
2660
+ // dropped and not subsequently recreated (CREATE clears the entry).
2661
+ const dropProvenance = new Map();
2662
+ for (const sqlFile of sqlFiles) {
2663
+ const before = new Set(knownTables);
2664
+ applyMigrationOps(sqlFile.ops, knownTables);
2665
+ // Record provenance for newly created tables
2666
+ for (const table of knownTables) {
2667
+ if (!before.has(table)) {
2668
+ const sources = tableProvenances.get(table) ?? [];
2669
+ sources.push({ table, tier: 'sql-migration', sourceFile: sqlFile.filePath, description: 'SQL migration' });
2670
+ tableProvenances.set(table, sources);
2671
+ }
2672
+ }
2673
+ // Genuinely-new tables introduced by this migration (excludes rename/rebuild
2674
+ // churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
2675
+ const createdHere = [];
2676
+ const createdTables = new Set();
2677
+ const droppedTables = new Set();
2678
+ for (const op of sqlFile.ops) {
2679
+ const t = stripIdentifier(op.table);
2680
+ if (op.op === 'CREATE') {
2681
+ createdTables.add(t);
2682
+ if (!before.has(t))
2683
+ createdHere.push(t);
2684
+ }
2685
+ else if (op.op === 'DROP') {
2686
+ droppedTables.add(t);
2687
+ }
2688
+ }
2689
+ for (const op of sqlFile.ops) {
2690
+ const t = stripIdentifier(op.table);
2691
+ if (op.op === 'DROP') {
2692
+ dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
2693
+ }
2694
+ else if (op.op === 'CREATE') {
2695
+ dropProvenance.delete(t);
2696
+ }
2697
+ }
2698
+ // A table created and dropped within this same file is a self-contained
2699
+ // fixture — a scratch table a test or script creates, uses, then tears
2700
+ // down. Its DROP is teardown, not a migration, so it must neither surface
2701
+ // as a stale-table-reference nor leave the name unknown: keep it known and
2702
+ // clear the drop provenance this file recorded for it. (A genuine dropped
2703
+ // table stays stale because its DROP lives in a migration that never also
2704
+ // CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
2705
+ for (const t of createdTables) {
2706
+ if (droppedTables.has(t)) {
2707
+ knownTables.add(t);
2708
+ if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
2709
+ dropProvenance.delete(t);
2710
+ }
2711
+ }
2712
+ }
2713
+ }
2714
+ // 1b. ORM tables from code files — with provenance from the table-source registry
2715
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2716
+ const ormTables = fact.ormTables ?? [];
2717
+ for (const t of ormTables)
2718
+ knownTables.add(t);
2719
+ const provenances = fact.tableProvenance ?? [];
2720
+ for (const p of provenances) {
2721
+ const sources = tableProvenances.get(p.table) ?? [];
2722
+ sources.push(p.source);
2723
+ tableProvenances.set(p.table, sources);
2724
+ }
2725
+ }
2726
+ // 1c. Prisma models
2727
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2728
+ const prismaModels = fact.prismaModels ?? [];
2729
+ for (const m of prismaModels) {
2730
+ knownTables.add(m);
2731
+ const sources = tableProvenances.get(m) ?? [];
2732
+ sources.push({ table: m, tier: 'prisma-model', sourceFile: filePath, description: 'Prisma model' });
2733
+ tableProvenances.set(m, sources);
2734
+ }
2735
+ }
2736
+ // 1d. Aggregate DDL-declared columns (Spec 39 — derived applicability).
2737
+ // The schema-sql and schema-code visitors emit per-file `ddlColumns`;
2738
+ // the reducer folds them into a single case-insensitive, deduplicated
2739
+ // set so downstream applicability predicates can ask "does ANY table
2740
+ // carry a tenant-scoping column?" without per-table column lists.
2741
+ //
2742
+ // Spec 62 Amendment B — additionally fold the per-table
2743
+ // `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
2744
+ // string[]>) so the Stage-4 missing-org-filter reducer can answer the
2745
+ // per-query question "does THIS table carry a tenant column?" that the
2746
+ // flat set cannot.
2747
+ const ddlColumns = new Set();
2748
+ const tableColumnsMap = new Map();
2749
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2750
+ const cols = fact.ddlColumns ?? [];
2751
+ for (const c of cols)
2752
+ ddlColumns.add(String(c).toLowerCase());
2753
+ const perTable = fact.ddlTableColumns ?? {};
2754
+ for (const [table, tableCols] of Object.entries(perTable)) {
2755
+ let set = tableColumnsMap.get(table);
2756
+ if (!set) {
2757
+ set = new Set();
2758
+ tableColumnsMap.set(table, set);
2759
+ }
2760
+ for (const c of tableCols)
2761
+ set.add(String(c).toLowerCase());
2762
+ }
2763
+ }
2764
+ const tableColumns = {};
2765
+ for (const [table, set] of tableColumnsMap)
2766
+ tableColumns[table] = [...set];
2767
+ return { knownTables, tableProvenances, dropProvenance, ddlColumns, tableColumns };
2768
+ }
2769
+ /**
2770
+ * Merge external config tables into the catalog and detect table references
2771
+ * that resolve to no known table. Emits `unknown-table` and
2772
+ * `stale-table-reference` violations.
2773
+ */
2774
+ function detectUnknownTables(opts) {
2775
+ const { knownTables, tableProvenances, dropProvenance, allFacts, schemaConfig } = opts;
2776
+ const violations = [];
2777
+ // ── 2. Unknown-table detection ────────────────────────────────────────
2778
+ //
2779
+ // Fail-open guardrail: we can only accuse when the table catalog is
2780
+ // built from authoritative sources. Empty catalog → cannot accuse.
2781
+ //
2782
+ // Authoritative sources (independent of query sites, not circular):
2783
+ // • SQL migration files via schema-sql (step 1a)
2784
+ // • ORM model definitions via schema-code (step 1b)
2785
+ // • Prisma schemas via schema-prisma (step 1c)
2786
+ //
2787
+ // External config tables (schemas from CodeIndexDB, wrangler.toml
2788
+ // pre-discovered tables) are added as a bonus but are not required.
2789
+ //
2790
+ // NOT authoritative: tables inferred from query-text alone. Those
2791
+ // never enter the catalog (table refs are for checking, not building).
2792
+ // Merge external tables from config (bonus, not required).
2793
+ const externalKnownTables = schemaConfig.knownTables ?? [];
2794
+ for (const t of externalKnownTables) {
2795
+ knownTables.add(t);
2796
+ const sources = tableProvenances.get(t) ?? [];
2797
+ sources.push({ table: t, tier: 'external-config', description: 'External configuration' });
2798
+ tableProvenances.set(t, sources);
2799
+ }
2800
+ // Also read the documented schemas config (structured {name, tables} objects).
2801
+ // The standalone UniversalSchemaAnalyzer.analyze() path reads schemas; the
2802
+ // pipeline reducer must read it too, otherwise configuring only schemas
2803
+ // silently triggers fail-open (knownTables.size === 0).
2804
+ const externalSchemas = schemaConfig.schemas ?? [];
2805
+ for (const schema of externalSchemas) {
2806
+ for (const table of schema.tables) {
2807
+ knownTables.add(table.name);
2808
+ const sources = tableProvenances.get(table.name) ?? [];
2809
+ sources.push({ table: table.name, tier: 'external-config', description: `Schema: ${schema.name}` });
2810
+ tableProvenances.set(table.name, sources);
2811
+ }
2812
+ }
2813
+ if (knownTables.size > 0) {
2814
+ // Collect all table references across all files
2815
+ const allTableRefs = [];
2816
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2817
+ const refs = fact.tableRefs ?? [];
2818
+ for (const ref of refs) {
2819
+ allTableRefs.push({ file: filePath, ...ref });
2820
+ }
2821
+ }
2822
+ // 10:1 fail-open ratio guard
2823
+ const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
2824
+ if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
2825
+ for (const ref of unknownRefs) {
2826
+ // The table existed and was dropped in a migration — a stale code
2827
+ // reference, not a typo. Name the dropping migration and the tables
2828
+ // that migration introduced (evidence, not proof of a successor).
2829
+ const drop = dropProvenance.get(ref.table);
2830
+ if (drop) {
2831
+ const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
2832
+ const created = drop.createdInSameMigration;
2833
+ const msg = created.length > 0
2834
+ ? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
2835
+ : `${ref.table} was dropped in ${migrationName} and was not recreated.`;
2836
+ violations.push({
2837
+ file: ref.file,
2838
+ line: ref.line,
2839
+ column: ref.column,
2840
+ severity: 'critical',
2841
+ message: msg,
2842
+ rule: 'stale-table-reference',
2843
+ analyzer: 'schema',
2844
+ symbol: ref.table,
2845
+ resolution: {
2846
+ action: 'update-stale-reference',
2847
+ // The tables `created` are evidence the migration replaced the
2848
+ // dropped table, not proof of a drop-in successor — schemas can
2849
+ // be split or changed incompatibly, in which case the file must
2850
+ // be removed rather than updated. The summary names them as
2851
+ // context and leaves update-vs-remove to the reviewer.
2852
+ summary: created.length > 0
2853
+ ? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
2854
+ : `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
2855
+ symbols: created.length > 0 ? created : [ref.table],
2856
+ files: [ref.file],
2857
+ lines: [ref.line],
2858
+ },
2859
+ });
2860
+ continue;
2861
+ }
2862
+ const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
2863
+ const msg = suggestions.length > 0
2864
+ ? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
2865
+ : `Reference to unknown table '${ref.table}' (${ref.type})`;
2866
+ const suggestionNames = suggestions.map((s) => s.replace(/^'|'$/g, ''));
2867
+ violations.push({
2868
+ file: ref.file,
2869
+ line: ref.line,
2870
+ column: ref.column,
2871
+ severity: 'critical',
2872
+ message: msg,
2873
+ rule: 'unknown-table',
2874
+ analyzer: 'schema',
2875
+ symbol: ref.table,
2876
+ resolution: {
2877
+ action: suggestionNames.length > 0 ? 'use-known-table' : 'register-or-fix-table',
2878
+ summary: suggestionNames.length > 0
2879
+ ? `Rename the table reference '${ref.table}' to the nearest known table: ${suggestions.join(', ')}.`
2880
+ : `The table '${ref.table}' is not in the known catalog — register it, or fix the reference to a known table.`,
2881
+ symbols: suggestionNames.length > 0 ? suggestionNames : [ref.table],
2882
+ files: [ref.file],
2883
+ lines: [ref.line],
2884
+ },
2885
+ });
2886
+ }
2887
+ }
2888
+ }
2889
+ return violations;
2890
+ }
2891
+ /**
2892
+ * Validate JSON schemas found in the corpus. Best-effort: failures are non-fatal.
2893
+ */
2894
+ function validateJsonSchemas(opts) {
2895
+ const { schemaConfig, allFacts, context, analyzeJsonSchemas } = opts;
2896
+ const violations = [];
2897
+ if (schemaConfig.validateJsonSchemas !== false) {
2898
+ try {
2899
+ // Collect JSON file paths from the lightweight visitor markers, then
2900
+ // parse each on demand so parsed objects never survive past this loop.
2901
+ const jsonFiles = [];
2902
+ for (const [filePath, fact] of iteratePerFileFacts(allFacts)) {
2903
+ if ('isJson' in fact) {
2904
+ jsonFiles.push(filePath);
2905
+ }
2906
+ }
2907
+ const readJson = (filePath) => {
2908
+ const raw = context.readSource?.(filePath);
2909
+ if (raw === undefined)
2910
+ return null;
2911
+ try {
2912
+ const parsed = JSON.parse(raw);
2913
+ return parsed !== null && typeof parsed === 'object' ? parsed : null;
2914
+ }
2915
+ catch {
2916
+ return null;
2917
+ }
2918
+ };
2919
+ const jsonResult = analyzeJsonSchemas(jsonFiles, readJson, schemaConfig);
2920
+ violations.push(...(jsonResult?.violations ?? []));
2921
+ }
2922
+ catch (e) {
2923
+ // JSON schema validation is best-effort (non-fatal)
2924
+ }
2925
+ }
2926
+ return violations;
2927
+ }
2494
2928
  /**
2495
2929
  * Schema Stage 3 reducer — cross-file unknown-table detection and JSON schema validation.
2496
2930
  *
2497
2931
  * Consumes facts from schema-sql, schema-code, schema-prisma, and schema-json visitors.
2498
2932
  * Builds the complete known-tables catalog and checks all table references against it.
2933
+ *
2934
+ * @returns The schema stage-3 reducer.
2499
2935
  */
2500
2936
  export function createSchemaReducer() {
2501
2937
  const getAnalyzer = lazySingleton(() => import('./analyzers/universal/UniversalSchemaAnalyzer.js').then((m) => ({
@@ -2512,296 +2948,20 @@ export function createSchemaReducer() {
2512
2948
  const { analyzer: a, analyzeJsonSchemas } = await getAnalyzer();
2513
2949
  const violations = [];
2514
2950
  const schemaConfig = (context.config ?? {});
2515
- // allFacts is keyed by visitor name, each value is { [filePath]: { ...per-file facts } }
2516
- // Flatten to per-file iteration helpers.
2517
- const perFile = function* () {
2518
- for (const [, visitorFacts] of Object.entries(allFacts)) {
2519
- if (typeof visitorFacts === 'object' && visitorFacts !== null) {
2520
- for (const [filePath, fact] of Object.entries(visitorFacts)) {
2521
- if (typeof fact === 'object' && fact !== null) {
2522
- yield [filePath, fact];
2523
- }
2524
- }
2525
- }
2526
- }
2527
- };
2528
- // ── 1. Build known-tables catalog ──────────────────────────────────────
2529
- const knownTables = new Set();
2530
- const tableProvenances = new Map(); // table name → origins
2531
- // 1a. SQL DDL replay — collect all DDL facts, sort by numeric prefix, replay
2532
- const sqlFiles = [];
2533
- for (const [filePath, fact] of perFile()) {
2534
- if ('ddlOps' in fact) {
2535
- sqlFiles.push({ filePath, ops: fact.ddlOps });
2536
- }
2537
- }
2538
- // Sort by numeric prefix in basename: "009_something" < "0010_rename"
2539
- // Falls back to localeCompare for non-numeric-prefixed names.
2540
- const numericPrefix = (p) => {
2541
- const base = p.split('/').pop() ?? p;
2542
- const m = base.match(/^(\d+)/);
2543
- return m ? parseInt(m[1], 10) : 0;
2544
- };
2545
- sqlFiles.sort((a, b) => {
2546
- const na = numericPrefix(a.filePath);
2547
- const nb = numericPrefix(b.filePath);
2548
- if (na !== nb)
2549
- return na - nb;
2550
- return a.filePath.localeCompare(b.filePath);
2551
- });
2552
- // Drop provenance — the last migration that removed a table, and the tables
2553
- // that same migration introduced. Lets unknown-table detection distinguish
2554
- // "never existed" from "existed and was dropped": the latter is a stale code
2555
- // reference, not a typo. `dropProvenance` ends up holding exactly the tables
2556
- // dropped and not subsequently recreated (CREATE clears the entry).
2557
- const dropProvenance = new Map();
2558
- for (const sqlFile of sqlFiles) {
2559
- const before = new Set(knownTables);
2560
- applyMigrationOps(sqlFile.ops, knownTables);
2561
- // Record provenance for newly created tables
2562
- for (const table of knownTables) {
2563
- if (!before.has(table)) {
2564
- const sources = tableProvenances.get(table) ?? [];
2565
- sources.push({ table, tier: 'sql-migration', sourceFile: sqlFile.filePath, description: 'SQL migration' });
2566
- tableProvenances.set(table, sources);
2567
- }
2568
- }
2569
- // Genuinely-new tables introduced by this migration (excludes rename/rebuild
2570
- // churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
2571
- const createdHere = [];
2572
- const createdTables = new Set();
2573
- const droppedTables = new Set();
2574
- for (const op of sqlFile.ops) {
2575
- const t = stripIdentifier(op.table);
2576
- if (op.op === 'CREATE') {
2577
- createdTables.add(t);
2578
- if (!before.has(t))
2579
- createdHere.push(t);
2580
- }
2581
- else if (op.op === 'DROP') {
2582
- droppedTables.add(t);
2583
- }
2584
- }
2585
- for (const op of sqlFile.ops) {
2586
- const t = stripIdentifier(op.table);
2587
- if (op.op === 'DROP') {
2588
- dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
2589
- }
2590
- else if (op.op === 'CREATE') {
2591
- dropProvenance.delete(t);
2592
- }
2593
- }
2594
- // A table created and dropped within this same file is a self-contained
2595
- // fixture — a scratch table a test or script creates, uses, then tears
2596
- // down. Its DROP is teardown, not a migration, so it must neither surface
2597
- // as a stale-table-reference nor leave the name unknown: keep it known and
2598
- // clear the drop provenance this file recorded for it. (A genuine dropped
2599
- // table stays stale because its DROP lives in a migration that never also
2600
- // CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
2601
- for (const t of createdTables) {
2602
- if (droppedTables.has(t)) {
2603
- knownTables.add(t);
2604
- if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
2605
- dropProvenance.delete(t);
2606
- }
2607
- }
2608
- }
2609
- }
2610
- // 1b. ORM tables from code files — with provenance from the table-source registry
2611
- for (const [filePath, fact] of perFile()) {
2612
- const ormTables = fact.ormTables ?? [];
2613
- for (const t of ormTables)
2614
- knownTables.add(t);
2615
- const provenances = fact.tableProvenance ?? [];
2616
- for (const p of provenances) {
2617
- const sources = tableProvenances.get(p.table) ?? [];
2618
- sources.push(p.source);
2619
- tableProvenances.set(p.table, sources);
2620
- }
2621
- }
2622
- // 1c. Prisma models
2623
- for (const [filePath, fact] of perFile()) {
2624
- const prismaModels = fact.prismaModels ?? [];
2625
- for (const m of prismaModels) {
2626
- knownTables.add(m);
2627
- const sources = tableProvenances.get(m) ?? [];
2628
- sources.push({ table: m, tier: 'prisma-model', sourceFile: filePath, description: 'Prisma model' });
2629
- tableProvenances.set(m, sources);
2630
- }
2631
- }
2632
- // 1d. Aggregate DDL-declared columns (Spec 39 — derived applicability).
2633
- // The schema-sql and schema-code visitors emit per-file `ddlColumns`;
2634
- // the reducer folds them into a single case-insensitive, deduplicated
2635
- // set so downstream applicability predicates can ask "does ANY table
2636
- // carry a tenant-scoping column?" without per-table column lists.
2637
- //
2638
- // Spec 62 Amendment B — additionally fold the per-table
2639
- // `ddlTableColumns` into a corpus-wide `tableColumns` (Record<table,
2640
- // string[]>) so the Stage-4 missing-org-filter reducer can answer the
2641
- // per-query question "does THIS table carry a tenant column?" that the
2642
- // flat set cannot.
2643
- const ddlColumns = new Set();
2644
- const tableColumnsMap = new Map();
2645
- for (const [filePath, fact] of perFile()) {
2646
- const cols = fact.ddlColumns ?? [];
2647
- for (const c of cols)
2648
- ddlColumns.add(String(c).toLowerCase());
2649
- const perTable = fact.ddlTableColumns ?? {};
2650
- for (const [table, tableCols] of Object.entries(perTable)) {
2651
- let set = tableColumnsMap.get(table);
2652
- if (!set) {
2653
- set = new Set();
2654
- tableColumnsMap.set(table, set);
2655
- }
2656
- for (const c of tableCols)
2657
- set.add(String(c).toLowerCase());
2658
- }
2659
- }
2660
- const tableColumns = {};
2661
- for (const [table, set] of tableColumnsMap)
2662
- tableColumns[table] = [...set];
2663
- // ── 2. Unknown-table detection ────────────────────────────────────────
2664
- //
2665
- // Fail-open guardrail: we can only accuse when the table catalog is
2666
- // built from authoritative sources. Empty catalog → cannot accuse.
2667
- //
2668
- // Authoritative sources (independent of query sites, not circular):
2669
- // • SQL migration files via schema-sql (step 1a)
2670
- // • ORM model definitions via schema-code (step 1b)
2671
- // • Prisma schemas via schema-prisma (step 1c)
2672
- //
2673
- // External config tables (schemas from CodeIndexDB, wrangler.toml
2674
- // pre-discovered tables) are added as a bonus but are not required.
2675
- //
2676
- // NOT authoritative: tables inferred from query-text alone. Those
2677
- // never enter the catalog (table refs are for checking, not building).
2678
- // Merge external tables from config (bonus, not required).
2679
- const externalKnownTables = schemaConfig.knownTables ?? [];
2680
- for (const t of externalKnownTables) {
2681
- knownTables.add(t);
2682
- const sources = tableProvenances.get(t) ?? [];
2683
- sources.push({ table: t, tier: 'external-config', description: 'External configuration' });
2684
- tableProvenances.set(t, sources);
2685
- }
2686
- // Also read the documented schemas config (structured {name, tables} objects).
2687
- // The standalone UniversalSchemaAnalyzer.analyze() path reads schemas; the
2688
- // pipeline reducer must read it too, otherwise configuring only schemas
2689
- // silently triggers fail-open (knownTables.size === 0).
2690
- const externalSchemas = schemaConfig.schemas ?? [];
2691
- for (const schema of externalSchemas) {
2692
- for (const table of schema.tables) {
2693
- knownTables.add(table.name);
2694
- const sources = tableProvenances.get(table.name) ?? [];
2695
- sources.push({ table: table.name, tier: 'external-config', description: `Schema: ${schema.name}` });
2696
- tableProvenances.set(table.name, sources);
2697
- }
2698
- }
2699
- if (knownTables.size > 0) {
2700
- // Collect all table references across all files
2701
- const allTableRefs = [];
2702
- for (const [filePath, fact] of perFile()) {
2703
- const refs = fact.tableRefs ?? [];
2704
- for (const ref of refs) {
2705
- allTableRefs.push({ file: filePath, ...ref });
2706
- }
2707
- }
2708
- // 10:1 fail-open ratio guard
2709
- const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
2710
- if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
2711
- for (const ref of unknownRefs) {
2712
- // The table existed and was dropped in a migration — a stale code
2713
- // reference, not a typo. Name the dropping migration and the tables
2714
- // that migration introduced (evidence, not proof of a successor).
2715
- const drop = dropProvenance.get(ref.table);
2716
- if (drop) {
2717
- const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
2718
- const created = drop.createdInSameMigration;
2719
- const msg = created.length > 0
2720
- ? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
2721
- : `${ref.table} was dropped in ${migrationName} and was not recreated.`;
2722
- violations.push({
2723
- file: ref.file,
2724
- line: ref.line,
2725
- column: ref.column,
2726
- severity: 'critical',
2727
- message: msg,
2728
- rule: 'stale-table-reference',
2729
- analyzer: 'schema',
2730
- symbol: ref.table,
2731
- resolution: {
2732
- action: 'update-stale-reference',
2733
- // The tables `created` are evidence the migration replaced the
2734
- // dropped table, not proof of a drop-in successor — schemas can
2735
- // be split or changed incompatibly, in which case the file must
2736
- // be removed rather than updated. The summary names them as
2737
- // context and leaves update-vs-remove to the reviewer.
2738
- summary: created.length > 0
2739
- ? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
2740
- : `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
2741
- symbols: created.length > 0 ? created : [ref.table],
2742
- files: [ref.file],
2743
- lines: [ref.line],
2744
- },
2745
- });
2746
- continue;
2747
- }
2748
- const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
2749
- const msg = suggestions.length > 0
2750
- ? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
2751
- : `Reference to unknown table '${ref.table}' (${ref.type})`;
2752
- const suggestionNames = suggestions.map((s) => s.replace(/^'|'$/g, ''));
2753
- violations.push({
2754
- file: ref.file,
2755
- line: ref.line,
2756
- column: ref.column,
2757
- severity: 'critical',
2758
- message: msg,
2759
- rule: 'unknown-table',
2760
- analyzer: 'schema',
2761
- symbol: ref.table,
2762
- resolution: {
2763
- action: suggestionNames.length > 0 ? 'use-known-table' : 'register-or-fix-table',
2764
- summary: suggestionNames.length > 0
2765
- ? `Rename the table reference '${ref.table}' to the nearest known table: ${suggestions.join(', ')}.`
2766
- : `The table '${ref.table}' is not in the known catalog — register it, or fix the reference to a known table.`,
2767
- symbols: suggestionNames.length > 0 ? suggestionNames : [ref.table],
2768
- files: [ref.file],
2769
- lines: [ref.line],
2770
- },
2771
- });
2772
- }
2773
- }
2774
- }
2775
- // ── 3. JSON schema validation ──────────────────────────────────────────
2776
- if (schemaConfig.validateJsonSchemas !== false) {
2777
- try {
2778
- // Collect JSON file paths from the lightweight visitor markers, then
2779
- // parse each on demand so parsed objects never survive past this loop.
2780
- const jsonFiles = [];
2781
- for (const [filePath, fact] of perFile()) {
2782
- if ('isJson' in fact) {
2783
- jsonFiles.push(filePath);
2784
- }
2785
- }
2786
- const readJson = (filePath) => {
2787
- const raw = context.readSource?.(filePath);
2788
- if (raw === undefined)
2789
- return null;
2790
- try {
2791
- const parsed = JSON.parse(raw);
2792
- return parsed !== null && typeof parsed === 'object' ? parsed : null;
2793
- }
2794
- catch {
2795
- return null;
2796
- }
2797
- };
2798
- const jsonResult = analyzeJsonSchemas(jsonFiles, readJson, schemaConfig);
2799
- violations.push(...(jsonResult?.violations ?? []));
2800
- }
2801
- catch (e) {
2802
- // JSON schema validation is best-effort (non-fatal)
2803
- }
2804
- }
2951
+ const { knownTables, tableProvenances, dropProvenance, ddlColumns, tableColumns } = buildSchemaCatalog(allFacts);
2952
+ violations.push(...detectUnknownTables({
2953
+ knownTables,
2954
+ tableProvenances,
2955
+ dropProvenance,
2956
+ allFacts,
2957
+ schemaConfig,
2958
+ }));
2959
+ violations.push(...validateJsonSchemas({
2960
+ schemaConfig,
2961
+ allFacts,
2962
+ context,
2963
+ analyzeJsonSchemas,
2964
+ }));
2805
2965
  // ── 4. Build table catalog for metadata ───────────────────────────────
2806
2966
  const catalogEntries = [];
2807
2967
  for (const [table, sources] of tableProvenances) {
@@ -2810,7 +2970,7 @@ export function createSchemaReducer() {
2810
2970
  return {
2811
2971
  violations,
2812
2972
  facts: { tableCatalog: catalogEntries, ddlColumns: [...ddlColumns].sort(), tableColumns },
2813
- factsConsumed: [...perFile()].length,
2973
+ factsConsumed: [...iteratePerFileFacts(allFacts)].length,
2814
2974
  };
2815
2975
  },
2816
2976
  defaultConfig: {},