@booklib/core 2.0.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 (374) hide show
  1. package/.cursor/rules/booklib-standards.mdc +40 -0
  2. package/.gemini/context.md +372 -0
  3. package/AGENTS.md +166 -0
  4. package/CHANGELOG.md +226 -0
  5. package/CLAUDE.md +81 -0
  6. package/CODE_OF_CONDUCT.md +31 -0
  7. package/CONTRIBUTING.md +304 -0
  8. package/LICENSE +21 -0
  9. package/PLAN.md +28 -0
  10. package/README.ja.md +198 -0
  11. package/README.ko.md +198 -0
  12. package/README.md +503 -0
  13. package/README.pt-BR.md +198 -0
  14. package/README.uk.md +241 -0
  15. package/README.zh-CN.md +198 -0
  16. package/SECURITY.md +9 -0
  17. package/agents/architecture-reviewer.md +136 -0
  18. package/agents/booklib-reviewer.md +90 -0
  19. package/agents/data-reviewer.md +107 -0
  20. package/agents/jvm-reviewer.md +146 -0
  21. package/agents/python-reviewer.md +128 -0
  22. package/agents/rust-reviewer.md +115 -0
  23. package/agents/ts-reviewer.md +110 -0
  24. package/agents/ui-reviewer.md +117 -0
  25. package/assets/logo.svg +36 -0
  26. package/bin/booklib-mcp.js +304 -0
  27. package/bin/booklib.js +1705 -0
  28. package/bin/skills.cjs +1292 -0
  29. package/booklib-router.mdc +36 -0
  30. package/booklib.config.json +19 -0
  31. package/commands/animation-at-work.md +10 -0
  32. package/commands/clean-code-reviewer.md +10 -0
  33. package/commands/data-intensive-patterns.md +10 -0
  34. package/commands/data-pipelines.md +10 -0
  35. package/commands/design-patterns.md +10 -0
  36. package/commands/domain-driven-design.md +10 -0
  37. package/commands/effective-java.md +10 -0
  38. package/commands/effective-kotlin.md +10 -0
  39. package/commands/effective-python.md +10 -0
  40. package/commands/effective-typescript.md +10 -0
  41. package/commands/kotlin-in-action.md +10 -0
  42. package/commands/lean-startup.md +10 -0
  43. package/commands/microservices-patterns.md +10 -0
  44. package/commands/programming-with-rust.md +10 -0
  45. package/commands/refactoring-ui.md +10 -0
  46. package/commands/rust-in-action.md +10 -0
  47. package/commands/skill-router.md +10 -0
  48. package/commands/spring-boot-in-action.md +10 -0
  49. package/commands/storytelling-with-data.md +10 -0
  50. package/commands/system-design-interview.md +10 -0
  51. package/commands/using-asyncio-python.md +10 -0
  52. package/commands/web-scraping-python.md +10 -0
  53. package/community/registry.json +1616 -0
  54. package/hooks/hooks.json +23 -0
  55. package/hooks/posttooluse-capture.mjs +67 -0
  56. package/hooks/suggest.js +153 -0
  57. package/lib/agent-behaviors.js +40 -0
  58. package/lib/agent-detector.js +96 -0
  59. package/lib/config-loader.js +39 -0
  60. package/lib/conflict-resolver.js +148 -0
  61. package/lib/context-builder.js +574 -0
  62. package/lib/discovery-engine.js +298 -0
  63. package/lib/doctor/hook-installer.js +83 -0
  64. package/lib/doctor/usage-tracker.js +87 -0
  65. package/lib/engine/ai-features.js +253 -0
  66. package/lib/engine/auditor.js +103 -0
  67. package/lib/engine/bm25-index.js +178 -0
  68. package/lib/engine/capture.js +120 -0
  69. package/lib/engine/corrections.js +198 -0
  70. package/lib/engine/doctor.js +195 -0
  71. package/lib/engine/graph-injector.js +137 -0
  72. package/lib/engine/graph.js +161 -0
  73. package/lib/engine/handoff.js +405 -0
  74. package/lib/engine/indexer.js +242 -0
  75. package/lib/engine/parser.js +53 -0
  76. package/lib/engine/query-expander.js +42 -0
  77. package/lib/engine/reranker.js +40 -0
  78. package/lib/engine/rrf.js +59 -0
  79. package/lib/engine/scanner.js +151 -0
  80. package/lib/engine/searcher.js +139 -0
  81. package/lib/engine/session-coordinator.js +306 -0
  82. package/lib/engine/session-manager.js +429 -0
  83. package/lib/engine/synthesizer.js +70 -0
  84. package/lib/installer.js +70 -0
  85. package/lib/instinct-block.js +33 -0
  86. package/lib/mcp-config-writer.js +88 -0
  87. package/lib/paths.js +57 -0
  88. package/lib/profiles/design.md +19 -0
  89. package/lib/profiles/general.md +16 -0
  90. package/lib/profiles/research-analysis.md +22 -0
  91. package/lib/profiles/software-development.md +23 -0
  92. package/lib/profiles/writing-content.md +19 -0
  93. package/lib/project-initializer.js +916 -0
  94. package/lib/registry/skills.js +102 -0
  95. package/lib/registry-searcher.js +99 -0
  96. package/lib/rules/rules-manager.js +169 -0
  97. package/lib/skill-fetcher.js +333 -0
  98. package/lib/well-known-builder.js +70 -0
  99. package/lib/wizard/index.js +404 -0
  100. package/lib/wizard/integration-detector.js +41 -0
  101. package/lib/wizard/project-detector.js +100 -0
  102. package/lib/wizard/prompt.js +156 -0
  103. package/lib/wizard/registry-embeddings.js +107 -0
  104. package/lib/wizard/skill-recommender.js +69 -0
  105. package/llms-full.txt +254 -0
  106. package/llms.txt +70 -0
  107. package/package.json +45 -0
  108. package/research-reports/2026-04-01-current-architecture.md +160 -0
  109. package/research-reports/IDEAS.md +93 -0
  110. package/rules/common/clean-code.md +42 -0
  111. package/rules/java/effective-java.md +42 -0
  112. package/rules/kotlin/effective-kotlin.md +37 -0
  113. package/rules/python/effective-python.md +38 -0
  114. package/rules/rust/rust.md +37 -0
  115. package/rules/typescript/effective-typescript.md +42 -0
  116. package/scripts/gen-llms-full.mjs +36 -0
  117. package/scripts/gen-og.mjs +142 -0
  118. package/scripts/validate-frontmatter.js +25 -0
  119. package/skills/animation-at-work/SKILL.md +270 -0
  120. package/skills/animation-at-work/assets/example_asset.txt +1 -0
  121. package/skills/animation-at-work/evals/evals.json +44 -0
  122. package/skills/animation-at-work/evals/results.json +13 -0
  123. package/skills/animation-at-work/examples/after.md +64 -0
  124. package/skills/animation-at-work/examples/before.md +35 -0
  125. package/skills/animation-at-work/references/api_reference.md +369 -0
  126. package/skills/animation-at-work/references/review-checklist.md +79 -0
  127. package/skills/animation-at-work/scripts/audit_animations.py +295 -0
  128. package/skills/animation-at-work/scripts/example.py +1 -0
  129. package/skills/clean-code-reviewer/SKILL.md +444 -0
  130. package/skills/clean-code-reviewer/audit.json +35 -0
  131. package/skills/clean-code-reviewer/evals/evals.json +185 -0
  132. package/skills/clean-code-reviewer/evals/results.json +13 -0
  133. package/skills/clean-code-reviewer/examples/after.md +48 -0
  134. package/skills/clean-code-reviewer/examples/before.md +33 -0
  135. package/skills/clean-code-reviewer/references/api_reference.md +158 -0
  136. package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
  137. package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
  138. package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
  139. package/skills/data-intensive-patterns/SKILL.md +267 -0
  140. package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
  141. package/skills/data-intensive-patterns/evals/evals.json +54 -0
  142. package/skills/data-intensive-patterns/evals/results.json +13 -0
  143. package/skills/data-intensive-patterns/examples/after.md +61 -0
  144. package/skills/data-intensive-patterns/examples/before.md +38 -0
  145. package/skills/data-intensive-patterns/references/api_reference.md +34 -0
  146. package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
  147. package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
  148. package/skills/data-intensive-patterns/scripts/adr.py +213 -0
  149. package/skills/data-intensive-patterns/scripts/example.py +1 -0
  150. package/skills/data-pipelines/SKILL.md +259 -0
  151. package/skills/data-pipelines/assets/example_asset.txt +1 -0
  152. package/skills/data-pipelines/evals/evals.json +45 -0
  153. package/skills/data-pipelines/evals/results.json +13 -0
  154. package/skills/data-pipelines/examples/after.md +97 -0
  155. package/skills/data-pipelines/examples/before.md +37 -0
  156. package/skills/data-pipelines/references/api_reference.md +301 -0
  157. package/skills/data-pipelines/references/review-checklist.md +181 -0
  158. package/skills/data-pipelines/scripts/example.py +1 -0
  159. package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
  160. package/skills/design-patterns/SKILL.md +271 -0
  161. package/skills/design-patterns/assets/example_asset.txt +1 -0
  162. package/skills/design-patterns/evals/evals.json +46 -0
  163. package/skills/design-patterns/evals/results.json +13 -0
  164. package/skills/design-patterns/examples/after.md +52 -0
  165. package/skills/design-patterns/examples/before.md +29 -0
  166. package/skills/design-patterns/references/api_reference.md +1 -0
  167. package/skills/design-patterns/references/patterns-catalog.md +726 -0
  168. package/skills/design-patterns/references/review-checklist.md +173 -0
  169. package/skills/design-patterns/scripts/example.py +1 -0
  170. package/skills/design-patterns/scripts/scaffold.py +807 -0
  171. package/skills/domain-driven-design/SKILL.md +142 -0
  172. package/skills/domain-driven-design/assets/example_asset.txt +1 -0
  173. package/skills/domain-driven-design/evals/evals.json +48 -0
  174. package/skills/domain-driven-design/evals/results.json +13 -0
  175. package/skills/domain-driven-design/examples/after.md +80 -0
  176. package/skills/domain-driven-design/examples/before.md +43 -0
  177. package/skills/domain-driven-design/references/api_reference.md +1 -0
  178. package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
  179. package/skills/domain-driven-design/references/review-checklist.md +158 -0
  180. package/skills/domain-driven-design/scripts/example.py +1 -0
  181. package/skills/domain-driven-design/scripts/scaffold.py +421 -0
  182. package/skills/effective-java/SKILL.md +227 -0
  183. package/skills/effective-java/assets/example_asset.txt +1 -0
  184. package/skills/effective-java/evals/evals.json +46 -0
  185. package/skills/effective-java/evals/results.json +13 -0
  186. package/skills/effective-java/examples/after.md +83 -0
  187. package/skills/effective-java/examples/before.md +37 -0
  188. package/skills/effective-java/references/api_reference.md +1 -0
  189. package/skills/effective-java/references/items-catalog.md +955 -0
  190. package/skills/effective-java/references/review-checklist.md +216 -0
  191. package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
  192. package/skills/effective-java/scripts/example.py +1 -0
  193. package/skills/effective-kotlin/SKILL.md +271 -0
  194. package/skills/effective-kotlin/assets/example_asset.txt +1 -0
  195. package/skills/effective-kotlin/audit.json +29 -0
  196. package/skills/effective-kotlin/evals/evals.json +45 -0
  197. package/skills/effective-kotlin/evals/results.json +13 -0
  198. package/skills/effective-kotlin/examples/after.md +36 -0
  199. package/skills/effective-kotlin/examples/before.md +38 -0
  200. package/skills/effective-kotlin/references/api_reference.md +1 -0
  201. package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
  202. package/skills/effective-kotlin/references/review-checklist.md +126 -0
  203. package/skills/effective-kotlin/scripts/example.py +1 -0
  204. package/skills/effective-python/SKILL.md +441 -0
  205. package/skills/effective-python/evals/evals.json +44 -0
  206. package/skills/effective-python/evals/results.json +13 -0
  207. package/skills/effective-python/examples/after.md +56 -0
  208. package/skills/effective-python/examples/before.md +40 -0
  209. package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
  210. package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
  211. package/skills/effective-python/ref-03-functions.md +186 -0
  212. package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
  213. package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
  214. package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
  215. package/skills/effective-python/ref-07-concurrency.md +213 -0
  216. package/skills/effective-python/ref-08-robustness-performance.md +248 -0
  217. package/skills/effective-python/ref-09-testing-debugging.md +253 -0
  218. package/skills/effective-python/ref-10-collaboration.md +175 -0
  219. package/skills/effective-python/references/api_reference.md +218 -0
  220. package/skills/effective-python/references/practices-catalog.md +483 -0
  221. package/skills/effective-python/references/review-checklist.md +190 -0
  222. package/skills/effective-python/scripts/lint.py +173 -0
  223. package/skills/effective-typescript/SKILL.md +262 -0
  224. package/skills/effective-typescript/audit.json +29 -0
  225. package/skills/effective-typescript/evals/evals.json +37 -0
  226. package/skills/effective-typescript/evals/results.json +13 -0
  227. package/skills/effective-typescript/examples/after.md +70 -0
  228. package/skills/effective-typescript/examples/before.md +47 -0
  229. package/skills/effective-typescript/references/api_reference.md +118 -0
  230. package/skills/effective-typescript/references/practices-catalog.md +371 -0
  231. package/skills/effective-typescript/scripts/review.py +169 -0
  232. package/skills/kotlin-in-action/SKILL.md +261 -0
  233. package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
  234. package/skills/kotlin-in-action/evals/evals.json +43 -0
  235. package/skills/kotlin-in-action/evals/results.json +13 -0
  236. package/skills/kotlin-in-action/examples/after.md +53 -0
  237. package/skills/kotlin-in-action/examples/before.md +39 -0
  238. package/skills/kotlin-in-action/references/api_reference.md +1 -0
  239. package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
  240. package/skills/kotlin-in-action/references/review-checklist.md +204 -0
  241. package/skills/kotlin-in-action/scripts/example.py +1 -0
  242. package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
  243. package/skills/lean-startup/SKILL.md +160 -0
  244. package/skills/lean-startup/assets/example_asset.txt +1 -0
  245. package/skills/lean-startup/evals/evals.json +43 -0
  246. package/skills/lean-startup/evals/results.json +13 -0
  247. package/skills/lean-startup/examples/after.md +80 -0
  248. package/skills/lean-startup/examples/before.md +34 -0
  249. package/skills/lean-startup/references/api_reference.md +319 -0
  250. package/skills/lean-startup/references/review-checklist.md +137 -0
  251. package/skills/lean-startup/scripts/example.py +1 -0
  252. package/skills/lean-startup/scripts/new_experiment.py +286 -0
  253. package/skills/microservices-patterns/SKILL.md +384 -0
  254. package/skills/microservices-patterns/evals/evals.json +45 -0
  255. package/skills/microservices-patterns/evals/results.json +13 -0
  256. package/skills/microservices-patterns/examples/after.md +69 -0
  257. package/skills/microservices-patterns/examples/before.md +40 -0
  258. package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
  259. package/skills/microservices-patterns/references/review-checklist.md +169 -0
  260. package/skills/microservices-patterns/scripts/new_service.py +583 -0
  261. package/skills/programming-with-rust/SKILL.md +209 -0
  262. package/skills/programming-with-rust/evals/evals.json +37 -0
  263. package/skills/programming-with-rust/evals/results.json +13 -0
  264. package/skills/programming-with-rust/examples/after.md +107 -0
  265. package/skills/programming-with-rust/examples/before.md +59 -0
  266. package/skills/programming-with-rust/references/api_reference.md +152 -0
  267. package/skills/programming-with-rust/references/practices-catalog.md +335 -0
  268. package/skills/programming-with-rust/scripts/review.py +142 -0
  269. package/skills/refactoring-ui/SKILL.md +362 -0
  270. package/skills/refactoring-ui/assets/example_asset.txt +1 -0
  271. package/skills/refactoring-ui/evals/evals.json +45 -0
  272. package/skills/refactoring-ui/evals/results.json +13 -0
  273. package/skills/refactoring-ui/examples/after.md +85 -0
  274. package/skills/refactoring-ui/examples/before.md +58 -0
  275. package/skills/refactoring-ui/references/api_reference.md +355 -0
  276. package/skills/refactoring-ui/references/review-checklist.md +114 -0
  277. package/skills/refactoring-ui/scripts/audit_css.py +250 -0
  278. package/skills/refactoring-ui/scripts/example.py +1 -0
  279. package/skills/rust-in-action/SKILL.md +350 -0
  280. package/skills/rust-in-action/evals/evals.json +38 -0
  281. package/skills/rust-in-action/evals/results.json +13 -0
  282. package/skills/rust-in-action/examples/after.md +156 -0
  283. package/skills/rust-in-action/examples/before.md +56 -0
  284. package/skills/rust-in-action/references/practices-catalog.md +346 -0
  285. package/skills/rust-in-action/scripts/review.py +147 -0
  286. package/skills/skill-router/SKILL.md +186 -0
  287. package/skills/skill-router/evals/evals.json +38 -0
  288. package/skills/skill-router/evals/results.json +13 -0
  289. package/skills/skill-router/examples/after.md +63 -0
  290. package/skills/skill-router/examples/before.md +39 -0
  291. package/skills/skill-router/references/api_reference.md +24 -0
  292. package/skills/skill-router/references/routing-heuristics.md +89 -0
  293. package/skills/skill-router/references/skill-catalog.md +174 -0
  294. package/skills/skill-router/scripts/route.py +266 -0
  295. package/skills/spring-boot-in-action/SKILL.md +340 -0
  296. package/skills/spring-boot-in-action/evals/evals.json +39 -0
  297. package/skills/spring-boot-in-action/evals/results.json +13 -0
  298. package/skills/spring-boot-in-action/examples/after.md +185 -0
  299. package/skills/spring-boot-in-action/examples/before.md +84 -0
  300. package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
  301. package/skills/spring-boot-in-action/scripts/review.py +184 -0
  302. package/skills/storytelling-with-data/SKILL.md +241 -0
  303. package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
  304. package/skills/storytelling-with-data/evals/evals.json +47 -0
  305. package/skills/storytelling-with-data/evals/results.json +13 -0
  306. package/skills/storytelling-with-data/examples/after.md +50 -0
  307. package/skills/storytelling-with-data/examples/before.md +33 -0
  308. package/skills/storytelling-with-data/references/api_reference.md +379 -0
  309. package/skills/storytelling-with-data/references/review-checklist.md +111 -0
  310. package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
  311. package/skills/storytelling-with-data/scripts/example.py +1 -0
  312. package/skills/system-design-interview/SKILL.md +233 -0
  313. package/skills/system-design-interview/assets/example_asset.txt +1 -0
  314. package/skills/system-design-interview/evals/evals.json +46 -0
  315. package/skills/system-design-interview/evals/results.json +13 -0
  316. package/skills/system-design-interview/examples/after.md +94 -0
  317. package/skills/system-design-interview/examples/before.md +27 -0
  318. package/skills/system-design-interview/references/api_reference.md +582 -0
  319. package/skills/system-design-interview/references/review-checklist.md +201 -0
  320. package/skills/system-design-interview/scripts/example.py +1 -0
  321. package/skills/system-design-interview/scripts/new_design.py +421 -0
  322. package/skills/using-asyncio-python/SKILL.md +290 -0
  323. package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
  324. package/skills/using-asyncio-python/evals/evals.json +43 -0
  325. package/skills/using-asyncio-python/evals/results.json +13 -0
  326. package/skills/using-asyncio-python/examples/after.md +68 -0
  327. package/skills/using-asyncio-python/examples/before.md +39 -0
  328. package/skills/using-asyncio-python/references/api_reference.md +267 -0
  329. package/skills/using-asyncio-python/references/review-checklist.md +149 -0
  330. package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
  331. package/skills/using-asyncio-python/scripts/example.py +1 -0
  332. package/skills/web-scraping-python/SKILL.md +280 -0
  333. package/skills/web-scraping-python/assets/example_asset.txt +1 -0
  334. package/skills/web-scraping-python/evals/evals.json +46 -0
  335. package/skills/web-scraping-python/evals/results.json +13 -0
  336. package/skills/web-scraping-python/examples/after.md +109 -0
  337. package/skills/web-scraping-python/examples/before.md +40 -0
  338. package/skills/web-scraping-python/references/api_reference.md +393 -0
  339. package/skills/web-scraping-python/references/review-checklist.md +163 -0
  340. package/skills/web-scraping-python/scripts/example.py +1 -0
  341. package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
  342. package/skills/writing-plans/audit.json +34 -0
  343. package/tests/agent-detector.test.js +83 -0
  344. package/tests/corrections.test.js +245 -0
  345. package/tests/doctor/hook-installer.test.js +72 -0
  346. package/tests/doctor/usage-tracker.test.js +140 -0
  347. package/tests/engine/benchmark-eval.test.js +31 -0
  348. package/tests/engine/bm25-index.test.js +85 -0
  349. package/tests/engine/capture-command.test.js +35 -0
  350. package/tests/engine/capture.test.js +17 -0
  351. package/tests/engine/graph-augmented-search.test.js +107 -0
  352. package/tests/engine/graph-injector.test.js +44 -0
  353. package/tests/engine/graph.test.js +216 -0
  354. package/tests/engine/hybrid-searcher.test.js +74 -0
  355. package/tests/engine/indexer-bm25.test.js +37 -0
  356. package/tests/engine/mcp-tools.test.js +73 -0
  357. package/tests/engine/project-initializer-mcp.test.js +99 -0
  358. package/tests/engine/query-expander.test.js +36 -0
  359. package/tests/engine/reranker.test.js +51 -0
  360. package/tests/engine/rrf.test.js +49 -0
  361. package/tests/engine/srag-prefix.test.js +47 -0
  362. package/tests/instinct-block.test.js +23 -0
  363. package/tests/mcp-config-writer.test.js +60 -0
  364. package/tests/project-initializer-new-agents.test.js +48 -0
  365. package/tests/rules/rules-manager.test.js +230 -0
  366. package/tests/well-known-builder.test.js +40 -0
  367. package/tests/wizard/integration-detector.test.js +31 -0
  368. package/tests/wizard/project-detector.test.js +51 -0
  369. package/tests/wizard/prompt-session.test.js +61 -0
  370. package/tests/wizard/prompt.test.js +16 -0
  371. package/tests/wizard/registry-embeddings.test.js +35 -0
  372. package/tests/wizard/skill-recommender.test.js +34 -0
  373. package/tests/wizard/slot-count.test.js +25 -0
  374. package/vercel.json +21 -0
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ audit_animations.py - Audit CSS/SCSS for animation anti-patterns.
4
+
5
+ Usage:
6
+ python audit_animations.py <file_or_directory>
7
+
8
+ Scans .css and .scss files for animation anti-patterns documented in
9
+ "Animation at Work" by Rachel Nabors.
10
+
11
+ Checks performed:
12
+ 1. Animating layout-triggering properties (use transform instead)
13
+ 2. transition: all (too broad)
14
+ 3. Transitions > 500ms or animations > 1000ms (too slow)
15
+ 4. Durations < 100ms (too fast to perceive)
16
+ 5. linear easing on UI transitions (use ease-out or cubic-bezier)
17
+ 6. Missing prefers-reduced-motion in files that animate
18
+ 7. infinite animations without a pause mechanism
19
+
20
+ Outputs: file, line, offending CSS, and recommended fix.
21
+ Summary at end: total issues by category.
22
+ """
23
+
24
+ import argparse
25
+ import pathlib
26
+ import re
27
+ import sys
28
+ from collections import defaultdict
29
+ from typing import NamedTuple
30
+
31
+
32
+ class Issue(NamedTuple):
33
+ file: str
34
+ line: int
35
+ category: str
36
+ snippet: str
37
+ advice: str
38
+
39
+
40
+ # Properties that trigger layout recalculation — animating them is expensive.
41
+ LAYOUT_TRIGGERING = [
42
+ "width", "height", "top", "left", "right", "bottom",
43
+ "margin", "margin-top", "margin-right", "margin-bottom", "margin-left",
44
+ "padding", "padding-top", "padding-right", "padding-bottom", "padding-left",
45
+ ]
46
+
47
+ CATEGORIES = {
48
+ "layout_property": "Layout-triggering property animated",
49
+ "transition_all": "transition: all used",
50
+ "too_slow": "Duration too long (sluggish UI)",
51
+ "too_fast": "Duration too short (imperceptible)",
52
+ "linear_easing": "Linear easing on UI transition",
53
+ "no_reduced_motion": "Missing prefers-reduced-motion",
54
+ "infinite_no_pause": "Infinite animation without pause mechanism",
55
+ }
56
+
57
+
58
+ def parse_duration_ms(value: str) -> float | None:
59
+ """Convert a CSS duration string (e.g. '0.3s', '300ms') to milliseconds."""
60
+ value = value.strip()
61
+ if value.endswith("ms"):
62
+ try:
63
+ return float(value[:-2])
64
+ except ValueError:
65
+ return None
66
+ if value.endswith("s"):
67
+ try:
68
+ return float(value[:-1]) * 1000
69
+ except ValueError:
70
+ return None
71
+ return None
72
+
73
+
74
+ def find_css_files(path: pathlib.Path) -> list[pathlib.Path]:
75
+ if path.is_file():
76
+ if path.suffix in {".css", ".scss"}:
77
+ return [path]
78
+ print(f"WARNING: {path} is not a .css or .scss file — skipping.")
79
+ return []
80
+ return sorted(path.rglob("*.css")) + sorted(path.rglob("*.scss"))
81
+
82
+
83
+ def audit_file(filepath: pathlib.Path) -> list[Issue]:
84
+ issues: list[Issue] = []
85
+ try:
86
+ lines = filepath.read_text(encoding="utf-8", errors="replace").splitlines()
87
+ except OSError as exc:
88
+ print(f"ERROR reading {filepath}: {exc}")
89
+ return []
90
+
91
+ file_str = filepath.as_posix()
92
+ full_text = "\n".join(lines)
93
+
94
+ has_animation = bool(
95
+ re.search(r"\b(transition|animation)\s*:", full_text)
96
+ )
97
+ has_reduced_motion = "prefers-reduced-motion" in full_text
98
+
99
+ for lineno, raw_line in enumerate(lines, start=1):
100
+ line = raw_line.strip()
101
+ if not line or line.startswith("//") or line.startswith("/*"):
102
+ continue
103
+
104
+ # 1. Animating layout-triggering properties
105
+ transition_match = re.match(r"transition\s*:\s*(.+)", line, re.IGNORECASE)
106
+ if transition_match:
107
+ props_part = transition_match.group(1)
108
+ for prop in LAYOUT_TRIGGERING:
109
+ if re.search(r"\b" + re.escape(prop) + r"\b", props_part, re.IGNORECASE):
110
+ issues.append(Issue(
111
+ file=file_str,
112
+ line=lineno,
113
+ category="layout_property",
114
+ snippet=line[:120],
115
+ advice=(
116
+ f"Animating '{prop}' triggers layout recalculation on every frame. "
117
+ "Use 'transform: translate/scale' or 'opacity' instead — "
118
+ "these are compositor-only and do not cause reflow."
119
+ ),
120
+ ))
121
+
122
+ # 2. transition: all
123
+ if re.search(r"transition\s*:\s*all\b", line, re.IGNORECASE):
124
+ issues.append(Issue(
125
+ file=file_str,
126
+ line=lineno,
127
+ category="transition_all",
128
+ snippet=line[:120],
129
+ advice=(
130
+ "'transition: all' animates every animatable property, including "
131
+ "layout-triggering ones you may not intend. List specific properties: "
132
+ "e.g., 'transition: opacity 0.2s ease-out, transform 0.2s ease-out'."
133
+ ),
134
+ ))
135
+
136
+ # 3 & 4. Duration checks — transition and animation shorthand
137
+ duration_patterns = [
138
+ re.compile(r"transition\s*:[^;]+", re.IGNORECASE),
139
+ re.compile(r"animation\s*:[^;]+", re.IGNORECASE),
140
+ re.compile(r"transition-duration\s*:\s*([^;]+)", re.IGNORECASE),
141
+ re.compile(r"animation-duration\s*:\s*([^;]+)", re.IGNORECASE),
142
+ ]
143
+ for pat in duration_patterns:
144
+ m = pat.search(line)
145
+ if not m:
146
+ continue
147
+ value_str = m.group(0)
148
+ is_animation = "animation" in value_str.lower() and "transition" not in value_str.lower()
149
+ for dur_match in re.finditer(r"\d+(?:\.\d+)?(?:ms|s)\b", value_str):
150
+ dur_ms = parse_duration_ms(dur_match.group(0))
151
+ if dur_ms is None:
152
+ continue
153
+ slow_limit = 1000 if is_animation else 500
154
+ if dur_ms > slow_limit:
155
+ issues.append(Issue(
156
+ file=file_str,
157
+ line=lineno,
158
+ category="too_slow",
159
+ snippet=line[:120],
160
+ advice=(
161
+ f"Duration {dur_ms:.0f}ms feels sluggish for UI feedback. "
162
+ f"Keep UI transitions under {slow_limit}ms. "
163
+ "Aim for 200-300ms for most interactions."
164
+ ),
165
+ ))
166
+ elif dur_ms < 100 and dur_ms > 0:
167
+ issues.append(Issue(
168
+ file=file_str,
169
+ line=lineno,
170
+ category="too_fast",
171
+ snippet=line[:120],
172
+ advice=(
173
+ f"Duration {dur_ms:.0f}ms is below the human perception threshold (~100ms). "
174
+ "The animation will not be noticed. Use 100-200ms for snappy transitions."
175
+ ),
176
+ ))
177
+
178
+ # 5. Linear easing on transitions
179
+ if re.search(r"transition\s*:", line, re.IGNORECASE):
180
+ if re.search(r"\blinear\b", line, re.IGNORECASE):
181
+ issues.append(Issue(
182
+ file=file_str,
183
+ line=lineno,
184
+ category="linear_easing",
185
+ snippet=line[:120],
186
+ advice=(
187
+ "Linear easing feels mechanical and unnatural for UI elements. "
188
+ "Use 'ease-out' for elements entering the screen, 'ease-in' for "
189
+ "elements leaving, or a custom cubic-bezier for branded motion."
190
+ ),
191
+ ))
192
+
193
+ # 7. Infinite animation without pause mechanism
194
+ if re.search(r"animation-iteration-count\s*:\s*infinite\b", line, re.IGNORECASE):
195
+ # Check nearby lines (±10) for a paused state or play-state control
196
+ start = max(0, lineno - 10)
197
+ end = min(len(lines), lineno + 10)
198
+ context_block = "\n".join(lines[start:end])
199
+ if "animation-play-state" not in context_block and "paused" not in context_block:
200
+ issues.append(Issue(
201
+ file=file_str,
202
+ line=lineno,
203
+ category="infinite_no_pause",
204
+ snippet=line[:120],
205
+ advice=(
206
+ "Infinite animations can be distracting and drain battery on mobile. "
207
+ "Add 'animation-play-state: paused' controlled via :hover, :focus, "
208
+ "or a JS toggle so users can pause it."
209
+ ),
210
+ ))
211
+
212
+ # 6. Missing prefers-reduced-motion
213
+ if has_animation and not has_reduced_motion:
214
+ issues.append(Issue(
215
+ file=file_str,
216
+ line=0,
217
+ category="no_reduced_motion",
218
+ snippet="(entire file)",
219
+ advice=(
220
+ "This file contains animations but no '@media (prefers-reduced-motion: reduce)' "
221
+ "block. Add one to disable or reduce motion for users who request it — "
222
+ "required for WCAG 2.1 AA compliance."
223
+ ),
224
+ ))
225
+
226
+ return issues
227
+
228
+
229
+ def print_issues(issues: list[Issue]) -> None:
230
+ for issue in issues:
231
+ loc = f"{issue.file}:{issue.line}" if issue.line else issue.file
232
+ category_label = CATEGORIES.get(issue.category, issue.category)
233
+ print(f"\n[{category_label}]")
234
+ print(f" Location : {loc}")
235
+ print(f" CSS : {issue.snippet}")
236
+ print(f" Fix : {issue.advice}")
237
+
238
+
239
+ def print_summary(issues: list[Issue]) -> None:
240
+ counts: dict[str, int] = defaultdict(int)
241
+ for issue in issues:
242
+ counts[issue.category] += 1
243
+ print("\n" + "=" * 60)
244
+ print("SUMMARY")
245
+ print("=" * 60)
246
+ if not counts:
247
+ print("No issues found.")
248
+ return
249
+ for cat, label in CATEGORIES.items():
250
+ count = counts.get(cat, 0)
251
+ if count:
252
+ print(f" {count:3d} {label}")
253
+ print(f" ---")
254
+ print(f" {sum(counts.values()):3d} Total issues")
255
+
256
+
257
+ def main() -> None:
258
+ parser = argparse.ArgumentParser(
259
+ description="Audit CSS/SCSS files for animation anti-patterns."
260
+ )
261
+ parser.add_argument(
262
+ "path",
263
+ help="A .css/.scss file or directory to scan recursively.",
264
+ )
265
+ args = parser.parse_args()
266
+
267
+ target = pathlib.Path(args.path)
268
+ if not target.exists():
269
+ print(f"ERROR: Path not found: {target}")
270
+ sys.exit(1)
271
+
272
+ files = find_css_files(target)
273
+ if not files:
274
+ print("No .css or .scss files found.")
275
+ sys.exit(0)
276
+
277
+ print(f"Scanning {len(files)} file(s) ...\n")
278
+
279
+ all_issues: list[Issue] = []
280
+ for f in files:
281
+ file_issues = audit_file(f)
282
+ all_issues.extend(file_issues)
283
+
284
+ if all_issues:
285
+ print_issues(all_issues)
286
+ else:
287
+ print("No animation anti-patterns detected.")
288
+
289
+ print_summary(all_issues)
290
+
291
+ sys.exit(1 if all_issues else 0)
292
+
293
+
294
+ if __name__ == "__main__":
295
+ main()