code-auditor-mcp 3.1.1 → 3.4.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 (361) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +724 -1
  3. package/README.md +85 -11
  4. package/dist/__test_tmp__/spec18_test.d.ts +2 -0
  5. package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
  6. package/dist/__test_tmp__/spec18_test.js +5 -0
  7. package/dist/__test_tmp__/spec18_test.js.map +1 -0
  8. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
  9. package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
  11. package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
  12. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
  13. package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
  15. package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
  16. package/dist/analyzers/analyzerUtils.js +2 -2
  17. package/dist/analyzers/analyzerUtils.js.map +1 -1
  18. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
  19. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  20. package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -2
  21. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  22. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
  23. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -3
  25. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  26. package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
  27. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  28. package/dist/analyzers/cross-language/SchemaValidator.js +2 -2
  29. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  30. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
  31. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
  32. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
  33. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
  34. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  35. package/dist/analyzers/documentationAnalyzer.js +8 -4
  36. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  37. package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
  38. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
  39. package/dist/analyzers/orm/adapterRegistry.js +73 -0
  40. package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
  41. package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
  42. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
  43. package/dist/analyzers/orm/drizzleAdapter.js +166 -0
  44. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
  45. package/dist/analyzers/orm/index.d.ts +14 -0
  46. package/dist/analyzers/orm/index.d.ts.map +1 -0
  47. package/dist/analyzers/orm/index.js +24 -0
  48. package/dist/analyzers/orm/index.js.map +1 -0
  49. package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
  50. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
  51. package/dist/analyzers/orm/prismaAdapter.js +170 -0
  52. package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
  53. package/dist/analyzers/orm/types.d.ts +63 -0
  54. package/dist/analyzers/orm/types.d.ts.map +1 -0
  55. package/dist/analyzers/orm/types.js +10 -0
  56. package/dist/analyzers/orm/types.js.map +1 -0
  57. package/dist/analyzers/provenance.d.ts +133 -0
  58. package/dist/analyzers/provenance.d.ts.map +1 -0
  59. package/dist/analyzers/provenance.js +1041 -0
  60. package/dist/analyzers/provenance.js.map +1 -0
  61. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  62. package/dist/analyzers/reactAnalyzer.js +117 -1
  63. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  64. package/dist/analyzers/ruleRegistry.d.ts +38 -0
  65. package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
  66. package/dist/analyzers/ruleRegistry.js +123 -0
  67. package/dist/analyzers/ruleRegistry.js.map +1 -0
  68. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
  69. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
  70. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
  71. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
  72. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
  73. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  74. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
  75. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  76. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
  77. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  78. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -53
  79. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  80. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  81. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
  82. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  83. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  84. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
  85. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  86. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
  87. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  88. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
  89. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  90. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
  91. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
  92. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
  93. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
  94. package/dist/auditRunner.d.ts.map +1 -1
  95. package/dist/auditRunner.js +499 -4
  96. package/dist/auditRunner.js.map +1 -1
  97. package/dist/baseline.d.ts +98 -0
  98. package/dist/baseline.d.ts.map +1 -0
  99. package/dist/baseline.js +184 -0
  100. package/dist/baseline.js.map +1 -0
  101. package/dist/churn/churnExtractor.d.ts +32 -0
  102. package/dist/churn/churnExtractor.d.ts.map +1 -0
  103. package/dist/churn/churnExtractor.js +264 -0
  104. package/dist/churn/churnExtractor.js.map +1 -0
  105. package/dist/cli.js +1218 -11
  106. package/dist/cli.js.map +1 -1
  107. package/dist/codeIndexDB-enhanced.js +1 -1
  108. package/dist/codeIndexDB-enhanced.js.map +1 -1
  109. package/dist/codeIndexDB.d.ts +101 -0
  110. package/dist/codeIndexDB.d.ts.map +1 -1
  111. package/dist/codeIndexDB.js +665 -5
  112. package/dist/codeIndexDB.js.map +1 -1
  113. package/dist/config/configLoader.d.ts.map +1 -1
  114. package/dist/config/configLoader.js +104 -7
  115. package/dist/config/configLoader.js.map +1 -1
  116. package/dist/config/defaults.d.ts +72 -1
  117. package/dist/config/defaults.d.ts.map +1 -1
  118. package/dist/config/defaults.js +146 -15
  119. package/dist/config/defaults.js.map +1 -1
  120. package/dist/config/pathProfiles.d.ts +38 -0
  121. package/dist/config/pathProfiles.d.ts.map +1 -0
  122. package/dist/config/pathProfiles.js +47 -0
  123. package/dist/config/pathProfiles.js.map +1 -0
  124. package/dist/conventions/conventionMiner.d.ts +63 -0
  125. package/dist/conventions/conventionMiner.d.ts.map +1 -0
  126. package/dist/conventions/conventionMiner.js +696 -0
  127. package/dist/conventions/conventionMiner.js.map +1 -0
  128. package/dist/coverage/coverageService.d.ts +49 -0
  129. package/dist/coverage/coverageService.d.ts.map +1 -0
  130. package/dist/coverage/coverageService.js +117 -0
  131. package/dist/coverage/coverageService.js.map +1 -0
  132. package/dist/coverage/istanbulParser.d.ts +84 -0
  133. package/dist/coverage/istanbulParser.d.ts.map +1 -0
  134. package/dist/coverage/istanbulParser.js +120 -0
  135. package/dist/coverage/istanbulParser.js.map +1 -0
  136. package/dist/coverage/lcovParser.d.ts +40 -0
  137. package/dist/coverage/lcovParser.d.ts.map +1 -0
  138. package/dist/coverage/lcovParser.js +168 -0
  139. package/dist/coverage/lcovParser.js.map +1 -0
  140. package/dist/fingerprint.d.ts +17 -0
  141. package/dist/fingerprint.d.ts.map +1 -1
  142. package/dist/fingerprint.js +42 -0
  143. package/dist/fingerprint.js.map +1 -1
  144. package/dist/grammars/tree-sitter-css.wasm +0 -0
  145. package/dist/graph/blastRadius.d.ts +27 -0
  146. package/dist/graph/blastRadius.d.ts.map +1 -0
  147. package/dist/graph/blastRadius.js +150 -0
  148. package/dist/graph/blastRadius.js.map +1 -0
  149. package/dist/graph/callGraph.d.ts +78 -0
  150. package/dist/graph/callGraph.d.ts.map +1 -0
  151. package/dist/graph/callGraph.js +484 -0
  152. package/dist/graph/callGraph.js.map +1 -0
  153. package/dist/graph/importGraph.d.ts +91 -0
  154. package/dist/graph/importGraph.d.ts.map +1 -0
  155. package/dist/graph/importGraph.js +602 -0
  156. package/dist/graph/importGraph.js.map +1 -0
  157. package/dist/graph/outputFormatter.d.ts +55 -0
  158. package/dist/graph/outputFormatter.d.ts.map +1 -0
  159. package/dist/graph/outputFormatter.js +283 -0
  160. package/dist/graph/outputFormatter.js.map +1 -0
  161. package/dist/hooks/codex.d.ts +36 -1
  162. package/dist/hooks/codex.d.ts.map +1 -1
  163. package/dist/hooks/codex.js +88 -54
  164. package/dist/hooks/codex.js.map +1 -1
  165. package/dist/hotspots/hotspotScorer.d.ts +20 -0
  166. package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
  167. package/dist/hotspots/hotspotScorer.js +202 -0
  168. package/dist/hotspots/hotspotScorer.js.map +1 -0
  169. package/dist/invariants/invariant-rules.schema.json +42 -1
  170. package/dist/invariants/ruleEngine.d.ts +4 -1
  171. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  172. package/dist/invariants/ruleEngine.js +141 -5
  173. package/dist/invariants/ruleEngine.js.map +1 -1
  174. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  175. package/dist/invariants/ruleValidator.js +62 -1
  176. package/dist/invariants/ruleValidator.js.map +1 -1
  177. package/dist/invariants/types.d.ts +23 -2
  178. package/dist/invariants/types.d.ts.map +1 -1
  179. package/dist/languages/UniversalAnalyzer.d.ts +1 -1
  180. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  181. package/dist/languages/UniversalAnalyzer.js +61 -4
  182. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  183. package/dist/languages/index.d.ts +1 -0
  184. package/dist/languages/index.d.ts.map +1 -1
  185. package/dist/languages/index.js +3 -0
  186. package/dist/languages/index.js.map +1 -1
  187. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
  188. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
  189. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
  191. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  192. package/dist/languages/tree-sitter/parser.js +2 -0
  193. package/dist/languages/tree-sitter/parser.js.map +1 -1
  194. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  195. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
  196. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  197. package/dist/ledger.d.ts +128 -0
  198. package/dist/ledger.d.ts.map +1 -0
  199. package/dist/ledger.js +345 -0
  200. package/dist/ledger.js.map +1 -0
  201. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  202. package/dist/mcp-tools/projectTasks.js +9 -14
  203. package/dist/mcp-tools/projectTasks.js.map +1 -1
  204. package/dist/mcp.js +0 -0
  205. package/dist/mcpAuditJobs.d.ts.map +1 -1
  206. package/dist/mcpAuditJobs.js +3 -0
  207. package/dist/mcpAuditJobs.js.map +1 -1
  208. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  209. package/dist/reporting/csvReportGenerator.js +7 -1
  210. package/dist/reporting/csvReportGenerator.js.map +1 -1
  211. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  212. package/dist/reporting/htmlReportGenerator.js +5 -1
  213. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  214. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  215. package/dist/reporting/jsonReportGenerator.js +10 -2
  216. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  217. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  218. package/dist/reporting/sarifReportGenerator.js +17 -17
  219. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  220. package/dist/scripts/runBench.d.ts +94 -0
  221. package/dist/scripts/runBench.d.ts.map +1 -0
  222. package/dist/scripts/runBench.js +1559 -0
  223. package/dist/scripts/runBench.js.map +1 -0
  224. package/dist/search/QueryParser.d.ts.map +1 -1
  225. package/dist/search/QueryParser.js +51 -4
  226. package/dist/search/QueryParser.js.map +1 -1
  227. package/dist/services/CodeMapGenerator.d.ts +33 -0
  228. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  229. package/dist/services/CodeMapGenerator.js +404 -0
  230. package/dist/services/CodeMapGenerator.js.map +1 -1
  231. package/dist/styles/normalizer.d.ts +37 -0
  232. package/dist/styles/normalizer.d.ts.map +1 -0
  233. package/dist/styles/normalizer.js +301 -0
  234. package/dist/styles/normalizer.js.map +1 -0
  235. package/dist/styles/styleExtractor.d.ts +38 -0
  236. package/dist/styles/styleExtractor.d.ts.map +1 -0
  237. package/dist/styles/styleExtractor.js +630 -0
  238. package/dist/styles/styleExtractor.js.map +1 -0
  239. package/dist/styles/styleIndexer.d.ts +35 -0
  240. package/dist/styles/styleIndexer.d.ts.map +1 -0
  241. package/dist/styles/styleIndexer.js +266 -0
  242. package/dist/styles/styleIndexer.js.map +1 -0
  243. package/dist/styles/tailwindConfigLoader.d.ts +38 -0
  244. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
  245. package/dist/styles/tailwindConfigLoader.js +374 -0
  246. package/dist/styles/tailwindConfigLoader.js.map +1 -0
  247. package/dist/styles/tailwindExpander.d.ts +38 -0
  248. package/dist/styles/tailwindExpander.d.ts.map +1 -0
  249. package/dist/styles/tailwindExpander.js +637 -0
  250. package/dist/styles/tailwindExpander.js.map +1 -0
  251. package/dist/styles/types.d.ts +92 -0
  252. package/dist/styles/types.d.ts.map +1 -0
  253. package/dist/styles/types.js +10 -0
  254. package/dist/styles/types.js.map +1 -0
  255. package/dist/symbols.d.ts +21 -0
  256. package/dist/symbols.d.ts.map +1 -0
  257. package/dist/symbols.js +31 -0
  258. package/dist/symbols.js.map +1 -0
  259. package/dist/types.d.ts +326 -3
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/dist/utils/fileDiscovery.d.ts +1 -0
  263. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  264. package/dist/utils/fileDiscovery.js +8 -2
  265. package/dist/utils/fileDiscovery.js.map +1 -1
  266. package/package.json +9 -5
  267. package/plugin/.claude-plugin/plugin.json +1 -1
  268. package/plugin/scripts/hook-audit.sh +5 -1
  269. package/plugin/skills/code-auditor/SKILL.md +51 -0
  270. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
  271. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
  272. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
  273. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
  274. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
  275. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
  276. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
  277. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
  278. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
  279. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
  280. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
  281. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
  282. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
  283. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
  284. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
  285. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
  286. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
  287. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
  288. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
  289. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
  290. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
  291. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
  292. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
  293. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
  294. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
  295. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
  296. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
  297. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
  298. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
  299. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
  300. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
  301. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
  302. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
  303. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
  304. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
  305. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
  306. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
  307. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
  308. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
  309. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
  310. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
  311. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
  312. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
  313. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
  314. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
  315. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
  316. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
  317. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
  318. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
  319. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
  320. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
  321. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
  322. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
  323. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
  324. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
  325. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
  326. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
  327. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
  328. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
  329. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
  330. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
  331. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
  332. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
  333. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
  334. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
  335. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
  336. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
  337. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
  338. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
  339. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
  340. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
  341. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
  342. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
  343. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
  344. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
  345. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
  346. package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
  347. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
  348. package/dist/analyzers/dataAccessAnalyzer.js +0 -474
  349. package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
  350. package/dist/analyzers/dryAnalyzer.d.ts +0 -31
  351. package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
  352. package/dist/analyzers/dryAnalyzer.js +0 -734
  353. package/dist/analyzers/dryAnalyzer.js.map +0 -1
  354. package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
  355. package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
  356. package/dist/analyzers/schemaAnalyzer.js +0 -564
  357. package/dist/analyzers/schemaAnalyzer.js.map +0 -1
  358. package/dist/analyzers/solidAnalyzer.d.ts +0 -33
  359. package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
  360. package/dist/analyzers/solidAnalyzer.js +0 -847
  361. package/dist/analyzers/solidAnalyzer.js.map +0 -1
