code-auditor-mcp 3.1.0 → 3.4.0

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 (360) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +665 -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 -4
  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 -5
  25. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
  26. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  27. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +0 -3
  28. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  29. package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
  30. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  31. package/dist/analyzers/cross-language/SchemaValidator.js +2 -5
  32. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  33. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
  34. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
  35. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
  36. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
  37. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  38. package/dist/analyzers/documentationAnalyzer.js +8 -4
  39. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  40. package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
  41. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
  42. package/dist/analyzers/orm/adapterRegistry.js +73 -0
  43. package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
  44. package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
  45. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
  46. package/dist/analyzers/orm/drizzleAdapter.js +166 -0
  47. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
  48. package/dist/analyzers/orm/index.d.ts +14 -0
  49. package/dist/analyzers/orm/index.d.ts.map +1 -0
  50. package/dist/analyzers/orm/index.js +24 -0
  51. package/dist/analyzers/orm/index.js.map +1 -0
  52. package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
  53. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
  54. package/dist/analyzers/orm/prismaAdapter.js +170 -0
  55. package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
  56. package/dist/analyzers/orm/types.d.ts +63 -0
  57. package/dist/analyzers/orm/types.d.ts.map +1 -0
  58. package/dist/analyzers/orm/types.js +10 -0
  59. package/dist/analyzers/orm/types.js.map +1 -0
  60. package/dist/analyzers/provenance.d.ts +133 -0
  61. package/dist/analyzers/provenance.d.ts.map +1 -0
  62. package/dist/analyzers/provenance.js +1041 -0
  63. package/dist/analyzers/provenance.js.map +1 -0
  64. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  65. package/dist/analyzers/reactAnalyzer.js +117 -1
  66. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  67. package/dist/analyzers/ruleRegistry.d.ts +38 -0
  68. package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
  69. package/dist/analyzers/ruleRegistry.js +123 -0
  70. package/dist/analyzers/ruleRegistry.js.map +1 -0
  71. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
  72. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
  73. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
  74. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
  75. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
  76. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  77. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
  78. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  79. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
  80. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  81. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -103
  82. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  83. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  84. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
  85. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  86. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  87. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
  88. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  89. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
  90. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  91. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
  92. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  93. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
  94. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
  95. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
  96. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
  97. package/dist/auditRunner.d.ts.map +1 -1
  98. package/dist/auditRunner.js +483 -3
  99. package/dist/auditRunner.js.map +1 -1
  100. package/dist/baseline.d.ts +98 -0
  101. package/dist/baseline.d.ts.map +1 -0
  102. package/dist/baseline.js +184 -0
  103. package/dist/baseline.js.map +1 -0
  104. package/dist/churn/churnExtractor.d.ts +32 -0
  105. package/dist/churn/churnExtractor.d.ts.map +1 -0
  106. package/dist/churn/churnExtractor.js +261 -0
  107. package/dist/churn/churnExtractor.js.map +1 -0
  108. package/dist/cli.js +1215 -11
  109. package/dist/cli.js.map +1 -1
  110. package/dist/codeIndexDB-enhanced.js +1 -1
  111. package/dist/codeIndexDB-enhanced.js.map +1 -1
  112. package/dist/codeIndexDB.d.ts +101 -0
  113. package/dist/codeIndexDB.d.ts.map +1 -1
  114. package/dist/codeIndexDB.js +661 -5
  115. package/dist/codeIndexDB.js.map +1 -1
  116. package/dist/config/configLoader.d.ts.map +1 -1
  117. package/dist/config/configLoader.js +95 -1
  118. package/dist/config/configLoader.js.map +1 -1
  119. package/dist/config/defaults.d.ts +72 -1
  120. package/dist/config/defaults.d.ts.map +1 -1
  121. package/dist/config/defaults.js +146 -15
  122. package/dist/config/defaults.js.map +1 -1
  123. package/dist/config/pathProfiles.d.ts +38 -0
  124. package/dist/config/pathProfiles.d.ts.map +1 -0
  125. package/dist/config/pathProfiles.js +47 -0
  126. package/dist/config/pathProfiles.js.map +1 -0
  127. package/dist/conventions/conventionMiner.d.ts +63 -0
  128. package/dist/conventions/conventionMiner.d.ts.map +1 -0
  129. package/dist/conventions/conventionMiner.js +696 -0
  130. package/dist/conventions/conventionMiner.js.map +1 -0
  131. package/dist/coverage/coverageService.d.ts +49 -0
  132. package/dist/coverage/coverageService.d.ts.map +1 -0
  133. package/dist/coverage/coverageService.js +117 -0
  134. package/dist/coverage/coverageService.js.map +1 -0
  135. package/dist/coverage/istanbulParser.d.ts +84 -0
  136. package/dist/coverage/istanbulParser.d.ts.map +1 -0
  137. package/dist/coverage/istanbulParser.js +120 -0
  138. package/dist/coverage/istanbulParser.js.map +1 -0
  139. package/dist/coverage/lcovParser.d.ts +40 -0
  140. package/dist/coverage/lcovParser.d.ts.map +1 -0
  141. package/dist/coverage/lcovParser.js +168 -0
  142. package/dist/coverage/lcovParser.js.map +1 -0
  143. package/dist/fingerprint.d.ts +17 -0
  144. package/dist/fingerprint.d.ts.map +1 -1
  145. package/dist/fingerprint.js +42 -0
  146. package/dist/fingerprint.js.map +1 -1
  147. package/dist/grammars/tree-sitter-css.wasm +0 -0
  148. package/dist/graph/blastRadius.d.ts +27 -0
  149. package/dist/graph/blastRadius.d.ts.map +1 -0
  150. package/dist/graph/blastRadius.js +150 -0
  151. package/dist/graph/blastRadius.js.map +1 -0
  152. package/dist/graph/callGraph.d.ts +78 -0
  153. package/dist/graph/callGraph.d.ts.map +1 -0
  154. package/dist/graph/callGraph.js +484 -0
  155. package/dist/graph/callGraph.js.map +1 -0
  156. package/dist/graph/importGraph.d.ts +91 -0
  157. package/dist/graph/importGraph.d.ts.map +1 -0
  158. package/dist/graph/importGraph.js +602 -0
  159. package/dist/graph/importGraph.js.map +1 -0
  160. package/dist/graph/outputFormatter.d.ts +55 -0
  161. package/dist/graph/outputFormatter.d.ts.map +1 -0
  162. package/dist/graph/outputFormatter.js +283 -0
  163. package/dist/graph/outputFormatter.js.map +1 -0
  164. package/dist/hooks/codex.d.ts +36 -1
  165. package/dist/hooks/codex.d.ts.map +1 -1
  166. package/dist/hooks/codex.js +88 -54
  167. package/dist/hooks/codex.js.map +1 -1
  168. package/dist/hotspots/hotspotScorer.d.ts +20 -0
  169. package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
  170. package/dist/hotspots/hotspotScorer.js +202 -0
  171. package/dist/hotspots/hotspotScorer.js.map +1 -0
  172. package/dist/invariants/invariant-rules.schema.json +42 -1
  173. package/dist/invariants/ruleEngine.d.ts +4 -1
  174. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  175. package/dist/invariants/ruleEngine.js +141 -5
  176. package/dist/invariants/ruleEngine.js.map +1 -1
  177. package/dist/invariants/ruleValidator.d.ts.map +1 -1
  178. package/dist/invariants/ruleValidator.js +62 -1
  179. package/dist/invariants/ruleValidator.js.map +1 -1
  180. package/dist/invariants/types.d.ts +23 -2
  181. package/dist/invariants/types.d.ts.map +1 -1
  182. package/dist/languages/UniversalAnalyzer.d.ts +1 -1
  183. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  184. package/dist/languages/UniversalAnalyzer.js +59 -24
  185. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  186. package/dist/languages/index.d.ts +1 -0
  187. package/dist/languages/index.d.ts.map +1 -1
  188. package/dist/languages/index.js +3 -0
  189. package/dist/languages/index.js.map +1 -1
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
  191. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
  192. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
  193. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
  194. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  195. package/dist/languages/tree-sitter/parser.js +2 -0
  196. package/dist/languages/tree-sitter/parser.js.map +1 -1
  197. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  198. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
  199. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  200. package/dist/ledger.d.ts +128 -0
  201. package/dist/ledger.d.ts.map +1 -0
  202. package/dist/ledger.js +345 -0
  203. package/dist/ledger.js.map +1 -0
  204. package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
  205. package/dist/mcp-tools/projectTasks.js +9 -14
  206. package/dist/mcp-tools/projectTasks.js.map +1 -1
  207. package/dist/mcp.js +0 -0
  208. package/dist/mcpAuditJobs.d.ts.map +1 -1
  209. package/dist/mcpAuditJobs.js +3 -0
  210. package/dist/mcpAuditJobs.js.map +1 -1
  211. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  212. package/dist/reporting/csvReportGenerator.js +7 -1
  213. package/dist/reporting/csvReportGenerator.js.map +1 -1
  214. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  215. package/dist/reporting/htmlReportGenerator.js +5 -1
  216. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  217. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  218. package/dist/reporting/jsonReportGenerator.js +10 -2
  219. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  220. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  221. package/dist/reporting/sarifReportGenerator.js +17 -17
  222. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  223. package/dist/scripts/runBench.d.ts +94 -0
  224. package/dist/scripts/runBench.d.ts.map +1 -0
  225. package/dist/scripts/runBench.js +1559 -0
  226. package/dist/scripts/runBench.js.map +1 -0
  227. package/dist/search/QueryParser.d.ts.map +1 -1
  228. package/dist/search/QueryParser.js +51 -4
  229. package/dist/search/QueryParser.js.map +1 -1
  230. package/dist/services/CodeMapGenerator.d.ts +33 -0
  231. package/dist/services/CodeMapGenerator.d.ts.map +1 -1
  232. package/dist/services/CodeMapGenerator.js +404 -0
  233. package/dist/services/CodeMapGenerator.js.map +1 -1
  234. package/dist/styles/normalizer.d.ts +37 -0
  235. package/dist/styles/normalizer.d.ts.map +1 -0
  236. package/dist/styles/normalizer.js +301 -0
  237. package/dist/styles/normalizer.js.map +1 -0
  238. package/dist/styles/styleExtractor.d.ts +38 -0
  239. package/dist/styles/styleExtractor.d.ts.map +1 -0
  240. package/dist/styles/styleExtractor.js +630 -0
  241. package/dist/styles/styleExtractor.js.map +1 -0
  242. package/dist/styles/styleIndexer.d.ts +35 -0
  243. package/dist/styles/styleIndexer.d.ts.map +1 -0
  244. package/dist/styles/styleIndexer.js +266 -0
  245. package/dist/styles/styleIndexer.js.map +1 -0
  246. package/dist/styles/tailwindConfigLoader.d.ts +38 -0
  247. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
  248. package/dist/styles/tailwindConfigLoader.js +374 -0
  249. package/dist/styles/tailwindConfigLoader.js.map +1 -0
  250. package/dist/styles/tailwindExpander.d.ts +38 -0
  251. package/dist/styles/tailwindExpander.d.ts.map +1 -0
  252. package/dist/styles/tailwindExpander.js +637 -0
  253. package/dist/styles/tailwindExpander.js.map +1 -0
  254. package/dist/styles/types.d.ts +92 -0
  255. package/dist/styles/types.d.ts.map +1 -0
  256. package/dist/styles/types.js +10 -0
  257. package/dist/styles/types.js.map +1 -0
  258. package/dist/symbols.d.ts +21 -0
  259. package/dist/symbols.d.ts.map +1 -0
  260. package/dist/symbols.js +31 -0
  261. package/dist/symbols.js.map +1 -0
  262. package/dist/types.d.ts +326 -3
  263. package/dist/types.d.ts.map +1 -1
  264. package/dist/types.js.map +1 -1
  265. package/package.json +9 -5
  266. package/plugin/.claude-plugin/plugin.json +1 -1
  267. package/plugin/scripts/hook-audit.sh +5 -1
  268. package/plugin/skills/code-auditor/SKILL.md +51 -0
  269. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
  270. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
  271. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
  272. package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
  273. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
  274. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
  275. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
  276. package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
  277. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
  278. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
  279. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
  280. package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
  281. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
  282. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
  283. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
  284. package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
  285. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
  286. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
  287. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
  288. package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
  289. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
  290. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
  291. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
  292. package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
  293. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
  294. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
  295. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
  296. package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
  297. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
  298. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
  299. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
  300. package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
  301. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
  302. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
  303. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
  304. package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
  305. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
  306. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
  307. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
  308. package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
  309. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
  310. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
  311. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
  312. package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
  313. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
  314. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
  315. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
  316. package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
  317. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
  318. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
  319. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
  320. package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
  321. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
  322. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
  323. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
  324. package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
  325. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
  326. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
  327. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
  328. package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
  329. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
  330. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
  331. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
  332. package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
  333. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
  334. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
  335. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
  336. package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
  337. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
  338. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
  339. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
  340. package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
  341. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
  342. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
  343. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
  344. package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
  345. package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
  346. package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
  347. package/dist/analyzers/dataAccessAnalyzer.js +0 -474
  348. package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
  349. package/dist/analyzers/dryAnalyzer.d.ts +0 -31
  350. package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
  351. package/dist/analyzers/dryAnalyzer.js +0 -734
  352. package/dist/analyzers/dryAnalyzer.js.map +0 -1
  353. package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
  354. package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
  355. package/dist/analyzers/schemaAnalyzer.js +0 -564
  356. package/dist/analyzers/schemaAnalyzer.js.map +0 -1
  357. package/dist/analyzers/solidAnalyzer.d.ts +0 -33
  358. package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
  359. package/dist/analyzers/solidAnalyzer.js +0 -847
  360. package/dist/analyzers/solidAnalyzer.js.map +0 -1
