code-auditor-mcp 4.1.0 → 5.0.1

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