@@ -2,15 +2,20 @@
2
2
  * Audit Runner (Functional)
3
3
  * Main orchestrator for running code audits
4
4
  */
5
+ import { promises as fs } from 'fs';
5
6
  import { statSync, readFileSync } from 'fs';
6
7
  import path from 'path';
8
+ import { fileURLToPath } from 'url';
7
9
  import { execSync } from 'child_process';
10
+ import { randomUUID } from 'node:crypto';
8
11
  import { AuditAbortedError, AuditHandoffError, } from './types.js';
9
12
  import { discoverFiles } from './utils/fileDiscovery.js';
10
13
  import { loadConfig } from './config/configLoader.js';
11
14
  import { generateReport } from './reporting/reportGenerator.js';
12
15
  import { extractFunctionsFromFile } from './functionScanner.js';
13
16
  import { isMcpDebugEnabled, logMcpDebug, logMcpInfo } from './mcpDiagnostics.js';
17
+ import { loadBaseline, matchFindings, hashBaseline } from './baseline.js';
18
+ import { computeImpact, LATENCY_BUDGET_MS } from './graph/blastRadius.js';
14
19
  // Import universal analyzers
15
20
  import { initializeLanguages } from './languages/index.js';
16
21
  import { UniversalSOLIDAnalyzer } from './analyzers/universal/UniversalSOLIDAnalyzer.js';
