code-auditor-mcp 4.1.1 → 5.0.2

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 (1087) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +381 -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 +427 -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 +348 -0
  67. package/dist/analyzers/receiverRoot.d.ts.map +1 -0
  68. package/dist/analyzers/receiverRoot.js +1032 -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 +241 -0
  85. package/dist/analyzers/tsEcosystem.js.map +1 -0
  86. package/dist/analyzers/tsExpressionDescriptor.d.ts +156 -0
  87. package/dist/analyzers/tsExpressionDescriptor.d.ts.map +1 -0
  88. package/dist/analyzers/tsExpressionDescriptor.js +374 -0
  89. package/dist/analyzers/tsExpressionDescriptor.js.map +1 -0
  90. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +6 -0
  91. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  92. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +107 -44
  93. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  94. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  95. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +23 -35
  96. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  97. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +287 -49
  98. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  99. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +2130 -512
  100. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  101. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  102. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +2 -18
  103. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  104. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -33
  105. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  106. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +20 -143
  107. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  108. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +4 -3
  109. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  110. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +36 -13
  111. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  112. package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
  113. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js +6 -0
  114. package/dist/analyzers/universal/UniversalSecretsAnalyzer.js.map +1 -1
  115. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts +15 -0
  116. package/dist/analyzers/universal/UniversalSecurityAnalyzer.d.ts.map +1 -1
  117. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js +25 -0
  118. package/dist/analyzers/universal/UniversalSecurityAnalyzer.js.map +1 -1
  119. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +21 -32
  120. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  121. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +166 -196
  122. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  123. package/dist/analyzers/universal/functionConcerns.d.ts +3 -0
  124. package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
  125. package/dist/analyzers/universal/functionConcerns.js +4 -1
  126. package/dist/analyzers/universal/functionConcerns.js.map +1 -1
  127. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +310 -69
  128. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  129. package/dist/analyzers/universal/schema/codeAnalysis.js +709 -383
  130. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  131. package/dist/analyzers/universal/schema/config.d.ts +3 -14
  132. package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
  133. package/dist/analyzers/universal/schema/config.js +3 -16
  134. package/dist/analyzers/universal/schema/config.js.map +1 -1
  135. package/dist/analyzers/universal/schema/discovery.d.ts +22 -1
  136. package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
  137. package/dist/analyzers/universal/schema/discovery.js +36 -20
  138. package/dist/analyzers/universal/schema/discovery.js.map +1 -1
  139. package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -1
  140. package/dist/analyzers/universal/schema/jsonSchema.js +13 -3
  141. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  142. package/dist/analyzers/universal/schema/migrations.d.ts +187 -27
  143. package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -1
  144. package/dist/analyzers/universal/schema/migrations.js +443 -186
  145. package/dist/analyzers/universal/schema/migrations.js.map +1 -1
  146. package/dist/analyzers/universal/schema/types.d.ts +17 -3
  147. package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
  148. package/dist/analyzers/universal/schema/violations.js +1 -1
  149. package/dist/analyzers/universal/schema/violations.js.map +1 -1
  150. package/dist/analyzers/universal/styleScale.d.ts +52 -0
  151. package/dist/analyzers/universal/styleScale.d.ts.map +1 -0
  152. package/dist/analyzers/universal/styleScale.js +95 -0
  153. package/dist/analyzers/universal/styleScale.js.map +1 -0
  154. package/dist/applyDataDirEnv.js +1 -1
  155. package/dist/applyDataDirEnv.js.map +1 -1
  156. package/dist/auditRouter.d.ts +26 -30
  157. package/dist/auditRouter.d.ts.map +1 -1
  158. package/dist/auditRouter.js +373 -143
  159. package/dist/auditRouter.js.map +1 -1
  160. package/dist/auditRunner.d.ts +66 -1
  161. package/dist/auditRunner.d.ts.map +1 -1
  162. package/dist/auditRunner.js +1544 -1034
  163. package/dist/auditRunner.js.map +1 -1
  164. package/dist/baseline.d.ts +30 -3
  165. package/dist/baseline.d.ts.map +1 -1
  166. package/dist/baseline.js +70 -9
  167. package/dist/baseline.js.map +1 -1
  168. package/dist/cli.js +983 -821
  169. package/dist/cli.js.map +1 -1
  170. package/dist/codeIndex/analyzerConfig.d.ts +60 -0
  171. package/dist/codeIndex/analyzerConfig.d.ts.map +1 -0
  172. package/dist/codeIndex/analyzerConfig.js +98 -0
  173. package/dist/codeIndex/analyzerConfig.js.map +1 -0
  174. package/dist/codeIndex/auditResults.d.ts +39 -0
  175. package/dist/codeIndex/auditResults.d.ts.map +1 -0
  176. package/dist/codeIndex/auditResults.js +99 -0
  177. package/dist/codeIndex/auditResults.js.map +1 -0
  178. package/dist/codeIndex/codeMap.d.ts +59 -0
  179. package/dist/codeIndex/codeMap.d.ts.map +1 -0
  180. package/dist/codeIndex/codeMap.js +72 -0
  181. package/dist/codeIndex/codeMap.js.map +1 -0
  182. package/dist/codeIndex/conventions.d.ts +33 -0
  183. package/dist/codeIndex/conventions.d.ts.map +1 -0
  184. package/dist/codeIndex/conventions.js +84 -0
  185. package/dist/codeIndex/conventions.js.map +1 -0
  186. package/dist/codeIndex/coverage.d.ts +78 -0
  187. package/dist/codeIndex/coverage.d.ts.map +1 -0
  188. package/dist/codeIndex/coverage.js +164 -0
  189. package/dist/codeIndex/coverage.js.map +1 -0
  190. package/dist/codeIndex/dependencyGraph.d.ts +97 -0
  191. package/dist/codeIndex/dependencyGraph.d.ts.map +1 -0
  192. package/dist/codeIndex/dependencyGraph.js +241 -0
  193. package/dist/codeIndex/dependencyGraph.js.map +1 -0
  194. package/dist/codeIndex/functionIndex.d.ts +111 -0
  195. package/dist/codeIndex/functionIndex.d.ts.map +1 -0
  196. package/dist/codeIndex/functionIndex.js +306 -0
  197. package/dist/codeIndex/functionIndex.js.map +1 -0
  198. package/dist/codeIndex/inferredReceivers.d.ts +32 -0
  199. package/dist/codeIndex/inferredReceivers.d.ts.map +1 -0
  200. package/dist/codeIndex/inferredReceivers.js +41 -0
  201. package/dist/codeIndex/inferredReceivers.js.map +1 -0
  202. package/dist/codeIndex/meta.d.ts +30 -0
  203. package/dist/codeIndex/meta.d.ts.map +1 -0
  204. package/dist/codeIndex/meta.js +37 -0
  205. package/dist/codeIndex/meta.js.map +1 -0
  206. package/dist/codeIndex/migrations.d.ts +96 -0
  207. package/dist/codeIndex/migrations.d.ts.map +1 -0
  208. package/dist/codeIndex/migrations.js +1302 -0
  209. package/dist/codeIndex/migrations.js.map +1 -0
  210. package/dist/codeIndex/projectTasks.d.ts +54 -0
  211. package/dist/codeIndex/projectTasks.d.ts.map +1 -0
  212. package/dist/codeIndex/projectTasks.js +81 -0
  213. package/dist/codeIndex/projectTasks.js.map +1 -0
  214. package/dist/codeIndex/provenance.d.ts +47 -0
  215. package/dist/codeIndex/provenance.d.ts.map +1 -0
  216. package/dist/codeIndex/provenance.js +48 -0
  217. package/dist/codeIndex/provenance.js.map +1 -0
  218. package/dist/codeIndex/rawSql.d.ts +54 -0
  219. package/dist/codeIndex/rawSql.d.ts.map +1 -0
  220. package/dist/codeIndex/rawSql.js +71 -0
  221. package/dist/codeIndex/rawSql.js.map +1 -0
  222. package/dist/codeIndex/schema.d.ts +99 -0
  223. package/dist/codeIndex/schema.d.ts.map +1 -0
  224. package/dist/codeIndex/schema.js +174 -0
  225. package/dist/codeIndex/schema.js.map +1 -0
  226. package/dist/codeIndex/search.d.ts +40 -0
  227. package/dist/codeIndex/search.d.ts.map +1 -0
  228. package/dist/codeIndex/search.js +316 -0
  229. package/dist/codeIndex/search.js.map +1 -0
  230. package/dist/codeIndex/shared.d.ts +27 -0
  231. package/dist/codeIndex/shared.d.ts.map +1 -0
  232. package/dist/codeIndex/shared.js +46 -0
  233. package/dist/codeIndex/shared.js.map +1 -0
  234. package/dist/codeIndex/sqliteCollection.d.ts +81 -0
  235. package/dist/codeIndex/sqliteCollection.d.ts.map +1 -0
  236. package/dist/codeIndex/sqliteCollection.js +250 -0
  237. package/dist/codeIndex/sqliteCollection.js.map +1 -0
  238. package/dist/codeIndex/whitelist.d.ts +59 -0
  239. package/dist/codeIndex/whitelist.d.ts.map +1 -0
  240. package/dist/codeIndex/whitelist.js +188 -0
  241. package/dist/codeIndex/whitelist.js.map +1 -0
  242. package/dist/codeIndexDB.d.ts +132 -271
  243. package/dist/codeIndexDB.d.ts.map +1 -1
  244. package/dist/codeIndexDB.js +365 -2729
  245. package/dist/codeIndexDB.js.map +1 -1
  246. package/dist/codeIndexService.d.ts +87 -12
  247. package/dist/codeIndexService.d.ts.map +1 -1
  248. package/dist/codeIndexService.js +110 -34
  249. package/dist/codeIndexService.js.map +1 -1
  250. package/dist/componentScanner.d.ts +35 -0
  251. package/dist/componentScanner.d.ts.map +1 -1
  252. package/dist/componentScanner.js +114 -80
  253. package/dist/componentScanner.js.map +1 -1
  254. package/dist/config/configGeneratePath.d.ts +4 -0
  255. package/dist/config/configGeneratePath.d.ts.map +1 -1
  256. package/dist/config/configGeneratePath.js +4 -0
  257. package/dist/config/configGeneratePath.js.map +1 -1
  258. package/dist/config/configLoader.d.ts +15 -0
  259. package/dist/config/configLoader.d.ts.map +1 -1
  260. package/dist/config/configLoader.js +15 -16
  261. package/dist/config/configLoader.js.map +1 -1
  262. package/dist/config/defaults.d.ts +11 -7
  263. package/dist/config/defaults.d.ts.map +1 -1
  264. package/dist/config/defaults.js +16 -14
  265. package/dist/config/defaults.js.map +1 -1
  266. package/dist/config/effectiveConfig.d.ts +17 -2
  267. package/dist/config/effectiveConfig.d.ts.map +1 -1
  268. package/dist/config/effectiveConfig.js +32 -25
  269. package/dist/config/effectiveConfig.js.map +1 -1
  270. package/dist/config/lintConfigReader.d.ts +6 -0
  271. package/dist/config/lintConfigReader.d.ts.map +1 -1
  272. package/dist/config/lintConfigReader.js +6 -0
  273. package/dist/config/lintConfigReader.js.map +1 -1
  274. package/dist/config/staticObjectExtract.d.ts +4 -0
  275. package/dist/config/staticObjectExtract.d.ts.map +1 -1
  276. package/dist/config/staticObjectExtract.js +7 -3
  277. package/dist/config/staticObjectExtract.js.map +1 -1
  278. package/dist/config/thresholdRationales.d.ts +2 -0
  279. package/dist/config/thresholdRationales.d.ts.map +1 -1
  280. package/dist/config/thresholdRationales.js +5 -2
  281. package/dist/config/thresholdRationales.js.map +1 -1
  282. package/dist/constants.d.ts +1 -1
  283. package/dist/conventions/conventionMiner.d.ts +105 -2
  284. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  285. package/dist/conventions/conventionMiner.js +334 -259
  286. package/dist/conventions/conventionMiner.js.map +1 -1
  287. package/dist/coverage/coverageService.js +4 -4
  288. package/dist/coverage/coverageService.js.map +1 -1
  289. package/dist/daemon/args.d.ts +3 -0
  290. package/dist/daemon/args.d.ts.map +1 -1
  291. package/dist/daemon/args.js +3 -0
  292. package/dist/daemon/args.js.map +1 -1
  293. package/dist/daemon/core.d.ts +33 -27
  294. package/dist/daemon/core.d.ts.map +1 -1
  295. package/dist/daemon/core.js +198 -162
  296. package/dist/daemon/core.js.map +1 -1
  297. package/dist/daemon/lspServer.d.ts +8 -0
  298. package/dist/daemon/lspServer.d.ts.map +1 -1
  299. package/dist/daemon/lspServer.js +8 -0
  300. package/dist/daemon/lspServer.js.map +1 -1
  301. package/dist/daemon/main.d.ts +2 -0
  302. package/dist/daemon/main.d.ts.map +1 -1
  303. package/dist/daemon/main.js +22 -4
  304. package/dist/daemon/main.js.map +1 -1
  305. package/dist/daemon/resolve.d.ts +18 -3
  306. package/dist/daemon/resolve.d.ts.map +1 -1
  307. package/dist/daemon/resolve.js +25 -4
  308. package/dist/daemon/resolve.js.map +1 -1
  309. package/dist/daemon/socketClient.d.ts +10 -1
  310. package/dist/daemon/socketClient.d.ts.map +1 -1
  311. package/dist/daemon/socketClient.js +10 -1
  312. package/dist/daemon/socketClient.js.map +1 -1
  313. package/dist/daemon/socketServer.d.ts +13 -0
  314. package/dist/daemon/socketServer.d.ts.map +1 -1
  315. package/dist/daemon/socketServer.js +13 -0
  316. package/dist/daemon/socketServer.js.map +1 -1
  317. package/dist/dataPaths.d.ts +17 -8
  318. package/dist/dataPaths.d.ts.map +1 -1
  319. package/dist/dataPaths.js +16 -44
  320. package/dist/dataPaths.js.map +1 -1
  321. package/dist/dismissals.d.ts +22 -1
  322. package/dist/dismissals.d.ts.map +1 -1
  323. package/dist/dismissals.js +22 -1
  324. package/dist/dismissals.js.map +1 -1
  325. package/dist/enforcement/gate.d.ts +1 -0
  326. package/dist/enforcement/gate.d.ts.map +1 -1
  327. package/dist/enforcement/gate.js +1 -0
  328. package/dist/enforcement/gate.js.map +1 -1
  329. package/dist/fingerprint.d.ts +6 -0
  330. package/dist/fingerprint.d.ts.map +1 -1
  331. package/dist/fingerprint.js +46 -1
  332. package/dist/fingerprint.js.map +1 -1
  333. package/dist/functionScanner.d.ts +30 -1
  334. package/dist/functionScanner.d.ts.map +1 -1
  335. package/dist/functionScanner.js +258 -256
  336. package/dist/functionScanner.js.map +1 -1
  337. package/dist/generators/AWSQConfigGenerator.d.ts +23 -0
  338. package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
  339. package/dist/generators/AWSQConfigGenerator.js +23 -0
  340. package/dist/generators/AWSQConfigGenerator.js.map +1 -1
  341. package/dist/generators/BaseConfigGenerator.d.ts +7 -0
  342. package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
  343. package/dist/generators/BaseConfigGenerator.js +7 -0
  344. package/dist/generators/BaseConfigGenerator.js.map +1 -1
  345. package/dist/generators/ClaudeConfigGenerator.d.ts +23 -0
  346. package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
  347. package/dist/generators/ClaudeConfigGenerator.js +23 -0
  348. package/dist/generators/ClaudeConfigGenerator.js.map +1 -1
  349. package/dist/generators/ClineConfigGenerator.d.ts +23 -0
  350. package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
  351. package/dist/generators/ClineConfigGenerator.js +23 -0
  352. package/dist/generators/ClineConfigGenerator.js.map +1 -1
  353. package/dist/generators/CodeiumConfigGenerator.d.ts +23 -0
  354. package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
  355. package/dist/generators/CodeiumConfigGenerator.js +23 -0
  356. package/dist/generators/CodeiumConfigGenerator.js.map +1 -1
  357. package/dist/generators/CodexConfigGenerator.d.ts +23 -0
  358. package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
  359. package/dist/generators/CodexConfigGenerator.js +23 -0
  360. package/dist/generators/CodexConfigGenerator.js.map +1 -1
  361. package/dist/generators/ConfigGeneratorFactory.d.ts +9 -0
  362. package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
  363. package/dist/generators/ConfigGeneratorFactory.js +9 -0
  364. package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
  365. package/dist/generators/ContinueConfigGenerator.d.ts +23 -0
  366. package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
  367. package/dist/generators/ContinueConfigGenerator.js +23 -0
  368. package/dist/generators/ContinueConfigGenerator.js.map +1 -1
  369. package/dist/generators/CopilotConfigGenerator.d.ts +23 -0
  370. package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
  371. package/dist/generators/CopilotConfigGenerator.js +23 -0
  372. package/dist/generators/CopilotConfigGenerator.js.map +1 -1
  373. package/dist/generators/CursorConfigGenerator.d.ts +23 -0
  374. package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
  375. package/dist/generators/CursorConfigGenerator.js +23 -0
  376. package/dist/generators/CursorConfigGenerator.js.map +1 -1
  377. package/dist/generators/GeminiConfigGenerator.d.ts +23 -0
  378. package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
  379. package/dist/generators/GeminiConfigGenerator.js +23 -0
  380. package/dist/generators/GeminiConfigGenerator.js.map +1 -1
  381. package/dist/generators/VSCodeConfigGenerator.d.ts +23 -0
  382. package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
  383. package/dist/generators/VSCodeConfigGenerator.js +23 -0
  384. package/dist/generators/VSCodeConfigGenerator.js.map +1 -1
  385. package/dist/generators/ZCodeConfigGenerator.d.ts +23 -0
  386. package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
  387. package/dist/generators/ZCodeConfigGenerator.js +23 -0
  388. package/dist/generators/ZCodeConfigGenerator.js.map +1 -1
  389. package/dist/graph/blastRadius.d.ts.map +1 -1
  390. package/dist/graph/blastRadius.js +12 -7
  391. package/dist/graph/blastRadius.js.map +1 -1
  392. package/dist/graph/callGraph.d.ts +31 -0
  393. package/dist/graph/callGraph.d.ts.map +1 -1
  394. package/dist/graph/callGraph.js +62 -32
  395. package/dist/graph/callGraph.js.map +1 -1
  396. package/dist/graph/importClassification.d.ts +28 -0
  397. package/dist/graph/importClassification.d.ts.map +1 -1
  398. package/dist/graph/importClassification.js +57 -0
  399. package/dist/graph/importClassification.js.map +1 -1
  400. package/dist/graph/importGraph.d.ts +38 -0
  401. package/dist/graph/importGraph.d.ts.map +1 -1
  402. package/dist/graph/importGraph.js +38 -0
  403. package/dist/graph/importGraph.js.map +1 -1
  404. package/dist/graph/outputFormatter.d.ts +12 -4
  405. package/dist/graph/outputFormatter.d.ts.map +1 -1
  406. package/dist/graph/outputFormatter.js +12 -62
  407. package/dist/graph/outputFormatter.js.map +1 -1
  408. package/dist/graph/resolutionEnvironment.d.ts +61 -0
  409. package/dist/graph/resolutionEnvironment.d.ts.map +1 -0
  410. package/dist/graph/resolutionEnvironment.js +57 -0
  411. package/dist/graph/resolutionEnvironment.js.map +1 -0
  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 +827 -850
  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 +491 -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 +1030 -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 +207 -0
  739. package/dist/phase/receiverConsumers.d.ts.map +1 -0
  740. package/dist/phase/receiverConsumers.js +1063 -0
  741. package/dist/phase/receiverConsumers.js.map +1 -0
  742. package/dist/phase/receiverProvenance.d.ts +120 -0
  743. package/dist/phase/receiverProvenance.d.ts.map +1 -0
  744. package/dist/phase/receiverProvenance.js +377 -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 +2199 -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 +1144 -984
  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 +114 -71
  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 +18 -9
  1078. package/plugin/.claude-plugin/manifest.json +16 -0
  1079. package/plugin/.claude-plugin/plugin.json +1 -1
  1080. package/plugin/hooks/hooks.json +7 -3
  1081. package/plugin/scripts/hook-audit.sh +46 -16
  1082. package/plugin/scripts/hook-common.sh +81 -12
  1083. package/plugin/scripts/hook-drift-check.sh +76 -0
  1084. package/plugin/scripts/hook-self-audit.sh +22 -14
  1085. package/plugin/scripts/hook-warm.sh +5 -6
  1086. package/plugin/skills/code-auditor/SKILL.md +1 -1
  1087. package/server.json +10 -4
@@ -6,15 +6,16 @@ import { promises as fs } from 'fs';
6
6
  import path from 'path';
7
7
  import { execFileSync } from 'child_process';
8
8
  import { randomUUID } from 'node:crypto';
9
- import { AuditAbortedError, AuditHandoffError, } from './types.js';
10
- import { discoverFiles, discoverFilesDetailed } from './utils/fileDiscovery.js';
9
+ import os from 'node:os';
10
+ import { AuditAbortedError, } from './types.js';
11
+ import { discoverFiles, discoverFilesDetailed, getSourceExtensions } from './utils/fileDiscovery.js';
11
12
  import { FileAccounting } from './services/fileAccounting.js';
12
13
  import { loadConfig, findConfigFileUp } from './config/configLoader.js';
13
14
  import { mergePathProfiles } from './config/defaults.js';
15
+ import { resolvePathProfile } from './config/pathProfiles.js';
14
16
  import { checkThresholdRationales } from './config/thresholdRationales.js';
15
17
  import { readProjectLintThresholds, thresholdsToAnalyzerConfig } from './config/lintConfigReader.js';
16
18
  import { computeThresholdSources } from './config/effectiveConfig.js';
