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