@@ -18,12 +23,24 @@ import { UniversalDRYAnalyzer } from './analyzers/universal/UniversalDRYAnalyzer
18
23
  import { UniversalDataAccessAnalyzer } from './analyzers/universal/UniversalDataAccessAnalyzer.js';
19
24
  import { UniversalDocumentationAnalyzer } from './analyzers/universal/UniversalDocumentationAnalyzer.js';
20
25
  import { UniversalSchemaAnalyzer } from './analyzers/universal/UniversalSchemaAnalyzer.js';
26
+ import { UniversalStylesAnalyzer } from './analyzers/universal/UniversalStylesAnalyzer.js';
27
+ import { UniversalConventionsAnalyzer } from './analyzers/universal/UniversalConventionsAnalyzer.js';
28
+ import { CrossDomainAnalyzer } from './analyzers/crossDomain/CrossDomainAnalyzer.js';
29
+ import { initializeOrmAdapters } from './analyzers/orm/index.js';
30
+ import { syncStyleIndex } from './styles/styleIndexer.js';
21
31
  import { reactAnalyzer } from './analyzers/reactAnalyzer.js';
22
32
  import { invariantsAnalyzer } from './analyzers/invariantsAnalyzer.js';
23
33
  import { hasRules } from './invariants/ruleEngine.js';
24
34
  import { CodeIndexDB } from './codeIndexDB.js';
35
+ import { writeAuditToLedger, detectRunInput } from './ledger.js';
36
+ // Package version — read once at module load
37
+ const __auditRunnerDirname = path.dirname(fileURLToPath(import.meta.url));
38
+ const _pkg = JSON.parse(readFileSync(path.join(__auditRunnerDirname, '..', 'package.json'), 'utf-8'));
39
+ const TOOL_VERSION = String(_pkg.version || '0.0.0');
25
40
  // Initialize the canonical language system once
26
41
  initializeLanguages();