@@ -0,0 +1,750 @@
1
+ /**
2
+ * Universal Styles Analyzer — Spec 10 R3.
3
+ *
4
+ * Detects style fragmentation, value drift, token bypass, dead classes,
5
+ * off-scale values, mechanism fragmentation, declaration-set similarity,
6
+ * and z-index sprawl by querying the full style index (SQLite).
7
+ *
8
+ * Unlike other analyzers that process one AST at a time via analyzeAST(),
9
+ * this analyzer queries the cross-file style_declarations table so it can
10
+ * compute histograms, clusters, and distributions across the entire codebase.
11
+ */
12
+ import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
13
+ import { CodeIndexDB } from '../../codeIndexDB.js';
14
+ // ---------------------------------------------------------------------------
15
+ // Default configuration
16
+ // ---------------------------------------------------------------------------
17
+ export const DEFAULT_STYLES_CONFIG = {
18
+ minCorpus: 20,
19
+ colorDeltaE: 2.0,
20
+ outlierMaxShare: 0.05,
21
+ modeMinCount: 10,
22
+ scaleProperties: [
23
+ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
24
+ 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
25
+ 'gap', 'row-gap', 'column-gap', 'font-size',
26
+ ],
27
+ zIndexMaxDistinct: 6,
28
+ mechanismFragmentationMinMechanisms: 3,
29
+ declarationSetMinDeclarations: 5,
30
+ declarationSetSimilarityThreshold: 0.9,
31
+ };
32
+ // ---------------------------------------------------------------------------
33
+ // Tailwind v4 default spacing scale (px equivalents)
34
+ // ---------------------------------------------------------------------------
35
+ const TAILWIND_SPACING_PX = {
36
+ '0': 0, 'px': 1, '0.5': 2,
37
+ '1': 4, '2': 8, '3': 12, '4': 16,
38
+ '5': 20, '6': 24, '7': 28, '8': 32,
39
+ '9': 36, '10': 40, '11': 44, '12': 48,
40
+ '14': 56, '16': 64, '20': 80, '24': 96,
41
+ '28': 112, '32': 128, '36': 144, '40': 160,
42
+ '44': 176, '48': 192, '52': 208, '56': 224,
43
+ '60': 240, '64': 256, '72': 288, '80': 320,
44
+ '96': 384,
45
+ };
46
+ const TAILWIND_SCALE_VALUES = Object.values(TAILWIND_SPACING_PX).sort((a, b) => a - b);
47
+ // ---------------------------------------------------------------------------
48
+ // Analyzer
49
+ // ---------------------------------------------------------------------------
50
+ export class UniversalStylesAnalyzer extends UniversalAnalyzer {
51
+ name = 'styles';
52
+ description = 'Detects style fragmentation, value drift, token bypass, dead classes, ' +
53
+ 'off-scale values, mechanism mixing, declaration-set similarity, and z-index sprawl';
54
+ category = 'style';
55
+ /**
56
+ * Override analyze() to query the full style index in one pass instead
57
+ * of per-file AST processing. The base class analyze() loop is bypassed.
58
+ */
59
+ async analyze(files, config = {}, options = {}) {
60
+ const startTime = Date.now();
61
+ const cfg = { ...DEFAULT_STYLES_CONFIG, ...config };
62
+ const violations = [];
63
+ // Open DB from the project root
64
+ let rawDb = null;
65
+ try {
66
+ const db = CodeIndexDB.getInstance();
67
+ await db.initialize();
68
+ rawDb = db.rawDb;
69
+ }
70
+ catch {
71
+ return {
72
+ violations: [],
73
+ errors: [{ file: '', error: 'Failed to open style index database' }],
74
+ filesProcessed: 0,
75
+ executionTime: Date.now() - startTime,
76
+ metrics: { filesAnalyzed: 0, totalViolations: 0, executionTime: Date.now() - startTime },
77
+ };
78
+ }
79
+ if (!rawDb) {
80
+ return {
81
+ violations: [],
82
+ errors: [],
83
+ filesProcessed: 0,
84
+ executionTime: Date.now() - startTime,
85
+ metrics: { filesAnalyzed: 0, totalViolations: 0, executionTime: Date.now() - startTime },
86
+ };
87
+ }
88
+ // Query all declarations, tokens, and class usage
89
+ const declarations = this.queryDeclarations(rawDb);
90
+ const tokens = this.queryTokens(rawDb);
91
+ const classUsage = this.queryClassUsage(rawDb);
92
+ if (declarations.length === 0) {
93
+ return {
94
+ violations: [],
95
+ errors: [],
96
+ filesProcessed: files.length,
97
+ executionTime: Date.now() - startTime,
98
+ metrics: { filesAnalyzed: files.length, totalViolations: 0, executionTime: Date.now() - startTime },
99
+ };
100
+ }
101
+ // Build helpers
102
+ const tokenValueMap = new Map(); // normalized value → token name
103
+ for (const t of tokens) {
104
+ tokenValueMap.set(t.value, t.name);
105
+ }
106
+ // Declarations by property
107
+ const byProperty = new Map();
108
+ for (const d of declarations) {
109
+ const list = byProperty.get(d.property) || [];
110
+ list.push(d);
111
+ byProperty.set(d.property, list);
112
+ }
113
+ // Run detectors
114
+ violations.push(...this.detectValueDrift(byProperty, cfg, declarations));
115
+ violations.push(...this.detectOffScaleValues(byProperty, cfg));
116
+ violations.push(...this.detectUndefinedClasses(classUsage, declarations, byProperty));
117
+ violations.push(...this.detectTokenBypass(declarations, tokenValueMap, cfg));
118
+ violations.push(...this.detectMechanismFragmentation(declarations, cfg));
119
+ violations.push(...this.detectDeclarationSetSimilarity(declarations, cfg));
120
+ violations.push(...this.detectZIndexInventory(byProperty, cfg));
121
+ // Apply severity overrides from config
122
+ const severityOverrides = config.severityOverrides ?? {};
123
+ if (Object.keys(severityOverrides).length > 0) {
124
+ for (const v of violations) {
125
+ const override = severityOverrides[v.rule];
126
+ if (override) {
127
+ v.severity = override;
128
+ }
129
+ }
130
+ }
131
+ const filtered = violations.filter(v => v.severity !== 'off');
132
+ return {
133
+ violations: filtered,
134
+ errors: [],
135
+ filesProcessed: files.length,
136
+ executionTime: Date.now() - startTime,
137
+ metrics: {
138
+ filesAnalyzed: files.length,
139
+ totalViolations: filtered.length,
140
+ executionTime: Date.now() - startTime,
141
+ },
142
+ };
143
+ }
144
+ /** Not used — we override analyze() directly. */
145
+ async analyzeAST(_ast, _adapter, _config, _sourceCode) {
146
+ return [];
147
+ }
148
+ // -----------------------------------------------------------------------
149
+ // Database queries
150
+ // -----------------------------------------------------------------------
151
+ queryDeclarations(rawDb) {
152
+ try {
153
+ return rawDb.prepare('SELECT * FROM style_declarations ORDER BY property, file_path, line').all();
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ }
159
+ queryTokens(rawDb) {
160
+ try {
161
+ return rawDb.prepare('SELECT * FROM style_tokens').all();
162
+ }
163
+ catch {
164
+ return [];
165
+ }
166
+ }
167
+ queryClassUsage(rawDb) {
168
+ try {
169
+ return rawDb.prepare('SELECT * FROM style_class_usage').all();
170
+ }
171
+ catch {
172
+ return [];
173
+ }
174
+ }
175
+ // -----------------------------------------------------------------------
176
+ // Detector 1: Value Drift
177
+ // -----------------------------------------------------------------------
178
+ /**
179
+ * For each property with enough declarations, cluster values and flag
180
+ * low-share stragglers (outliers) as style drift.
181
+ *
182
+ * - Colors: cluster by delta-E distance (< colorDeltaE).
183
+ * - Non-colors: exact-value histogram; flag share < outlierMaxShare
184
+ * when the mode count ≥ modeMinCount.
185
+ */
186
+ detectValueDrift(byProperty, cfg, allDecls) {
187
+ const violations = [];
188
+ for (const [property, decls] of byProperty) {
189
+ if (decls.length < cfg.minCorpus)
190
+ continue;
191
+ // Determine if this property holds color values
192
+ const isColorProp = this.isColorProperty(property);
193
+ if (isColorProp) {
194
+ violations.push(...this.detectColorDrift(property, decls, cfg));
195
+ }
196
+ else {
197
+ violations.push(...this.detectExactValueDrift(property, decls, cfg));
198
+ }
199
+ }
200
+ return violations;
201
+ }
202
+ isColorProperty(property) {
203
+ const colorProps = new Set([
204
+ 'color', 'background-color', 'background', 'border-color',
205
+ 'border-top-color', 'border-right-color', 'border-bottom-color',
206
+ 'border-left-color', 'outline-color', 'fill', 'stroke',
207
+ 'text-decoration-color', 'caret-color', 'column-rule-color',
208
+ 'accent-color', 'scrollbar-color',
209
+ ]);
210
+ return colorProps.has(property);
211
+ }
212
+ /**
213
+ * Color drift: cluster values by delta-E, flag stragglers.
214
+ */
215
+ detectColorDrift(property, decls, cfg) {
216
+ const violations = [];
217
+ // Parse all color values
218
+ const colors = [];
219
+ for (const d of decls) {
220
+ const rgb = this.parseColorToRGB(d.raw_value);
221
+ if (rgb) {
222
+ colors.push({ decl: d, rgb });
223
+ }
224
+ }
225
+ if (colors.length < cfg.minCorpus)
226
+ return violations;
227
+ // Cluster by delta-E
228
+ const clusters = this.clusterByDeltaE(colors, cfg.colorDeltaE);
229
+ // Find the dominant cluster (largest)
230
+ clusters.sort((a, b) => b.length - a.length);
231
+ const dominant = clusters[0];
232
+ if (!dominant || dominant.length < cfg.modeMinCount)
233
+ return violations;
234
+ const dominantSize = dominant.length;
235
+ // Flag stragglers in non-dominant clusters
236
+ for (let i = 1; i < clusters.length; i++) {
237
+ const cluster = clusters[i];
238
+ const share = cluster.length / colors.length;
239
+ if (share < cfg.outlierMaxShare && dominantSize >= cfg.modeMinCount) {
240
+ // Report once per distinct color value in the straggler cluster
241
+ const seenValues = new Set();
242
+ for (const item of cluster) {
243
+ const normVal = item.decl.raw_value.toLowerCase();
244
+ if (seenValues.has(normVal))
245
+ continue;
246
+ seenValues.add(normVal);
247
+ violations.push(this.makeViolation(item.decl.file_path, item.decl.line, `Color drift in "${property}": "${item.decl.raw_value}" is a rare value ` +
248
+ `(used ${cluster.length} time${cluster.length === 1 ? '' : 's'}, ` +
249
+ `${(share * 100).toFixed(1)}% of ${colors.length} usages). ` +
250
+ `Dominant cluster has ${dominantSize} values. Consider using a design token.`, 'warning', 'styles/value-drift', 'color'));
251
+ }
252
+ }
253
+ }
254
+ return violations;
255
+ }
256
+ /**
257
+ * Exact-value drift for non-color properties.
258
+ */
259
+ detectExactValueDrift(property, decls, cfg) {
260
+ const violations = [];
261
+ // Build value histogram
262
+ const histogram = new Map();
263
+ for (const d of decls) {
264
+ const key = d.normalized_value ?? d.raw_value;
265
+ const list = histogram.get(key) || [];
266
+ list.push(d);
267
+ histogram.set(key, list);
268
+ }
269
+ // Find the mode (most frequent value)
270
+ let modeKey = '';
271
+ let modeCount = 0;
272
+ for (const [key, list] of histogram) {
273
+ if (list.length > modeCount) {
274
+ modeCount = list.length;
275
+ modeKey = key;
276
+ }
277
+ }
278
+ if (modeCount < cfg.modeMinCount)
279
+ return violations;
280
+ // Flag low-share values
281
+ const total = decls.length;
282
+ for (const [key, list] of histogram) {
283
+ if (key === modeKey)
284
+ continue;
285
+ const share = list.length / total;
286
+ if (share < cfg.outlierMaxShare) {
287
+ const sample = list[0];
288
+ violations.push(this.makeViolation(sample.file_path, sample.line, `Value drift in "${property}": "${sample.raw_value}" is rare ` +
289
+ `(${list.length} of ${total} usages, ${(share * 100).toFixed(1)}%). ` +
290
+ `The dominant value "${modeKey}" is used ${modeCount} times. ` +
291
+ `Consider using a consistent value or design token.`, 'warning', 'styles/value-drift', 'exact'));
292
+ }
293
+ }
294
+ return violations;
295
+ }
296
+ // -----------------------------------------------------------------------
297
+ // Detector 2: Off-Scale Values
298
+ // -----------------------------------------------------------------------
299
+ /**
300
+ * For scale-family properties (margin, padding, gap, font-size),
301
+ * infer the project scale from modal values + Tailwind defaults,
302
+ * and flag values that don't fit the scale.
303
+ */
304
+ detectOffScaleValues(byProperty, cfg) {
305
+ const violations = [];
306
+ for (const property of cfg.scaleProperties) {
307
+ const decls = byProperty.get(property);
308
+ if (!decls || decls.length < cfg.minCorpus)
309
+ continue;
310
+ // Parse values to px-equivalent numbers
311
+ const parsed = [];
312
+ for (const d of decls) {
313
+ const px = this.parseLengthToPx(d.raw_value);
314
+ if (px !== null) {
315
+ parsed.push({ decl: d, px });
316
+ }
317
+ }
318
+ if (parsed.length < cfg.minCorpus)
319
+ continue;
320
+ // Infer the project scale step
321
+ const step = this.inferScaleStep(parsed.map(p => p.px));
322
+ if (step === null || step === 0)
323
+ continue;
324
+ // Flag off-scale values
325
+ for (const { decl, px } of parsed) {
326
+ const remainder = px % step;
327
+ // Allow near-zero remainders (floating point tolerance: < 1px)
328
+ if (Math.abs(remainder) > 1 && Math.abs(remainder - step) > 1) {
329
+ violations.push(this.makeViolation(decl.file_path, decl.line, `Off-scale "${property}" value: "${decl.raw_value}" (${px}px) ` +
330
+ `does not align with the inferred ${step}px scale step. ` +
331
+ `Nearby scale values: ${Math.floor(px / step) * step}px or ` +
332
+ `${Math.ceil(px / step) * step}px.`, 'warning', 'styles/off-scale'));
333
+ }
334
+ }
335
+ }
336
+ return violations;
337
+ }
338
+ /**
339
+ * Infer the dominant scale step from a set of px values.
340
+ * Uses the Tailwind scale as candidate steps.
341
+ */
342
+ inferScaleStep(values) {
343
+ if (values.length < 3)
344
+ return null;
345
+ // Count how many values align with each tailwind step
346
+ const candidates = [2, 4, 8, 16];
347
+ let bestStep = 4;
348
+ let bestScore = 0;
349
+ for (const step of candidates) {
350
+ let score = 0;
351
+ for (const v of values) {
352
+ if (v % step === 0)
353
+ score++;
354
+ }
355
+ if (score > bestScore) {
356
+ bestScore = score;
357
+ bestStep = step;
358
+ }
359
+ }
360
+ // Require at least 60% alignment
361
+ if (bestScore / values.length < 0.6)
362
+ return null;
363
+ return bestStep;
364
+ }
365
+ // -----------------------------------------------------------------------
366
+ // Detector 3: Undefined Classes
367
+ // -----------------------------------------------------------------------
368
+ /**
369
+ * Flag CSS classes used in markup that have no matching definition in
370
+ * any CSS/SCSS file. Files with unresolvable class usage are exempted.
371
+ */
372
+ detectUndefinedClasses(classUsage, _declarations, byProperty) {
373
+ const violations = [];
374
+ // Collect all defined class names from CSS declarations
375
+ // (context field contains selectors like ".btn-primary")
376
+ const definedClasses = new Set();
377
+ for (const [, decls] of byProperty) {
378
+ for (const d of decls) {
379
+ const ctx = d.context;
380
+ if (!ctx)
381
+ continue;
382
+ // Extract class selectors from context
383
+ const matches = ctx.matchAll(/\.([a-zA-Z0-9_-]+)/g);
384
+ for (const m of matches) {
385
+ definedClasses.add(m[1]);
386
+ }
387
+ }
388
+ }
389
+ // Also add common Tailwind utilities
390
+ const tailwindUtils = new Set([
391
+ 'flex', 'grid', 'block', 'inline', 'hidden', 'relative', 'absolute',
392
+ 'fixed', 'sticky', 'static', 'container', 'w-full', 'h-full',
393
+ 'text-sm', 'text-base', 'text-lg', 'text-xl', 'font-bold', 'font-normal',
394
+ 'bg-white', 'bg-black', 'rounded', 'rounded-lg', 'shadow', 'shadow-md',
395
+ 'p-4', 'm-4', 'gap-4', 'border', 'cursor-pointer', 'hover',
396
+ 'text-center', 'text-left', 'items-center', 'justify-between',
397
+ ]);
398
+ // Track files with unresolvable class usage (dynamic classes)
399
+ const unresolvableFiles = new Set();
400
+ for (const u of classUsage) {
401
+ if (u.unresolvable) {
402
+ unresolvableFiles.add(u.file_path);
403
+ }
404
+ }
405
+ // Check each class usage
406
+ const seen = new Set(); // deduplicate by (className, filePath)
407
+ for (const u of classUsage) {
408
+ const key = `${u.class_name}::${u.file_path}`;
409
+ if (seen.has(key))
410
+ continue;
411
+ seen.add(key);
412
+ // Skip unresolvable files
413
+ if (unresolvableFiles.has(u.file_path))
414
+ continue;
415
+ // Skip known classes
416
+ if (definedClasses.has(u.class_name))
417
+ continue;
418
+ if (tailwindUtils.has(u.class_name))
419
+ continue;
420
+ // Skip pseudo-class/variant selectors (hover:, focus:, sm:, etc.)
421
+ if (u.class_name.includes(':'))
422
+ continue;
423
+ // Skip common HTML attributes and dynamic-looking classes
424
+ if (/^[A-Z]/.test(u.class_name))
425
+ continue; // PascalCase — likely a component
426
+ if (u.class_name.includes('[') || u.class_name.includes(']'))
427
+ continue; // arbitrary values
428
+ if (u.class_name.includes('('))
429
+ continue; // function-like
430
+ if (/^\d/.test(u.class_name))
431
+ continue; // numeric
432
+ violations.push(this.makeViolation(u.file_path, u.line, `Undefined CSS class: "${u.class_name}" has no matching definition ` +
433
+ `in any stylesheet or Tailwind utility set.`, 'warning', 'styles/undefined-class'));
434
+ }
435
+ return violations;
436
+ }
437
+ // -----------------------------------------------------------------------
438
+ // Detector 4: Token Bypass
439
+ // -----------------------------------------------------------------------
440
+ /**
441
+ * Flag raw values that match a known design token's value but don't
442
+ * reference the token via tokenRef.
443
+ */
444
+ detectTokenBypass(declarations, tokenValueMap, cfg) {
445
+ const violations = [];
446
+ if (tokenValueMap.size === 0)
447
+ return violations;
448
+ for (const d of declarations) {
449
+ // Skip if already referencing a token
450
+ if (d.token_ref)
451
+ continue;
452
+ // Normalize the raw value for comparison
453
+ const normalized = this.normalizeForTokenMatch(d.raw_value);
454
+ const tokenName = tokenValueMap.get(normalized);
455
+ if (!tokenName)
456
+ continue;
457
+ violations.push(this.makeViolation(d.file_path, d.line, `Token bypass: "${d.raw_value}" for "${d.property}" matches design ` +
458
+ `token "${tokenName}" but was used as a raw value. ` +
459
+ `Use the token reference instead to keep styles consistent.`, 'warning', 'styles/token-bypass'));
460
+ }
461
+ return violations;
462
+ }
463
+ /** Normalize a raw value for token-value comparison. */
464
+ normalizeForTokenMatch(raw) {
465
+ let v = raw.toLowerCase().trim();
466
+ // Remove spaces after commas in functional notation
467
+ v = v.replace(/,\s+/g, ',');
468
+ // Expand shorthand hex: #fff → #ffffff
469
+ if (/^#[0-9a-f]{3}$/.test(v)) {
470
+ v = '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3];
471
+ }
472
+ return v;
473
+ }
474
+ // -----------------------------------------------------------------------
475
+ // Detector 5: Mechanism Fragmentation
476
+ // -----------------------------------------------------------------------
477
+ /**
478
+ * Flag when the same (property, value) is applied via ≥3 different
479
+ * mechanisms across the codebase, or when a single file/component
480
+ * mixes ≥3 different mechanisms.
481
+ */
482
+ detectMechanismFragmentation(declarations, cfg) {
483
+ const violations = [];
484
+ // (A) Cross-file: same (property, raw_value) via ≥3 mechanisms
485
+ const pvMap = new Map(); // "property::value" → set of mechanisms
486
+ const pvSample = new Map(); // keep one sample for reporting
487
+ for (const d of declarations) {
488
+ const key = `${d.property}::${d.normalized_value ?? d.raw_value}`;
489
+ const mechs = pvMap.get(key) || new Set();
490
+ mechs.add(d.mechanism);
491
+ pvMap.set(key, mechs);
492
+ if (!pvSample.has(key)) {
493
+ pvSample.set(key, d);
494
+ }
495
+ }
496
+ for (const [key, mechs] of pvMap) {
497
+ if (mechs.size < cfg.mechanismFragmentationMinMechanisms)
498
+ continue;
499
+ const sample = pvSample.get(key);
500
+ const [prop, value] = key.split('::');
501
+ violations.push(this.makeViolation(sample.file_path, sample.line, `Mechanism fragmentation: "${prop}: ${value}" is applied via ` +
502
+ `${mechs.size} different mechanisms (${[...mechs].sort().join(', ')}). ` +
503
+ `Consolidate to a single mechanism or design token.`, 'warning', 'styles/mechanism-fragmentation'));
504
+ }
505
+ // (B) Per-file: a single file mixing ≥3 mechanisms
506
+ const fileMechs = new Map();
507
+ for (const d of declarations) {
508
+ const mechs = fileMechs.get(d.file_path) || new Set();
509
+ mechs.add(d.mechanism);
510
+ fileMechs.set(d.file_path, mechs);
511
+ }
512
+ for (const [file, mechs] of fileMechs) {
513
+ if (mechs.size < cfg.mechanismFragmentationMinMechanisms)
514
+ continue;
515
+ violations.push(this.makeViolation(file, 1, `Mechanism mixing: ${file} uses ${mechs.size} different style ` +
516
+ `mechanisms (${[...mechs].sort().join(', ')}). ` +
517
+ `Consolidate to fewer mechanisms for maintainability.`, 'suggestion', 'styles/mechanism-mixing'));
518
+ }
519
+ return violations;
520
+ }
521
+ // -----------------------------------------------------------------------
522
+ // Detector 6: Declaration-Set Similarity
523
+ // -----------------------------------------------------------------------
524
+ /**
525
+ * Detect two rule blocks (contexts) whose declaration sets overlap at
526
+ * ≥ similarityThreshold (default 0.9) and that each have ≥ minDeclarations.
527
+ * This catches near-duplicate CSS rules that share most declarations.
528
+ */
529
+ detectDeclarationSetSimilarity(declarations, cfg) {
530
+ const violations = [];
531
+ // Group declarations by (file, context) to form rule blocks
532
+ const blocks = new Map();
533
+ for (const d of declarations) {
534
+ if (!d.context)
535
+ continue;
536
+ const key = `${d.file_path}::${d.context}`;
537
+ const list = blocks.get(key) || [];
538
+ list.push(d);
539
+ blocks.set(key, list);
540
+ }
541
+ // Convert blocks to declaration-value sets
542
+ const blockEntries = [...blocks.entries()]
543
+ .map(([key, decls]) => {
544
+ const valueSet = new Set(decls.map(d => `${d.property}:${d.normalized_value ?? d.raw_value}`));
545
+ return {
546
+ key,
547
+ filePath: decls[0].file_path,
548
+ context: decls[0].context,
549
+ line: decls[0].line,
550
+ declCount: decls.length,
551
+ valueSet,
552
+ };
553
+ })
554
+ .filter(b => b.declCount >= cfg.declarationSetMinDeclarations);
555
+ if (blockEntries.length < 2)
556
+ return violations;
557
+ // Compare pairs
558
+ const reported = new Set(); // "keyA::keyB"
559
+ for (let i = 0; i < blockEntries.length; i++) {
560
+ for (let j = i + 1; j < blockEntries.length; j++) {
561
+ const a = blockEntries[i];
562
+ const b = blockEntries[j];
563
+ // Skip same-file same-context (already the same block) or same key
564
+ if (a.key === b.key)
565
+ continue;
566
+ const pairKey = [a.key, b.key].sort().join('::');
567
+ if (reported.has(pairKey))
568
+ continue;
569
+ reported.add(pairKey);
570
+ // Compute Jaccard similarity
571
+ const intersection = new Set([...a.valueSet].filter(x => b.valueSet.has(x)));
572
+ const union = new Set([...a.valueSet, ...b.valueSet]);
573
+ const similarity = intersection.size / union.size;
574
+ if (similarity >= cfg.declarationSetSimilarityThreshold) {
575
+ violations.push(this.makeViolation(a.filePath, a.line, `Declaration-set similarity: "${a.context}" and "${b.context}" ` +
576
+ `in ${b.filePath} share ${intersection.size} of ${union.size} ` +
577
+ `declarations (${(similarity * 100).toFixed(0)}%). ` +
578
+ `Consider consolidating these rules or extracting a shared mixin.`, 'suggestion', 'styles/declaration-set-similarity'));
579
+ }
580
+ }
581
+ }
582
+ return violations;
583
+ }
584
+ // -----------------------------------------------------------------------
585
+ // Detector 7: Z-Index Inventory
586
+ // -----------------------------------------------------------------------
587
+ /**
588
+ * Z-index sprawl: flag when there are too many distinct z-index values,
589
+ * suggesting a lack of a z-index scale/system.
590
+ */
591
+ detectZIndexInventory(byProperty, cfg) {
592
+ const violations = [];
593
+ const decls = byProperty.get('z-index');
594
+ if (!decls || decls.length === 0)
595
+ return violations;
596
+ // Collect distinct numeric z-index values
597
+ const values = new Map();
598
+ for (const d of decls) {
599
+ const num = parseInt(d.raw_value, 10);
600
+ if (isNaN(num))
601
+ continue;
602
+ const list = values.get(num) || [];
603
+ list.push(d);
604
+ values.set(num, list);
605
+ }
606
+ if (values.size === 0)
607
+ return violations;
608
+ // Flag if too many distinct values
609
+ if (values.size > cfg.zIndexMaxDistinct) {
610
+ const sortedVals = [...values.keys()].sort((a, b) => a - b);
611
+ const sample = values.get(sortedVals[0])[0];
612
+ violations.push(this.makeViolation(sample.file_path, sample.line, `Z-index sprawl: ${values.size} distinct z-index values ` +
613
+ `(${sortedVals.join(', ')}). Consider defining a z-index scale ` +
614
+ `(e.g., $z-layers: (dropdown: 100, modal: 200, toast: 300)).`, 'warning', 'styles/z-index-sprawl'));
615
+ }
616
+ // Flag singletons (z-index values used only once)
617
+ for (const [val, list] of values) {
618
+ if (list.length === 1 && values.size > 2) {
619
+ const d = list[0];
620
+ violations.push(this.makeViolation(d.file_path, d.line, `Singleton z-index: z-index: ${val} is used only once. ` +
621
+ `Consider whether this value belongs in a shared z-index scale.`, 'suggestion', 'styles/z-index-singleton'));
622
+ }
623
+ }
624
+ return violations;
625
+ }
626
+ // -----------------------------------------------------------------------
627
+ // Helpers
628
+ // -----------------------------------------------------------------------
629
+ makeViolation(filePath, line, message, severity, rule, symbol) {
630
+ const v = {
631
+ file: filePath,
632
+ line,
633
+ column: 1,
634
+ severity,
635
+ message,
636
+ rule,
637
+ analyzer: this.name,
638
+ };
639
+ if (symbol) {
640
+ v.functionName = symbol;
641
+ }
642
+ return v;
643
+ }
644
+ /** Parse a CSS color string to [R, G, B] or null. */
645
+ parseColorToRGB(raw) {
646
+ try {
647
+ let v = raw.toLowerCase().trim();
648
+ // Hex
649
+ if (v.startsWith('#')) {
650
+ if (v.length === 4) {
651
+ // #rgb → #rrggbb
652
+ v = '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3];
653
+ }
654
+ if (v.length === 7) {
655
+ return [
656
+ parseInt(v.slice(1, 3), 16),
657
+ parseInt(v.slice(3, 5), 16),
658
+ parseInt(v.slice(5, 7), 16),
659
+ ];
660
+ }
661
+ if (v.length === 9) {
662
+ return [
663
+ parseInt(v.slice(1, 3), 16),
664
+ parseInt(v.slice(3, 5), 16),
665
+ parseInt(v.slice(5, 7), 16),
666
+ ];
667
+ }
668
+ }
669
+ // rgb(r, g, b) or rgb(r g b)
670
+ const rgbMatch = v.match(/rgb\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)\s*\)/);
671
+ if (rgbMatch) {
672
+ return [
673
+ parseInt(rgbMatch[1]),
674
+ parseInt(rgbMatch[2]),
675
+ parseInt(rgbMatch[3]),
676
+ ];
677
+ }
678
+ // Named colors — minimal set for common use
679
+ const named = {
680
+ 'white': [255, 255, 255], 'black': [0, 0, 0],
681
+ 'red': [255, 0, 0], 'blue': [0, 0, 255], 'green': [0, 128, 0],
682
+ 'transparent': [0, 0, 0],
683
+ };
684
+ if (named[v])
685
+ return named[v];
686
+ return null;
687
+ }
688
+ catch {
689
+ return null;
690
+ }
691
+ }
692
+ /** Compute delta-E (CIE76) between two RGB colors. */
693
+ deltaE(a, b) {
694
+ const dr = a[0] - b[0];
695
+ const dg = a[1] - b[1];
696
+ const db = a[2] - b[2];
697
+ return Math.sqrt(dr * dr + dg * dg + db * db);
698
+ }
699
+ /**
700
+ * Cluster colors by delta-E distance.
701
+ * Simple greedy algorithm: each item joins the first cluster it's close enough to,
702
+ * or starts a new cluster.
703
+ */
704
+ clusterByDeltaE(items, threshold) {
705
+ const clusters = [];
706
+ for (const item of items) {
707
+ let placed = false;
708
+ for (const cluster of clusters) {
709
+ // Use the first item's RGB as cluster centroid
710
+ const centroid = cluster[0].rgb;
711
+ if (this.deltaE(item.rgb, centroid) < threshold) {
712
+ cluster.push(item);
713
+ placed = true;
714
+ break;
715
+ }
716
+ }
717
+ if (!placed) {
718
+ clusters.push([item]);
719
+ }
720
+ }
721
+ return clusters;
722
+ }
723
+ /** Parse a CSS length value to px-equivalent, or null if not parseable. */
724
+ parseLengthToPx(raw) {
725
+ try {
726
+ const v = raw.trim().toLowerCase();
727
+ if (v === '0' || v === '0px')
728
+ return 0;
729
+ const match = v.match(/^(-?\d+(?:\.\d+)?)\s*(px|rem|em|%|vh|vw|pt|cm|mm)?$/);
730
+ if (!match)
731
+ return null;
732
+ const num = parseFloat(match[1]);
733
+ const unit = match[2] || 'px';
734
+ // Approximate conversions (assuming 16px base for rem/em)
735
+ switch (unit) {
736
+ case 'px': return num;
737
+ case 'rem': return num * 16;
738
+ case 'em': return num * 16;
739
+ case 'pt': return num * 1.333;
740
+ case 'cm': return num * 37.795;
741
+ case 'mm': return num * 3.7795;
742
+ default: return null; // can't convert %/vh/vw without context
743
+ }
744
+ }
745
+ catch {
746
+ return null;
747
+ }
748
+ }
749
+ }
750
+ //# sourceMappingURL=UniversalStylesAnalyzer.js.map