17
- import { ALL_ANALYZERS } from './analyzers/ruleRegistry.js';
18
19
  import { applyPresets, getPreset } from './presets/presets.js';
19
20
  import { generateReport } from './reporting/reportGenerator.js';
20
21
  import { extractFunctionsFromFile } from './functionScanner.js';
@@ -22,17 +23,27 @@ import { isMcpDebugEnabled, logMcpDebug, logMcpInfo } from './mcpDiagnostics.js'
22
23
  import { loadBaseline, matchFindings, hashBaseline } from './baseline.js';
23
24
  import { applyDismissals } from './dismissals.js';
24
25
  import { computeImpact, LATENCY_BUDGET_MS } from './graph/blastRadius.js';
25
- import { readTsconfigAliases, readPackageEntryPoints, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
26
+ import { buildResolutionEnvironment } from './graph/resolutionEnvironment.js';
26
27
  // Import universal analyzers
27
28
  import { initializeLanguages } from './languages/index.js';
28
29
  import { initializeOrmAdapters } from './analyzers/orm/index.js';
29
30
  import { syncStyleIndex } from './styles/styleIndexer.js';
30
31
  import { CodeIndexDB } from './codeIndexDB.js';
32
+ import { notifyLegacyIndexLocation } from './legacyIndexNotice.js';
31
33
  import { writeAuditToLedger, detectRunInput } from './ledger.js';
32
34
  // Pipeline imports (Spec 25 — pipeline replaces hand-rolled analyzer loop)
33
- import { runPipeline, writeIndexFactsToDb, makeVisitorStatus, getFilesProcessed, isVisitorStatus, buildCoverageReport } from './pipeline.js';
34
- import { createSolidVisitor, createDryVisitor, createDataAccessVisitor, createOrgFilterReducer, createDocumentationVisitor, createSecretsVisitor, createSecurityVisitor, createFunctionIndexVisitor, createStylesCssVisitor, createStylesSourceVisitor, createReactVisitor, createStylesReducer, createConventionsReducer, createCrossDomainReducer, createInvariantsReducer, createSchemaSqlVisitor, createSchemaCodeVisitor, createSchemaPrismaVisitor, createSchemaJsonVisitor, createSchemaReducer, createCrossLanguageEntityVisitor, createSchemaValidatorReducer, createAPIContractReducer, createDependencyGraphReducer, } from './pipelineAdapters.js';
35
+ import { runPipeline, makeVisitorStatus, getFilesProcessed, isVisitorStatus } from './pipeline.js';
36
+ import { getRuleTimingSortedDesc } from './analyzers/ruleTiming.js';
37
+ import { createFunctionIndexVisitor, createStylesCssVisitor, createStylesSourceVisitor, createStylesReducer, createInvariantsReducer, createSchemaCodeVisitor, } from './pipelineAdapters.js';
35
38
  import { computeSizeDistributions } from './reporting/sizeDistribution.js';
39
+ import { splitRoutes, attributeRoutes, enabledMigratedRules } from './phase/routing.js';
40
+ import { runPhaseModel } from './phase/phaseModel.js';
41
+ import { resolvePhaseThresholds } from './phase/config.js';
42
+ import { deriveCoverage, presentFormatsOf } from './phase/coverage.js';
43
+ import { MIGRATED_RULES, RULE_ANALYZER } from './phase/rules/registry.js';
44
+ import { seedDryPairs, resolveBlockConfig } from './phase/rules/dry.js';
45
+ import { checkUnresolvedReceiverImports, checkUnprovenQueryReceivers, checkUnresolvedQueries, checkUnparseableSql, dedupeCannotFireByReceiver } from './analyzers/universal/schema/codeAnalysis.js';
46
+ import { readProjectManifest, computeManifestStaleness } from './analyzers/manifestStaleness.js';
36
47
  // Package version — stamped into the build (see constants.ts), not read from
37
48
  // package.json at runtime, so a stale binary reports the version it was built as.
38
49
  import { PACKAGE_VERSION } from './constants.js';
@@ -44,1076 +55,1538 @@ initializeOrmAdapters();
44
55
  /** Schema sub-visitor names — shown as separate rows in the CLI table. */
45
56
  const SCHEMA_SUB_VISITORS = ['schema-sql', 'schema-code', 'schema-prisma', 'schema-json'];
46
57
  /**
47
- * Create an audit runner with the given options
58
+ * CPU time consumed since `start` (a prior `process.cpuUsage()` snapshot), in
59
+ * milliseconds. Load-independent: wall clock measures how long the run took on
60
+ * the wall (inflated by co-tenant load), CPU time measures how much processor
61
+ * the run's rules actually burned. The gate-budget check asserts on this.
48
62
  */
49
- export function createAuditRunner(options = {}) {
50
- const analyzerRegistry = Object.fromEntries(ALL_ANALYZERS.map((name) => [name, { name }]));
51
- /**
52
- * Load configuration from file
53
- */
54
- async function loadConfiguration(configPath) {
55
- const { config } = await loadConfig({ configPath, projectRoot: path.dirname(configPath) });
56
- return { ...options, ...config };
63
+ function cpuDurationMs(start) {
64
+ const cpu = process.cpuUsage(start);
65
+ return (cpu.user + cpu.system) / 1000;
66
+ }
67
+ /** An empty visitor result for an analyzer that emitted nothing this run. The
68
+ * call sites below shared the same four-field object literal (violations,
69
+ * status, executionTime, analyzerName); this factory keeps the shape in one
70
+ * place so a new field can't drift between them. */
71
+ function makeEmptyAnalyzerResult(analyzerName, filesProcessed) {
72
+ return {
73
+ violations: [],
74
+ status: makeVisitorStatus(filesProcessed),
75
+ executionTime: 0,
76
+ analyzerName,
77
+ };
78
+ }
79
+ /**
80
+ * Spec 68 §15 — the full pipeline stage set, always run. Derived from the
81
+ * migrated rules' own `analyzer` labels (each `RuleDefinition` carries the
82
+ * namespace it re-emits into) plus the pipeline-only `invariants` reducer
83
+ * (user-defined rules from `.codeauditor.json`; it auto-disables when none are
84
+ * configured). No selection remains: `indexOnly` skips analysis wholesale, but
85
+ * config cannot pick a subset of analyzers.
86
+ */
87
+ const RUN_ANALYZERS = [
88
+ ...new Set(MIGRATED_RULES.map((r) => r.analyzer)),
89
+ 'invariants',
90
+ ].sort();
91
+ /**
92
+ * §6.6 — the phase-model pool size. Defaults to `max(1, cpus - 1)` and is a
93
+ * config threshold (env `CODE_AUDITOR_WORKERS`), never a selection gate: the
94
+ * fact merge is file-sorted, so the value never reorders findings (§6.4).
95
+ * `CODE_AUDITOR_WORKERS=1` is the determinism-test escape hatch that forces the
96
+ * serial path for byte-identical-report comparisons.
97
+ */
98
+ function resolveWorkerCount() {
99
+ const env = Number(process.env.CODE_AUDITOR_WORKERS);
100
+ if (Number.isInteger(env) && env > 0)
101
+ return env;
102
+ return Math.max(1, os.cpus().length - 1);
103
+ }
104
+ /**
105
+ * Resolve the effective run configuration: load `.codeauditor.json` (walking up
106
+ * from the audit path so a scoped `--path src` still finds project-root config),
107
+ * enforce the Spec 36 R5 threshold-rationale guard, merge the project's own lint
108
+ * thresholds as a base layer, apply shareable presets, and merge built-in path
109
+ * profiles. Returns everything `run` needs to proceed plus the diagnostics that
110
+ * surface in result metadata (`rejected`, `lintConfigDiagnostics`).
111
+ */
112
+ async function resolveRunConfig(baseOptions, runOptions) {
113
+ const rootForConfig = runOptions?.projectRoot || baseOptions.projectRoot || process.cwd();
114
+ const configPath = await findConfigFileUp(rootForConfig);
115
+ let fileConfig = {};
116
+ let rejected = [];
117
+ if (configPath) {
118
+ const loaded = await loadConfig({ configPath, projectRoot: rootForConfig });
119
+ fileConfig = loaded.config;
120
+ rejected = loaded.rejected;
57
121
  }
58
- /**
59
- * Run the audit
60
- */
61
- async function run(runOptions) {
62
- // Auto-load .codeauditor.json so the programmatic API respects the same
63
- // config the CLI surface reads. Walk UP from the audit path so a scoped
64
- // audit (`--path src`) still finds the project-root config rather than
65
- // silently using defaults. RunOptions (caller) override file config, and
66
- // createAuditRunner options override both.
67
- const rootForConfig = runOptions?.projectRoot || options.projectRoot || process.cwd();
68
- const configPath = await findConfigFileUp(rootForConfig);
69
- let fileConfig = {};
70
- let rejected = [];
71
- if (configPath) {
72
- const loaded = await loadConfig({ configPath, projectRoot: rootForConfig });
73
- fileConfig = loaded.config;
74
- rejected = loaded.rejected;
75
- }
76
- const mergedOptions = { ...fileConfig, ...options, ...runOptions };
77
- // Spec 36 R5 — a threshold change needs a written rationale. Check the
78
- // user-facing analyzerConfigs layer (project config + inline options)
79
- // BEFORE presets merge in, so curated presets never trip the guard. Any
80
- // changed threshold without a rationale is a config error, not a warning.
81
- const thresholdCheck = checkThresholdRationales(mergedOptions.analyzerConfigs, mergedOptions.rationales);
82
- if (thresholdCheck.errors.length > 0) {
83
- const message = [
84
- 'Configuration error — threshold changes require a rationale (Spec 36 R5):',
85
- ...thresholdCheck.errors.map((e) => ` - ${e}`),
86
- ].join('\n');
87
- throw new Error(message);
88
- }
89
- const thresholdChanges = thresholdCheck.changes.map((c) => ({
90
- key: c.key,
91
- defaultValue: c.defaultValue,
92
- effectiveValue: c.effectiveValue,
93
- }));
94
- // ── Spec 50 R2 — project lint config as threshold authority ──────────
95
- // Read the project's own ESLint config (flat or legacy) and merge its size
96
- // thresholds (max-lines-per-function, max-params, complexity) as a base layer
97
- // UNDER the user's analyzerConfigs. Because we only fill keys the project did
98
- // not set, `.codeauditor.json` always wins. Runs AFTER checkThresholdRationales
99
- // so lint-sourced values (the project's own declared lint rules, not a
100
- // code-auditor threshold decision) never trip the Spec 36 R5 guard. Fail-open:
101
- // no config, or an unloadable config, is absent — defaults, not an error.
102
- const projectAnalyzerConfigs = (mergedOptions.analyzerConfigs ?? {});
103
- const lintRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
104
- const lintResult = await readProjectLintThresholds(lintRoot);
105
- const lintThresholds = lintResult?.thresholds ?? {};
106
- // Spec 61 R3.2 — an ESLint config that exists but could not be read
107
- // statically is a `cannot-fire` coverage diagnostic, surfaced (never
108
- // silently treated as "no config").
109
- const lintConfigDiagnostics = lintResult?.diagnostic ? [lintResult.diagnostic] : [];
110
- if (Object.keys(lintThresholds).length > 0) {
111
- mergedOptions.analyzerConfigs = mergeLintUnderProject(thresholdsToAnalyzerConfig(lintThresholds), projectAnalyzerConfigs);
122
+ const mergedOptions = { ...fileConfig, ...baseOptions, ...runOptions };
123
+ // Spec 36 R5 — a threshold change needs a written rationale. Check the
124
+ // user-facing analyzerConfigs layer (project config + inline options)
125
+ // BEFORE presets merge in, so curated presets never trip the guard. Any
126
+ // changed threshold without a rationale is a config error, not a warning.
127
+ const thresholdCheck = checkThresholdRationales(mergedOptions.analyzerConfigs, mergedOptions.rationales);
128
+ if (thresholdCheck.errors.length > 0) {
129
+ const message = [
130
+ 'Configuration error — threshold changes require a rationale (Spec 36 R5):',
131
+ ...thresholdCheck.errors.map((e) => ` - ${e}`),
132
+ ].join('\n');
133
+ throw new Error(message);
134
+ }
135
+ const thresholdChanges = thresholdCheck.changes.map((c) => ({
136
+ key: c.key,
137
+ defaultValue: c.defaultValue,
138
+ effectiveValue: c.effectiveValue,
139
+ }));
140
+ // ── Spec 50 R2 — project lint config as threshold authority ──────────
141
+ // Read the project's own ESLint config (flat or legacy) and merge its size
142
+ // thresholds (max-lines-per-function, max-params, complexity) as a base layer
143
+ // UNDER the user's analyzerConfigs. Because we only fill keys the project did
144
+ // not set, `.codeauditor.json` always wins. Runs AFTER checkThresholdRationales
145
+ // so lint-sourced values never trip the Spec 36 R5 guard. Fail-open: no config,
146
+ // or an unloadable config, is absent — defaults, not an error.
147
+ const projectAnalyzerConfigs = (mergedOptions.analyzerConfigs ?? {});
148
+ const lintRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
149
+ const lintResult = await readProjectLintThresholds(lintRoot);
150
+ const lintThresholds = lintResult?.thresholds ?? {};
151
+ // Spec 61 R3.2 — an ESLint config that exists but could not be read
152
+ // statically is a `cannot-fire` coverage diagnostic, surfaced (never
153
+ // silently treated as "no config").
154
+ const lintConfigDiagnostics = lintResult?.diagnostic ? [lintResult.diagnostic] : [];
155
+ if (Object.keys(lintThresholds).length > 0) {
156
+ mergedOptions.analyzerConfigs = mergeLintUnderProject(thresholdsToAnalyzerConfig(lintThresholds), projectAnalyzerConfigs);
157
+ }
158
+ // Resolve shareable presets (Spec 38 R4). Unknown ids are dropped (matching
159
+ // mergePresets semantics); the merged preset layer becomes the base under
160
+ // the project config / run options already present in mergedOptions.
161
+ const presetIds = Array.isArray(mergedOptions.presets) ? mergedOptions.presets : [];
162
+ if (presetIds.length > 0) {
163
+ mergedOptions.analyzerConfigs = applyPresets(presetIds, mergedOptions.analyzerConfigs);
164
+ }
165
+ // Spec 50 R2 — which config layer supplied each size threshold (coverage output).
166
+ const resolvedPresets = presetIds
167
+ .map((id) => getPreset(id))
168
+ .filter((p) => p !== undefined);
169
+ const thresholdSources = computeThresholdSources({
170
+ projectConfig: projectAnalyzerConfigs,
171
+ lintThresholds,
172
+ presets: resolvedPresets,
173
+ });
174
+ // Always merge built-in path profiles — corpus audits and projects without
175
+ // .codeauditor.json must still get the built-in scripts-and-tests profile.
176
+ mergedOptions.pathProfiles = mergePathProfiles(mergedOptions.pathProfiles, mergedOptions.builtin);
177
+ return {
178
+ mergedOptions,
179
+ configPath,
180
+ rejected,
181
+ lintConfigDiagnostics,
182
+ thresholdChanges,
183
+ thresholdSources,
184
+ };
185
+ }
186
+ /**
187
+ * Resolve the audit's file set from the configured scope (`all`, `git:<ref>`,
188
+ * `changed`, or an explicit files array), detect changed functions for scoped
189
+ * runs, compute blast-radius impact for those functions, and re-walk discovery
190
+ * for the unfiltered corpus file list (the import classifier's input — see the
191
+ * Spec 60.1 Correction 1 comment below). Returns everything `run` needs to
192
+ * build the pipeline, plus `scope`/`isScoped`/`scopeResultType` for downstream
193
+ * metadata and the ledger.
194
+ */
195
+ async function discoverAuditFiles(mergedOptions, root, fileAccounting) {
196
+ const scope = mergedOptions.scope ?? 'all';
197
+ const isScoped = scope !== 'all';
198
+ const scopeResultType = isScoped ? 'scoped' : 'full';
199
+ reportProgress(mergedOptions, {
200
+ phase: 'discovery',
201
+ message: `Discovering files... (scope: ${scopeResultType})`
202
+ });
203
+ const { files, skippedExtensions } = await resolveDiscoveryScope(mergedOptions, scope, fileAccounting);
204
+ throwIfAborted(mergedOptions.abortSignal);
205
+ // Detect changed functions for non-all scopes
206
+ let changedFunctions;
207
+ if (isScoped && files.length > 0) {
208
+ changedFunctions = await detectChangedFunctionsForScope(mergedOptions, files);
209
+ }
210
+ let blastRadius;
211
+ if (isScoped && changedFunctions && changedFunctions.length > 0) {
212
+ blastRadius = await computeBlastRadiusForScope(mergedOptions, changedFunctions);
213
+ }
214
+ const corpusFiles = await buildImportClassifierCorpus(mergedOptions, root, files, isScoped);
215
+ logMcpInfo('discovery', 'file discovery finished', {
216
+ projectRoot: path.resolve(root),
217
+ totalFiles: files.length,
218
+ corpusFiles: corpusFiles.length,
219
+ scope: scopeResultType,
220
+ indexFunctions: !!mergedOptions.indexFunctions
221
+ });
222
+ return {
223
+ files,
224
+ changedFunctions,
225
+ skippedExtensions,
226
+ blastRadius,
227
+ corpusFiles,
228
+ scope,
229
+ isScoped,
230
+ scopeResultType,
231
+ };
232
+ }
233
+ /**
234
+ * Resolve the audit scope into a concrete file list. This is the dispatch the
235
+ * orchestrator used to hold inline: `git:<ref>` diffs the worktree, `changed`
236
+ * reads the index's modified-file set, an array of paths/globs is an explicit
237
+ * files scope, and anything else is the full discovery walk.
238
+ */
239
+ async function resolveDiscoveryScope(mergedOptions, scope, fileAccounting) {
240
+ let files;
241
+ // Extensions present on disk that discovery skipped (Spec 43 R5 follow-up).
242
+ // Populated only for the `all` scope — scoped runs are explicitly scoped by
243
+ // the user, so "what wasn't analyzed" there is the scope itself, not the
244
+ // extension filter.
245
+ let skippedExtensions;
246
+ if (typeof scope === 'string' && scope.startsWith('git:')) {
247
+ // git:<ref> scope
248
+ const gitRef = scope.slice(4);
249
+ files = resolveGitScopeFiles(mergedOptions, gitRef);
250
+ logMcpInfo('discovery', 'git scope resolved', {
251
+ ref: gitRef,
252
+ fileCount: files.length
253
+ });
254
+ }
255
+ else if (scope === 'changed') {
256
+ // Changed scope: detect modified files. Fail open on an unavailable index
257
+ // (a future schema version is the sharpest case): when the caller pinned
258
+ // explicit files we audit those without touching the index; otherwise there
259
+ // is nothing to diff against, so the run degrades to zero files and the
260
+ // analyze phase reports the index skip as a coverage diagnostic (never a
261
+ // fatal — the hook must complete with the exit code it would otherwise have).
262
+ let db;
263
+ try {
264
+ db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
265
+ await db.initialize();
112
266
  }
113
- // Resolve shareable presets (Spec 38 R4). Unknown ids are dropped (matching
114
- // mergePresets semantics); the merged preset layer becomes the base under
115
- // the project config / run options already present in mergedOptions.
116
- const presetIds = Array.isArray(mergedOptions.presets) ? mergedOptions.presets : [];
117
- if (presetIds.length > 0) {
118
- mergedOptions.analyzerConfigs = applyPresets(presetIds, mergedOptions.analyzerConfigs);
267
+ catch (err) {
268
+ logMcpInfo('discovery', 'changed scope index unavailable (continuing)', {
269
+ error: err instanceof Error ? err.message : String(err)
270
+ });
119
271
  }
120
- // Spec 50 R2 — which config layer supplied each size threshold (coverage output).
121
- const resolvedPresets = presetIds
122
- .map((id) => getPreset(id))
123
- .filter((p) => p !== undefined);
124
- const thresholdSources = computeThresholdSources({
125
- projectConfig: projectAnalyzerConfigs,
126
- lintThresholds,
127
- presets: resolvedPresets,
272
+ const modifiedFiles = mergedOptions.explicitFiles !== undefined
273
+ ? mergedOptions.explicitFiles
274
+ : (db ? await db.detectModifiedFiles(path.resolve(mergedOptions.projectRoot || process.cwd())) : []);
275
+ files = [...new Set(modifiedFiles.map((f) => path.resolve(f)))].sort();
276
+ logMcpInfo('discovery', 'changed scope resolved', {
277
+ fileCount: files.length
128
278
  });
129
- // Always merge built-in path profiles — corpus audits and projects without
130
- // .codeauditor.json must still get the built-in scripts-and-tests profile.
131
- mergedOptions.pathProfiles = mergePathProfiles(mergedOptions.pathProfiles, mergedOptions.builtin);
132
- const startTime = Date.now();
133
- // ── Scope resolution ─────────────────────────────────────────────
134
- const scope = mergedOptions.scope ?? 'all';
135
- const isScoped = scope !== 'all';
136
- const scopeResultType = isScoped ? 'scoped' : 'full';
137
- reportProgress(mergedOptions, {
138
- phase: 'discovery',
139
- message: `Discovering files... (scope: ${scopeResultType})`
279
+ }
280
+ else if (Array.isArray(scope)) {
281
+ // files scope: explicit file paths/globs
282
+ files = await resolveFilesScope(mergedOptions, scope);
283
+ logMcpInfo('discovery', 'files scope resolved', {
284
+ fileCount: files.length
140
285
  });
141
- // Discover files based on scope
142
- let files;
143
- let changedFunctions;
144
- // Extensions present on disk that discovery skipped (Spec 43 R5 follow-up).
145
- // Populated only for the `all` scope — scoped runs are explicitly scoped by
146
- // the user, so "what wasn't analyzed" there is the scope itself, not the
147
- // extension filter.
148
- let skippedExtensions;
149
- // Spec 44 — file accounting accumulator, owned by the run. Threaded through
150
- // discovery (full `all` scope) and the pipeline (stage 1/2) so every touched
151
- // file lands in exactly one terminal state and the balance can be asserted.
152
- const fileAccounting = new FileAccounting();
153
- if (typeof scope === 'string' && scope.startsWith('git:')) {
154
- // git:<ref> scope
155
- const gitRef = scope.slice(4);
156
- files = resolveGitScopeFiles(mergedOptions, gitRef);
157
- logMcpInfo('discovery', 'git scope resolved', {
158
- ref: gitRef,
159
- fileCount: files.length
160
- });
161
- }
162
- else if (scope === 'changed') {
163
- // Changed scope: detect modified files
164
- const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
165
- await db.initialize();
166
- const modifiedFiles = mergedOptions.explicitFiles !== undefined
167
- ? mergedOptions.explicitFiles
168
- : await db.detectModifiedFiles(path.resolve(mergedOptions.projectRoot || process.cwd()));
169
- files = [...new Set(modifiedFiles.map((f) => path.resolve(f)))].sort();
170
- logMcpInfo('discovery', 'changed scope resolved', {
171
- fileCount: files.length
172
- });
286
+ }
287
+ else {
288
+ // all scope: current behavior
289
+ const discovered = await discoverProjectFiles(mergedOptions, fileAccounting);
290
+ files = discovered.files;
291
+ skippedExtensions = discovered.skippedExtensions;
292
+ }
293
+ return { files, skippedExtensions };
294
+ }
295
+ /**
296
+ * Detect changed functions for a scoped run via the code index (Spec 60.1).
297
+ * Returns `undefined` when the index is unavailable — the caller treats that as
298
+ * "no diff to report", never a fatal.
299
+ */
300
+ async function detectChangedFunctionsForScope(mergedOptions, files) {
301
+ try {
302
+ const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
303
+ await db.initialize();
304
+ const detection = await db.detectChangedFunctions(files);
305
+ logMcpInfo('discovery', 'changed function detection', {
306
+ changedFunctionCount: detection.changedFunctions.length,
307
+ deletedCount: detection.deletedFunctions.length,
308
+ errors: detection.errors.length
309
+ });
310
+ return detection.changedFunctions;
311
+ }
312
+ catch (err) {
313
+ logMcpInfo('discovery', 'changed function detection failed (continuing)', {
314
+ error: err instanceof Error ? err.message : String(err)
315
+ });
316
+ return undefined;
317
+ }
318
+ }
319
+ /**
320
+ * Compute transitive-caller impact for changed functions (Spec 14 R6). Advisory:
321
+ * on error, or when the 100ms budget is exceeded, this returns `undefined` and
322
+ * the feature is disabled for the run — never a fatal.
323
+ */
324
+ async function computeBlastRadiusForScope(mergedOptions, changedFunctions) {
325
+ try {
326
+ const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
327
+ const rawDb = db.rawDb;
328
+ const functionIds = [];
329
+ // One batched query per chunk of changed functions, not one query per
330
+ // function (loop-query). `changedFunctions` can be large, so chunk to the
331
+ // SQLite bind-parameter cap; `ORDER BY id` + first-wins by (name, file_path)
332
+ // reproduces the old `.get` (first matching row) semantics exactly.
333
+ const idByKey = new Map();
334
+ const BATCH = 900;
335
+ for (let i = 0; i < changedFunctions.length; i += BATCH) {
336
+ const chunk = changedFunctions.slice(i, i + BATCH);
337
+ const orClause = chunk.map(() => '(name = ? AND file_path = ?)').join(' OR ');
338
+ const params = chunk.flatMap((fn) => [fn.name, fn.filePath]);
339
+ const rows = rawDb.prepare(`SELECT id, name, file_path FROM functions WHERE ${orClause} ORDER BY id`).all(...params);
340
+ for (const r of rows) {
341
+ const key = `${r.name}\0${r.file_path}`;
342
+ if (!idByKey.has(key))
343
+ idByKey.set(key, r.id);
344
+ }
173
345
  }
174
- else if (Array.isArray(scope)) {
175
- // files scope: explicit file paths/globs
176
- files = await resolveFilesScope(mergedOptions, scope);
177
- logMcpInfo('discovery', 'files scope resolved', {
178
- fileCount: files.length
179
- });
346
+ for (const fn of changedFunctions) {
347
+ const id = idByKey.get(`${fn.name}\0${fn.filePath}`);
348
+ if (id != null)
349
+ functionIds.push(id);
180
350
  }
181
- else {
182
- // all scope: current behavior
183
- const discovered = await discoverProjectFiles(mergedOptions, fileAccounting);
184
- files = discovered.files;
185
- skippedExtensions = discovered.skippedExtensions;
351
+ if (functionIds.length === 0)
352
+ return undefined;
353
+ const impact = computeImpact(rawDb, functionIds);
354
+ if (impact.latencyMs > LATENCY_BUDGET_MS) {
355
+ logMcpInfo('blast-radius', `latency ${impact.latencyMs}ms exceeds ${LATENCY_BUDGET_MS}ms budget — blast radius disabled for this run`, {});
356
+ return undefined;
186
357
  }
187
- throwIfAborted(mergedOptions.abortSignal);
188
- // Detect changed functions for non-all scopes
189
- if (isScoped && files.length > 0) {
358
+ logMcpInfo('blast-radius', 'computed', {
359
+ editedFunctionCount: impact.editedFunctionCount,
360
+ transitiveCallers: impact.transitiveCallers,
361
+ reachableExports: impact.reachableExports,
362
+ latencyMs: impact.latencyMs,
363
+ });
364
+ return impact;
365
+ }
366
+ catch (err) {
367
+ // Blast radius is advisory — non-fatal
368
+ logMcpInfo('blast-radius', 'computation failed (continuing)', {
369
+ error: err instanceof Error ? err.message : String(err)
370
+ });
371
+ return undefined;
372
+ }
373
+ }
374
+ /**
375
+ * Build the import classifier's corpus file set (Spec 60.1 Correction 1). The
376
+ * classifier's corpus must be the UNFILTERED stage-1 discovery list, not the
377
+ * audit's `files` list, which is narrowed at two points before `_infra` is
378
+ * built:
379
+ * 1. The polyglot dispatch narrows `files` to the per-language list it
380
+ * actually hands to the TypeScript analyzer — files with no detectable
381
+ * language (`.json`, `.sql`) drop out here.
382
+ * 2. `filterFiles` (fileDiscovery.ts, called from `findFiles`) applies
383
+ * `includePaths` as a positive-selection glob filter — a `.json` file that
384
+ * survives step 1 is dropped here (or by the default `includePaths` from
385
+ * config/defaults.ts, which omits `.json`).
386
+ * Either way `_infra.files` no longer contains `.json`, so a real
387
+ * `./invariant-rules.schema.json` import classifies `internal-broken`.
388
+ * Classification answers "does this import resolve to a real file", independent
389
+ * of what the audit chooses to analyze.
390
+ *
391
+ * For a full (`all`) audit the corpus is the unfiltered discovery walk. For a
392
+ * scoped run that walk is the defect Spec 68 §hook-contract names: a
393
+ * single-file scope must not enumerate the whole project. A scoped run's corpus
394
+ * is instead "the files in scope + whatever the index already holds about the
395
+ * rest" — the indexed source files (`functions.file_path`) plus every file the
396
+ * index has ever resolved an import to (`import_specifiers.resolved_path`, which
397
+ * is how non-function import targets like `.json`/`.css`/`.sql` stay classifiable
398
+ * without a walk). If the index is unavailable the corpus degrades to the
399
+ * in-scope files alone — out-of-scope imports then read `internal-broken`, never
400
+ * a fatal.
401
+ */
402
+ async function buildImportClassifierCorpus(mergedOptions, root, files, isScoped) {
403
+ if (!isScoped) {
404
+ return discoverFiles(root);
405
+ }
406
+ const corpus = new Set(files);
407
+ try {
408
+ const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
409
+ await db.initialize();
410
+ const indexed = db.rawDb
411
+ .prepare('SELECT DISTINCT file_path AS p FROM functions')
412
+ .all();
413
+ for (const { p } of indexed)
414
+ corpus.add(p);
415
+ const resolved = db.rawDb
416
+ .prepare('SELECT DISTINCT resolved_path AS p FROM import_specifiers WHERE resolved_path IS NOT NULL')
417
+ .all();
418
+ for (const { p } of resolved)
419
+ corpus.add(p);
420
+ }
421
+ catch {
422
+ // Index unavailable — the in-scope files alone remain the corpus.
423
+ }
424
+ return [...corpus].sort();
425
+ }
426
+ /**
427
+ * Collect per-file function metadata for the index (Spec 44 / Spec 60.1). Only
428
+ * script files are extracted here; Go files are indexed by the SOLID analyzer
429
+ * directly. Threads the abort signal and a progress callback so a long extraction
430
+ * stays cancellable and observable.
431
+ */
432
+ async function collectIndexedFunctions(mergedOptions, files) {
433
+ let collectedFunctions = [];
434
+ const fileToFunctionsMap = new Map(); // Track functions per file for sync
435
+ if (mergedOptions.indexFunctions) {
436
+ reportProgress(mergedOptions, {
437
+ phase: 'function-indexing',
438
+ message: 'Collecting functions from files...'
439
+ });
440
+ // Collect functions from TypeScript/JavaScript files
441
+ // Note: Go files will be indexed by the Universal SOLID analyzer directly
442
+ const scriptFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx') ||
443
+ f.endsWith('.js') || f.endsWith('.jsx') ||
444
+ f.endsWith('.mts') || f.endsWith('.cts') ||
445
+ f.endsWith('.mjs') || f.endsWith('.cjs'));
446
+ logMcpInfo('function-indexing', 'extracting functions from script files', {
447
+ scriptFileCount: scriptFiles.length
448
+ });
449
+ for (let i = 0; i < scriptFiles.length; i++) {
190
450
  try {
191
- const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
192
- await db.initialize();
193
- const detection = await db.detectChangedFunctions(files);
194
- changedFunctions = detection.changedFunctions;
195
- logMcpInfo('discovery', 'changed function detection', {
196
- changedFunctionCount: changedFunctions.length,
197
- deletedCount: detection.deletedFunctions.length,
198
- errors: detection.errors.length
451
+ if (i % 10 === 0) {
452
+ throwIfAborted(mergedOptions.abortSignal);
453
+ }
454
+ if (i > 0 && i % 50 === 0) {
455
+ logMcpInfo('function-indexing', 'progress', {
456
+ current: i,
457
+ total: scriptFiles.length
458
+ });
459
+ }
460
+ const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], {
461
+ unusedImportsConfig: mergedOptions.unusedImportsConfig
199
462
  });
200
- }
201
- catch (err) {
202
- logMcpInfo('discovery', 'changed function detection failed (continuing)', {
203
- error: err instanceof Error ? err.message : String(err)
463
+ collectedFunctions.push(...fileFunctions);
464
+ fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync
465
+ reportProgress(mergedOptions, {
466
+ phase: 'function-indexing',
467
+ current: i + 1,
468
+ total: scriptFiles.length,
469
+ message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}`
204
470
  });
205
- }
206
- }
207
- // ── Blast-radius impact (Spec 14 R6) ──────────────────────────────
208
- // Compute transitive-caller impact for changed functions.
209
- // If latency exceeds the 100ms budget, skip and emit a warning;
210
- // the feature is disabled-by-default when over budget.
211
- let blastRadius;
212
- if (isScoped && changedFunctions && changedFunctions.length > 0) {
213
- try {
214
- const db = CodeIndexDB.getInstance(undefined, mergedOptions.projectRoot || process.cwd());
215
- const rawDb = db.rawDb;
216
- const functionIds = [];
217
- for (const fn of changedFunctions) {
218
- const row = rawDb.prepare('SELECT id FROM functions WHERE name = ? AND file_path = ?').get(fn.name, fn.filePath);
219
- if (row)
220
- functionIds.push(row.id);
221
- }
222
- if (functionIds.length > 0) {
223
- const impact = computeImpact(rawDb, functionIds);
224
- if (impact.latencyMs > LATENCY_BUDGET_MS) {
225
- logMcpInfo('blast-radius', `latency ${impact.latencyMs}ms exceeds ${LATENCY_BUDGET_MS}ms budget — blast radius disabled for this run`, {});
226
- }
227
- else {
228
- blastRadius = impact;
229
- logMcpInfo('blast-radius', 'computed', {
230
- editedFunctionCount: impact.editedFunctionCount,
231
- transitiveCallers: impact.transitiveCallers,
232
- reachableExports: impact.reachableExports,
233
- latencyMs: impact.latencyMs,
234
- });
235
- }
471
+ if (isMcpDebugEnabled()) {
472
+ logMcpDebug('function-indexing', scriptFiles[i], {
473
+ symbols: fileFunctions.length
474
+ });
236
475
  }
237
476
  }
238
- catch (err) {
239
- // Blast radius is advisory — non-fatal
240
- logMcpInfo('blast-radius', 'computation failed (continuing)', {
241
- error: err instanceof Error ? err.message : String(err)
477
+ catch (error) {
478
+ logMcpInfo('function-indexing', 'extract failed (continuing)', {
479
+ file: scriptFiles[i],
480
+ error: error instanceof Error ? error.message : String(error)
242
481
  });
243
482
  }
244
483
  }
245
- const handoffRemaining = [];
246
- const maxPerRun = mergedOptions.maxFilesPerRun;
247
- if (typeof maxPerRun === 'number' && maxPerRun > 0 && files.length > maxPerRun) {
248
- handoffRemaining.push(...files.slice(maxPerRun));
249
- files = files.slice(0, maxPerRun);
250
- }
251
- const root = path.resolve(mergedOptions.projectRoot || process.cwd());
252
- // Spec 60.1 Correction 1 — the import classifier's corpus file set must be
253
- // the UNFILTERED stage-1 discovery list, not the audit's `files` list.
254
- // `files` is narrowed at two points before `_infra` is built:
255
- // 1. Polyglot (Go) path — `LanguageOrchestrator.discoverAndGroupFiles`
256
- // drops files with no detectable language (`.json`, `.sql`), then
257
- // `TypeScriptAnalyzer.analyze` (RuntimeManager.ts) re-runs `runAudit`
258
- // with `includePaths: files` = the TS-only list.
259
- // 2. `filterFiles` (fileDiscovery.ts, called from `findFiles`) applies
260
- // `includePaths` as a positive-selection glob filter — a `.json` file
261
- // that survives step 1 is dropped here (or by the default `includePaths`
262
- // from config/defaults.ts, which omits `.json`).
263
- // Either way `_infra.files` no longer contains `.json`, so a real
264
- // `./invariant-rules.schema.json` import classifies `internal-broken`.
265
- // Classification answers "does this import resolve to a real file",
266
- // independent of what the audit chooses to analyze, so it re-walks
267
- // discovery with ALL_EXTENSIONS and no include/exclude narrowing.
268
- const corpusFiles = await discoverFiles(root);
269
- logMcpInfo('discovery', 'file discovery finished', {
270
- projectRoot: path.resolve(root),
271
- totalFiles: files.length,
272
- corpusFiles: corpusFiles.length,
273
- scope: scopeResultType,
274
- indexFunctions: !!mergedOptions.indexFunctions
275
- });
276
- // Collect functions if enabled
277
- let collectedFunctions = [];
278
- const fileToFunctionsMap = new Map(); // Track functions per file for sync
279
- if (mergedOptions.indexFunctions) {
280
- reportProgress(mergedOptions, {
281
- phase: 'function-indexing',
282
- message: 'Collecting functions from files...'
484
+ }
485
+ return { collectedFunctions, fileToFunctionsMap };
486
+ }
487
+ /**
488
+ * Sync style declarations, tokens, and class usage before the styles analyzer
489
+ * runs (Spec 10), mirroring the function index sync. Non-fatal: on failure the
490
+ * styles analyzer runs with whatever is already in the index.
491
+ */
492
+ async function syncStyles(analyzers, root, files, isScoped) {
493
+ let styleConsumedFiles = [];
494
+ let styleContributingFiles;
495
+ if (analyzers.includes('styles')) {
496
+ try {
497
+ const styleDb = CodeIndexDB.getInstance(undefined, root);
498
+ await styleDb.initialize();
499
+ const styleSyncResult = await syncStyleIndex(styleDb.rawDb, files, root, { scoped: isScoped });
500
+ styleConsumedFiles = styleSyncResult.consumedFiles;
501
+ styleContributingFiles = styleSyncResult.contributingFiles;
502
+ logMcpInfo('style-index', 'style index sync complete', {
503
+ changed: styleSyncResult.changed,
504
+ skipped: styleSyncResult.skipped,
505
+ removed: styleSyncResult.removed,
506
+ errors: styleSyncResult.errors,
507
+ consumed: styleSyncResult.consumedFiles.length,
283
508
  });
284
- // Collect functions from TypeScript/JavaScript files
285
- // Note: Go files will be indexed by the Universal SOLID analyzer directly
286
- const scriptFiles = files.filter(f => f.endsWith('.ts') || f.endsWith('.tsx') ||
287
- f.endsWith('.js') || f.endsWith('.jsx') ||
288
- f.endsWith('.mts') || f.endsWith('.cts') ||
289
- f.endsWith('.mjs') || f.endsWith('.cjs'));
290
- logMcpInfo('function-indexing', 'extracting functions from script files', {
291
- scriptFileCount: scriptFiles.length
509
+ }
510
+ catch (err) {
511
+ // Non-fatal: styles analyzer will run with whatever is in the index
512
+ logMcpInfo('style-index', 'style index sync failed (continuing)', {
513
+ error: err instanceof Error ? err.message : String(err)
292
514
  });
293
- for (let i = 0; i < scriptFiles.length; i++) {
515
+ }
516
+ }
517
+ return { styleConsumedFiles, styleContributingFiles };
518
+ }
519
+ /**
520
+ * Assemble the Stage-2 visitors / Stage-3 reducers / Stage-4 derived reducers
521
+ * for this run. Spec 68 §15 — once every registry rule is migrated, the legacy
522
+ * pipeline's rule-emitting visitors/reducers are skipped (their findings are
523
+ * stripped to zero at the both-paths split); only the infrastructure with a
524
+ * side-effect the phase model reads from the index keeps running.
525
+ */
526
+ function buildPipelineAdapters(analyzers) {
527
+ const pipelineVisitors = [];
528
+ const pipelineReducers = [];
529
+ const pipelineDerivedReducers = [];
530
+ let reactBundle;
531
+ let solidBundle;
532
+ // Always-on infrastructure: function-index visitor populates the
533
+ // `functions` table so conventions + cross-domain reducers have data
534
+ // even on a cold run with no prior index sync.
535
+ pipelineVisitors.push(createFunctionIndexVisitor());
536
+ // styles-css visitor — AST-extracts .css files into style_* tables (Spec 26 Phase 2)
537
+ if (analyzers.includes('styles'))
538
+ pipelineVisitors.push(createStylesCssVisitor());
539
+ // styles-source visitor — AST-extracts TS/JS CSS-in-JS into style_* tables,
540
+ // reusing the stage-1 parse (eliminates the style-index re-parse).
541
+ if (analyzers.includes('styles'))
542
+ pipelineVisitors.push(createStylesSourceVisitor());
543
+ // The DRY visitor's findings are all migrated and stripped, and its pair-seed
544
+ // side-effect now comes from the phase `code-block` fact (`seedDryPairs`, Spec
545
+ // 70 2b), so there is no legacy DRY visitor to register.
546
+ // The invariant reducer is pipeline-only (its rules come from
547
+ // `.codeauditor.json`, not the registry), so it always runs — it is the one
548
+ // legacy path that still emits non-migrated findings.
549
+ if (analyzers.includes('invariants'))
550
+ pipelineReducers.push(createInvariantsReducer());
551
+ // The styles reducer persists the styles-css visitor's facts into the
552
+ // `style_*` tables (the styles-source visitor writes its own). The phase
553
+ // `defined-classes` / `unread-style-sources` corpus producers read those
554
+ // tables, so this reducer must keep running even though its *findings* are
555
+ // migrated and stripped.
556
+ if (analyzers.includes('styles'))
557
+ pipelineReducers.push(createStylesReducer());
558
+ // The schema-code visitor's findings are migrated and stripped, but it must
559
+ // keep running for its one remaining live emit: the `reserved-word` finding
560
+ // off `checkNamingConventions` (unregistered emission, §13 disposition) and the
561
+ // ORM/DDL table catalog. Its `unresolved-query` diagnostic (Spec 58 R1) and its
562
+ // `unparseable` cannot-fire diagnostic (Spec 70 R2) are both re-derived
563
+ // corpus-side (Spec 70 1b / R2), so this visitor no longer emits either.
564
+ if (analyzers.includes('schema'))
565
+ pipelineVisitors.push(createSchemaCodeVisitor());
566
+ return {
567
+ pipelineVisitors,
568
+ pipelineReducers,
569
+ pipelineDerivedReducers,
570
+ reactBundle,
571
+ solidBundle,
572
+ };
573
+ }
574
+ /**
575
+ * Build the per-analyzer namespaced config passed to every pipeline stage. Each
576
+ * analyzer gets its own namespace; `_infra` holds shared infrastructure keys.
577
+ */
578
+ function buildPipelineAnalyzerConfig(analyzers, mergedOptions, root, files, corpusFiles, provenanceTiming) {
579
+ const pipelineAnalyzerConfig = {};
580
+ for (const name of analyzers) {
581
+ pipelineAnalyzerConfig[name] = { ...(mergedOptions.analyzerConfigs?.[name] ?? {}) };
582
+ }
583
+ // Spec 70 R1 — the corpus's dialect is threaded to the data-access analyzer
584
+ // (whose SQL-content facts parse rather than regex) and to DB-receiver
585
+ // resolution. Explicit config (`databaseType`) overrides detection; otherwise
586
+ // the dialect is *detected* from the dependency manifest (pg/neon → postgres,
587
+ // better-sqlite3/D1 → sqlite, mysql2 → mysql). A null dialect means the SQL
588
+ // facts `cannot-fire` — and `sqlDialectReason` names *why* (undetermined or
589
+ // ambiguous) so the abstention is visible rather than silent.
590
+ const resolution = buildResolutionEnvironment(root, {
591
+ databaseType: mergedOptions.databaseType,
592
+ importVirtualModules: mergedOptions.importVirtualModules,
593
+ });
594
+ const sqlDialect = resolution.sqlDialect;
595
+ const sqlDialectReason = resolution.sqlDialectReason;
596
+ // Spec 62 Amendment B — the missing-org-filter Stage-4 reducer reads the
597
+ // data-access config namespace, so it inherits the data-access analyzer's
598
+ // config rather than a fresh empty namespace.
599
+ if (analyzers.includes('data-access')) {
600
+ pipelineAnalyzerConfig['data-access'] = {
601
+ ...(pipelineAnalyzerConfig['data-access'] ?? {}),
602
+ dialect: sqlDialect,
603
+ };
604
+ pipelineAnalyzerConfig['data-access-org-filter'] = { ...(pipelineAnalyzerConfig['data-access'] ?? {}) };
605
+ }
606
+ // Pass invariant rules from .codeauditor.json into the invariants pipeline config.
607
+ if (analyzers.includes('invariants') && mergedOptions.rules) {
608
+ pipelineAnalyzerConfig['invariants'] = {
609
+ ...(pipelineAnalyzerConfig['invariants'] ?? {}),
610
+ rules: mergedOptions.rules,
611
+ };
612
+ }
613
+ // Cross-domain config lives at the top level (not analyzerConfigs)
614
+ if (analyzers.includes('cross-domain') && mergedOptions.crossDomain) {
615
+ pipelineAnalyzerConfig['cross-domain'] = {
616
+ ...(pipelineAnalyzerConfig['cross-domain'] ?? {}),
617
+ ...mergedOptions.crossDomain,
618
+ };
619
+ }
620
+ // Schema config: all table discovery flows through visitors and facts.
621
+ if (analyzers.includes('schema')) {
622
+ const scConfig = mergedOptions.analyzerConfigs?.schema ?? {};
623
+ const schemaConfig = {
624
+ ...(pipelineAnalyzerConfig['schema'] ?? {}),
625
+ sqlTagNames: scConfig.sqlTagNames ?? ['sql', 'db'],
626
+ dbBindingNames: scConfig.dbBindingNames ?? ['env.DB'],
627
+ fileGateGlobs: scConfig.fileGateGlobs,
628
+ maxQueriesPerFunction: scConfig.maxQueriesPerFunction,
629
+ validateQueryPatterns: scConfig.validateQueryPatterns,
630
+ checkNamingConventions: scConfig.checkNamingConventions,
631
+ tableSources: scConfig.tableSources,
632
+ dbWrapperNames: scConfig.dbWrapperNames,
633
+ detection: scConfig.detection,
634
+ schemas: scConfig.schemas,
635
+ knownTables: scConfig.knownTables,
636
+ // Spec 70 R2 — thread the corpus's named dialect so a provenanced static
637
+ // SQL argument that the dialect cannot parse (PRAGMA/VACUUM/ANALYZE) is
638
+ // reported as unreadable (cannot-fire), not silently "no tables".
639
+ sqlDialect,
640
+ // Spec 70 (detection) — the named reason when the dialect is null, so an
641
+ // undetermined/ambiguous corpus emits a "dialect undetermined" cannot-fire
642
+ // rather than reading the empty reference set as clean.
643
+ sqlDialectReason,
644
+ };
645
+ pipelineAnalyzerConfig['schema'] = schemaConfig;
646
+ // Pipeline resolves config by visitor name; the schema-* sub-visitors need
647
+ // the same namespace as the schema reducer.
648
+ for (const sub of ['schema-code', 'schema-sql', 'schema-prisma', 'schema-json']) {
649
+ pipelineAnalyzerConfig[sub] = schemaConfig;
650
+ }
651
+ }
652
+ // Global/infrastructure settings passed to all pipeline stages
653
+ pipelineAnalyzerConfig['_infra'] = {
654
+ pathProfiles: mergedOptions.pathProfiles,
655
+ projectRoot: root,
656
+ _provenanceTiming: provenanceTiming,
657
+ files,
658
+ corpusFiles,
659
+ importVirtualModules: resolution.importVirtualModules,
660
+ tsconfigAliases: resolution.tsconfigAliases,
661
+ packageEntryPoints: resolution.packageEntryPoints,
662
+ declaredTypePackages: resolution.declaredTypePackages,
663
+ // Spec 70 R1 — surfaced under `_infra` so `runPipeline` can hand the named
664
+ // dialect to the phase model's data-access producer without reading the
665
+ // data-access namespace.
666
+ sqlDialect,
667
+ // Spec 70 (detection) — the named reason when `sqlDialect` is null.
668
+ sqlDialectReason,
669
+ };
670
+ return pipelineAnalyzerConfig;
671
+ }
672
+ /**
673
+ * Assemble the `PipelineConfig` handed to `runPipeline`.
674
+ */
675
+ function buildPipelineConfig(inputs) {
676
+ const { mergedOptions, root, files, isScoped, fileAccounting, styleConsumedFiles, styleContributingFiles, analyzers, auditIndex, pipelineAnalyzerConfig, pipelineVisitors, pipelineReducers, pipelineDerivedReducers, } = inputs;
677
+ return {
678
+ projectRoot: root,
679
+ explicitFiles: files,
680
+ visitors: pipelineVisitors,
681
+ reducers: pipelineReducers,
682
+ derivedReducers: pipelineDerivedReducers,
683
+ config: pipelineAnalyzerConfig,
684
+ abortSignal: mergedOptions.abortSignal,
685
+ progressCallback: (progress) => {
686
+ throwIfAborted(mergedOptions.abortSignal);
687
+ reportProgress(mergedOptions, progress);
688
+ },
689
+ isScoped,
690
+ fileAccounting,
691
+ consumedFilePaths: styleConsumedFiles,
692
+ styleContributingFiles,
693
+ onStage2Complete: async () => {
694
+ // Post-stage-2 setup: rebuild the call-graph from the functions table
695
+ // (populated by the function-index visitor).
696
+ if (auditIndex && analyzers.includes('conventions')) {
294
697
  try {
295
- if (i % 10 === 0) {
296
- throwIfAborted(mergedOptions.abortSignal);
297
- }
298
- if (i > 0 && i % 50 === 0) {
299
- logMcpInfo('function-indexing', 'progress', {
300
- current: i,
301
- total: scriptFiles.length
302
- });
303
- }
304
- const fileFunctions = await extractFunctionsFromFile(scriptFiles[i], {
305
- unusedImportsConfig: mergedOptions.unusedImportsConfig
306
- });
307
- collectedFunctions.push(...fileFunctions);
308
- fileToFunctionsMap.set(scriptFiles[i], fileFunctions); // Store for sync
309
- reportProgress(mergedOptions, {
310
- phase: 'function-indexing',
311
- current: i + 1,
312
- total: scriptFiles.length,
313
- message: `Collected ${fileFunctions.length} items from ${scriptFiles[i]}`
314
- });
315
- if (isMcpDebugEnabled()) {
316
- logMcpDebug('function-indexing', scriptFiles[i], {
317
- symbols: fileFunctions.length
318
- });
319
- }
698
+ await auditIndex.graph.updateDependencyGraph();
320
699
  }
321
- catch (error) {
322
- logMcpInfo('function-indexing', 'extract failed (continuing)', {
323
- file: scriptFiles[i],
324
- error: error instanceof Error ? error.message : String(error)
700
+ catch (err) {
701
+ logMcpInfo('analysis', 'updateDependencyGraph failed (non-fatal)', {
702
+ error: err instanceof Error ? err.message : String(err)
325
703
  });
326
704
  }
327
705
  }
706
+ },
707
+ };
708
+ }
709
+ /**
710
+ * Run the both-paths split (Spec 68 §11.1): strip migrated rules' legacy
711
+ * emission and re-emit phase findings into the correct analyzer buckets.
712
+ * Mutates `analyzerResults` and `pipelineAnalyzerConfig` (dry divergence).
713
+ */
714
+ async function applyPhaseModelSplit(inputs) {
715
+ const { mergedOptions, root, files, isScoped, pipelineAnalyzerConfig, analyzerResults, pipelineIndexHandle } = inputs;
716
+ let phaseFindings = [];
717
+ let phaseIncompleteFacts = new Map();
718
+ let routeAttribution;
719
+ let receiverDiagnostics = [];
720
+ const { migrated } = splitRoutes();
721
+ routeAttribution = Object.fromEntries(attributeRoutes());
722
+ if (migrated.size > 0) {
723
+ // The `dry/diverging-clone` rule reads its knobs from `dry.divergence`.
724
+ const topLevelDivergence = mergedOptions.divergence;
725
+ if (topLevelDivergence && !pipelineAnalyzerConfig['dry']?.divergence) {
726
+ pipelineAnalyzerConfig['dry'] = { ...(pipelineAnalyzerConfig['dry'] ?? {}), divergence: topLevelDivergence };
328
727
  }
329
- // Run analyzers
330
- const analyzerResults = {};
331
- const enabledAnalyzers = getEnabledAnalyzers(mergedOptions, analyzerRegistry);
332
- // ── Style index sync (Spec 10) ────────────────────────────────────
333
- // Sync style declarations, tokens, and class usage before the
334
- // styles analyzer runs, mirroring the function index sync pattern.
335
- // Spec 44: the indexer readFileSyncs every discovered file (except
336
- // `.css`/`.scss`), so `consumedFiles` is the "any layer read it" evidence
337
- // for files dropped at stage 2 — threaded into the pipeline below.
338
- let styleConsumedFiles = [];
339
- // In-scope files that contributed style data during the sync. Undefined when
340
- // the sync did not run or failed — the styles reducer must not short-circuit
341
- // on that (it falls back to full analysis), so we only assign on success.
342
- let styleContributingFiles;
343
- if (enabledAnalyzers.includes('styles')) {
344
- try {
345
- const styleDb = CodeIndexDB.getInstance(undefined, root);
346
- await styleDb.initialize();
347
- const styleSyncResult = await syncStyleIndex(styleDb.rawDb, files, root, { scoped: isScoped });
348
- styleConsumedFiles = styleSyncResult.consumedFiles;
349
- styleContributingFiles = styleSyncResult.contributingFiles;
350
- logMcpInfo('style-index', 'style index sync complete', {
351
- changed: styleSyncResult.changed,
352
- skipped: styleSyncResult.skipped,
353
- removed: styleSyncResult.removed,
354
- errors: styleSyncResult.errors,
355
- consumed: styleSyncResult.consumedFiles.length,
356
- });
728
+ const thresholds = resolvePhaseThresholds(pipelineAnalyzerConfig);
729
+ const profiles = mergedOptions.pathProfiles;
730
+ const profileByFile = new Map();
731
+ const resolveFileProfile = (file) => {
732
+ let resolved = profileByFile.get(file);
733
+ if (!resolved) {
734
+ resolved = profiles?.length
735
+ ? resolvePathProfile(file, root, profiles)
736
+ : { overrides: {}, excludeFromGate: false, excludeFromAnalysis: false, matchedProfileNames: [] };
737
+ profileByFile.set(file, resolved);
357
738
  }
358
- catch (err) {
359
- // Non-fatal: styles analyzer will run with whatever is in the index
360
- logMcpInfo('style-index', 'style index sync failed (continuing)', {
361
- error: err instanceof Error ? err.message : String(err)
362
- });
739
+ return resolved;
740
+ };
741
+ const phaseFiles = files.filter((f) => !resolveFileProfile(f).excludeFromAnalysis);
742
+ const infraConfig = (pipelineAnalyzerConfig['_infra'] ?? {});
743
+ const schemaPhaseConfig = (pipelineAnalyzerConfig['schema'] ?? {});
744
+ const externalTables = [];
745
+ for (const t of schemaPhaseConfig.knownTables ?? []) {
746
+ externalTables.push({ name: t, source: 'external-config', columns: [] });
747
+ }
748
+ for (const s of schemaPhaseConfig.schemas ?? []) {
749
+ for (const t of s.tables) {
750
+ externalTables.push({ name: t.name, source: `Schema: ${s.name}`, columns: t.columns.map((c) => c.name) });
363
751
  }
364
752
  }
365
- // Spec 21: Shared timing accumulator for provenance resolution.
366
- // Injected into data-access and schema analyzers so they can report
367
- // per-file buildProvenanceContext() wall time (hook-latency measurement).
368
- const provenanceTiming = { totalMs: 0 };
369
- let pipelineCoverage;
370
- let pipelineTableCatalog;
371
- let pipelineStageTiming;
372
- let pipelineSkippedFiles;
373
- let pipelineUnparsedFiles;
374
- let pipelineInputPresence;
375
- let pipelineRuleTiming;
376
- let pipelineFileAccounting;
377
- let pipelineDiagnostics;
378
- let pipelineTestCoverage;
379
- let pipelineDeadClusters;
380
- let pipelineSizeDistributions;
381
- logMcpInfo('analysis', 'enabled analyzers', {
382
- names: enabledAnalyzers,
383
- fileCount: files.length,
384
- scope: scopeResultType
753
+ const phaseResult = await runPhaseModel(phaseFiles, thresholds, {
754
+ projectRoot: root,
755
+ scoped: isScoped,
756
+ corpusFiles: infraConfig.corpusFiles,
757
+ importVirtualModules: infraConfig.importVirtualModules,
758
+ tsconfigAliases: infraConfig.tsconfigAliases,
759
+ packageEntryPoints: infraConfig.packageEntryPoints,
760
+ indexHandle: pipelineIndexHandle,
761
+ enabledRules: enabledMigratedRules(),
762
+ externalTables,
763
+ workerCount: resolveWorkerCount(),
764
+ sqlDialect: infraConfig.sqlDialect,
765
+ sqlDialectReason: infraConfig.sqlDialectReason,
766
+ declaredTypePackages: infraConfig.declaredTypePackages,
767
+ // Spec 70 2b — the diverging-clone write: seed `dry_pair_history` from the
768
+ // phase `code-block` fact (replacing `createDryVisitor`/`persistDryPairs`).
769
+ persistDryPairHistory: (facts) => {
770
+ const dryCfg = resolveBlockConfig(thresholds.get('dry/duplicate') ?? {});
771
+ const codeBlocks = facts.get('code-block') ?? [];
772
+ return persistDryPairs(seedDryPairs(codeBlocks, dryCfg), root);
773
+ },
385
774
  });
386
- logMcpDebug('analysis', 'registry keys', { keys: Object.keys(analyzerRegistry) });
387
- const requestedConcurrency = Number(mergedOptions.analyzerConcurrency);
388
- const analyzerConcurrency = Number.isFinite(requestedConcurrency) && requestedConcurrency > 0
389
- ? Math.min(Math.floor(requestedConcurrency), enabledAnalyzers.length || 1)
390
- : 1;
391
- logMcpInfo('analysis', 'analyzer concurrency', { analyzerConcurrency });
392
- // ── Initialize CodeIndexDB for analyzer DB access ────────────────────
393
- // Styles, conventions, and cross-domain analyzers read config.indexHandle
394
- // for DB access. The singleton is shared — initialize once before the
395
- // analyzer run loop so ensureInitialized() is a no-op for each call.
396
- let auditIndex;
397
- try {
398
- auditIndex = CodeIndexDB.getInstance(undefined, root);
399
- await auditIndex.initialize();
400
- logMcpInfo('analysis', 'code index initialized for analyzers', { isInitialized: auditIndex.isInitialized, dbPath: auditIndex.dbPath });
775
+ phaseFindings = phaseResult.findings;
776
+ phaseIncompleteFacts = phaseResult.incompleteFacts;
777
+ // Spec 69 §10 + Part 2b — re-home the pre-pass's diagnostics (deleted from
778
+ // `pipeline.ts`) on the phase result. Cannot-fire: re-derive per file from
779
+ // `phaseResult.unprovenQueryReceivers` + `phaseResult.unresolvedImports`, keyed
780
+ // to the TS-family surface the legacy `schema-code` visitor covered (Go
781
+ // receivers/imports never reached it). Manifest-stale: the ecosystem-list
782
+ // self-check, unchanged from the deleted `buildPipelineResult` loop.
783
+ receiverDiagnostics = await buildReceiverDiagnostics(phaseResult.unprovenQueryReceivers, phaseResult.unresolvedImports, phaseResult.unresolvedQuerySites, phaseResult.unparseableSql, root);
784
+ // Strip the migrated rules' legacy emission from every analyzer result.
785
+ let strippedCount = 0;
786
+ for (const result of Object.values(analyzerResults)) {
787
+ const before = result.violations.length;
788
+ result.violations = result.violations.filter((v) => !migrated.has(v.rule));
789
+ strippedCount += before - result.violations.length;
401
790
  }
402
- catch (err) {
403
- // Non-fatal: analyzers that need indexHandle will skip DB-dependent
404
- // checks and report "No index handle available" in their errors.
405
- logMcpInfo('analysis', 'failed to initialize code index for analyzers (continuing)', {
406
- error: err instanceof Error ? err.message : String(err)
791
+ // Re-emit phase findings as violations, bucketed by the rule's analyzer.
792
+ const byAnalyzer = new Map();
793
+ const ruleAnalyzerOverride = {
794
+ 'table-naming-convention': 'schema-code',
795
+ 'too-many-queries': 'schema-code',
796
+ };
797
+ for (const f of phaseFindings) {
798
+ const analyzer = ruleAnalyzerOverride[f.ruleId] ?? RULE_ANALYZER.get(f.ruleId) ?? 'phase';
799
+ const resolved = resolveFileProfile(f.file);
800
+ const profile = resolved.matchedProfileNames.length > 0
801
+ ? resolved.matchedProfileNames[resolved.matchedProfileNames.length - 1]
802
+ : undefined;
803
+ const bucket = byAnalyzer.get(analyzer) ?? [];
804
+ bucket.push({
805
+ file: f.file,
806
+ rule: f.ruleId,
807
+ severity: f.severity,
808
+ message: f.message,
809
+ line: f.line,
810
+ column: f.column,
811
+ symbol: f.symbol,
812
+ resolution: f.resolution,
813
+ analyzer,
814
+ ...(f.fix ? { fix: f.fix } : {}),
815
+ ...(f.details !== undefined ? { details: f.details } : {}),
816
+ ...(profile ? { profile } : {}),
817
+ ...(resolved.excludeFromGate ? { gateExcluded: true } : {}),
407
818
  });
819
+ byAnalyzer.set(analyzer, bucket);
408
820
  }
409
- // ── Analyzer execution ──────────────────────────────────────────────
410
- // Spec 25 R3: The pipeline replaces the hand-rolled concurrent
411
- // analyzer loop. Visitors run per-file in stage 2, corpus reducers
412
- // run in stage 3, and derived reducers (cross-domain) run in stage 4.
413
- // Schema runs outside the pipeline because it needs ALL file types (SQL, JSON, TS).
414
- // Invariants runs inside the pipeline as a Stage 3 reducer (Spec 41 Part C).
415
- // Build IndexHandle for pipeline reducers
416
- let pipelineIndexHandle;
417
- if (auditIndex) {
418
- pipelineIndexHandle = {
419
- query: (sql, params) => auditIndex.query(sql, params),
420
- count: (table, where, params) => auditIndex.count(table, where, params),
421
- tableHasRows: (table) => auditIndex.tableHasRows(table),
422
- run: (sql, params) => auditIndex.rawDb.prepare(sql).run(...(params ?? [])),
423
- exec: (sql) => auditIndex.rawDb.exec(sql),
424
- getMeta: (key) => auditIndex.getMeta(key),
425
- getUntestedTopDecile: (td) => auditIndex.getUntestedTopDecile(td),
426
- rawDb: auditIndex.rawDb,
427
- };
428
- }
429
- // ── 1. Build pipeline adapters ───────────────────────────────────────
430
- const pipelineVisitors = [];
431
- const pipelineReducers = [];
432
- const pipelineDerivedReducers = [];
433
- let dryBundle;
434
- let reactBundle;
435
- let solidBundle;
436
- // Always-on infrastructure: function-index visitor populates the
437
- // `functions` table so conventions + cross-domain reducers have data
438
- // even on a cold run with no prior index sync.
439
- pipelineVisitors.push(createFunctionIndexVisitor());
440
- // styles-css visitor — AST-extracts .css files into style_* tables (Spec 26 Phase 2)
441
- if (enabledAnalyzers.includes('styles'))
442
- pipelineVisitors.push(createStylesCssVisitor());
443
- // styles-source visitor — AST-extracts TS/JS CSS-in-JS into style_* tables,
444
- // reusing the stage-1 parse (eliminates the style-index re-parse).
445
- if (enabledAnalyzers.includes('styles'))
446
- pipelineVisitors.push(createStylesSourceVisitor());
447
- if (enabledAnalyzers.includes('solid')) {
448
- solidBundle = createSolidVisitor();
449
- pipelineVisitors.push(solidBundle.visitor);
821
+ for (const [analyzer, violations] of byAnalyzer) {
822
+ if (analyzerResults[analyzer]) {
823
+ analyzerResults[analyzer].violations.push(...violations);
824
+ }
825
+ else {
826
+ analyzerResults[analyzer] = {
827
+ ...makeEmptyAnalyzerResult(analyzer, phaseFiles.length),
828
+ violations,
829
+ };
830
+ }
450
831
  }
451
- if (enabledAnalyzers.includes('dry')) {
452
- dryBundle = createDryVisitor();
453
- pipelineVisitors.push(dryBundle.visitor);
832
+ // Every migrated analyzer must appear in the results map even when it
833
+ // emitted nothing this run.
834
+ for (const analyzer of new Set(RULE_ANALYZER.values())) {
835
+ if (!analyzerResults[analyzer]) {
836
+ analyzerResults[analyzer] = makeEmptyAnalyzerResult(analyzer, phaseFiles.length);
837
+ }
454
838
  }
455
- if (enabledAnalyzers.includes('data-access')) {
456
- pipelineVisitors.push(createDataAccessVisitor());
457
- // Spec 62 Amendment B — the missing-org-filter rule is a Stage-4 derived
458
- // reducer that joins the data-access query facts against the declared +
459
- // DDL-discovered tenant tiers. Registered whenever data-access is enabled,
460
- // mirroring the data-access visitor it consumes.
461
- pipelineDerivedReducers.push(createOrgFilterReducer());
839
+ logMcpInfo('analysis', 'phase model (both paths)', {
840
+ migrated: migrated.size,
841
+ stripped: strippedCount,
842
+ reemitted: phaseFindings.length,
843
+ });
844
+ }
845
+ return { phaseFindings, phaseIncompleteFacts, routeAttribution, receiverDiagnostics };
846
+ }
847
+ /**
848
+ * Spec 69 §10 + Part 2b — re-derive the pre-pass's coverage diagnostics from the
849
+ * phase result: the `cannot-fire` surface (unproven query receivers + unresolved
850
+ * DB-looking imports, keyed to the TS-family files the legacy `schema-code`
851
+ * visitor covered) and the `manifest-stale` ecosystem-list self-check. Replaces
852
+ * `collectCannotFireDiagnostics` (deleted from `pipelineAdapters.ts`) and the
853
+ * `manifest-staleness` loop (deleted from `buildPipelineResult`).
854
+ */
855
+ async function buildReceiverDiagnostics(unprovenQueryReceivers, unresolvedImports, unresolvedQuerySites, unparseableSql, projectRoot) {
856
+ const diagnostics = [];
857
+ // Group the two signals by file, then per-file dedup so the call-site signal
858
+ // wins over the import-level signal — mirroring the deleted
859
+ // `collectCannotFireDiagnostics` (which ran per TS-family file inside the
860
+ // schema-code visitor, so Go files never reached it).
861
+ const files = new Set();
862
+ for (const u of unprovenQueryReceivers)
863
+ if (!u.file.endsWith('.go'))
864
+ files.add(u.file);
865
+ for (const u of unresolvedImports)
866
+ if (!u.importer.endsWith('.go'))
867
+ files.add(u.importer);
868
+ for (const file of files) {
869
+ const perFile = [];
870
+ const unresolvedHere = unresolvedImports.filter((u) => path.resolve(u.importer) === path.resolve(file));
871
+ if (unresolvedHere.length > 0) {
872
+ perFile.push(...checkUnresolvedReceiverImports(unresolvedHere.map((u) => ({ source: u.source, names: [...u.names] })), file));
462
873
  }
463
- if (enabledAnalyzers.includes('secrets'))
464
- pipelineVisitors.push(createSecretsVisitor());
465
- if (enabledAnalyzers.includes('security'))
466
- pipelineVisitors.push(createSecurityVisitor());
467
- if (enabledAnalyzers.includes('react')) {
468
- reactBundle = createReactVisitor();
469
- pipelineVisitors.push(reactBundle.visitor);
874
+ const unprovenHere = unprovenQueryReceivers.filter((u) => path.resolve(u.file) === path.resolve(file));
875
+ if (unprovenHere.length > 0) {
876
+ perFile.push(...checkUnprovenQueryReceivers(unprovenHere.map((u) => ({ receiver: u.receiver, method: u.method, line: u.line, reason: u.reason })), file));
470
877
  }
471
- if (enabledAnalyzers.includes('documentation'))
472
- pipelineVisitors.push(createDocumentationVisitor());
473
- if (enabledAnalyzers.includes('styles'))
474
- pipelineReducers.push(createStylesReducer());
475
- if (enabledAnalyzers.includes('conventions'))
476
- pipelineReducers.push(createConventionsReducer());
477
- if (enabledAnalyzers.includes('invariants'))
478
- pipelineReducers.push(createInvariantsReducer());
479
- if (enabledAnalyzers.includes('cross-domain'))
480
- pipelineDerivedReducers.push(createCrossDomainReducer());
481
- if (enabledAnalyzers.includes('schema')) {
482
- pipelineVisitors.push(createSchemaSqlVisitor());
483
- pipelineVisitors.push(createSchemaCodeVisitor());
484
- pipelineVisitors.push(createSchemaPrismaVisitor());
485
- pipelineVisitors.push(createSchemaJsonVisitor());
486
- pipelineReducers.push(createSchemaReducer());
878
+ diagnostics.push(...dedupeCannotFireByReceiver(perFile));
879
+ }
880
+ // Spec 70 1b — the `unresolved-query` diagnostics (re-admitted DB-calls whose
881
+ // SQL is held in an unresolvable identifier). Emitted separately from
882
+ // `cannot-fire`: a distinct kind, never receiver-deduped (the legacy
883
+ // `schema-code` visitor emitted them verbatim, per file, in visitor order).
884
+ const unresolvedByFile = new Map();
885
+ for (const site of unresolvedQuerySites) {
886
+ if (site.file.endsWith('.go'))
887
+ continue;
888
+ let list = unresolvedByFile.get(site.file);
889
+ if (!list) {
890
+ list = [];
891
+ unresolvedByFile.set(site.file, list);
487
892
  }
488
- // Cross-language analyzers (SchemaValidator, APIContractAnalyzer,
489
- // DependencyGraphBuilder) — three Stage-4 reducers fed by one shared
490
- // entity-extraction visitor. The visitor is cheap (per-file) and runs
491
- // whenever ANY cross-language analyzer is enabled; the corpus-wide reducers
492
- // short-circuit on scoped/diff runs.
493
- const crossLanguageEnabled = ['schema-validator', 'api-contract', 'dependency-graph']
494
- .some((a) => enabledAnalyzers.includes(a));
495
- if (crossLanguageEnabled) {
496
- pipelineVisitors.push(createCrossLanguageEntityVisitor());
893
+ list.push(site);
894
+ }
895
+ for (const [file, sites] of unresolvedByFile) {
896
+ diagnostics.push(...checkUnresolvedQueries(sites.map((s) => ({ identifier: s.identifier, location: s.location })), file));
897
+ }
898
+ // Spec 70 R2 — the `unparseable` cannot-fire diagnostics. Emitted per file in
899
+ // fact order, mirroring the deleted legacy `schema-code` visitor, which ran
900
+ // `checkUnparseableSql` over every admitted tag/DB-call parse within the file.
901
+ const unparseableByFile = new Map();
902
+ for (const u of unparseableSql) {
903
+ if (u.file.endsWith('.go'))
904
+ continue;
905
+ let list = unparseableByFile.get(u.file);
906
+ if (!list) {
907
+ list = [];
908
+ unparseableByFile.set(u.file, list);
497
909
  }
498
- if (enabledAnalyzers.includes('schema-validator'))
499
- pipelineDerivedReducers.push(createSchemaValidatorReducer());
500
- if (enabledAnalyzers.includes('api-contract'))
501
- pipelineDerivedReducers.push(createAPIContractReducer());
502
- if (enabledAnalyzers.includes('dependency-graph'))
503
- pipelineDerivedReducers.push(createDependencyGraphReducer());
504
- // ── 2. Safeguard warnings ────────────────────────────────────────────
505
- if (auditIndex) {
506
- if (enabledAnalyzers.includes('cross-domain')) {
507
- const suCount = auditIndex.count('schema_usage');
508
- if (suCount === 0) {
509
- console.warn('[code-audit] ⚠ cross-domain analyzer requires schema_usage data. '
510
- + 'This is populated during a full audit run by the schema analyzer. '
511
- + 'If this warning persists, run a full "code-audit audit --path ." first.');
910
+ list.push(u);
911
+ }
912
+ for (const [file, records] of unparseableByFile) {
913
+ diagnostics.push(...checkUnparseableSql(records, file));
914
+ }
915
+ // Part 2b — the manifest staleness self-check, unchanged from the deleted
916
+ // `buildPipelineResult` loop: our hardcoded ecosystem lists vs. the project's
917
+ // manifest, emitted as a diagnostic (never a verdict input).
918
+ const manifest = await readProjectManifest(projectRoot);
919
+ for (const stale of computeManifestStaleness(manifest)) {
920
+ diagnostics.push({
921
+ analyzerName: 'data-access',
922
+ kind: 'manifest-stale',
923
+ message: `our list names '${stale.package}', this project doesn't depend on it`,
924
+ file: stale.manifestPath,
925
+ line: 0,
926
+ details: { ecosystem: stale.ecosystem, package: stale.package },
927
+ });
928
+ }
929
+ return diagnostics;
930
+ }
931
+ /**
932
+ * Persist seeded DRY pairs into `dry_pair_history` (Spec 13 R5 Phase 1) so the
933
+ * migrated `dry/diverging-clone` rule can read them through the index handle.
934
+ * The pairs come from `seedDryPairs` over the phase `code-block` fact (Spec 70
935
+ * 2b — the writer moved off the legacy DRY visitor). Runs BEFORE the phase
936
+ * model's `clone-pair-history` read. Advisory — non-fatal on failure.
937
+ * @param dryPairs The seeded DRY pairs to persist into `dry_pair_history`.
938
+ * @param root The project root used to open the code-index database handle.
939
+ * @returns Resolves when the advisory write completes (never rejects).
940
+ */
941
+ export async function persistDryPairs(dryPairs, root) {
942
+ try {
943
+ if (dryPairs.length > 0) {
944
+ const indexDb = CodeIndexDB.getInstance(undefined, root);
945
+ await indexDb.initialize();
946
+ const dryPersistRunId = randomUUID();
947
+ const insertStmt = indexDb.rawDb.prepare(`
948
+ INSERT OR IGNORE INTO dry_pair_history
949
+ (pair_fingerprint, file1, symbol1, line1, content_hash1,
950
+ file2, symbol2, line2, content_hash2, similarity, timestamp, run_id)
951
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), ?)
952
+ `);
953
+ const tx = indexDb.rawDb.transaction(() => {
954
+ for (const pair of dryPairs) {
955
+ insertStmt.run(pair.pairFingerprint, pair.file1, pair.symbol1, pair.line1, pair.contentHash1, pair.file2, pair.symbol2, pair.line2, pair.contentHash2, pair.similarity, dryPersistRunId);
512
956
  }
513
- }
957
+ });
958
+ tx();
514
959
  }
515
- // ── 3. Run pipeline ──────────────────────────────────────────────────
516
- const hasPipelineAnalyzers = pipelineVisitors.length > 0
517
- || pipelineReducers.length > 0
518
- || pipelineDerivedReducers.length > 0;
519
- if (hasPipelineAnalyzers) {
520
- // Build per-analyzer namespaced config — no flat Object.assign merge.
521
- // Each analyzer gets its own namespace; _infra holds shared infrastructure keys
522
- // that every visitor/reducer receives alongside its own config.
523
- const pipelineAnalyzerConfig = {};
524
- for (const name of enabledAnalyzers) {
525
- pipelineAnalyzerConfig[name] = { ...(mergedOptions.analyzerConfigs?.[name] ?? {}) };
526
- }
527
- // Spec 62 Amendment B — the missing-org-filter Stage-4 reducer reads the
528
- // data-access config namespace (orgFilterTables/orgFilterColumns/schemas),
529
- // so it inherits the data-access analyzer's config rather than a fresh
530
- // empty namespace. `data-access-org-filter` is never in enabledAnalyzers
531
- // (it is auto-registered), so this is the only place its config is set.
532
- if (enabledAnalyzers.includes('data-access')) {
533
- pipelineAnalyzerConfig['data-access-org-filter'] = { ...(pipelineAnalyzerConfig['data-access'] ?? {}) };
534
- }
535
- // Pass invariant rules from .codeauditor.json into the invariants pipeline config.
536
- // The rules field lives at the top level of the loaded config (not under analyzerConfigs).
537
- if (enabledAnalyzers.includes('invariants') && mergedOptions.rules) {
538
- pipelineAnalyzerConfig['invariants'] = {
539
- ...(pipelineAnalyzerConfig['invariants'] ?? {}),
540
- rules: mergedOptions.rules,
541
- };
960
+ }
961
+ catch {
962
+ // dry_pair_history persistence is advisory — non-fatal
963
+ }
964
+ }
965
+ /**
966
+ * Run the React analyzer's cross-component finalization (require-error-boundary
967
+ * / raw-element checks), appending extra violations into the `react` result.
968
+ */
969
+ async function finalizeReact(reactBundle, analyzerResults, pipelineAnalyzerConfig) {
970
+ if (reactBundle) {
971
+ try {
972
+ const reactResult = analyzerResults['react'];
973
+ if (reactResult) {
974
+ // Pass the react namespace (not the whole namespaced config) so
975
+ // finalize reads `requireErrorBoundaries`/`rawElementCheck` at the
976
+ // same level the visitor's `context.config` does.
977
+ const extraViolations = await reactBundle.finalizeCrossComponent(pipelineAnalyzerConfig['react'] ?? {});
978
+ reactResult.violations.push(...extraViolations);
542
979
  }
543
- // Cross-domain config lives at the top level (not analyzerConfigs)
544
- if (enabledAnalyzers.includes('cross-domain') && mergedOptions.crossDomain) {
545
- pipelineAnalyzerConfig['cross-domain'] = {
546
- ...(pipelineAnalyzerConfig['cross-domain'] ?? {}),
547
- ...mergedOptions.crossDomain,
548
- };
980
+ }
981
+ catch (err) {
982
+ logMcpInfo('analysis', 'react finalization failed (non-fatal)', {
983
+ error: err instanceof Error ? err.message : String(err)
984
+ });
985
+ }
986
+ }
987
+ }
988
+ /**
989
+ * Build the pipeline, run it, and fold its outputs — plus the both-paths phase
990
+ * split and derived coverage — into a single result bundle for `run`.
991
+ */
992
+ async function runPipelineStage(inputs) {
993
+ const { mergedOptions, root, files, corpusFiles, isScoped, fileAccounting, styleConsumedFiles, styleContributingFiles, analyzers, provenanceTiming, } = inputs;
994
+ const analyzerResults = {};
995
+ let indexFactsWritten = 0;
996
+ let writeIndexFactsMs = 0;
997
+ const { auditIndex, pipelineIndexHandle, indexSkipDiagnostic } = await initializeAuditIndex(root);
998
+ const { pipelineVisitors, pipelineReducers, pipelineDerivedReducers, reactBundle, solidBundle, } = buildPipelineAdapters(analyzers);
999
+ // ── Safeguard warnings ────────────────────────────────────────────────
1000
+ if (auditIndex && analyzers.includes('cross-domain')) {
1001
+ const suCount = auditIndex.rawSql.count('schema_usage');
1002
+ if (suCount === 0) {
1003
+ console.warn('[code-audit] ⚠ cross-domain analyzer requires schema_usage data. '
1004
+ + 'This is populated during a full audit run by the schema analyzer. '
1005
+ + 'If this warning persists, run a full "code-audit audit --path ." first.');
1006
+ }
1007
+ }
1008
+ const hasPipelineAnalyzers = pipelineVisitors.length > 0
1009
+ || pipelineReducers.length > 0
1010
+ || pipelineDerivedReducers.length > 0;
1011
+ // Outputs hoisted so derived coverage (after the split) can read them.
1012
+ let phaseFindings = [];
1013
+ let phaseIncompleteFacts = new Map();
1014
+ let pipelineCoverage;
1015
+ let pipelineTableCatalog;
1016
+ let pipelineStageTiming;
1017
+ let pipelineSkippedFiles;
1018
+ let pipelineUnparsedFiles;
1019
+ let pipelineInputPresence;
1020
+ let pipelineRuleTiming;
1021
+ let pipelineFileAccounting;
1022
+ let pipelineDiagnostics;
1023
+ let pipelineTestCoverage;
1024
+ let pipelineDeadClusters;
1025
+ let pipelineSizeDistributions;
1026
+ let routeAttribution;
1027
+ let receiverDiagnostics = [];
1028
+ if (hasPipelineAnalyzers) {
1029
+ const pipelineAnalyzerConfig = buildPipelineAnalyzerConfig(analyzers, mergedOptions, root, files, corpusFiles, provenanceTiming);
1030
+ const pipelineConfig = buildPipelineConfig({
1031
+ mergedOptions, root, files, isScoped, fileAccounting,
1032
+ styleConsumedFiles, styleContributingFiles, analyzers,
1033
+ auditIndex, pipelineAnalyzerConfig, pipelineVisitors, pipelineReducers,
1034
+ pipelineDerivedReducers,
1035
+ });
1036
+ try {
1037
+ logMcpInfo('analysis', 'running pipeline', { visitorCount: pipelineVisitors.length, reducerCount: pipelineReducers.length, derivedReducerCount: pipelineDerivedReducers.length, fileCount: files.length });
1038
+ const pipelineResult = await runPipeline(pipelineConfig, pipelineIndexHandle);
1039
+ // §6.3 — the parent is the only index writer; surface its count and
1040
+ // wall-clock via stageTiming.
1041
+ const stageWriteCount = pipelineResult.metadata.stageTiming?.['index-fact-write-count'];
1042
+ const stageWriteMs = pipelineResult.metadata.stageTiming?.['index-fact-write'];
1043
+ if (typeof stageWriteCount === 'number')
1044
+ indexFactsWritten = stageWriteCount;
1045
+ if (typeof stageWriteMs === 'number')
1046
+ writeIndexFactsMs = stageWriteMs;
1047
+ // Pull in pipeline results
1048
+ for (const [name, ar] of Object.entries(pipelineResult.analyzerResults)) {
1049
+ analyzerResults[name] = ar;
1050
+ logMcpDebug('analysis', `pipeline: ${name} completed`, {
1051
+ violations: ar.violations?.length ?? 0,
1052
+ status: ar.status?.status,
1053
+ });
549
1054
  }
550
- // Schema config: all table discovery flows through visitors and facts;
551
- // the reducer builds the known-tables catalog solely from fact data.
552
- if (enabledAnalyzers.includes('schema')) {
553
- const scConfig = mergedOptions.analyzerConfigs?.schema ?? {};
554
- const schemaConfig = {
555
- ...(pipelineAnalyzerConfig['schema'] ?? {}),
556
- sqlTagNames: scConfig.sqlTagNames ?? ['sql', 'db'],
557
- dbReceiverNames: scConfig.dbReceiverNames,
558
- dbCallMethods: scConfig.dbCallMethods,
559
- dbBindingNames: scConfig.dbBindingNames ?? ['env.DB'],
560
- fileGateGlobs: scConfig.fileGateGlobs,
561
- // Forward the per-function query ceiling + query-pattern/naming gates
562
- // so the configured value reaches checkQueryPatterns instead of being
563
- // dropped (which made the too-many-queries message print "undefined").
564
- maxQueriesPerFunction: scConfig.maxQueriesPerFunction,
565
- validateQueryPatterns: scConfig.validateQueryPatterns,
566
- checkNamingConventions: scConfig.checkNamingConventions,
567
- // Declarative ORM table-source registry (Spec 29 R2). Defaults to the
568
- // Drizzle builders in the visitor when unset; presets (drizzle, typeorm,
569
- // knex) supply their own, and it must not be dropped before the visitor.
570
- tableSources: scConfig.tableSources,
571
- // Provenance context knobs that the visitor reads via buildProvenanceContext.
572
- dbWrapperNames: scConfig.dbWrapperNames,
573
- detection: scConfig.detection,
574
- // External schema references — the pipeline reducer gates unknown-table
575
- // detection on these being non-empty (fail-open law: no external
576
- // authority means the rule cannot accuse).
577
- schemas: scConfig.schemas,
578
- knownTables: scConfig.knownTables,
579
- };
580
- pipelineAnalyzerConfig['schema'] = schemaConfig;
581
- // Pipeline resolves config by visitor name (rawConfig[visitor.name]).
582
- // The schema-* sub-visitors need the same namespace as the schema reducer.
583
- for (const sub of ['schema-code', 'schema-sql', 'schema-prisma', 'schema-json']) {
584
- pipelineAnalyzerConfig[sub] = schemaConfig;
585
- }
1055
+ await finalizeReact(reactBundle, analyzerResults, pipelineAnalyzerConfig);
1056
+ const split = await applyPhaseModelSplit({
1057
+ mergedOptions, root, files, isScoped, pipelineAnalyzerConfig, analyzerResults, pipelineIndexHandle,
1058
+ });
1059
+ phaseFindings = split.phaseFindings;
1060
+ phaseIncompleteFacts = split.phaseIncompleteFacts;
1061
+ routeAttribution = split.routeAttribution;
1062
+ receiverDiagnostics = split.receiverDiagnostics;
1063
+ // Spec 68 §8 — derived coverage, computed AFTER the split so phaseFindings
1064
+ // holds the migrated rules' re-emitted findings.
1065
+ pipelineCoverage = deriveCoverage({
1066
+ rules: MIGRATED_RULES,
1067
+ findings: phaseFindings,
1068
+ presentFormats: presentFormatsOf(files),
1069
+ enabledRules: enabledMigratedRules(),
1070
+ incompleteFacts: phaseIncompleteFacts,
1071
+ groupOf: (ruleId) => RULE_ANALYZER.get(ruleId) ?? ruleId,
1072
+ });
1073
+ // Spec 29: extract table catalog from pipeline metadata for audit report
1074
+ pipelineTableCatalog = pipelineResult.metadata?.tableCatalog;
1075
+ pipelineStageTiming = pipelineResult.metadata?.stageTiming;
1076
+ pipelineSkippedFiles = pipelineResult.metadata?.skippedFiles;
1077
+ pipelineUnparsedFiles = pipelineResult.metadata?.unparsedFiles;
1078
+ pipelineInputPresence = pipelineResult.metadata?.inputPresence;
1079
+ // Spec 38 R2 — read the live accumulator rather than the `runPipeline`
1080
+ // snapshot: the phase model's `analyzeAll` records its per-rule timing
1081
+ // after `runPipeline` already snapshotted (empty) `ruleTiming`. The
1082
+ // accumulator was reset at `runPipeline` entry, so this is legacy + phase.
1083
+ pipelineRuleTiming = getRuleTimingSortedDesc();
1084
+ pipelineFileAccounting = pipelineResult.metadata?.fileAccounting;
1085
+ pipelineDiagnostics = [
1086
+ ...(pipelineResult.metadata?.diagnostics ?? []),
1087
+ ...receiverDiagnostics,
1088
+ ];
1089
+ pipelineTestCoverage = pipelineResult.metadata?.testCoverage;
1090
+ pipelineDeadClusters = pipelineResult.metadata?.deadClusters;
1091
+ // Spec 60 R2 — aggregate the SOLID analyzer's raw size readings.
1092
+ if (solidBundle) {
1093
+ const samples = await solidBundle.getSizeSamples();
1094
+ pipelineSizeDistributions = computeSizeDistributions(samples);
586
1095
  }
587
- // Global/infrastructure settings passed to all pipeline stages
588
- pipelineAnalyzerConfig['_infra'] = {
589
- pathProfiles: mergedOptions.pathProfiles,
590
- projectRoot: root,
591
- _provenanceTiming: provenanceTiming,
592
- files,
593
- // Spec 60.1 Correction 1 — unfiltered discovery list, used as the
594
- // classifier's corpus set (see the computation above for why `files`
595
- // is too narrow).
596
- corpusFiles,
597
- // Spec 60.1 — virtual-module list (config, default ['.blitz']) and the
598
- // project's tsconfig `paths` + `baseUrl` (alias classification *and*
599
- // resolution). Threaded here so the function-index visitor and the
600
- // reachability reducer read them once per run rather than re-reading
601
- // tsconfig per file.
602
- importVirtualModules: mergedOptions.importVirtualModules ?? DEFAULT_VIRTUAL_MODULES,
603
- tsconfigAliases: readTsconfigAliases(root),
604
- // Files reachable only through package.json (main/module/types/bin/exports
605
- // + their sibling facades) are entry points, not dead modules — knex's
606
- // `knex.mjs` / `knex.d.mts` beside `main: knex.js` were flagged
607
- // `unreferenced-module` because no in-tree import reaches them.
608
- packageEntryPoints: readPackageEntryPoints(root).entryPaths,
609
- };
610
- const pipelineConfig = {
611
- projectRoot: root,
612
- explicitFiles: files,
613
- visitors: pipelineVisitors,
614
- reducers: pipelineReducers,
615
- derivedReducers: pipelineDerivedReducers,
616
- config: pipelineAnalyzerConfig,
617
- abortSignal: mergedOptions.abortSignal,
618
- progressCallback: (progress) => {
619
- throwIfAborted(mergedOptions.abortSignal);
620
- reportProgress(mergedOptions, progress);
621
- },
622
- isScoped,
623
- fileAccounting,
624
- consumedFilePaths: styleConsumedFiles,
625
- styleContributingFiles,
626
- onStage2Complete: async (ctx) => {
627
- // Post-stage-2 setup: rebuild function_calls from the functions table
628
- // (populated by the function-index visitor), then mine conventions.
629
- if (auditIndex && enabledAnalyzers.includes('conventions')) {
630
- try {
631
- await auditIndex.updateDependencyGraph();
632
- }
633
- catch (err) {
634
- logMcpInfo('analysis', 'updateDependencyGraph failed (non-fatal)', {
635
- error: err instanceof Error ? err.message : String(err)
636
- });
637
- }
638
- try {
639
- // Convention mining reads source on demand via readFileSync
640
- // (mineImportForm/mineExportShape have an internal fallback).
641
- auditIndex.mineAllConventions(root);
642
- }
643
- catch (err) {
644
- logMcpInfo('analysis', 'convention mining failed (non-fatal)', {
645
- error: err instanceof Error ? err.message : String(err)
646
- });
647
- }
648
- }
649
- },
650
- };
651
- try {
652
- logMcpInfo('analysis', 'running pipeline', { visitorCount: pipelineVisitors.length, reducerCount: pipelineReducers.length, derivedReducerCount: pipelineDerivedReducers.length, fileCount: files.length });
653
- const pipelineResult = await runPipeline(pipelineConfig, pipelineIndexHandle);
654
- // Write index facts (schema_usage from schema visitor, etc.)
655
- if (pipelineIndexHandle && pipelineResult.indexFacts && pipelineResult.indexFacts.length > 0) {
656
- writeIndexFactsToDb(pipelineIndexHandle, pipelineResult.indexFacts);
657
- }
658
- // Pull in pipeline results
659
- for (const [name, ar] of Object.entries(pipelineResult.analyzerResults)) {
660
- analyzerResults[name] = ar;
661
- logMcpDebug('analysis', `pipeline: ${name} completed`, {
662
- violations: ar.violations?.length ?? 0,
663
- status: ar.status?.status,
664
- });
665
- }
666
- // ── Schema sub-visitors: keep separate rows for visibility ────
667
- // Schema runs as 4 Stage 2 visitors + 1 Stage 3 reducer. Each visitor
668
- // keeps its own violations and status row so the CLI displays per-visitor
669
- // file/fact counts. Violations are NOT merged — the reducer only carries
670
- // cross-file violations (unknown-table, JSON schema validation).
671
- // Sub-visitors are added to orderedAnalyzerResults below (line ~650).
672
- // ── React finalization (cross-component checks) ──────────────────
673
- if (reactBundle) {
674
- try {
675
- const reactResult = analyzerResults['react'];
676
- if (reactResult) {
677
- // Pass the react namespace (not the whole namespaced config) so
678
- // finalize reads `requireErrorBoundaries`/`rawElementCheck` at the
679
- // same level the visitor's `context.config` does.
680
- const extraViolations = await reactBundle.finalizeCrossComponent(pipelineAnalyzerConfig['react'] ?? {});
681
- reactResult.violations.push(...extraViolations);
682
- }
683
- }
684
- catch (err) {
685
- logMcpInfo('analysis', 'react finalization failed (non-fatal)', {
686
- error: err instanceof Error ? err.message : String(err)
687
- });
688
- }
689
- }
690
- // Spec 27 — build per-rule coverage from final analyzer results
691
- // (computed AFTER react finalization so cross-component checks are included).
692
- // Spec 33 Item 14 — thread the pipeline's input-presence snapshot so
693
- // zero-violation rules promote from `unassessed` to `clean`/`notApplicable`.
694
- pipelineCoverage = buildCoverageReport(analyzerResults, pipelineConfig, pipelineResult.metadata?.inputPresence, new Map((pipelineResult.metadata?.ruleApplicability ?? []).map((a) => [
695
- a.ruleId,
696
- { applicable: a.applicable, reason: a.reason, kind: a.kind },
697
- ])));
698
- // Spec 29: extract table catalog from pipeline metadata for audit report
699
- pipelineTableCatalog = pipelineResult.metadata?.tableCatalog;
700
- pipelineStageTiming = pipelineResult.metadata?.stageTiming;
701
- pipelineSkippedFiles = pipelineResult.metadata?.skippedFiles;
702
- pipelineUnparsedFiles = pipelineResult.metadata?.unparsedFiles;
703
- pipelineInputPresence = pipelineResult.metadata?.inputPresence;
704
- pipelineRuleTiming = pipelineResult.metadata?.ruleTiming;
705
- pipelineFileAccounting = pipelineResult.metadata?.fileAccounting;
706
- pipelineDiagnostics = pipelineResult.metadata?.diagnostics;
707
- pipelineTestCoverage = pipelineResult.metadata?.testCoverage;
708
- pipelineDeadClusters = pipelineResult.metadata?.deadClusters;
709
- // Spec 60 R2 — aggregate the SOLID analyzer's raw size readings into
710
- // per-measure distributions (median/p95/max + tail annotation).
711
- if (solidBundle) {
712
- const samples = await solidBundle.getSizeSamples();
713
- pipelineSizeDistributions = computeSizeDistributions(samples);
714
- }
1096
+ }
1097
+ catch (error) {
1098
+ if (error instanceof AuditAbortedError) {
1099
+ throw error;
715
1100
  }
716
- catch (error) {
717
- if (error instanceof AuditAbortedError || error instanceof AuditHandoffError) {
718
- throw error;
719
- }
720
- // Pipeline failure — populate error results for all pipeline analyzers.
721
- // Exclude infrastructure visitors (function-index) that don't produce violations.
722
- const pipelineAnalyzerNames = new Set([
723
- ...pipelineVisitors.map(v => v.name),
724
- ...pipelineReducers.map(r => r.name),
725
- ...pipelineDerivedReducers.map(r => r.name),
726
- ]);
727
- pipelineAnalyzerNames.delete('function-index');
728
- for (const name of pipelineAnalyzerNames) {
729
- if (!analyzerResults[name]) {
730
- analyzerResults[name] = {
731
- violations: [],
732
- status: makeVisitorStatus(0),
733
- executionTime: 0,
734
- analyzerName: name,
735
- errors: [{ file: 'pipeline', error: error.message }],
736
- };
737
- }
1101
+ // Pipeline failure — one error, reported once below. Populate a `notRun`
1102
+ // placeholder (NOT `visitor-ran` with zero files) for each pipeline analyzer
1103
+ // so the zero-files diagnostic does not fan this single failure out into N
1104
+ // "matched zero source files" warnings. Exclude infrastructure visitors
1105
+ // (function-index) that don't produce violations. The single `pipeline-error`
1106
+ // diagnostic below is the one signal the CLI gates on for a non-zero exit.
1107
+ const pipelineAnalyzerNames = new Set([
1108
+ ...pipelineVisitors.map(v => v.name),
1109
+ ...pipelineReducers.map(r => r.name),
1110
+ ...pipelineDerivedReducers.map(r => r.name),
1111
+ ]);
1112
+ pipelineAnalyzerNames.delete('function-index');
1113
+ for (const name of pipelineAnalyzerNames) {
1114
+ if (!analyzerResults[name]) {
1115
+ analyzerResults[name] = {
1116
+ violations: [],
1117
+ executionTime: 0,
1118
+ analyzerName: name,
1119
+ status: { status: 'notRun', reason: `pipeline error: ${error.message}` },
1120
+ };
738
1121
  }
739
- reportError(mergedOptions, error, 'pipeline');
740
1122
  }
1123
+ pipelineDiagnostics = [{
1124
+ analyzerName: 'pipeline',
1125
+ kind: 'pipeline-error',
1126
+ message: error.message,
1127
+ }];
1128
+ reportError(mergedOptions, error, 'pipeline');
741
1129
  }
742
- // ── Zero-files diagnostic ─────────────────────────────────────────────
743
- // Extracted to runZeroFilesDiagnostics() for testability. Runs against the
744
- // raw analyzerResults before truthiness filtering so the "no result" pass
745
- // catches analyzers skipped by the registry, abort, or handoff exceptions.
746
- const hasGoFiles = files.some((f) => f.endsWith('.go'));
747
- const zeroFilesDiagnostics = runZeroFilesDiagnostics(enabledAnalyzers, analyzerResults, files.length, hasGoFiles);
748
- for (const w of zeroFilesDiagnostics) {
749
- console.warn(w.message);
1130
+ }
1131
+ // An unavailable index (e.g. a future schema version) is reported as a
1132
+ // coverage diagnostic — never raised as a fatal — so the run still completes
1133
+ // with the exit code it would otherwise have had.
1134
+ if (indexSkipDiagnostic) {
1135
+ pipelineDiagnostics = [...(pipelineDiagnostics ?? []), indexSkipDiagnostic];
1136
+ }
1137
+ return {
1138
+ analyzerResults,
1139
+ phaseFindings,
1140
+ phaseIncompleteFacts,
1141
+ pipelineCoverage,
1142
+ pipelineTableCatalog,
1143
+ pipelineStageTiming,
1144
+ pipelineSkippedFiles,
1145
+ pipelineUnparsedFiles,
1146
+ pipelineInputPresence,
1147
+ pipelineRuleTiming,
1148
+ pipelineFileAccounting,
1149
+ pipelineDiagnostics,
1150
+ pipelineTestCoverage,
1151
+ pipelineDeadClusters,
1152
+ pipelineSizeDistributions,
1153
+ routeAttribution,
1154
+ indexFactsWritten,
1155
+ writeIndexFactsMs,
1156
+ };
1157
+ }
1158
+ /**
1159
+ * Initialize the analyzer-facing CodeIndexDB and build the `IndexHandle` the
1160
+ * pipeline reducers read through. Both are optional: a failed init degrades to
1161
+ * `undefined` and the pipeline continues without index-backed facts. Hoisted out
1162
+ * of `runPipelineStage` so its body stays under the `function-length` ceiling.
1163
+ */
1164
+ async function initializeAuditIndex(root) {
1165
+ let auditIndex;
1166
+ let indexSkipDiagnostic;
1167
+ try {
1168
+ auditIndex = CodeIndexDB.getInstance(undefined, root);
1169
+ await auditIndex.initialize();
1170
+ logMcpInfo('analysis', 'code index initialized for analyzers', { isInitialized: auditIndex.isInitialized, dbPath: auditIndex.dbPath });
1171
+ }
1172
+ catch (err) {
1173
+ // Fail-open (Spec 71 R7 shape): a failed init — including a *future* index
1174
+ // schema version — must not crash the audit. Drop the half-built singleton
1175
+ // (its concern modules are undefined after a failed createSchema) so every
1176
+ // downstream `if (auditIndex)` guard reads "no index", and record the skip
1177
+ // as a coverage diagnostic naming the cause so "indexed analysis skipped"
1178
+ // is visible in the report, not raised as a fatal.
1179
+ auditIndex = undefined;
1180
+ const message = err instanceof Error ? err.message : String(err);
1181
+ const context = err?.context;
1182
+ indexSkipDiagnostic = {
1183
+ analyzerName: 'code-index',
1184
+ kind: 'engine-error',
1185
+ message: `Indexed analysis skipped: ${message}`,
1186
+ ...(context && typeof context.storedSchemaVersion === 'number' && typeof context.supportedSchemaVersion === 'number'
1187
+ ? { details: { storedSchemaVersion: context.storedSchemaVersion, supportedSchemaVersion: context.supportedSchemaVersion } }
1188
+ : {}),
1189
+ };
1190
+ logMcpInfo('analysis', 'failed to initialize code index for analyzers (continuing)', { error: message });
1191
+ }
1192
+ // Build IndexHandle for pipeline reducers
1193
+ let pipelineIndexHandle;
1194
+ if (auditIndex) {
1195
+ pipelineIndexHandle = {
1196
+ query: (sql, params) => auditIndex.rawSql.query(sql, params),
1197
+ count: (table) => auditIndex.rawSql.count(table),
1198
+ tableHasRows: (table) => auditIndex.rawSql.tableHasRows(table),
1199
+ run: (sql, params) => auditIndex.rawDb.prepare(sql).run(...(params ?? [])),
1200
+ exec: (sql) => auditIndex.rawDb.exec(sql),
1201
+ getMeta: (key) => auditIndex.meta.getMeta(key),
1202
+ getUntestedTopDecile: (td) => auditIndex.coverage.getUntestedTopDecile(td),
1203
+ rawDb: auditIndex.rawDb,
1204
+ };
1205
+ }
1206
+ return { auditIndex, pipelineIndexHandle, indexSkipDiagnostic };
1207
+ }
1208
+ /**
1209
+ * Build the ordered analyzer-results map from the raw pipeline output: filter
1210
+ * to truthy entries (so downstream consumers never see undefineds), and insert
1211
+ * the schema sub-visitors and the auto-registered `data-access-org-filter`
1212
+ * reducer in their display positions.
1213
+ */
1214
+ function buildOrderedAnalyzerResults(analyzers, analyzerResults) {
1215
+ const ordered = {};
1216
+ for (const analyzerName of analyzers) {
1217
+ if (analyzerResults[analyzerName]) {
1218
+ ordered[analyzerName] = analyzerResults[analyzerName];
750
1219
  }
751
- // Build ordered results — filter to truthy entries so downstream consumers
752
- // (DRY pair persistence, baseline, report generation) don't see undefineds.
753
- const orderedAnalyzerResults = {};
754
- for (const analyzerName of enabledAnalyzers) {
755
- if (analyzerResults[analyzerName]) {
756
- orderedAnalyzerResults[analyzerName] = analyzerResults[analyzerName];
757
- }
758
- // When schema is enabled, insert sub-visitor rows after the schema entry
759
- // so the CLI table shows per-visitor file/fact counts.
760
- if (analyzerName === 'schema') {
761
- for (const subName of SCHEMA_SUB_VISITORS) {
762
- if (analyzerResults[subName]) {
763
- orderedAnalyzerResults[subName] = analyzerResults[subName];
764
- }
1220
+ // When schema is enabled, insert sub-visitor rows after the schema entry
1221
+ // so the CLI table shows per-visitor file/fact counts.
1222
+ if (analyzerName === 'schema') {
1223
+ for (const subName of SCHEMA_SUB_VISITORS) {
1224
+ if (analyzerResults[subName]) {
1225
+ ordered[subName] = analyzerResults[subName];
765
1226
  }
766
1227
  }
767
- // Spec 62 Amendment B — `data-access-org-filter` is auto-registered (never
768
- // in enabledAnalyzers), so surface its Stage-4 row right after data-access.
769
- if (analyzerName === 'data-access' && analyzerResults['data-access-org-filter']) {
770
- orderedAnalyzerResults['data-access-org-filter'] = analyzerResults['data-access-org-filter'];
771
- }
772
1228
  }
773
- // ── Normalize the analyzer field on every violation to its result key ──
774
- // Violations are emitted by analyzers that don't always stamp `analyzer`
775
- // correctly: the react analyzer omits it entirely, and the schema sub-visitor
776
- // helpers (createSchemaViolation) hardcode `analyzer: 'schema'` for every
777
- // sub-visitor, collapsing schema-code/schema-sql/schema-prisma/schema-json
778
- // into the parent 'schema'. Both surface in the baseline metadata as
779
- // `unknown: 338` / an over-broad `schema` bucket. The result key IS the
780
- // analyzer that emitted the finding, so stamp it here as the single source
781
- // of truth — a no-op for every analyzer that already labels itself, and it
782
- // also fixes any future analyzer that forgets. Runs before baseline
783
- // classification (Spec 18) so fingerprints and analyzerCounts are correct.
784
- // Spec 62 Amendment B — the `data-access-org-filter` reducer stamps its
785
- // findings with `analyzer: 'data-access'` so the ledger group
786
- // `data-access/missing-org-filter` stays stable across the Stage-2 → Stage-4
787
- // move. The result key is otherwise the single source of truth (see above).
788
- const analyzerFieldOverride = {
789
- 'data-access-org-filter': 'data-access',
790
- };
791
- for (const [resultName, result] of Object.entries(orderedAnalyzerResults)) {
792
- const stampedAnalyzer = analyzerFieldOverride[resultName] ?? resultName;
793
- for (const v of result.violations) {
794
- v.analyzer = stampedAnalyzer;
795
- }
1229
+ // Spec 62 Amendment B — `data-access-org-filter` is auto-registered (never
1230
+ // in analyzers), so surface its Stage-4 row right after data-access.
1231
+ if (analyzerName === 'data-access' && analyzerResults['data-access-org-filter']) {
1232
+ ordered['data-access-org-filter'] = analyzerResults['data-access-org-filter'];
796
1233
  }
797
- // ── Spec 13 R5 Phase 1: Persist seeded DRY pairs ──────────────────
798
- // Store pairs from DRY analysis in dry_pair_history for divergence tracking.
799
- // Pair identity is fingerprint-based (file + nodeType + line),
800
- // NOT content-hash-based — so a diverging clone stays the same pair.
801
- let dryPersistRunId = null;
802
- try {
803
- const dryPairs = dryBundle?.getDryPairs();
804
- if (dryPairs && dryPairs.length > 0) {
805
- const indexDb = CodeIndexDB.getInstance(undefined, root);
806
- await indexDb.initialize();
807
- dryPersistRunId = randomUUID();
808
- const insertStmt = indexDb.rawDb.prepare(`
809
- INSERT OR IGNORE INTO dry_pair_history
810
- (pair_fingerprint, file1, symbol1, line1, content_hash1,
811
- file2, symbol2, line2, content_hash2, similarity, timestamp, run_id)
812
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), ?)
813
- `);
814
- const tx = indexDb.rawDb.transaction(() => {
815
- for (const pair of dryPairs) {
816
- insertStmt.run(pair.pairFingerprint, pair.file1, pair.symbol1, pair.line1, pair.contentHash1, pair.file2, pair.symbol2, pair.line2, pair.contentHash2, pair.similarity, dryPersistRunId);
817
- }
818
- });
819
- tx();
1234
+ }
1235
+ return ordered;
1236
+ }
1237
+ /**
1238
+ * Stamp the `analyzer` field on every violation to its result key. Violations
1239
+ * are emitted by analyzers that don't always stamp `analyzer` correctly: the
1240
+ * react analyzer omits it entirely, schema sub-visitor helpers hardcode
1241
+ * `'schema'`, and the `data-access-org-filter` reducer deliberately labels
1242
+ * itself `data-access` to keep the ledger group stable. The result key is the
1243
+ * single source of truth.
1244
+ */
1245
+ function stampAnalyzerFields(ordered) {
1246
+ const overrides = {
1247
+ 'data-access-org-filter': 'data-access',
1248
+ };
1249
+ for (const [resultName, result] of Object.entries(ordered)) {
1250
+ const stamped = overrides[resultName] ?? resultName;
1251
+ for (const v of result.violations) {
1252
+ v.analyzer = stamped;
1253
+ }
1254
+ }
1255
+ }
1256
+ /**
1257
+ * Spec 13 R2 — attach hotspot + reachability scores to every violation and
1258
+ * reorder within severity tiers (live code first, then higher hotspot, original
1259
+ * order as tiebreaker). Advisory — failure is non-fatal (no churn/hotspot data
1260
+ * is common on a first run).
1261
+ */
1262
+ async function applyHotspotScoring(ordered, root) {
1263
+ try {
1264
+ const indexDb = CodeIndexDB.getInstance(undefined, root);
1265
+ await indexDb.initialize();
1266
+ const hotspotRows = indexDb.rawDb
1267
+ .prepare('SELECT target, type, score FROM hotspot_scores')
1268
+ .all();
1269
+ const hotspotByTarget = new Map();
1270
+ const hotspotByFile = new Map();
1271
+ for (const row of hotspotRows) {
1272
+ hotspotByTarget.set(row.target, row.score);
1273
+ if (row.type === 'file') {
1274
+ hotspotByFile.set(row.target, row.score);
820
1275
  }
821
1276
  }
822
- catch {
823
- // dry_pair_history persistence is advisory — non-fatal
1277
+ const reachabilityByFile = new Map();
1278
+ const reachRows = indexDb.rawDb
1279
+ .prepare("SELECT node_key, weight FROM graph_cache WHERE graph_type = 'reachability'")
1280
+ .all();
1281
+ for (const row of reachRows) {
1282
+ reachabilityByFile.set(row.node_key, row.weight);
824
1283
  }
825
- // ── Spec 13 R2 — Hotspot scoring & finding reordering ──────────────
826
- // Attach hotspot scores to violations and reorder within severity tiers.
827
- // Falls back gracefully when no churn/hotspot data exists.
828
- //
829
- // Reachability (live vs dead code) is a second ranking axis written by the
830
- // dependency-graph reducer to graph_cache. Within a severity tier, dead
831
- // code sinks below live code, so a finding in an unreferenced module drops
832
- // while a finding on a framework entry point rises.
833
- try {
834
- const indexDb = CodeIndexDB.getInstance(undefined, root);
835
- await indexDb.initialize();
836
- // Build hotspot lookup: target -> score
837
- const hotspotRows = indexDb.rawDb
838
- .prepare('SELECT target, type, score FROM hotspot_scores')
839
- .all();
840
- const hotspotByTarget = new Map();
841
- const hotspotByFile = new Map();
842
- for (const row of hotspotRows) {
843
- hotspotByTarget.set(row.target, row.score);
844
- if (row.type === 'file') {
845
- hotspotByFile.set(row.target, row.score);
846
- }
847
- }
848
- // Build reachability lookup: file -> score (0 dead, 0.5 imported, 1 entry)
849
- const reachabilityByFile = new Map();
850
- const reachRows = indexDb.rawDb
851
- .prepare("SELECT node_key, weight FROM graph_cache WHERE graph_type = 'reachability'")
852
- .all();
853
- for (const row of reachRows) {
854
- reachabilityByFile.set(row.node_key, row.weight);
855
- }
856
- // Attach hotspot + reachability to each violation and reorder within each analyzer
857
- for (const analyzerName of Object.keys(orderedAnalyzerResults)) {
858
- const result = orderedAnalyzerResults[analyzerName];
859
- const violations = result.violations;
860
- for (const v of violations) {
861
- const funcName = v.functionName;
862
- const file = v.file;
863
- // Try function-level hotspot first: "filePath::functionName"
864
- const funcTarget = funcName ? `${file}::${funcName}` : undefined;
865
- v.hotspot = (funcTarget ? hotspotByTarget.get(funcTarget) : undefined)
866
- ?? hotspotByFile.get(file)
867
- ?? 0;
868
- // Files with no cross-language entities (no reachability data) get a
869
- // neutral 0.5 so we never demote a finding we know nothing about.
870
- v.reachability = reachabilityByFile.get(file) ?? 0.5;
871
- }
872
- // Reorder within severity tiers: reachability descending, then hotspot
873
- // descending, preserving original order as tiebreaker.
874
- const severityOrder = {
875
- critical: 0,
876
- severe: 1,
877
- high: 2,
878
- info: 3,
879
- };
880
- const indexed = violations.map((v, i) => ({ v, i }));
881
- indexed.sort((a, b) => {
882
- const sevA = severityOrder[a.v.severity] ?? 99;
883
- const sevB = severityOrder[b.v.severity] ?? 99;
884
- if (sevA !== sevB)
885
- return sevA - sevB;
886
- // Within same severity: live code first, dead code last
887
- const reachA = a.v.reachability ?? 0.5;
888
- const reachB = b.v.reachability ?? 0.5;
889
- if (reachA !== reachB)
890
- return reachB - reachA;
891
- // Then higher hotspot first
892
- const hsA = a.v.hotspot ?? 0;
893
- const hsB = b.v.hotspot ?? 0;
894
- if (hsA !== hsB)
895
- return hsB - hsA;
896
- // Tiebreaker: original order
897
- return a.i - b.i;
898
- });
899
- result.violations = indexed.map(x => x.v);
1284
+ for (const analyzerName of Object.keys(ordered)) {
1285
+ const result = ordered[analyzerName];
1286
+ const violations = result.violations;
1287
+ for (const v of violations) {
1288
+ const funcName = v.symbol;
1289
+ const file = v.file;
1290
+ // Try function-level hotspot first: "filePath::symbol"
1291
+ const funcTarget = funcName ? `${file}::${funcName}` : undefined;
1292
+ v.hotspot = (funcTarget ? hotspotByTarget.get(funcTarget) : undefined)
1293
+ ?? hotspotByFile.get(file)
1294
+ ?? 0;
1295
+ // Files with no cross-language entities (no reachability data) get a
1296
+ // neutral 0.5 so we never demote a finding we know nothing about.
1297
+ v.reachability = reachabilityByFile.get(file) ?? 0.5;
900
1298
  }
1299
+ const severityOrder = {
1300
+ critical: 0,
1301
+ severe: 1,
1302
+ high: 2,
1303
+ info: 3,
1304
+ };
1305
+ const indexed = violations.map((v, i) => ({ v, i }));
1306
+ indexed.sort((a, b) => {
1307
+ const sevA = severityOrder[a.v.severity] ?? 99;
1308
+ const sevB = severityOrder[b.v.severity] ?? 99;
1309
+ if (sevA !== sevB)
1310
+ return sevA - sevB;
1311
+ const reachA = a.v.reachability ?? 0.5;
1312
+ const reachB = b.v.reachability ?? 0.5;
1313
+ if (reachA !== reachB)
1314
+ return reachB - reachA;
1315
+ const hsA = a.v.hotspot ?? 0;
1316
+ const hsB = b.v.hotspot ?? 0;
1317
+ if (hsA !== hsB)
1318
+ return hsB - hsA;
1319
+ return a.i - b.i;
1320
+ });
1321
+ result.violations = indexed.map((x) => x.v);
1322
+ }
1323
+ }
1324
+ catch {
1325
+ // Hotspot/reachability reordering is advisory — failure is non-fatal
1326
+ }
1327
+ }
1328
+ /**
1329
+ * Spec 18 R1 — classify the ordered findings against the committed baseline,
1330
+ * tagging each violation new/known and computing the baseline metadata. Runs
1331
+ * after the hook-contract guard so it classifies exactly the set that reaches
1332
+ * the report.
1333
+ */
1334
+ function classifyBaselineFindings(ordered, mergedOptions, files, isScoped) {
1335
+ const projectRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
1336
+ const baseline = loadBaseline(projectRoot);
1337
+ let baselineMetadata;
1338
+ if (baseline) {
1339
+ const allViolations = Object.values(ordered).flatMap((r) => r.violations);
1340
+ // For scoped runs, limit "fixed" to in-scope files
1341
+ const scopedFiles = isScoped ? files : undefined;
1342
+ const classified = matchFindings(allViolations, baseline, scopedFiles);
1343
+ // Tag violations with their baseline status
1344
+ for (const v of classified.new) {
1345
+ v.new = true;
1346
+ }
1347
+ for (const v of classified.known) {
1348
+ v.new = false;
901
1349
  }
902
- catch {
903
- // Hotspot/reachability reordering is advisory — failure is non-fatal
1350
+ baselineMetadata = {
1351
+ present: true,
1352
+ hash: hashBaseline(baseline),
1353
+ newCount: classified.new.length,
1354
+ fixedCount: classified.fixed.length,
1355
+ knownCount: classified.known.length,
1356
+ previousKnownCount: baseline.metadata.totalFindings,
1357
+ };
1358
+ logMcpInfo('baseline', 'baseline classification complete', {
1359
+ new: classified.new.length,
1360
+ fixed: classified.fixed.length,
1361
+ known: classified.known.length,
1362
+ });
1363
+ }
1364
+ return { projectRoot, baselineMetadata };
1365
+ }
1366
+ /**
1367
+ * Build the final `AuditResult` from the ordered findings and the pipeline
1368
+ * bundle — summary, metadata assembly (coverage, baseline, diagnostics, the
1369
+ * Spec 60 size/test/distribution reports), and hook-contract-clean violations.
1370
+ */
1371
+ function buildAuditResult(inputs) {
1372
+ const { ordered, files, analyzers, mergedOptions, isScoped, scopeResultType, indexFactsWritten, writeIndexFactsMs, startTime, startCpu, provenanceTiming, blastRadius, zeroFilesDiagnostics, pipelineDiagnostics, rejected, lintConfigDiagnostics, configPath, baselineMetadata, pipelineCoverage, routeAttribution, pipelineTableCatalog, pipelineStageTiming, thresholdChanges, thresholdSources, pipelineSkippedFiles, pipelineUnparsedFiles, skippedExtensions, pipelineInputPresence, pipelineRuleTiming, pipelineFileAccounting, pipelineTestCoverage, pipelineDeadClusters, pipelineSizeDistributions, collectedFunctions, fileToFunctionsMap, } = inputs;
1373
+ // Spec 69 §10 R4 — count query-shaped call sites whose DB receiver is
1374
+ // unproven, keyed by the analyzer that owns the receiver-resolution
1375
+ // instrument (`schema`). This is the site-level `cannot-fire` surface that
1376
+ // `summary.byAnalyzer[].unprovenSites` carries per analyzer.
1377
+ const unprovenSitesByAnalyzer = new Map();
1378
+ for (const d of pipelineDiagnostics ?? []) {
1379
+ if (d.kind !== 'cannot-fire' || d.analyzerName !== 'schema')
1380
+ continue;
1381
+ unprovenSitesByAnalyzer.set(d.analyzerName, (unprovenSitesByAnalyzer.get(d.analyzerName) ?? 0) + 1);
1382
+ }
1383
+ const summary = generateSummary(ordered, files.length, unprovenSitesByAnalyzer);
1384
+ return {
1385
+ timestamp: new Date(),
1386
+ summary,
1387
+ analyzerResults: ordered,
1388
+ recommendations: [],
1389
+ metadata: {
1390
+ auditDuration: Date.now() - startTime,
1391
+ auditCpuMs: cpuDurationMs(startCpu),
1392
+ filesAnalyzed: files.length,
1393
+ ...(indexFactsWritten > 0 && { indexFactsWritten, writeIndexFactsMs }),
1394
+ analyzersRun: analyzers,
1395
+ ...(isScoped && { analyzedFiles: files }),
1396
+ configUsed: mergedOptions,
1397
+ scope: scopeResultType,
1398
+ provenanceResolutionMs: provenanceTiming.totalMs,
1399
+ ...(blastRadius && { blastRadius }),
1400
+ ...((zeroFilesDiagnostics.length > 0 || (pipelineDiagnostics?.length ?? 0) > 0 || rejected.length > 0 || lintConfigDiagnostics.length > 0) && {
1401
+ diagnostics: [
1402
+ ...zeroFilesDiagnostics,
1403
+ ...(pipelineDiagnostics ?? []),
1404
+ ...lintConfigDiagnostics,
1405
+ ...rejected.map((entry) => ({
1406
+ analyzerName: 'config',
1407
+ kind: 'config-key-rejected',
1408
+ message: `${entry.key}: ${entry.value} (${entry.reason})`,
1409
+ ...(configPath ? { file: configPath } : {}),
1410
+ details: { key: entry.key, value: entry.value, reason: entry.reason },
1411
+ })),
1412
+ ],
1413
+ }),
1414
+ ...(baselineMetadata && { baseline: baselineMetadata }),
1415
+ ...(pipelineCoverage && { coverage: pipelineCoverage }),
1416
+ ...(routeAttribution && { routeAttribution }),
1417
+ ...(pipelineTableCatalog && { tableCatalog: pipelineTableCatalog }),
1418
+ ...(pipelineStageTiming && { stageTiming: pipelineStageTiming }),
1419
+ ...(thresholdChanges.length > 0 && { thresholdChanges }),
1420
+ ...(thresholdSources.length > 0 && { thresholdSources }),
1421
+ ...(pipelineSkippedFiles && pipelineSkippedFiles.length > 0 && { skippedFiles: pipelineSkippedFiles }),
1422
+ ...(pipelineUnparsedFiles && pipelineUnparsedFiles.length > 0 && { unparsedFiles: pipelineUnparsedFiles }),
1423
+ ...(skippedExtensions && skippedExtensions.length > 0 && { skippedExtensions }),
1424
+ ...(pipelineInputPresence && { inputPresence: pipelineInputPresence }),
1425
+ ...(pipelineRuleTiming && { ruleTiming: pipelineRuleTiming }),
1426
+ ...(pipelineFileAccounting && { fileAccounting: pipelineFileAccounting }),
1427
+ ...(pipelineTestCoverage && { testCoverage: pipelineTestCoverage }),
1428
+ ...(pipelineDeadClusters && pipelineDeadClusters.length > 0 && { deadClusters: pipelineDeadClusters }),
1429
+ ...(pipelineSizeDistributions && pipelineSizeDistributions.length > 0 && { sizeDistributions: pipelineSizeDistributions }),
1430
+ ...(collectedFunctions.length > 0 && {
1431
+ collectedFunctions,
1432
+ fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
1433
+ })
904
1434
  }
905
- // ── Spec 13 R5 Phase 2: Divergence tracking pass ──────────────────
906
- // For every pair in dry_pair_history, compare the last 2 similarity
907
- // measurements. If similarity has declined by ≥ divergenceThreshold
908
- // for divergenceRuns consecutive runs, emit dry/diverging-clone.
909
- //
910
- // New rows are inserted by Phase 1 for pairs the DRY analyzer re-detects
911
- // this run. Pairs that weren't re-detected don't get a new measurement
912
- // this pass — full source re-read is deferred to the 20% case.
1435
+ };
1436
+ }
1437
+ /**
1438
+ * Spec 11 R1 — fire-and-forget write to the findings ledger. Non-fatal: the
1439
+ * ledger is advisory, so a failed write never invalidates the audit result.
1440
+ * Forked shard workers set `writeToLedger:false` (handled by the caller's
1441
+ * guard here) so the parent run is the single writer.
1442
+ */
1443
+ function fireLedgerWrite(inputs) {
1444
+ if (inputs.mergedOptions.writeToLedger === false) {
1445
+ return;
1446
+ }
1447
+ void (async () => {
913
1448
  try {
914
- const divergenceCfg = mergedOptions.analyzerConfigs?.dry?.divergence
915
- ?? mergedOptions.divergence
916
- ?? { divergenceThreshold: 0.05, divergenceRuns: 2, minPairSimilarity: 0.5 };
917
- const threshold = divergenceCfg.divergenceThreshold ?? 0.05;
918
- const requiredDeclines = divergenceCfg.divergenceRuns ?? 2;
919
- if (threshold > 0) {
920
- const indexDb = CodeIndexDB.getInstance(undefined, root);
921
- await indexDb.initialize();
922
- // Get all distinct pair fingerprints
923
- const fingerprints = indexDb.rawDb
924
- .prepare('SELECT DISTINCT pair_fingerprint FROM dry_pair_history')
925
- .all();
926
- if (fingerprints.length > 0) {
927
- const getRows = indexDb.rawDb.prepare(`
928
- SELECT similarity, timestamp
929
- FROM dry_pair_history
930
- WHERE pair_fingerprint = ?
931
- ORDER BY timestamp ASC
932
- `);
933
- const divergingViolations = [];
934
- for (const { pair_fingerprint: fp } of fingerprints) {
935
- const rows = getRows.all(fp);
936
- if (rows.length < requiredDeclines + 1)
937
- continue;
938
- // Check last `requiredDeclines` consecutive pairs for decline
939
- let consecutiveDeclines = 0;
940
- for (let i = rows.length - requiredDeclines; i < rows.length; i++) {
941
- if (rows[i].similarity < rows[i - 1].similarity - threshold) {
942
- consecutiveDeclines++;
943
- }
944
- }
945
- if (consecutiveDeclines >= requiredDeclines) {
946
- // Get file/line info from the last row
947
- const lastRow = indexDb.rawDb.prepare(`
948
- SELECT file1, file2, line1, line2 FROM dry_pair_history
949
- WHERE pair_fingerprint = ?
950
- ORDER BY timestamp DESC LIMIT 1
951
- `).get(fp);
952
- if (lastRow) {
953
- const currentSim = rows[rows.length - 1].similarity;
954
- const prevSim = rows[rows.length - 2].similarity;
955
- const drop = Math.round((prevSim - currentSim) * 1000) / 1000;
956
- divergingViolations.push({
957
- file: lastRow.file1,
958
- line: lastRow.line1,
959
- severity: 'severe',
960
- message: `Clone pair has diverged: similarity dropped ${drop} (from ${prevSim.toFixed(3)} to ${currentSim.toFixed(3)}) across ${requiredDeclines} consecutive runs (pair: ${fp.slice(0, 12)}…). Review ${lastRow.file1}:${lastRow.line1} and ${lastRow.file2}:${lastRow.line2} for diverged logic.`,
961
- analyzer: 'dry',
962
- rule: 'dry/diverging-clone',
963
- });
964
- }
965
- }
966
- }
967
- if (divergingViolations.length > 0) {
968
- // Append to the dry analyzer result, or create one if none exists
969
- if (orderedAnalyzerResults['dry']) {
970
- orderedAnalyzerResults['dry'].violations.push(...divergingViolations);
971
- }
972
- else {
973
- orderedAnalyzerResults['dry'] = {
974
- violations: divergingViolations,
975
- status: makeVisitorStatus(0),
976
- executionTime: 0,
977
- analyzerName: 'dry',
978
- };
979
- }
980
- }
981
- }
982
- }
1449
+ const indexDb = CodeIndexDB.getInstance(undefined, inputs.root);
1450
+ await indexDb.initialize();
1451
+ const violations = Object.values(inputs.ordered).flatMap((ar) => ar.violations);
1452
+ const scopeStr = Array.isArray(inputs.scope) ? `files:${inputs.scope.length}` : (inputs.scope ?? 'all');
1453
+ return writeAuditToLedger(indexDb.rawDb, detectRunInput(process.argv.slice(2).join(' '), inputs.options.surface ?? 'cli', scopeStr, inputs.root, TOOL_VERSION), violations, Date.now() - inputs.startTime, 0, // exit status TBD — updateLedgerRunStatus by CLI after return
1454
+ { coverage: inputs.result.metadata.coverage });
983
1455
  }
984
- catch {
985
- // Divergence tracking is advisory — non-fatal
1456
+ catch (_err) {
1457
+ // ledger write is non-fatal — audit result is still valid
1458
+ return null;
986
1459
  }
1460
+ })();
1461
+ }
1462
+ /**
1463
+ * Create an audit runner with the given options
1464
+ *
1465
+ * @param options - Audit runner options (project root, analyzer config, thresholds).
1466
+ */
1467
+ export function createAuditRunner(options = {}) {
1468
+ /**
1469
+ * Load configuration from file
1470
+ */
1471
+ async function loadConfiguration(configPath) {
1472
+ const { config } = await loadConfig({ configPath, projectRoot: path.dirname(configPath) });
1473
+ return { ...options, ...config };
1474
+ }
1475
+ /**
1476
+ * Run the audit
1477
+ */
1478
+ async function run(runOptions) {
1479
+ const { mergedOptions, configPath, rejected, lintConfigDiagnostics, thresholdChanges, thresholdSources, } = await resolveRunConfig(options, runOptions);
1480
+ const startTime = Date.now();
1481
+ // CPU-time clock for the load-independent gate metric (Spec 38 R3 / Spec 43
1482
+ // R2-R3): wall clock conflates a slow rule with a slow machine; CPU time
1483
+ // isolates the rule cost. Captured alongside the wall clock so both stay in
1484
+ // the same scope (everything from scope resolution through result creation).
1485
+ const startCpu = process.cpuUsage();
1486
+ const root = path.resolve(mergedOptions.projectRoot || process.cwd());
1487
+ // One-time notice for a legacy in-repo index location (`.code-index` /
1488
+ // `node_modules/.cache/code-auditor`) left by a pre-move build — reported once,
1489
+ // never deleted (see legacyIndexNotice.ts).
1490
+ notifyLegacyIndexLocation(root);
1491
+ // Spec 44 — file accounting accumulator, owned by the run. Threaded through
1492
+ // discovery (full `all` scope) and the pipeline (stage 1/2) so every touched
1493
+ // file lands in exactly one terminal state and the balance can be asserted.
1494
+ const fileAccounting = new FileAccounting();
1495
+ const { files, changedFunctions, skippedExtensions, blastRadius, corpusFiles, scope, isScoped, scopeResultType, } = await discoverAuditFiles(mergedOptions, root, fileAccounting);
1496
+ const { collectedFunctions, fileToFunctionsMap } = await collectIndexedFunctions(mergedOptions, files);
1497
+ // Run analyzers
1498
+ // Spec 68 §15 — no selection model: the full pipeline always runs. `indexOnly`
1499
+ // skips analysis wholesale (the index-only harness); config cannot pick a subset.
1500
+ const analyzers = mergedOptions.indexOnly ? [] : [...RUN_ANALYZERS];
1501
+ logMcpInfo('analysis', 'enabled analyzers', {
1502
+ names: analyzers,
1503
+ fileCount: files.length,
1504
+ scope: scopeResultType
1505
+ });
1506
+ // Spec 21: Shared timing accumulator for provenance resolution.
1507
+ const provenanceTiming = { totalMs: 0 };
1508
+ const { styleConsumedFiles, styleContributingFiles } = await syncStyles(analyzers, root, files, isScoped);
1509
+ const pipeline = await runPipelineStage({
1510
+ mergedOptions, root, files, corpusFiles, isScoped, fileAccounting,
1511
+ styleConsumedFiles, styleContributingFiles, analyzers, provenanceTiming,
1512
+ });
1513
+ const { analyzerResults, phaseFindings, phaseIncompleteFacts, pipelineCoverage, pipelineTableCatalog, pipelineStageTiming, pipelineSkippedFiles, pipelineUnparsedFiles, pipelineInputPresence, pipelineRuleTiming, pipelineFileAccounting, pipelineDiagnostics, pipelineTestCoverage, pipelineDeadClusters, pipelineSizeDistributions, routeAttribution, indexFactsWritten, writeIndexFactsMs, } = pipeline;
1514
+ // ── Zero-files diagnostic ─────────────────────────────────────────────
1515
+ // Extracted to runZeroFilesDiagnostics() for testability. Runs against the
1516
+ // raw analyzerResults before truthiness filtering so the "no result" pass
1517
+ // catches analyzers skipped by the registry, abort, or handoff exceptions.
1518
+ const hasGoFiles = files.some((f) => f.endsWith('.go'));
1519
+ const zeroFilesDiagnostics = runZeroFilesDiagnostics(analyzers, analyzerResults, files.length, hasGoFiles);
1520
+ for (const w of zeroFilesDiagnostics) {
1521
+ console.warn(w.message);
1522
+ }
1523
+ // Build ordered results — filter to truthy entries so downstream consumers
1524
+ // (DRY pair persistence, baseline, report generation) don't see undefineds.
1525
+ const orderedAnalyzerResults = buildOrderedAnalyzerResults(analyzers, analyzerResults);
1526
+ // Normalize the analyzer field on every violation to its result key (the
1527
+ // single source of truth — see stampAnalyzerFields).
1528
+ stampAnalyzerFields(orderedAnalyzerResults);
1529
+ // Spec 13 R2 — hotspot scoring & finding reordering (advisory).
1530
+ await applyHotspotScoring(orderedAnalyzerResults, root);
987
1531
  // Hook-contract guard: no violation may carry an empty file path, line 0, or
988
1532
  // missing line. A sentinel violation with file:'' broke the Claude Code hook's
989
- // JSON consumer (Spec 15 regression). line:0 from CrossDomainAnalyzer broke it
990
- // again (Spec 22 alarm #1). This guard is permanent — every analyzer code path
991
- // must produce properly anchored violations.
992
- //
993
- // Runs BEFORE result construction so the CLI/MCP surfaces never see bad
994
- // violations. Operates on the originals (via splice), not a copy.
1533
+ // JSON consumer (Spec 15 regression); line:0 from CrossDomainAnalyzer broke it
1534
+ // again (Spec 22 alarm #1). Permanent — every analyzer code path must produce
1535
+ // properly anchored violations. Operates on the originals (via splice).
995
1536
  for (const ar of Object.values(orderedAnalyzerResults)) {
996
1537
  validateHookContract(ar.violations);
997
1538
  }
998
- // ── Baseline classification (Spec 18 R1) ────────────────────────────
999
- // Runs AFTER validateHookContract (and after divergence tracking) so it
1000
- // classifies exactly the violation set that reaches the report. Previously
1001
- // this ran before the hook-contract guard spliced out malformed violations
1002
- // (empty file / line 0 / missing line), so newCount+knownCount over-counted
1003
- // by the number of spliced findings and no longer reconciled with
1004
- // summary.totalViolations.
1005
- const projectRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
1006
- const baseline = loadBaseline(projectRoot);
1007
- let baselineMetadata;
1008
- if (baseline) {
1009
- const allViolations = Object.values(orderedAnalyzerResults).flatMap((r) => r.violations);
1010
- // For scoped runs, limit "fixed" to in-scope files
1011
- const scopedFiles = isScoped ? files : undefined;
1012
- const classified = matchFindings(allViolations, baseline, scopedFiles);
1013
- // Tag violations with their baseline status
1014
- for (const v of classified.new) {
1015
- v.new = true;
1016
- }
1017
- for (const v of classified.known) {
1018
- v.new = false;
1019
- }
1020
- baselineMetadata = {
1021
- present: true,
1022
- hash: hashBaseline(baseline),
1023
- newCount: classified.new.length,
1024
- fixedCount: classified.fixed.length,
1025
- knownCount: classified.known.length,
1026
- previousKnownCount: baseline.metadata.totalFindings,
1027
- };
1028
- logMcpInfo('baseline', 'baseline classification complete', {
1029
- new: classified.new.length,
1030
- fixed: classified.fixed.length,
1031
- known: classified.known.length,
1032
- });
1033
- }
1034
- // Generate summary
1035
- const summary = generateSummary(orderedAnalyzerResults, files.length);
1036
- // Create result
1037
- const result = {
1038
- timestamp: new Date(),
1039
- summary,
1040
- analyzerResults: orderedAnalyzerResults,
1041
- recommendations: [],
1042
- metadata: {
1043
- auditDuration: Date.now() - startTime,
1044
- filesAnalyzed: files.length,
1045
- analyzersRun: enabledAnalyzers,
1046
- ...(isScoped && { analyzedFiles: files }),
1047
- configUsed: mergedOptions,
1048
- scope: scopeResultType,
1049
- provenanceResolutionMs: provenanceTiming.totalMs,
1050
- ...(blastRadius && { blastRadius }),
1051
- ...((zeroFilesDiagnostics.length > 0 || (pipelineDiagnostics?.length ?? 0) > 0 || rejected.length > 0 || lintConfigDiagnostics.length > 0) && {
1052
- diagnostics: [
1053
- ...zeroFilesDiagnostics,
1054
- ...(pipelineDiagnostics ?? []),
1055
- ...lintConfigDiagnostics,
1056
- ...rejected.map((entry) => ({
1057
- analyzerName: 'config',
1058
- kind: 'config-key-rejected',
1059
- message: `${entry.key}: ${entry.value} (${entry.reason})`,
1060
- ...(configPath ? { file: configPath } : {}),
1061
- details: { key: entry.key, value: entry.value, reason: entry.reason },
1062
- })),
1063
- ],
1064
- }),
1065
- ...(baselineMetadata && { baseline: baselineMetadata }),
1066
- ...(pipelineCoverage && { coverage: pipelineCoverage }),
1067
- ...(pipelineTableCatalog && { tableCatalog: pipelineTableCatalog }),
1068
- ...(pipelineStageTiming && { stageTiming: pipelineStageTiming }),
1069
- ...(thresholdChanges.length > 0 && { thresholdChanges }),
1070
- ...(thresholdSources.length > 0 && { thresholdSources }),
1071
- ...(pipelineSkippedFiles && pipelineSkippedFiles.length > 0 && { skippedFiles: pipelineSkippedFiles }),
1072
- ...(pipelineUnparsedFiles && pipelineUnparsedFiles.length > 0 && { unparsedFiles: pipelineUnparsedFiles }),
1073
- ...(skippedExtensions && skippedExtensions.length > 0 && { skippedExtensions }),
1074
- ...(pipelineInputPresence && { inputPresence: pipelineInputPresence }),
1075
- ...(pipelineRuleTiming && { ruleTiming: pipelineRuleTiming }),
1076
- ...(pipelineFileAccounting && { fileAccounting: pipelineFileAccounting }),
1077
- ...(pipelineTestCoverage && { testCoverage: pipelineTestCoverage }),
1078
- ...(pipelineDeadClusters && pipelineDeadClusters.length > 0 && { deadClusters: pipelineDeadClusters }),
1079
- ...(pipelineSizeDistributions && pipelineSizeDistributions.length > 0 && { sizeDistributions: pipelineSizeDistributions }),
1080
- ...(collectedFunctions.length > 0 && {
1081
- collectedFunctions,
1082
- fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
1083
- })
1084
- }
1085
- };
1539
+ // Baseline classification (Spec 18 R1) — after the hook-contract guard so it
1540
+ // classifies exactly the violation set that reaches the report.
1541
+ const { projectRoot, baselineMetadata } = classifyBaselineFindings(orderedAnalyzerResults, mergedOptions, files, isScoped);
1542
+ const result = buildAuditResult({
1543
+ ordered: orderedAnalyzerResults,
1544
+ files,
1545
+ analyzers,
1546
+ mergedOptions,
1547
+ isScoped,
1548
+ scopeResultType,
1549
+ indexFactsWritten,
1550
+ writeIndexFactsMs,
1551
+ startTime,
1552
+ startCpu,
1553
+ provenanceTiming,
1554
+ blastRadius,
1555
+ zeroFilesDiagnostics,
1556
+ pipelineDiagnostics,
1557
+ rejected,
1558
+ lintConfigDiagnostics,
1559
+ configPath,
1560
+ baselineMetadata,
1561
+ pipelineCoverage,
1562
+ routeAttribution,
1563
+ pipelineTableCatalog,
1564
+ pipelineStageTiming,
1565
+ thresholdChanges,
1566
+ thresholdSources,
1567
+ pipelineSkippedFiles,
1568
+ pipelineUnparsedFiles,
1569
+ skippedExtensions,
1570
+ pipelineInputPresence,
1571
+ pipelineRuleTiming,
1572
+ pipelineFileAccounting,
1573
+ pipelineTestCoverage,
1574
+ pipelineDeadClusters,
1575
+ pipelineSizeDistributions,
1576
+ collectedFunctions,
1577
+ fileToFunctionsMap,
1578
+ });
1086
1579
  // Report completion
1087
1580
  reportProgress(mergedOptions, {
1088
1581
  phase: 'reporting',
1089
1582
  message: 'Generating reports...'
1090
1583
  });
1091
- if (handoffRemaining.length > 0) {
1092
- throw new AuditHandoffError(`${handoffRemaining.length} file(s) deferred to the next worker chunk`, result, handoffRemaining);
1093
- }
1094
1584
  // Spec 57 — apply committed dismissals: mark matching findings dismissed
1095
1585
  // (they never gate) and record the dismissed count in the summary (never
1096
1586
  // subtracted from totalViolations).
1097
1587
  applyDismissals(result, projectRoot);
1098
1588
  // Spec 11 R1 — write to findings ledger (non-fatal: ledger is advisory).
1099
- // Forked shard workers set writeToLedger:false so the parent run is the
1100
- // single ledger writer (see AuditRunnerOptions.writeToLedger).
1101
- if (mergedOptions.writeToLedger !== false) {
1102
- void (async () => {
1103
- try {
1104
- const indexDb = CodeIndexDB.getInstance(undefined, root);
1105
- await indexDb.initialize();
1106
- const violations = Object.values(orderedAnalyzerResults).flatMap(ar => ar.violations);
1107
- const scopeStr = Array.isArray(scope) ? `files:${scope.length}` : (scope ?? 'all');
1108
- return writeAuditToLedger(indexDb.rawDb, detectRunInput(process.argv.slice(2).join(' '), options.surface ?? 'cli', scopeStr, root, TOOL_VERSION), violations, Date.now() - startTime, 0, // exit status TBD — updateLedgerRunStatus by CLI after return
1109
- { coverage: result.metadata.coverage });
1110
- }
1111
- catch (_err) {
1112
- // ledger write is non-fatal — audit result is still valid
1113
- return null;
1114
- }
1115
- })();
1116
- }
1589
+ fireLedgerWrite({ mergedOptions, root, options, scope, ordered: orderedAnalyzerResults, result, startTime });
1117
1590
  return result;
1118
1591
  }
1119
1592
  /**
@@ -1219,9 +1692,18 @@ function resolveGitScopeFiles(options, ref) {
1219
1692
  /**
1220
1693
  * Resolve files scope: paths can be file paths or globs.
1221
1694
  * Absolute paths are used directly; relative paths are resolved
1222
- * against the project root; globs use discoverFiles.
1695
+ * against the project root; globs use discoverFiles. A direct path whose
1696
+ * extension no analyzer claims (nor a raw/markup extension the pipeline reads
1697
+ * directly) is dropped — see `getSourceExtensions` — so a prose edit never
1698
+ * reaches the pipeline as a "dark analyzer" and trips the zero-files gate.
1699
+ *
1700
+ * @param options - The merged audit-runner options (`projectRoot`, `excludePaths`,
1701
+ * `fileExtensions`).
1702
+ * @param scopeFiles - Candidate paths or globs to resolve into an absolute file
1703
+ * set.
1704
+ * @returns The sorted set of absolute paths that are in audit scope.
1223
1705
  */
1224
- async function resolveFilesScope(options, scopeFiles) {
1706
+ export async function resolveFilesScope(options, scopeFiles) {
1225
1707
  const rootDir = path.resolve(options.projectRoot || process.cwd());
1226
1708
  const result = new Set();
1227
1709
  for (const item of scopeFiles) {
@@ -1237,8 +1719,20 @@ async function resolveFilesScope(options, scopeFiles) {
1237
1719
  result.add(m);
1238
1720
  }
1239
1721
  else {
1240
- // Direct file path — skip files that don't exist
1722
+ // Direct file path — skip files that don't exist, and files no analyzer
1723
+ // understands. A file whose extension is not claimed by the language
1724
+ // registry (nor a raw/markup extension the pipeline reads directly) was
1725
+ // never in scope: it must not enter `files` and trip the zero-files gate
1726
+ // as a "dark analyzer", which would turn a prose edit into an exit-2 hook
1727
+ // failure. The Spec 32 parse-failure gate is untouched — a claimed-but-
1728
+ // unparseable file (a `.ts` that fails to parse) still reaches the
1729
+ // pipeline and stays loud. The gate is the registry union (via
1730
+ // `getSourceExtensions`), not a hand-maintained extension list, so a parser
1731
+ // added to the registry is in scope here without a second edit.
1241
1732
  const resolved = path.isAbsolute(item) ? item : path.resolve(rootDir, item);
1733
+ const ext = path.extname(resolved).toLowerCase();
1734
+ if (!getSourceExtensions().has(ext))
1735
+ continue;
1242
1736
  try {
1243
1737
  await fs.stat(resolved);
1244
1738
  result.add(resolved);
@@ -1251,27 +1745,21 @@ async function resolveFilesScope(options, scopeFiles) {
1251
1745
  return [...result].sort();
1252
1746
  }
1253
1747
  /**
1254
- * Get list of enabled analyzers
1255
- */
1256
- function getEnabledAnalyzers(options, registry) {
1257
- // Explicit array (including empty = run no analyzers, e.g. index-only harness)
1258
- if (options.enabledAnalyzers !== undefined) {
1259
- // invariants auto-disables at runtime inside the pipeline reducer when no
1260
- // rules are configured — it stays in the list so coverage can report its
1261
- // rules as notApplicable (Spec 27 criterion 5).
1262
- return options.enabledAnalyzers;
1263
- }
1264
- const allAnalyzers = Object.keys(registry);
1265
- return allAnalyzers;
1266
- }
1267
- /**
1268
- * Generate audit summary
1748
+ * Generate audit summary.
1749
+ *
1750
+ * Exported for the Spec 69 §10 R4 pin: `summary.byAnalyzer[].unprovenSites`
1751
+ * must carry the site-level cannot-fire count per analyzer.
1752
+ *
1753
+ * @param analyzerResults per-analyzer results, keyed by analyzer name
1754
+ * @param filesAnalyzed total number of files processed this run
1755
+ * @param unprovenSitesByAnalyzer per-analyzer count of unproven (cannot-fire) sites
1269
1756
  */
1270
- function generateSummary(analyzerResults, filesAnalyzed) {
1757
+ export function generateSummary(analyzerResults, filesAnalyzed, unprovenSitesByAnalyzer = new Map()) {
1271
1758
  let totalViolations = 0;
1272
1759
  let criticalIssues = 0;
1273
1760
  let severe = 0;
1274
1761
  let high = 0;
1762
+ let heldOut = 0;
1275
1763
  const violationsByCategory = {};
1276
1764
  const byAnalyzer = {};
1277
1765
  for (const [analyzer, result] of Object.entries(analyzerResults)) {
@@ -1279,6 +1767,8 @@ function generateSummary(analyzerResults, filesAnalyzed) {
1279
1767
  for (const violation of result.violations) {
1280
1768
  totalViolations++;
1281
1769
  analyzerViolations++;
1770
+ if (violation.gateExcluded)
1771
+ heldOut++;
1282
1772
  switch (violation.severity) {
1283
1773
  case 'critical':
1284
1774
  criticalIssues++;
@@ -1297,6 +1787,7 @@ function generateSummary(analyzerResults, filesAnalyzed) {
1297
1787
  violations: analyzerViolations,
1298
1788
  filesProcessed: getFilesProcessed(result.status),
1299
1789
  fatalErrors: result.errors ? result.errors.length : 0,
1790
+ unprovenSites: unprovenSitesByAnalyzer.get(analyzer) ?? 0,
1300
1791
  };
1301
1792
  }
1302
1793
  // Compute top issues from violationsByCategory
@@ -1304,6 +1795,12 @@ function generateSummary(analyzerResults, filesAnalyzed) {
1304
1795
  .sort(([, a], [, b]) => b - a)
1305
1796
  .slice(0, 5)
1306
1797
  .map(([type, count]) => ({ type, count }));
1798
+ // Spec 69 §10 R4 follow-up — the site-level cannot-fire total, summed across
1799
+ // analyzers, so the headline can state findings + unproven sites + held-out
1800
+ // readings together without re-deriving from per-analyzer buckets.
1801
+ let unprovenSites = 0;
1802
+ for (const n of unprovenSitesByAnalyzer.values())
1803
+ unprovenSites += n;
1307
1804
  return {
1308
1805
  totalFiles: filesAnalyzed,
1309
1806
  totalViolations,
@@ -1312,7 +1809,9 @@ function generateSummary(analyzerResults, filesAnalyzed) {
1312
1809
  high,
1313
1810
  violationsByCategory,
1314
1811
  byAnalyzer,
1315
- topIssues
1812
+ topIssues,
1813
+ unprovenSites,
1814
+ heldOut
1316
1815
  };
1317
1816
  }
1318
1817
  /**
@@ -1405,17 +1904,23 @@ function mergeLintUnderProject(lintFragment, projectConfig) {
1405
1904
  *
1406
1905
  * Pass 2: Every analyzer with a result entry but filesProcessed === 0 and no
1407
1906
  * errors fires a warning — the analyzer ran but matched zero source files.
1907
+ *
1908
+ * @param analyzers - Names of the enabled analyzers to check against the results.
1909
+ * @param analyzerResults - Map of analyzer name to its audit result.
1910
+ * @param totalFiles - Total files processed; a zero total suppresses the warning as expected.
1911
+ * @param hasGoFiles - Whether the corpus contained `.go` files (false excuses a skipped `go` analyzer).
1912
+ * @returns Diagnostic warnings for analyzers that produced no result or matched zero files.
1408
1913
  */
1409
- export function runZeroFilesDiagnostics(enabledAnalyzers, analyzerResults, totalFiles, hasGoFiles) {
1914
+ export function runZeroFilesDiagnostics(analyzers, analyzerResults, totalFiles, hasGoFiles) {
1410
1915
  // When there were zero files to process, zero-files is expected, not a bug.
1411
1916
  if (totalFiles === 0)
1412
1917
  return [];
1413
1918
  const warnings = [];
1414
1919
  // Pass 1: enabled but absent from results
1415
- for (const analyzerName of enabledAnalyzers) {
1920
+ for (const analyzerName of analyzers) {
1416
1921
  if (!analyzerResults[analyzerName]) {
1417
- // The `go` analyzer is a polyglot subprocess reached only via
1418
- // `runAuditDispatch` when `.go` files exist. On a TypeScript-only corpus
1922
+ // The `go` analyzer is a polyglot subprocess that runs only when `.go`
1923
+ // files exist. On a TypeScript-only corpus
1419
1924
  // it is legitimately notApplicable, not a dropped analyzer (defect #50) —
1420
1925
  // zero `.go` files means there was nothing for it to run on.
1421
1926
  if (analyzerName === 'go' && hasGoFiles === false)
@@ -1428,23 +1933,28 @@ export function runZeroFilesDiagnostics(enabledAnalyzers, analyzerResults, total
1428
1933
  });
1429
1934
  }
1430
1935
  }
1431
- // Pass 2: filesProcessed = 0 — a dark-analyzer failure regardless of errors.
1432
- // Visitors with declared extensions that matched zero files in the corpus are
1433
- // benign (converted to notRun by the pipeline), but a visitor that was dispatched
1434
- // files and still shows visitor-ran + 0 files is broken — whether it errored or
1435
- // silently returned.
1936
+ // Pass 2: filesProcessed = 0 — a dark-analyzer failure. Visitors with declared
1937
+ // extensions that matched zero files in the corpus are benign (converted to
1938
+ // notRun by the pipeline), but a visitor that was dispatched files and still
1939
+ // shows visitor-ran + 0 files is broken. When it errored, name the file(s) and
1940
+ // error(s) — "check file extensions" is the wrong cause for a parse/visit error
1941
+ // and would send the reader to look at configuration when a file failed.
1436
1942
  for (const [analyzerName, result] of Object.entries(analyzerResults)) {
1437
1943
  if (analyzerName === 'go' && hasGoFiles === false)
1438
1944
  continue;
1439
1945
  if (isVisitorStatus(result.status) && getFilesProcessed(result.status) === 0) {
1440
- const errCount = result.errors?.length ?? 0;
1441
- const errDetail = errCount > 0 ? ` (${errCount} file error(s))` : '';
1946
+ const errs = result.errors;
1947
+ const errDetail = errs && errs.length > 0
1948
+ ? ` — ${errs.length} file error(s): ${errs.slice(0, 3).map((e) => `${e.file}: ${e.error}`).join('; ')}${errs.length > 3 ? ' …' : ''}`
1949
+ : '';
1442
1950
  warnings.push({
1443
1951
  analyzerName,
1444
1952
  kind: 'zero-files',
1445
- message: `⚠️ ${analyzerName} analyzer: filesProcessed = 0${errDetail}. ` +
1446
- `The analyzer ran but matched zero source files. Check file extensions, ` +
1447
- `scanner configuration, and project structure.`,
1953
+ message: errDetail
1954
+ ? `⚠️ ${analyzerName} analyzer: filesProcessed = 0${errDetail}.`
1955
+ : `⚠️ ${analyzerName} analyzer: filesProcessed = 0. ` +
1956
+ `The analyzer ran but matched zero source files. Check file extensions, ` +
1957
+ `scanner configuration, and project structure.`,
1448
1958
  });
1449
1959
  }
1450
1960
  }