42
+ // Initialize ORM adapters for cross-domain schema extraction (Spec 15 R2)
43
+ initializeOrmAdapters();
27
44
  /**
28
45
  * Shared progress callback adapter: translates universal analyzer's (number) into
29
46
  * the object-based callback that auditRunner passes downstream.
@@ -75,6 +92,13 @@ const DEFAULT_ANALYZERS = {
75
92
  universalConfig.checkInterfaceSegregation = config.checkInterfaceSegregation;
76
93
  if (config.checkLiskovSubstitution !== undefined)
77
94
  universalConfig.checkLiskovSubstitution = config.checkLiskovSubstitution;
95
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
96
+ if (config.severityOverrides !== undefined)
97
+ universalConfig.severityOverrides = config.severityOverrides;
98
+ if (config.pathProfiles !== undefined)
99
+ universalConfig.pathProfiles = config.pathProfiles;
100
+ if (config.projectRoot !== undefined)
101
+ universalConfig.projectRoot = config.projectRoot;
78
102
  const result = await analyzer.analyze(files, universalConfig, {
79
103
  progressCallback: createProgressAdapter('solid', progressCallback),
80
104
  ...options,
@@ -127,6 +151,16 @@ const DEFAULT_ANALYZERS = {
127
151
  // R4.3: directAccess — "flag" (default) or "allow"
128
152
  if (config.directAccess !== undefined)
129
153
  universalConfig.directAccess = config.directAccess;
154
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
155
+ if (config.severityOverrides !== undefined)
156
+ universalConfig.severityOverrides = config.severityOverrides;
157
+ if (config.pathProfiles !== undefined)
158
+ universalConfig.pathProfiles = config.pathProfiles;
159
+ if (config.projectRoot !== undefined)
160
+ universalConfig.projectRoot = config.projectRoot;
161
+ // Spec 21: Forward shared provenance timing accumulator
162
+ if (config._provenanceTiming !== undefined)
163
+ universalConfig._provenanceTiming = config._provenanceTiming;
130
164
  return analyzer.analyze(files, universalConfig, {
131
165
  progressCallback: createProgressAdapter('data-access', progressCallback),
132
166
  ...options,
@@ -157,6 +191,13 @@ const DEFAULT_ANALYZERS = {
157
191
  fileHeaders: config.fileHeaders ?? config.requireFileDocs ?? false, // R1.5
158
192
  headerSkipGlobs: config.headerSkipGlobs, // R1.5 (undefined → analyzer default)
159
193
  };
194
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
195
+ if (config.severityOverrides !== undefined)
196
+ universalConfig.severityOverrides = config.severityOverrides;
197
+ if (config.pathProfiles !== undefined)
198
+ universalConfig.pathProfiles = config.pathProfiles;
199
+ if (config.projectRoot !== undefined)
200
+ universalConfig.projectRoot = config.projectRoot;
160
201
  return analyzer.analyze(files, universalConfig, {
161
202
  progressCallback: createProgressAdapter('documentation', progressCallback),
162
203
  ...options,
@@ -190,7 +231,6 @@ const DEFAULT_ANALYZERS = {
190
231
  // If database isn't available, continue without schemas
191
232
  }
192
233
  const universalConfig = {
193
- enableTableUsageTracking: config.enableTableUsageTracking ?? true,
194
234
  checkMissingReferences: config.checkMissingReferences ?? true,
195
235
  checkNamingConventions: config.checkNamingConventions ?? true,
196
236
  detectUnusedTables: config.detectUnusedTables ?? false,
@@ -205,6 +245,16 @@ const DEFAULT_ANALYZERS = {
205
245
  dbBindingNames: config.dbBindingNames ?? ['env.DB'], // R2.2
206
246
  fileGateGlobs: config.fileGateGlobs, // R2.2 (let analyzer use defaults)
207
247
  };
248
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
249
+ if (config.severityOverrides !== undefined)
250
+ universalConfig.severityOverrides = config.severityOverrides;
251
+ if (config.pathProfiles !== undefined)
252
+ universalConfig.pathProfiles = config.pathProfiles;
253
+ if (config.projectRoot !== undefined)
254
+ universalConfig.projectRoot = config.projectRoot;
255
+ // Spec 21: Forward shared provenance timing accumulator
256
+ if (config._provenanceTiming !== undefined)
257
+ universalConfig._provenanceTiming = config._provenanceTiming;
208
258
  const result = await analyzer.analyze(files, universalConfig);
209
259
  return {
210
260
  ...result,
@@ -217,6 +267,98 @@ const DEFAULT_ANALYZERS = {
217
267
  };
218
268
  },
219
269
  },
270
+ 'styles': {
271
+ name: 'styles',
272
+ description: 'Detects style fragmentation, value drift, token bypass, dead classes, off-scale values, mechanism fragmentation, and z-index sprawl',
273
+ category: 'style',
274
+ analyze: async (files, config, options, progressCallback) => {
275
+ const analyzer = new UniversalStylesAnalyzer();
276
+ const universalConfig = {};
277
+ if (config.minCorpus !== undefined)
278
+ universalConfig.minCorpus = config.minCorpus;
279
+ if (config.colorDeltaE !== undefined)
280
+ universalConfig.colorDeltaE = config.colorDeltaE;
281
+ if (config.outlierMaxShare !== undefined)
282
+ universalConfig.outlierMaxShare = config.outlierMaxShare;
283
+ if (config.modeMinCount !== undefined)
284
+ universalConfig.modeMinCount = config.modeMinCount;
285
+ if (config.scaleProperties !== undefined)
286
+ universalConfig.scaleProperties = config.scaleProperties;
287
+ if (config.zIndexMaxDistinct !== undefined)
288
+ universalConfig.zIndexMaxDistinct = config.zIndexMaxDistinct;
289
+ if (config.mechanismFragmentationMinMechanisms !== undefined)
290
+ universalConfig.mechanismFragmentationMinMechanisms = config.mechanismFragmentationMinMechanisms;
291
+ if (config.declarationSetMinDeclarations !== undefined)
292
+ universalConfig.declarationSetMinDeclarations = config.declarationSetMinDeclarations;
293
+ if (config.declarationSetSimilarityThreshold !== undefined)
294
+ universalConfig.declarationSetSimilarityThreshold = config.declarationSetSimilarityThreshold;
295
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
296
+ if (config.severityOverrides !== undefined)
297
+ universalConfig.severityOverrides = config.severityOverrides;
298
+ if (config.pathProfiles !== undefined)
299
+ universalConfig.pathProfiles = config.pathProfiles;
300
+ if (config.projectRoot !== undefined)
301
+ universalConfig.projectRoot = config.projectRoot;
302
+ return analyzer.analyze(files, universalConfig, {
303
+ progressCallback: createProgressAdapter('styles', progressCallback),
304
+ ...options,
305
+ });
306
+ },
307
+ },
308
+ 'conventions': {
309
+ name: 'conventions',
310
+ description: 'Mines codebase conventions and flags deviations at suggestion severity',
311
+ category: 'style',
312
+ analyze: async (files, config, options, progressCallback) => {
313
+ const analyzer = new UniversalConventionsAnalyzer();
314
+ const universalConfig = {};
315
+ if (config.minCorpus !== undefined)
316
+ universalConfig.minCorpus = config.minCorpus;
317
+ if (config.pairConfidence !== undefined)
318
+ universalConfig.pairConfidence = config.pairConfidence;
319
+ if (config.modeShare !== undefined)
320
+ universalConfig.modeShare = config.modeShare;
321
+ if (config.maxConventionsPerDomain !== undefined)
322
+ universalConfig.maxConventionsPerDomain = config.maxConventionsPerDomain;
323
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
324
+ if (config.severityOverrides !== undefined)
325
+ universalConfig.severityOverrides = config.severityOverrides;
326
+ if (config.pathProfiles !== undefined)
327
+ universalConfig.pathProfiles = config.pathProfiles;
328
+ if (config.projectRoot !== undefined)
329
+ universalConfig.projectRoot = config.projectRoot;
330
+ return analyzer.analyze(files, universalConfig, {
331
+ progressCallback: createProgressAdapter('conventions', progressCallback),
332
+ ...options,
333
+ });
334
+ },
335
+ },
336
+ 'cross-domain': {
337
+ name: 'cross-domain',
338
+ description: 'Detects cross-domain issues (schema lifecycle, validation bypass, coverage gaps)',
339
+ category: 'architecture',
340
+ analyze: async (files, config, options, progressCallback) => {
341
+ const analyzer = new CrossDomainAnalyzer();
342
+ const universalConfig = {};
343
+ if (config.schemaLifecycle !== undefined)
344
+ universalConfig.schemaLifecycle = config.schemaLifecycle;
345
+ if (config.validatorBypass !== undefined)
346
+ universalConfig.validatorBypass = config.validatorBypass;
347
+ if (config.coverage !== undefined)
348
+ universalConfig.coverage = config.coverage;
349
+ // Forward base-class properties (Spec-20 path profiles + severity overrides)
350
+ if (config.severityOverrides !== undefined)
351
+ universalConfig.severityOverrides = config.severityOverrides;
352
+ if (config.pathProfiles !== undefined)
353
+ universalConfig.pathProfiles = config.pathProfiles;
354
+ if (config.projectRoot !== undefined)
355
+ universalConfig.projectRoot = config.projectRoot;
356
+ return analyzer.analyze(files, universalConfig, {
357
+ progressCallback: createProgressAdapter('cross-domain', progressCallback),
358
+ ...options,
359
+ });
360
+ },
361
+ },
220
362
  };
221
363
  /**
222
364
  * Create an audit runner with the given options
@@ -240,7 +382,20 @@ export function createAuditRunner(options = {}) {
240
382
  * Run the audit
241
383
  */
242
384
  async function run(runOptions) {
243
- const mergedOptions = { ...options, ...runOptions };
385
+ // Auto-load .codeauditor.json from project root so the programmatic API
386
+ // respects the same config the CLI surface reads. RunOptions (caller)
387
+ // override file config, and createAuditRunner options override both.
388
+ const rootForConfig = runOptions?.projectRoot || options.projectRoot || process.cwd();
389
+ let fileConfig = {};
390
+ try {
391
+ const configPath = path.join(rootForConfig, '.codeauditor.json');
392
+ await fs.access(configPath);
393
+ fileConfig = await loadConfig({ configPath });
394
+ }
395
+ catch {
396
+ // No config file — proceed with defaults
397
+ }
398
+ const mergedOptions = { ...fileConfig, ...options, ...runOptions };
244
399
  const startTime = Date.now();
245
400
  // ── Scope resolution ─────────────────────────────────────────────
246
401
  const scope = mergedOptions.scope ?? 'all';
@@ -305,6 +460,44 @@ export function createAuditRunner(options = {}) {
305
460
  });
306
461
  }
307
462
  }
463
+ // ── Blast-radius impact (Spec 14 R6) ──────────────────────────────
464
+ // Compute transitive-caller impact for changed functions.
465
+ // If latency exceeds the 100ms budget, skip and emit a warning;
466
+ // the feature is disabled-by-default when over budget.
467
+ let blastRadius;
468
+ if (isScoped && changedFunctions && changedFunctions.length > 0) {
469
+ try {
470
+ const db = CodeIndexDB.getInstance();
471
+ const rawDb = db.rawDb;
472
+ const functionIds = [];
473
+ for (const fn of changedFunctions) {
474
+ const row = rawDb.prepare('SELECT id FROM functions WHERE name = ? AND file_path = ?').get(fn.name, fn.filePath);
475
+ if (row)
476
+ functionIds.push(row.id);
477
+ }
478
+ if (functionIds.length > 0) {
479
+ const impact = computeImpact(rawDb, functionIds);
480
+ if (impact.latencyMs > LATENCY_BUDGET_MS) {
481
+ logMcpInfo('blast-radius', `latency ${impact.latencyMs}ms exceeds ${LATENCY_BUDGET_MS}ms budget — blast radius disabled for this run`, {});
482
+ }
483
+ else {
484
+ blastRadius = impact;
485
+ logMcpInfo('blast-radius', 'computed', {
486
+ editedFunctionCount: impact.editedFunctionCount,
487
+ transitiveCallers: impact.transitiveCallers,
488
+ reachableExports: impact.reachableExports,
489
+ latencyMs: impact.latencyMs,
490
+ });
491
+ }
492
+ }
493
+ }
494
+ catch (err) {
495
+ // Blast radius is advisory — non-fatal
496
+ logMcpInfo('blast-radius', 'computation failed (continuing)', {
497
+ error: err instanceof Error ? err.message : String(err)
498
+ });
499
+ }
500
+ }
308
501
  const handoffRemaining = [];
309
502
  const maxPerRun = mergedOptions.maxFilesPerRun;
310
503
  if (typeof maxPerRun === 'number' && maxPerRun > 0 && files.length > maxPerRun) {
@@ -391,6 +584,32 @@ export function createAuditRunner(options = {}) {
391
584
  // Run analyzers
392
585
  const analyzerResults = {};
393
586
  const enabledAnalyzers = getEnabledAnalyzers(mergedOptions, analyzerRegistry);
587
+ // ── Style index sync (Spec 10) ────────────────────────────────────
588
+ // Sync style declarations, tokens, and class usage before the
589
+ // styles analyzer runs, mirroring the function index sync pattern.
590
+ if (enabledAnalyzers.includes('styles')) {
591
+ try {
592
+ const styleDb = CodeIndexDB.getInstance();
593
+ await styleDb.initialize();
594
+ const styleSyncResult = await syncStyleIndex(styleDb.rawDb, files, root, { scoped: isScoped });
595
+ logMcpInfo('style-index', 'style index sync complete', {
596
+ changed: styleSyncResult.changed,
597
+ skipped: styleSyncResult.skipped,
598
+ removed: styleSyncResult.removed,
599
+ errors: styleSyncResult.errors,
600
+ });
601
+ }
602
+ catch (err) {
603
+ // Non-fatal: styles analyzer will run with whatever is in the index
604
+ logMcpInfo('style-index', 'style index sync failed (continuing)', {
605
+ error: err instanceof Error ? err.message : String(err)
606
+ });
607
+ }
608
+ }
609
+ // Spec 21: Shared timing accumulator for provenance resolution.
610
+ // Injected into data-access and schema analyzers so they can report
611
+ // per-file buildProvenanceContext() wall time (hook-latency measurement).
612
+ const provenanceTiming = { totalMs: 0 };
394
613
  logMcpInfo('analysis', 'enabled analyzers', {
395
614
  names: enabledAnalyzers,
396
615
  fileCount: files.length,
@@ -421,11 +640,28 @@ export function createAuditRunner(options = {}) {
421
640
  analyzer: analyzerName,
422
641
  message: `Running ${analyzerName} analyzer...`
423
642
  });
424
- // Build analyzer config; inject full index for DRY on scoped runs
425
- const analyzerConfig = { ...(mergedOptions.analyzerConfigs?.[analyzerName] || {}) };
643
+ // Build analyzer config; inject full index for DRY on scoped runs.
644
+ // Skip pathProfiles for invariants (Spec-20 R3): no backdoor
645
+ // around declared laws — invariants severity is absolute.
646
+ const isInvariants = analyzerName === 'invariants';
647
+ const analyzerConfig = {
648
+ ...(mergedOptions.analyzerConfigs?.[analyzerName] || {}),
649
+ };
650
+ if (!isInvariants) {
651
+ analyzerConfig.pathProfiles = mergedOptions.pathProfiles;
652
+ analyzerConfig.projectRoot = root;
653
+ analyzerConfig.severityOverrides = mergedOptions.severityOverrides;
654
+ }
426
655
  if (analyzerName === 'dry' && isScoped && fullFunctionIndex) {
427
656
  analyzerConfig.fullFunctionIndex = fullFunctionIndex;
428
657
  }
658
+ // Spec 21: Pass shared provenance timing accumulator to analyzers
659
+ // that call buildProvenanceContext() (data-access and schema).
660
+ // The analyzers accumulate per-file timing into this object;
661
+ // we read it back after all analyzers complete.
662
+ if (analyzerName === 'data-access' || analyzerName === 'schema') {
663
+ analyzerConfig._provenanceTiming = provenanceTiming;
664
+ }
429
665
  logMcpInfo('analysis', `running ${analyzerName}`, { fileCount: files.length });
430
666
  try {
431
667
  const result = await analyzer.analyze(files, analyzerConfig, mergedOptions, (progress) => {
@@ -474,6 +710,208 @@ export function createAuditRunner(options = {}) {
474
710
  orderedAnalyzerResults[analyzerName] = analyzerResults[analyzerName];
475
711
  }
476
712
  }
713
+ // ── Spec 13 R5 Phase 1: Persist seeded DRY pairs ──────────────────
714
+ // Store pairs from DRY analysis in dry_pair_history for divergence tracking.
715
+ // Pair identity is fingerprint-based (file + nodeType + line),
716
+ // NOT content-hash-based — so a diverging clone stays the same pair.
717
+ let dryPersistRunId = null;
718
+ try {
719
+ const dryResult = orderedAnalyzerResults['dry'];
720
+ const dryPairs = dryResult?.dryPairs;
721
+ if (dryPairs && dryPairs.length > 0) {
722
+ const indexDb = CodeIndexDB.getInstance();
723
+ await indexDb.initialize();
724
+ dryPersistRunId = randomUUID();
725
+ const insertStmt = indexDb.rawDb.prepare(`
726
+ INSERT OR IGNORE INTO dry_pair_history
727
+ (pair_fingerprint, file1, symbol1, line1, content_hash1,
728
+ file2, symbol2, line2, content_hash2, similarity, timestamp, run_id)
729
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), ?)
730
+ `);
731
+ const tx = indexDb.rawDb.transaction(() => {
732
+ for (const pair of dryPairs) {
733
+ insertStmt.run(pair.pairFingerprint, pair.file1, pair.symbol1, pair.line1, pair.contentHash1, pair.file2, pair.symbol2, pair.line2, pair.contentHash2, pair.similarity, dryPersistRunId);
734
+ }
735
+ });
736
+ tx();
737
+ }
738
+ }
739
+ catch {
740
+ // dry_pair_history persistence is advisory — non-fatal
741
+ }
742
+ // ── Baseline classification (Spec 18 R1) ────────────────────────────
743
+ const projectRoot = path.resolve(mergedOptions.projectRoot || process.cwd());
744
+ const baseline = loadBaseline(projectRoot);
745
+ let baselineMetadata;
746
+ if (baseline) {
747
+ const allViolations = Object.values(orderedAnalyzerResults).flatMap((r) => r.violations);
748
+ // For scoped runs, limit "fixed" to in-scope files
749
+ const scopedFiles = isScoped ? files : undefined;
750
+ const classified = matchFindings(allViolations, baseline, scopedFiles);
751
+ // Tag violations with their baseline status
752
+ for (const v of classified.new) {
753
+ v.new = true;
754
+ }
755
+ for (const v of classified.known) {
756
+ v.new = false;
757
+ }
758
+ baselineMetadata = {
759
+ present: true,
760
+ hash: hashBaseline(baseline),
761
+ newCount: classified.new.length,
762
+ fixedCount: classified.fixed.length,
763
+ knownCount: classified.known.length,
764
+ previousKnownCount: baseline.metadata.totalFindings,
765
+ };
766
+ logMcpInfo('baseline', 'baseline classification complete', {
767
+ new: classified.new.length,
768
+ fixed: classified.fixed.length,
769
+ known: classified.known.length,
770
+ });
771
+ }
772
+ // ── Spec 13 R2 — Hotspot scoring & finding reordering ──────────────
773
+ // Attach hotspot scores to violations and reorder within severity tiers.
774
+ // Falls back gracefully when no churn/hotspot data exists.
775
+ try {
776
+ const indexDb = CodeIndexDB.getInstance();
777
+ await indexDb.initialize();
778
+ // Build hotspot lookup: target -> score
779
+ const hotspotRows = indexDb.rawDb
780
+ .prepare('SELECT target, type, score FROM hotspot_scores')
781
+ .all();
782
+ if (hotspotRows.length > 0) {
783
+ const hotspotByTarget = new Map();
784
+ const hotspotByFile = new Map();
785
+ for (const row of hotspotRows) {
786
+ hotspotByTarget.set(row.target, row.score);
787
+ if (row.type === 'file') {
788
+ hotspotByFile.set(row.target, row.score);
789
+ }
790
+ }
791
+ // Attach hotspot to each violation and reorder within each analyzer
792
+ for (const analyzerName of Object.keys(orderedAnalyzerResults)) {
793
+ const result = orderedAnalyzerResults[analyzerName];
794
+ const violations = result.violations;
795
+ // Attach hotspot scores
796
+ for (const v of violations) {
797
+ const funcName = v.functionName;
798
+ const file = v.file;
799
+ // Try function-level hotspot first: "filePath::functionName"
800
+ const funcTarget = funcName ? `${file}::${funcName}` : undefined;
801
+ v.hotspot = (funcTarget ? hotspotByTarget.get(funcTarget) : undefined)
802
+ ?? hotspotByFile.get(file)
803
+ ?? 0;
804
+ }
805
+ // Reorder within severity tiers: sort by hotspot descending,
806
+ // preserving original order as tiebreaker.
807
+ const severityOrder = {
808
+ critical: 0,
809
+ warning: 1,
810
+ suggestion: 2,
811
+ info: 3,
812
+ };
813
+ const indexed = violations.map((v, i) => ({ v, i }));
814
+ indexed.sort((a, b) => {
815
+ const sevA = severityOrder[a.v.severity] ?? 99;
816
+ const sevB = severityOrder[b.v.severity] ?? 99;
817
+ if (sevA !== sevB)
818
+ return sevA - sevB;
819
+ // Within same severity: higher hotspot first
820
+ const hsA = a.v.hotspot ?? 0;
821
+ const hsB = b.v.hotspot ?? 0;
822
+ if (hsA !== hsB)
823
+ return hsB - hsA;
824
+ // Tiebreaker: original order
825
+ return a.i - b.i;
826
+ });
827
+ result.violations = indexed.map(x => x.v);
828
+ }
829
+ }
830
+ }
831
+ catch {
832
+ // Hotspot reordering is advisory — failure is non-fatal
833
+ }
834
+ // ── Spec 13 R5 Phase 2: Divergence tracking pass ──────────────────
835
+ // For every pair in dry_pair_history, compare the last 2 similarity
836
+ // measurements. If similarity has declined by ≥ divergenceThreshold
837
+ // for divergenceRuns consecutive runs, emit dry/diverging-clone.
838
+ //
839
+ // New rows are inserted by Phase 1 for pairs the DRY analyzer re-detects
840
+ // this run. Pairs that weren't re-detected don't get a new measurement
841
+ // this pass — full source re-read is deferred to the 20% case.
842
+ try {
843
+ const divergenceCfg = mergedOptions.analyzerConfigs?.dry?.divergence
844
+ ?? mergedOptions.divergence
845
+ ?? { divergenceThreshold: 0.05, divergenceRuns: 2, minPairSimilarity: 0.5 };
846
+ const threshold = divergenceCfg.divergenceThreshold ?? 0.05;
847
+ const requiredDeclines = divergenceCfg.divergenceRuns ?? 2;
848
+ if (threshold > 0) {
849
+ const indexDb = CodeIndexDB.getInstance();
850
+ await indexDb.initialize();
851
+ // Get all distinct pair fingerprints
852
+ const fingerprints = indexDb.rawDb
853
+ .prepare('SELECT DISTINCT pair_fingerprint FROM dry_pair_history')
854
+ .all();
855
+ if (fingerprints.length > 0) {
856
+ const getRows = indexDb.rawDb.prepare(`
857
+ SELECT similarity, timestamp
858
+ FROM dry_pair_history
859
+ WHERE pair_fingerprint = ?
860
+ ORDER BY timestamp ASC
861
+ `);
862
+ const divergingViolations = [];
863
+ for (const { pair_fingerprint: fp } of fingerprints) {
864
+ const rows = getRows.all(fp);
865
+ if (rows.length < requiredDeclines + 1)
866
+ continue;
867
+ // Check last `requiredDeclines` consecutive pairs for decline
868
+ let consecutiveDeclines = 0;
869
+ for (let i = rows.length - requiredDeclines; i < rows.length; i++) {
870
+ if (rows[i].similarity < rows[i - 1].similarity - threshold) {
871
+ consecutiveDeclines++;
872
+ }
873
+ }
874
+ if (consecutiveDeclines >= requiredDeclines) {
875
+ // Get file/line info from the last row
876
+ const lastRow = indexDb.rawDb.prepare(`
877
+ SELECT file1, file2, line1, line2 FROM dry_pair_history
878
+ WHERE pair_fingerprint = ?
879
+ ORDER BY timestamp DESC LIMIT 1
880
+ `).get(fp);
881
+ if (lastRow) {
882
+ const currentSim = rows[rows.length - 1].similarity;
883
+ const prevSim = rows[rows.length - 2].similarity;
884
+ const drop = Math.round((prevSim - currentSim) * 1000) / 1000;
885
+ divergingViolations.push({
886
+ file: lastRow.file1,
887
+ line: lastRow.line1,
888
+ severity: 'suggestion',
889
+ 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.`,
890
+ analyzer: 'dry',
891
+ rule: 'dry/diverging-clone',
892
+ });
893
+ }
894
+ }
895
+ }
896
+ if (divergingViolations.length > 0) {
897
+ // Append to the dry analyzer result, or create one if none exists
898
+ if (orderedAnalyzerResults['dry']) {
899
+ orderedAnalyzerResults['dry'].violations.push(...divergingViolations);
900
+ }
901
+ else {
902
+ orderedAnalyzerResults['dry'] = {
903
+ violations: divergingViolations,
904
+ filesProcessed: 0,
905
+ executionTime: 0,
906
+ };
907
+ }
908
+ }
909
+ }
910
+ }
911
+ }
912
+ catch {
913
+ // Divergence tracking is advisory — non-fatal
914
+ }
477
915
  // Generate summary
478
916
  const summary = generateSummary(orderedAnalyzerResults, files.length);
479
917
  // Create result
@@ -488,6 +926,9 @@ export function createAuditRunner(options = {}) {
488
926
  analyzersRun: enabledAnalyzers,
489
927
  configUsed: mergedOptions,
490
928
  scope: scopeResultType,
929
+ provenanceResolutionMs: provenanceTiming.totalMs,
930
+ ...(blastRadius && { blastRadius }),
931
+ ...(baselineMetadata && { baseline: baselineMetadata }),
491
932
  ...(collectedFunctions.length > 0 && {
492
933
  collectedFunctions,
493
934
  fileToFunctionsMap: Object.fromEntries(fileToFunctionsMap)
@@ -502,6 +943,25 @@ export function createAuditRunner(options = {}) {
502
943
  if (handoffRemaining.length > 0) {
503
944
  throw new AuditHandoffError(`${handoffRemaining.length} file(s) deferred to the next worker chunk`, result, handoffRemaining);
504
945
  }
946
+ // Spec 11 R1 — write to findings ledger (non-fatal: ledger is advisory)
947
+ const ledgerRunId = (async () => {
948
+ try {
949
+ const indexDb = CodeIndexDB.getInstance();
950
+ await indexDb.initialize();
951
+ const violations = Object.values(orderedAnalyzerResults).flatMap(ar => ar.violations);
952
+ // Hook-contract guard: no violation may carry an empty file path or line 0.
953
+ // A sentinel violation with file:'' broke the Claude Code hook's JSON consumer
954
+ // (Spec 15 regression). This guard is permanent — every analyzer code path
955
+ // must produce properly anchored violations.
956
+ validateHookContract(violations);
957
+ const scopeStr = Array.isArray(scope) ? `files:${scope.length}` : (scope ?? 'all');
958
+ return writeAuditToLedger(indexDb.rawDb, detectRunInput(process.argv.slice(2).join(' '), options.surface ?? 'cli', scopeStr, root, TOOL_VERSION), violations, Date.now() - startTime, 0);
959
+ }
960
+ catch (_err) {
961
+ // ledger write is non-fatal — audit result is still valid
962
+ return null;
963
+ }
964
+ })();
505
965
  return result;
506
966
  }
507
967
  /**
@@ -692,6 +1152,41 @@ function generateSummary(analyzerResults, filesAnalyzed) {
692
1152
  topIssues
693
1153
  };
694
1154
  }
1155
+ /**
1156
+ * Hook-contract regression guard.
1157
+ *
1158
+ * Every violation must carry a non-empty `file` path. A sentinel violation with
1159
+ * `file: ''` (Spec 15 regression, commit 3419ed0) broke the Claude Code hook's
1160
+ * JSON consumer, which expects every violation to anchor to a real file.
1161
+ *
1162
+ * When `line` is present, it must be > 0 — a zero line means the analyzer
1163
+ * failed to locate the finding and shipped an unactionable anchor.
1164
+ *
1165
+ * Contract violations are logged as warnings and dropped from the pipeline
1166
+ * (they are never written to the ledger or surfaced to the hook).
1167
+ *
1168
+ * This guard is permanent — no analyzer code path may produce violations with
1169
+ * empty file paths or line 0.
1170
+ */
1171
+ function validateHookContract(violations) {
1172
+ for (let i = violations.length - 1; i >= 0; i--) {
1173
+ const v = violations[i];
1174
+ const problems = [];
1175
+ if (!v.file || v.file.trim() === '') {
1176
+ problems.push('empty file path');
1177
+ }
1178
+ if (v.line !== undefined && v.line === 0) {
1179
+ problems.push('line=0 (unactionable anchor)');
1180
+ }
1181
+ if (problems.length > 0) {
1182
+ console.warn(`[hook-contract] Dropping violation from analyzer '${v.analyzer ?? 'unknown'}': ` +
1183
+ `${problems.join(', ')}. Rule: ${v.rule ?? 'unknown'}. ` +
1184
+ `Message: "${v.message.slice(0, 120)}". ` +
1185
+ `This is a bug in the analyzer — violations must anchor to real files with valid lines.`);
1186
+ violations.splice(i, 1);
1187
+ }
1188
+ }
1189
+ }
695
1190
  /**
696
1191
  * Report progress
697
1192
  */