@jairodri/huckleberry-cli 1.0.2 → 1.1.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 (745) hide show
  1. package/README.md +85 -0
  2. package/agents/cos.json +2 -5
  3. package/agents/hermes/AGENTS.md +25 -49
  4. package/agents/hermes/fix-kimaki/SKILL.md +19 -92
  5. package/agents/hermes/hermes-fixer.md +44 -74
  6. package/agents/hermes.json +6 -18
  7. package/kanban/SKILL.md +18 -18
  8. package/kanban/package.json +1 -1
  9. package/kanban/src/dispatcher.mjs +1 -1
  10. package/kanban/templates/blog-social-campaign.yaml +6 -2
  11. package/kanban/templates/social-campaign.yaml +9 -4
  12. package/mcp/jev-calibration.mjs +335 -0
  13. package/mcp/jev-client.mjs +119 -0
  14. package/mcp/jev-thresholds.json +27 -0
  15. package/package.json +13 -9
  16. package/scripts/doctor-watchdog.sh +73 -0
  17. package/scripts/install-services.sh +34 -27
  18. package/scripts/self-improve-weekly.sh +34 -0
  19. package/scripts/sync-skills.mjs +121 -0
  20. package/skills/ab-test-setup/SKILL.md +266 -0
  21. package/skills/ab-test-setup/evals/evals.json +105 -0
  22. package/skills/ab-test-setup/references/sample-size-guide.md +263 -0
  23. package/skills/ab-test-setup/references/test-templates.md +277 -0
  24. package/skills/agent-reach/SKILL.md +132 -0
  25. package/skills/agent-reach/references/career.md +29 -0
  26. package/skills/agent-reach/references/dev.md +62 -0
  27. package/skills/agent-reach/references/search.md +33 -0
  28. package/skills/agent-reach/references/social.md +232 -0
  29. package/skills/agent-reach/references/video.md +130 -0
  30. package/skills/agent-reach/references/web.md +50 -0
  31. package/skills/ai-agent-seo/SKILL.md +450 -0
  32. package/skills/ai-cold-email-intelligence/SKILL.md +54 -0
  33. package/skills/ai-cold-email-intelligence/references/prompts.md +21 -0
  34. package/skills/ai-consensus-builder/SKILL.md +324 -0
  35. package/skills/ai-seo/SKILL.md +399 -0
  36. package/skills/ai-seo/evals/evals.json +90 -0
  37. package/skills/ai-seo/references/content-patterns.md +284 -0
  38. package/skills/ai-seo/references/platform-ranking-factors.md +152 -0
  39. package/skills/ai-visibility-audit/SKILL.md +322 -0
  40. package/skills/analytics-tracking/SKILL.md +309 -0
  41. package/skills/analytics-tracking/evals/evals.json +90 -0
  42. package/skills/analytics-tracking/references/event-library.md +260 -0
  43. package/skills/analytics-tracking/references/ga4-implementation.md +300 -0
  44. package/skills/analytics-tracking/references/gtm-implementation.md +390 -0
  45. package/skills/article-generator/SKILL.md +216 -0
  46. package/skills/auto-indexing/SKILL.md +179 -0
  47. package/skills/auto-indexing/scripts/auto-indexing.js +257 -0
  48. package/skills/auto-skill/SKILL.md +151 -0
  49. package/skills/auto-skill/correction-queue.js +102 -0
  50. package/skills/auto-skill/dedup.js +97 -0
  51. package/skills/auto-skill/skill-router.js +224 -0
  52. package/skills/autoresearch/SKILL.md +334 -0
  53. package/skills/autoresearch/autoresearch-autoresearch/SKILL.md.baseline +329 -0
  54. package/skills/autoresearch/autoresearch-autoresearch/changelog.md +34 -0
  55. package/skills/autoresearch/autoresearch-autoresearch/dashboard.html +116 -0
  56. package/skills/autoresearch/autoresearch-autoresearch/evals.md +39 -0
  57. package/skills/autoresearch/autoresearch-autoresearch/results.json +44 -0
  58. package/skills/autoresearch/autoresearch-autoresearch/results.tsv +3 -0
  59. package/skills/autoresearch/eval-guide.md +121 -0
  60. package/skills/b2b-cold-outreach/SKILL.md +322 -0
  61. package/skills/birdclaw/SKILL.md +82 -0
  62. package/skills/brainstorm/SKILL.md +168 -0
  63. package/skills/brand-kit-playground/SKILL.md +312 -0
  64. package/skills/brand-kit-playground/references/html-architecture.md +316 -0
  65. package/skills/brand-kit-playground/references/steal-patterns.md +88 -0
  66. package/skills/brand-voice/SKILL.md +223 -0
  67. package/skills/brand-voice/references/honesty-as-positioning.md +60 -0
  68. package/skills/brand-voice/references/voice-modes.md +1158 -0
  69. package/skills/business-advisory-council/README.md +136 -0
  70. package/skills/business-advisory-council/SKILL.md +94 -0
  71. package/skills/business-advisory-council/business-advisory-council.py +492 -0
  72. package/skills/business-advisory-council/discord-components-decision.json +8 -0
  73. package/skills/business-advisory-council/examples/steve-conley-partnership.json +6 -0
  74. package/skills/business-advisory-council/steve-conley-call-brief.json +6 -0
  75. package/skills/churn-prevention/SKILL.md +305 -0
  76. package/skills/churn-prevention/evals/evals.json +93 -0
  77. package/skills/churn-prevention/references/cancel-flow-patterns.md +316 -0
  78. package/skills/churn-prevention/references/dunning-playbook.md +408 -0
  79. package/skills/claw-compactor/LICENSE +21 -0
  80. package/skills/claw-compactor/README.md +459 -0
  81. package/skills/claw-compactor/SKILL.md +329 -0
  82. package/skills/claw-compactor/benchmark/RESULTS.md +104 -0
  83. package/skills/claw-compactor/benchmark/__init__.py +1 -0
  84. package/skills/claw-compactor/benchmark/compressors.py +520 -0
  85. package/skills/claw-compactor/benchmark/data/sample_01_devops.json +82 -0
  86. package/skills/claw-compactor/benchmark/data/sample_02_trading.json +54 -0
  87. package/skills/claw-compactor/benchmark/data/sample_03_ml_short.json +38 -0
  88. package/skills/claw-compactor/benchmark/data/sample_04_mixed_long.json +318 -0
  89. package/skills/claw-compactor/benchmark/data/sample_05_sysadmin.json +56 -0
  90. package/skills/claw-compactor/benchmark/evaluate.py +278 -0
  91. package/skills/claw-compactor/benchmark/report.py +241 -0
  92. package/skills/claw-compactor/benchmark/results/benchmark_results.json +405 -0
  93. package/skills/claw-compactor/benchmark/run_benchmark.py +238 -0
  94. package/skills/claw-compactor/engram.yaml +49 -0
  95. package/skills/claw-compactor/proxy/README.md +87 -0
  96. package/skills/claw-compactor/proxy/dashboard.html +804 -0
  97. package/skills/claw-compactor/proxy/event-log.mjs +213 -0
  98. package/skills/claw-compactor/proxy/fair-queue.mjs +288 -0
  99. package/skills/claw-compactor/proxy/metrics-store.mjs +425 -0
  100. package/skills/claw-compactor/proxy/package-lock.json +125 -0
  101. package/skills/claw-compactor/proxy/package.json +15 -0
  102. package/skills/claw-compactor/proxy/portal.html +360 -0
  103. package/skills/claw-compactor/proxy/process-registry.mjs +447 -0
  104. package/skills/claw-compactor/proxy/rate-limiter.mjs +117 -0
  105. package/skills/claw-compactor/proxy/redis-client.mjs +75 -0
  106. package/skills/claw-compactor/proxy/retry.mjs +182 -0
  107. package/skills/claw-compactor/proxy/server.mjs +2100 -0
  108. package/skills/claw-compactor/proxy/session-affinity.mjs +177 -0
  109. package/skills/claw-compactor/proxy/start.sh +23 -0
  110. package/skills/claw-compactor/proxy/test/event-log.test.mjs +237 -0
  111. package/skills/claw-compactor/proxy/test/helpers.mjs +103 -0
  112. package/skills/claw-compactor/proxy/test/integration.test.mjs +297 -0
  113. package/skills/claw-compactor/proxy/test/metrics-store.test.mjs +243 -0
  114. package/skills/claw-compactor/proxy/test/process-registry.test.mjs +390 -0
  115. package/skills/claw-compactor/proxy/test/rate-limiter.test.mjs +121 -0
  116. package/skills/claw-compactor/proxy/test/redis-client.test.mjs +62 -0
  117. package/skills/claw-compactor/proxy/test/token-tracker.test.mjs +200 -0
  118. package/skills/claw-compactor/proxy/token-tracker.mjs +482 -0
  119. package/skills/claw-compactor/pyproject.toml +26 -0
  120. package/skills/claw-compactor/references/README.md +44 -0
  121. package/skills/claw-compactor/references/architecture.md +197 -0
  122. package/skills/claw-compactor/references/benchmarks.md +94 -0
  123. package/skills/claw-compactor/references/compression-prompts.md +39 -0
  124. package/skills/claw-compactor/references/compression-techniques.md +219 -0
  125. package/skills/claw-compactor/references/testing.md +160 -0
  126. package/skills/claw-compactor/scripts/audit_memory.py +216 -0
  127. package/skills/claw-compactor/scripts/compress_memory.py +236 -0
  128. package/skills/claw-compactor/scripts/compressed_context.py +280 -0
  129. package/skills/claw-compactor/scripts/dedup_memory.py +147 -0
  130. package/skills/claw-compactor/scripts/dictionary_compress.py +170 -0
  131. package/skills/claw-compactor/scripts/engram-auto.sh +152 -0
  132. package/skills/claw-compactor/scripts/engram_auto.py +695 -0
  133. package/skills/claw-compactor/scripts/engram_cli.py +474 -0
  134. package/skills/claw-compactor/scripts/estimate_tokens.py +131 -0
  135. package/skills/claw-compactor/scripts/generate_summary_tiers.py +292 -0
  136. package/skills/claw-compactor/scripts/lib/__init__.py +34 -0
  137. package/skills/claw-compactor/scripts/lib/config.py +289 -0
  138. package/skills/claw-compactor/scripts/lib/dedup.py +119 -0
  139. package/skills/claw-compactor/scripts/lib/dictionary.py +281 -0
  140. package/skills/claw-compactor/scripts/lib/engram.py +793 -0
  141. package/skills/claw-compactor/scripts/lib/engram_prompts.py +175 -0
  142. package/skills/claw-compactor/scripts/lib/engram_storage.py +254 -0
  143. package/skills/claw-compactor/scripts/lib/exceptions.py +24 -0
  144. package/skills/claw-compactor/scripts/lib/markdown.py +312 -0
  145. package/skills/claw-compactor/scripts/lib/rle.py +165 -0
  146. package/skills/claw-compactor/scripts/lib/tokenizer_optimizer.py +188 -0
  147. package/skills/claw-compactor/scripts/lib/tokens.py +68 -0
  148. package/skills/claw-compactor/scripts/mem_compress.py +788 -0
  149. package/skills/claw-compactor/scripts/observation_compressor.py +346 -0
  150. package/skills/claw-compactor/tests/__init__.py +0 -0
  151. package/skills/claw-compactor/tests/conftest.py +94 -0
  152. package/skills/claw-compactor/tests/test_audit_comprehensive.py +123 -0
  153. package/skills/claw-compactor/tests/test_audit_memory.py +67 -0
  154. package/skills/claw-compactor/tests/test_benchmark.py +122 -0
  155. package/skills/claw-compactor/tests/test_cli_commands.py +228 -0
  156. package/skills/claw-compactor/tests/test_compress_memory.py +109 -0
  157. package/skills/claw-compactor/tests/test_compress_memory_comprehensive.py +191 -0
  158. package/skills/claw-compactor/tests/test_compressed_context.py +145 -0
  159. package/skills/claw-compactor/tests/test_config.py +54 -0
  160. package/skills/claw-compactor/tests/test_dedup_memory.py +124 -0
  161. package/skills/claw-compactor/tests/test_dictionary.py +327 -0
  162. package/skills/claw-compactor/tests/test_dictionary_comprehensive.py +241 -0
  163. package/skills/claw-compactor/tests/test_engram.py +1091 -0
  164. package/skills/claw-compactor/tests/test_engram_auto.py +529 -0
  165. package/skills/claw-compactor/tests/test_error_handling.py +194 -0
  166. package/skills/claw-compactor/tests/test_estimate_tokens.py +99 -0
  167. package/skills/claw-compactor/tests/test_generate_summary_tiers.py +105 -0
  168. package/skills/claw-compactor/tests/test_integration.py +249 -0
  169. package/skills/claw-compactor/tests/test_lib_dedup.py +135 -0
  170. package/skills/claw-compactor/tests/test_lib_markdown.py +388 -0
  171. package/skills/claw-compactor/tests/test_lib_tokens.py +82 -0
  172. package/skills/claw-compactor/tests/test_main_entry.py +176 -0
  173. package/skills/claw-compactor/tests/test_markdown_advanced.py +284 -0
  174. package/skills/claw-compactor/tests/test_new_features.py +77 -0
  175. package/skills/claw-compactor/tests/test_observation_comprehensive.py +261 -0
  176. package/skills/claw-compactor/tests/test_observation_compressor.py +128 -0
  177. package/skills/claw-compactor/tests/test_performance.py +172 -0
  178. package/skills/claw-compactor/tests/test_pipeline.py +109 -0
  179. package/skills/claw-compactor/tests/test_real_workspace.py +161 -0
  180. package/skills/claw-compactor/tests/test_rle.py +114 -0
  181. package/skills/claw-compactor/tests/test_rle_comprehensive.py +151 -0
  182. package/skills/claw-compactor/tests/test_roundtrip.py +253 -0
  183. package/skills/claw-compactor/tests/test_roundtrip_comprehensive.py +192 -0
  184. package/skills/claw-compactor/tests/test_tiers_comprehensive.py +171 -0
  185. package/skills/claw-compactor/tests/test_token_economics.py +237 -0
  186. package/skills/claw-compactor/tests/test_tokenizer_optimizer.py +175 -0
  187. package/skills/claw-compactor/tests/test_tokenizer_optimizer_comprehensive.py +241 -0
  188. package/skills/claw-compactor/tests/test_tokens.py +27 -0
  189. package/skills/cmo/CHANGELOG.md +61 -0
  190. package/skills/cmo/SKILL.md +500 -0
  191. package/skills/cmo/references/ai-slop-patterns.md +56 -0
  192. package/skills/cmo/references/analytics-guide.md +152 -0
  193. package/skills/cmo/references/ideas-library.md +197 -0
  194. package/skills/cmo/rules/brand-file-map.md +198 -0
  195. package/skills/cmo/rules/brand-memory.md +158 -0
  196. package/skills/cmo/rules/cli-runtime-index.md +95 -0
  197. package/skills/cmo/rules/command-reference.md +167 -0
  198. package/skills/cmo/rules/communication.md +15 -0
  199. package/skills/cmo/rules/context-switch.md +101 -0
  200. package/skills/cmo/rules/ecosystem.md +125 -0
  201. package/skills/cmo/rules/error-recovery.md +98 -0
  202. package/skills/cmo/rules/learning-loop.md +170 -0
  203. package/skills/cmo/rules/monorepo.md +78 -0
  204. package/skills/cmo/rules/output-format.md +124 -0
  205. package/skills/cmo/rules/persona.md +15 -0
  206. package/skills/cmo/rules/playbooks.md +327 -0
  207. package/skills/cmo/rules/progressive-enhancement.md +231 -0
  208. package/skills/cmo/rules/publish-index.md +106 -0
  209. package/skills/cmo/rules/quality-gate.md +90 -0
  210. package/skills/cmo/rules/recency-grounding.md +124 -0
  211. package/skills/cmo/rules/safety.md +80 -0
  212. package/skills/cmo/rules/studio-api-index.md +138 -0
  213. package/skills/cmo/rules/studio-integration.md +147 -0
  214. package/skills/cmo/rules/sub-agents.md +167 -0
  215. package/skills/cold-email/SKILL.md +240 -0
  216. package/skills/cold-email/evals/evals.json +94 -0
  217. package/skills/cold-email/references/benchmarks.md +83 -0
  218. package/skills/cold-email/references/follow-up-sequences.md +81 -0
  219. package/skills/cold-email/references/frameworks.md +90 -0
  220. package/skills/cold-email/references/personalization.md +79 -0
  221. package/skills/cold-email/references/subject-lines.md +53 -0
  222. package/skills/competitive-intel/SKILL.md +433 -0
  223. package/skills/competitive-intel/references/competitive-frameworks.md +145 -0
  224. package/skills/competitor-alternatives/SKILL.md +362 -0
  225. package/skills/competitor-alternatives/evals/evals.json +93 -0
  226. package/skills/competitor-alternatives/references/content-architecture.md +271 -0
  227. package/skills/competitor-alternatives/references/templates.md +223 -0
  228. package/skills/concreteness-fixer/SKILL.md +483 -0
  229. package/skills/concreteness-fixer/scripts/validation/concreteness-validator.js +221 -0
  230. package/skills/concreteness-fixer/utils/execFileNoThrow.js +16 -0
  231. package/skills/content-atomizer/SKILL.md +424 -0
  232. package/skills/content-atomizer/references/hook-strategies.md +56 -0
  233. package/skills/content-atomizer/references/platform-specs.md +118 -0
  234. package/skills/content-atomizer/references/platforms/bluesky.md +64 -0
  235. package/skills/content-atomizer/references/platforms/instagram.md +77 -0
  236. package/skills/content-atomizer/references/platforms/linkedin.md +120 -0
  237. package/skills/content-atomizer/references/platforms/reddit.md +73 -0
  238. package/skills/content-atomizer/references/platforms/threads.md +64 -0
  239. package/skills/content-atomizer/references/platforms/tiktok.md +55 -0
  240. package/skills/content-atomizer/references/platforms/twitter.md +123 -0
  241. package/skills/content-atomizer/references/platforms/youtube.md +56 -0
  242. package/skills/content-atomizer/references/tweet-patterns.md +40 -0
  243. package/skills/content-repost-manager/SKILL.md +245 -0
  244. package/skills/content-repost-manager/scripts/content-repost-scan.py +133 -0
  245. package/skills/context-recovery/.clawdhub/origin.json +7 -0
  246. package/skills/context-recovery/SKILL.md +198 -0
  247. package/skills/context-recovery/_meta.json +6 -0
  248. package/skills/conversion-flow-cro/SKILL.md +248 -0
  249. package/skills/conversion-flow-cro/references/flow-benchmarks.md +158 -0
  250. package/skills/copy-editing/SKILL.md +474 -0
  251. package/skills/copy-editing/evals/evals.json +89 -0
  252. package/skills/copy-editing/references/plain-english-alternatives.md +394 -0
  253. package/skills/copywriting/SKILL.md +349 -0
  254. package/skills/copywriting/evals/evals.json +111 -0
  255. package/skills/copywriting/references/awareness-level-guide.md +227 -0
  256. package/skills/copywriting/references/copy-frameworks.md +389 -0
  257. package/skills/copywriting/references/natural-transitions.md +272 -0
  258. package/skills/creative/SKILL.md +365 -0
  259. package/skills/creative/modes/ad-creative.md +1748 -0
  260. package/skills/creative/modes/product-photo.md +1520 -0
  261. package/skills/creative/modes/product-video.md +1657 -0
  262. package/skills/creative/modes/social-graphics.md +1511 -0
  263. package/skills/creative/modes/talking-head.md +2208 -0
  264. package/skills/creative/references/VISUAL_INTELLIGENCE.md +1233 -0
  265. package/skills/deep-serp-article/SKILL.md +677 -0
  266. package/skills/deep-serp-article/SKILL.md.bak +530 -0
  267. package/skills/earned-media/SKILL.md +260 -0
  268. package/skills/email-sequences/SKILL.md +446 -0
  269. package/skills/email-sequences/references/sequence-templates.md +1217 -0
  270. package/skills/firecrawl/.skill-meta.json +13 -0
  271. package/skills/firecrawl/SKILL.md +154 -0
  272. package/skills/firecrawl/rules/install.md +63 -0
  273. package/skills/firecrawl/rules/security.md +26 -0
  274. package/skills/form-cro/SKILL.md +429 -0
  275. package/skills/form-cro/evals/evals.json +90 -0
  276. package/skills/free-tool-strategy/SKILL.md +325 -0
  277. package/skills/free-tool-strategy/evals/evals.json +90 -0
  278. package/skills/free-tool-strategy/references/tool-types.md +253 -0
  279. package/skills/frontend-slides/SKILL.md +248 -0
  280. package/skills/frontend-slides/STYLE_PRESETS.md +347 -0
  281. package/skills/frontend-slides/animation-patterns.md +110 -0
  282. package/skills/frontend-slides/html-template.md +347 -0
  283. package/skills/frontend-slides/scripts/extract-pptx.py +96 -0
  284. package/skills/frontend-slides/viewport-base.css +153 -0
  285. package/skills/guardrail-framework/SKILL.md +319 -0
  286. package/skills/hook-generator/SKILL.md +438 -0
  287. package/skills/hook-generator/database/2026-PLATFORM-RESEARCH.md +211 -0
  288. package/skills/hook-generator/database/instagram-hooks.json +591 -0
  289. package/skills/hook-generator/database/linkedin-hooks.json +502 -0
  290. package/skills/hook-generator/database/medium-hooks.json +181 -0
  291. package/skills/hook-generator/database/tiktok-hooks.json +337 -0
  292. package/skills/hook-generator/database/twitter-hooks.json +567 -0
  293. package/skills/hook-generator/database/youtube-hooks.json +275 -0
  294. package/skills/hook-generator/package-lock.json +24 -0
  295. package/skills/hook-generator/package.json +5 -0
  296. package/skills/hook-generator/references/hook-generation-system.md +257 -0
  297. package/skills/hook-generator/references/hook-performance-analysis.json +153 -0
  298. package/skills/hook-generator/references/hooks-library.md +128 -0
  299. package/skills/hook-generator/references/platform-hook-frameworks.md +275 -0
  300. package/skills/hook-generator/scripts/generate-hooks-unified.js +722 -0
  301. package/skills/hook-generator/scripts/generate-hooks.js +225 -0
  302. package/skills/hook-generator/scripts/generate.sh +36 -0
  303. package/skills/hook-generator/scripts/ralph-hook-enhancer.js +864 -0
  304. package/skills/hook-generator/scripts/test-gemini-output.js +51 -0
  305. package/skills/humanizer/README.md +120 -0
  306. package/skills/humanizer/SKILL.md +699 -0
  307. package/skills/image-gen/SKILL.md +277 -0
  308. package/skills/image-gen/references/prompt-patterns.md +114 -0
  309. package/skills/image-gen/references/visual-metaphors.md +51 -0
  310. package/skills/impeccable/SKILL.md +86 -0
  311. package/skills/impeccable/reference/adapt.md +312 -0
  312. package/skills/impeccable/reference/adapt.native.md +58 -0
  313. package/skills/impeccable/reference/android.md +40 -0
  314. package/skills/impeccable/reference/animate.md +86 -0
  315. package/skills/impeccable/reference/audit.md +136 -0
  316. package/skills/impeccable/reference/audit.native.md +139 -0
  317. package/skills/impeccable/reference/bolder.md +31 -0
  318. package/skills/impeccable/reference/clarify.md +94 -0
  319. package/skills/impeccable/reference/colorize.md +86 -0
  320. package/skills/impeccable/reference/craft-floor.md +42 -0
  321. package/skills/impeccable/reference/craft.md +5 -0
  322. package/skills/impeccable/reference/critique.md +788 -0
  323. package/skills/impeccable/reference/degraded/asset-producer.md +90 -0
  324. package/skills/impeccable/reference/degraded/documenter.md +24 -0
  325. package/skills/impeccable/reference/degraded/finish-reviewer.md +37 -0
  326. package/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  327. package/skills/impeccable/reference/delight.md +70 -0
  328. package/skills/impeccable/reference/distill.md +111 -0
  329. package/skills/impeccable/reference/doctor.md +53 -0
  330. package/skills/impeccable/reference/document.md +416 -0
  331. package/skills/impeccable/reference/extract.md +69 -0
  332. package/skills/impeccable/reference/harden.md +336 -0
  333. package/skills/impeccable/reference/hooks.md +105 -0
  334. package/skills/impeccable/reference/init.md +125 -0
  335. package/skills/impeccable/reference/ios.md +45 -0
  336. package/skills/impeccable/reference/layout.md +84 -0
  337. package/skills/impeccable/reference/live-setup.md +102 -0
  338. package/skills/impeccable/reference/live.md +323 -0
  339. package/skills/impeccable/reference/new-work.md +110 -0
  340. package/skills/impeccable/reference/onboard.md +234 -0
  341. package/skills/impeccable/reference/operate.md +61 -0
  342. package/skills/impeccable/reference/optimize.md +258 -0
  343. package/skills/impeccable/reference/overdrive.md +127 -0
  344. package/skills/impeccable/reference/polish.md +97 -0
  345. package/skills/impeccable/reference/quieter.md +99 -0
  346. package/skills/impeccable/reference/routing.md +18 -0
  347. package/skills/impeccable/reference/shape.md +59 -0
  348. package/skills/impeccable/reference/typeset.md +80 -0
  349. package/skills/impeccable/reference/visualize.md +49 -0
  350. package/skills/impeccable/scripts/command-metadata.json +94 -0
  351. package/skills/impeccable/scripts/concept-seed.mjs +558 -0
  352. package/skills/impeccable/scripts/context-signals.mjs +334 -0
  353. package/skills/impeccable/scripts/context.mjs +1467 -0
  354. package/skills/impeccable/scripts/critique-storage.mjs +213 -0
  355. package/skills/impeccable/scripts/detect-csp.mjs +198 -0
  356. package/skills/impeccable/scripts/detect.mjs +21 -0
  357. package/skills/impeccable/scripts/detector/browser/injected/index.mjs +2023 -0
  358. package/skills/impeccable/scripts/detector/cli/main.mjs +438 -0
  359. package/skills/impeccable/scripts/detector/design-system.mjs +1046 -0
  360. package/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +8283 -0
  361. package/skills/impeccable/scripts/detector/detect-antipatterns.mjs +50 -0
  362. package/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +372 -0
  363. package/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +1168 -0
  364. package/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +1186 -0
  365. package/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +264 -0
  366. package/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  367. package/skills/impeccable/scripts/detector/findings.mjs +18 -0
  368. package/skills/impeccable/scripts/detector/node/file-system.mjs +203 -0
  369. package/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  370. package/skills/impeccable/scripts/detector/registry/antipatterns.mjs +617 -0
  371. package/skills/impeccable/scripts/detector/rules/checks.mjs +5580 -0
  372. package/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  373. package/skills/impeccable/scripts/detector/shared/constants.mjs +112 -0
  374. package/skills/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  375. package/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  376. package/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  377. package/skills/impeccable/scripts/doctor.mjs +336 -0
  378. package/skills/impeccable/scripts/embed-prompt.mjs +133 -0
  379. package/skills/impeccable/scripts/generate-image.mjs +240 -0
  380. package/skills/impeccable/scripts/hook-admin.mjs +801 -0
  381. package/skills/impeccable/scripts/hook-before-edit.mjs +508 -0
  382. package/skills/impeccable/scripts/hook-lib.mjs +2153 -0
  383. package/skills/impeccable/scripts/hook.mjs +78 -0
  384. package/skills/impeccable/scripts/lib/artifact-schema.mjs +93 -0
  385. package/skills/impeccable/scripts/lib/composition-catalog.mjs +200 -0
  386. package/skills/impeccable/scripts/lib/concept-catalog.mjs +384 -0
  387. package/skills/impeccable/scripts/lib/design-parser.mjs +925 -0
  388. package/skills/impeccable/scripts/lib/impeccable-config.mjs +658 -0
  389. package/skills/impeccable/scripts/lib/impeccable-paths.mjs +137 -0
  390. package/skills/impeccable/scripts/lib/is-generated.mjs +69 -0
  391. package/skills/impeccable/scripts/lib/provider.mjs +5 -0
  392. package/skills/impeccable/scripts/lib/roll-selection.mjs +362 -0
  393. package/skills/impeccable/scripts/lib/staleness-deep.mjs +478 -0
  394. package/skills/impeccable/scripts/lib/staleness-notice.mjs +169 -0
  395. package/skills/impeccable/scripts/lib/staleness.mjs +457 -0
  396. package/skills/impeccable/scripts/lib/surface-briefs.mjs +151 -0
  397. package/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  398. package/skills/impeccable/scripts/lib/target-slug.mjs +33 -0
  399. package/skills/impeccable/scripts/lib/template-extensions.mjs +146 -0
  400. package/skills/impeccable/scripts/live/accept-css.mjs +617 -0
  401. package/skills/impeccable/scripts/live/accept-verify.mjs +60 -0
  402. package/skills/impeccable/scripts/live/browser-script-parts.mjs +55 -0
  403. package/skills/impeccable/scripts/live/completion.mjs +28 -0
  404. package/skills/impeccable/scripts/live/event-validation.mjs +199 -0
  405. package/skills/impeccable/scripts/live/frameworks/astro.mjs +47 -0
  406. package/skills/impeccable/scripts/live/frameworks/detect-utils.mjs +73 -0
  407. package/skills/impeccable/scripts/live/frameworks/index.mjs +143 -0
  408. package/skills/impeccable/scripts/live/frameworks/journal.mjs +197 -0
  409. package/skills/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
  410. package/skills/impeccable/scripts/live/frameworks/nuxt.mjs +161 -0
  411. package/skills/impeccable/scripts/live/frameworks/script-src.mjs +17 -0
  412. package/skills/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
  413. package/skills/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
  414. package/skills/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
  415. package/skills/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
  416. package/skills/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
  417. package/skills/impeccable/scripts/live/generation-preflight.mjs +149 -0
  418. package/skills/impeccable/scripts/live/insert-ui.mjs +458 -0
  419. package/skills/impeccable/scripts/live/instructions.mjs +142 -0
  420. package/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  421. package/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  422. package/skills/impeccable/scripts/live/manual-edits-buffer.mjs +152 -0
  423. package/skills/impeccable/scripts/live/poll-lanes.mjs +14 -0
  424. package/skills/impeccable/scripts/live/roots.mjs +508 -0
  425. package/skills/impeccable/scripts/live/session-store.mjs +563 -0
  426. package/skills/impeccable/scripts/live/source-lock.mjs +105 -0
  427. package/skills/impeccable/scripts/live/source-search.mjs +105 -0
  428. package/skills/impeccable/scripts/live/svelte-ast.mjs +961 -0
  429. package/skills/impeccable/scripts/live/svelte-component.mjs +1342 -0
  430. package/skills/impeccable/scripts/live/sveltekit-adapter.mjs +316 -0
  431. package/skills/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
  432. package/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  433. package/skills/impeccable/scripts/live/vocabulary.mjs +171 -0
  434. package/skills/impeccable/scripts/live-accept.mjs +954 -0
  435. package/skills/impeccable/scripts/live-browser-dom.js +146 -0
  436. package/skills/impeccable/scripts/live-browser-session.js +123 -0
  437. package/skills/impeccable/scripts/live-browser.js +12520 -0
  438. package/skills/impeccable/scripts/live-commit-manual-edits.mjs +1244 -0
  439. package/skills/impeccable/scripts/live-complete.mjs +107 -0
  440. package/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  441. package/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  442. package/skills/impeccable/scripts/live-inject.mjs +503 -0
  443. package/skills/impeccable/scripts/live-insert.mjs +292 -0
  444. package/skills/impeccable/scripts/live-manual-edit-evidence.mjs +368 -0
  445. package/skills/impeccable/scripts/live-poll.mjs +429 -0
  446. package/skills/impeccable/scripts/live-resume.mjs +123 -0
  447. package/skills/impeccable/scripts/live-server.mjs +1669 -0
  448. package/skills/impeccable/scripts/live-status.mjs +71 -0
  449. package/skills/impeccable/scripts/live-target.mjs +30 -0
  450. package/skills/impeccable/scripts/live-wrap.mjs +927 -0
  451. package/skills/impeccable/scripts/live.mjs +359 -0
  452. package/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  453. package/skills/impeccable/scripts/palette.mjs +628 -0
  454. package/skills/impeccable/scripts/pin.mjs +221 -0
  455. package/skills/impeccable/scripts/serve-question.mjs +932 -0
  456. package/skills/impeccable/scripts/surface-brief.mjs +74 -0
  457. package/skills/inkstone-admin/SKILL.md +66 -0
  458. package/skills/jev-hook-gate/SKILL.md +111 -0
  459. package/skills/keyword-research/SKILL.md +503 -0
  460. package/skills/keyword-research/references/dr-cap-and-confidence.md +62 -0
  461. package/skills/keyword-research/references/keyword-examples.md +769 -0
  462. package/skills/landing-page-architect/SKILL.md +585 -0
  463. package/skills/landing-page-architect/references/architecture-reference.md +32 -0
  464. package/skills/launch-strategy/SKILL.md +384 -0
  465. package/skills/launch-strategy/evals/evals.json +91 -0
  466. package/skills/launch-strategy/references/launch-checklist.md +209 -0
  467. package/skills/launch-strategy/references/launch-timeline.md +159 -0
  468. package/skills/lead-email-craft/SKILL.md +522 -0
  469. package/skills/lead-magnets/SKILL.md +310 -0
  470. package/skills/lead-magnets/references/benchmarks.md +129 -0
  471. package/skills/lead-magnets/references/format-guide.md +196 -0
  472. package/skills/link-building-seo/SKILL.md +210 -0
  473. package/skills/lint-report.txt +1 -0
  474. package/skills/manifest.json +129 -0
  475. package/skills/marketing-demo/SKILL.md +249 -0
  476. package/skills/marketing-demo/references/remotion-examples.md +281 -0
  477. package/skills/marketing-ideas/SKILL.md +167 -0
  478. package/skills/marketing-ideas/evals/evals.json +90 -0
  479. package/skills/marketing-ideas/references/ideas-by-category.md +366 -0
  480. package/skills/marketing-psychology/SKILL.md +374 -0
  481. package/skills/marketing-psychology/evals/evals.json +88 -0
  482. package/skills/marketing-psychology/references/case-studies.md +148 -0
  483. package/skills/narrative-research-article/SKILL.md +469 -0
  484. package/skills/narrative-research-article/references/lanusse-patterns.md +166 -0
  485. package/skills/notebooklm/SKILL.md +674 -0
  486. package/skills/off-page-seo/SKILL.md +219 -0
  487. package/skills/onboarding-cro/SKILL.md +220 -0
  488. package/skills/onboarding-cro/evals/evals.json +92 -0
  489. package/skills/onboarding-cro/references/experiments.md +258 -0
  490. package/skills/original-research/SKILL.md +388 -0
  491. package/skills/page-cro/SKILL.md +269 -0
  492. package/skills/page-cro/references/cro-benchmarks.md +140 -0
  493. package/skills/paper-marketing/SKILL.md +448 -0
  494. package/skills/paper-marketing/references/agent-quality-rubric.md +68 -0
  495. package/skills/paper-marketing/references/brand-system-template.md +120 -0
  496. package/skills/paper-marketing/references/paper-mcp-rules.md +71 -0
  497. package/skills/paper-marketing/references/platform-conventions.md +43 -0
  498. package/skills/paywall-upgrade-cro/SKILL.md +227 -0
  499. package/skills/paywall-upgrade-cro/evals/evals.json +93 -0
  500. package/skills/paywall-upgrade-cro/references/experiments.md +164 -0
  501. package/skills/platform-formatting/SKILL.md +51 -0
  502. package/skills/popup-cro/SKILL.md +454 -0
  503. package/skills/popup-cro/evals/evals.json +94 -0
  504. package/skills/positioning-angles/SKILL.md +524 -0
  505. package/skills/positioning-angles/references/ad-matrix-and-examples.md +176 -0
  506. package/skills/positioning-angles/references/angle-frameworks.md +187 -0
  507. package/skills/positioning-angles/references/dunford-positioning.md +135 -0
  508. package/skills/positioning-angles/references/hormozi-offer.md +247 -0
  509. package/skills/positioning-angles/references/schwartz-sophistication.md +166 -0
  510. package/skills/positioning-angles/references/unique-mechanism.md +162 -0
  511. package/skills/pricing-strategy/SKILL.md +346 -0
  512. package/skills/pricing-strategy/evals/evals.json +90 -0
  513. package/skills/pricing-strategy/references/research-methods.md +152 -0
  514. package/skills/pricing-strategy/references/tier-structure.md +307 -0
  515. package/skills/programmatic-seo/SKILL.md +238 -0
  516. package/skills/programmatic-seo/evals/evals.json +94 -0
  517. package/skills/programmatic-seo/references/playbooks.md +308 -0
  518. package/skills/project-rag/SKILL.md +127 -0
  519. package/skills/quality-gate/SKILL.md +519 -0
  520. package/skills/quality-gate/judge-personas/buyer.md +47 -0
  521. package/skills/quality-gate/judge-personas/expert.md +47 -0
  522. package/skills/quality-gate/judge-personas/outsider.md +47 -0
  523. package/skills/quality-gate/judge-personas/scanner.md +41 -0
  524. package/skills/quality-gate/judge-personas/skeptic.md +40 -0
  525. package/skills/referral-program/SKILL.md +290 -0
  526. package/skills/referral-program/evals/evals.json +89 -0
  527. package/skills/referral-program/references/affiliate-programs.md +163 -0
  528. package/skills/referral-program/references/program-examples.md +143 -0
  529. package/skills/review-sites/SKILL.md +279 -0
  530. package/skills/revops/SKILL.md +343 -0
  531. package/skills/revops/evals/evals.json +91 -0
  532. package/skills/revops/references/automation-playbooks.md +290 -0
  533. package/skills/revops/references/lifecycle-definitions.md +278 -0
  534. package/skills/revops/references/routing-rules.md +203 -0
  535. package/skills/revops/references/scoring-models.md +247 -0
  536. package/skills/saas-product-seo/SKILL.md +324 -0
  537. package/skills/sales-enablement/SKILL.md +349 -0
  538. package/skills/sales-enablement/evals/evals.json +91 -0
  539. package/skills/sales-enablement/references/deck-frameworks.md +263 -0
  540. package/skills/sales-enablement/references/demo-scripts.md +355 -0
  541. package/skills/sales-enablement/references/objection-library.md +270 -0
  542. package/skills/sales-enablement/references/one-pager-templates.md +208 -0
  543. package/skills/schema-markup/SKILL.md +179 -0
  544. package/skills/schema-markup/evals/evals.json +87 -0
  545. package/skills/schema-markup/references/schema-examples.md +398 -0
  546. package/skills/self-improve/SKILL.md +166 -0
  547. package/skills/self-improving-agent/README.md +243 -0
  548. package/skills/self-improving-agent/SKILL.md +437 -0
  549. package/skills/self-improving-agent/scripts/__init__.py +16 -0
  550. package/skills/self-improving-agent/scripts/__pycache__/auto_detect.cpython-314.pyc +0 -0
  551. package/skills/self-improving-agent/scripts/__pycache__/nlm_curation.cpython-314.pyc +0 -0
  552. package/skills/self-improving-agent/scripts/__pycache__/self_improving_agent.cpython-314.pyc +0 -0
  553. package/skills/self-improving-agent/scripts/auto_detect.py +270 -0
  554. package/skills/self-improving-agent/scripts/init_expertise_registry.py +89 -0
  555. package/skills/self-improving-agent/scripts/nlm_curation.py +259 -0
  556. package/skills/self-improving-agent/scripts/self-improving-agent +113 -0
  557. package/skills/self-improving-agent/scripts/self_improving_agent.py +603 -0
  558. package/skills/self-improving-agent/scripts/test_curation_scorer.py +20 -0
  559. package/skills/self-improving-agent/scripts/test_self_improving.py +184 -0
  560. package/skills/send-email/SKILL.md +426 -0
  561. package/skills/send-email/references/batch-email-examples.md +807 -0
  562. package/skills/send-email/references/best-practices.md +453 -0
  563. package/skills/send-email/references/installation.md +123 -0
  564. package/skills/send-email/references/single-email-examples.md +470 -0
  565. package/skills/send-email/references/webhooks.md +221 -0
  566. package/skills/seo-audit/SKILL.md +362 -0
  567. package/skills/seo-audit/references/ai-writing-detection.md +200 -0
  568. package/skills/seo-audit/references/architecture-patterns.md +271 -0
  569. package/skills/seo-audit/references/schema-templates.md +427 -0
  570. package/skills/seo-content/SKILL.md +510 -0
  571. package/skills/seo-content/references/anti-ai-detection.md +184 -0
  572. package/skills/seo-content/references/content-structures.md +148 -0
  573. package/skills/seo-content/references/eeat-examples.md +235 -0
  574. package/skills/seo-content/references/on-page-seo.md +261 -0
  575. package/skills/seo-content/references/pseo-playbooks.md +226 -0
  576. package/skills/seo-content/references/schema-templates.md +104 -0
  577. package/skills/seo-content/references/serp-analysis-format.md +151 -0
  578. package/skills/seo-machine/SKILL.md +331 -0
  579. package/skills/seo-machine/assets/brand-template.md +73 -0
  580. package/skills/seo-machine/assets/link-inventory-template.md +74 -0
  581. package/skills/seo-machine/assets/roadmap-template.md +184 -0
  582. package/skills/seo-machine/references/api-stack-recipes.md +230 -0
  583. package/skills/seo-machine/references/exa-recipes.md +260 -0
  584. package/skills/seo-machine/references/manual-research.md +73 -0
  585. package/skills/seo-machine/references/methodology.md +87 -0
  586. package/skills/seo-machine/references/off-page.md +123 -0
  587. package/skills/seo-machine/references/patterns/alternatives.md +176 -0
  588. package/skills/seo-machine/references/patterns/compare.md +105 -0
  589. package/skills/seo-machine/references/patterns/playbooks.md +106 -0
  590. package/skills/seo-machine/references/patterns/use-case.md +109 -0
  591. package/skills/seo-machine/references/quality-bars.md +99 -0
  592. package/skills/seo-machine/references/stacks/astro.md +156 -0
  593. package/skills/seo-machine/references/stacks/detection.md +61 -0
  594. package/skills/seo-machine/references/stacks/markdown-fallback.md +181 -0
  595. package/skills/seo-machine/references/stacks/nextjs.md +108 -0
  596. package/skills/seo-machine/references/stacks/rails-inertia.md +99 -0
  597. package/skills/seo-machine/references/striking-distance.md +116 -0
  598. package/skills/seo-machine/references/technical-audit.md +153 -0
  599. package/skills/seo-machine/scripts/link_audit.py +265 -0
  600. package/skills/seo-machine/scripts/tech_audit.py +221 -0
  601. package/skills/seo-machine/scripts/word_count.py +154 -0
  602. package/skills/sharp-thesis-validator/SKILL.md +393 -0
  603. package/skills/sharp-thesis-validator/examples.md +186 -0
  604. package/skills/signup-flow-cro/SKILL.md +359 -0
  605. package/skills/signup-flow-cro/evals/evals.json +88 -0
  606. package/skills/site-architecture/SKILL.md +357 -0
  607. package/skills/site-architecture/evals/evals.json +88 -0
  608. package/skills/site-architecture/references/mermaid-templates.md +216 -0
  609. package/skills/site-architecture/references/navigation-patterns.md +305 -0
  610. package/skills/site-architecture/references/site-type-templates.md +293 -0
  611. package/skills/skill-self-improvement/SKILL.md +151 -0
  612. package/skills/slideshow-script/SKILL.md +274 -0
  613. package/skills/slideshow-script/references/content-spec-schema.md +77 -0
  614. package/skills/slideshow-script/references/frameworks.md +84 -0
  615. package/skills/slideshow-script/references/paips-framework.md +37 -0
  616. package/skills/social-campaign/SKILL.md +419 -0
  617. package/skills/social-campaign/references/paper-mcp-workflow.md +48 -0
  618. package/skills/social-content/SKILL.md +359 -0
  619. package/skills/social-content/evals/evals.json +92 -0
  620. package/skills/social-content/references/platforms.md +170 -0
  621. package/skills/social-content/references/post-templates.md +177 -0
  622. package/skills/social-content/references/reverse-engineering.md +195 -0
  623. package/skills/social-worker/SKILL.md +204 -0
  624. package/skills/startup-launcher/SKILL.md +252 -0
  625. package/skills/startup-launcher/references/file-router.md +64 -0
  626. package/skills/startup-launcher/references/platform-matrix.md +65 -0
  627. package/skills/startup-launcher/references/platforms/alternativeto.md +283 -0
  628. package/skills/startup-launcher/references/platforms/appsumo.md +495 -0
  629. package/skills/startup-launcher/references/platforms/auraplusplus.md +736 -0
  630. package/skills/startup-launcher/references/platforms/betalist.md +98 -0
  631. package/skills/startup-launcher/references/platforms/capterra.md +276 -0
  632. package/skills/startup-launcher/references/platforms/crunchbase.md +514 -0
  633. package/skills/startup-launcher/references/platforms/dealify.md +291 -0
  634. package/skills/startup-launcher/references/platforms/dealmirror.md +313 -0
  635. package/skills/startup-launcher/references/platforms/desifounder.md +490 -0
  636. package/skills/startup-launcher/references/platforms/devhunt.md +210 -0
  637. package/skills/startup-launcher/references/platforms/devpost.md +448 -0
  638. package/skills/startup-launcher/references/platforms/fazier.md +56 -0
  639. package/skills/startup-launcher/references/platforms/firsto.md +489 -0
  640. package/skills/startup-launcher/references/platforms/g2.md +226 -0
  641. package/skills/startup-launcher/references/platforms/hackernews.md +99 -0
  642. package/skills/startup-launcher/references/platforms/hackernoon.md +385 -0
  643. package/skills/startup-launcher/references/platforms/indiedeals.md +367 -0
  644. package/skills/startup-launcher/references/platforms/indiehackers.md +80 -0
  645. package/skills/startup-launcher/references/platforms/kenmoo.md +231 -0
  646. package/skills/startup-launcher/references/platforms/labstartups.md +618 -0
  647. package/skills/startup-launcher/references/platforms/launchboard.md +307 -0
  648. package/skills/startup-launcher/references/platforms/launchfast.md +560 -0
  649. package/skills/startup-launcher/references/platforms/launchigniter.md +56 -0
  650. package/skills/startup-launcher/references/platforms/launchingnext.md +674 -0
  651. package/skills/startup-launcher/references/platforms/launchit.md +574 -0
  652. package/skills/startup-launcher/references/platforms/launchyourapp.md +677 -0
  653. package/skills/startup-launcher/references/platforms/libhunt.md +220 -0
  654. package/skills/startup-launcher/references/platforms/ltdhunt.md +221 -0
  655. package/skills/startup-launcher/references/platforms/microlaunch.md +45 -0
  656. package/skills/startup-launcher/references/platforms/openalternative.md +195 -0
  657. package/skills/startup-launcher/references/platforms/peerlist.md +56 -0
  658. package/skills/startup-launcher/references/platforms/peerpush.md +64 -0
  659. package/skills/startup-launcher/references/platforms/primeclub.md +282 -0
  660. package/skills/startup-launcher/references/platforms/productcanyon.md +190 -0
  661. package/skills/startup-launcher/references/platforms/producthunt.md +98 -0
  662. package/skills/startup-launcher/references/platforms/rankinpublic.md +315 -0
  663. package/skills/startup-launcher/references/platforms/rockethub.md +374 -0
  664. package/skills/startup-launcher/references/platforms/saasgenius.md +175 -0
  665. package/skills/startup-launcher/references/platforms/saashub.md +205 -0
  666. package/skills/startup-launcher/references/platforms/saasmantra.md +221 -0
  667. package/skills/startup-launcher/references/platforms/saaspirate.md +287 -0
  668. package/skills/startup-launcher/references/platforms/saaswarrior.md +190 -0
  669. package/skills/startup-launcher/references/platforms/saaszilla.md +194 -0
  670. package/skills/startup-launcher/references/platforms/selected.md +925 -0
  671. package/skills/startup-launcher/references/platforms/sideprojectors.md +86 -0
  672. package/skills/startup-launcher/references/platforms/softonic.md +240 -0
  673. package/skills/startup-launcher/references/platforms/sourceforge.md +251 -0
  674. package/skills/startup-launcher/references/platforms/stacksocial.md +244 -0
  675. package/skills/startup-launcher/references/platforms/startupstash.md +326 -0
  676. package/skills/startup-launcher/references/platforms/theresanaiforthat.md +253 -0
  677. package/skills/startup-launcher/references/platforms/tinylaunch.md +70 -0
  678. package/skills/startup-launcher/references/platforms/tinystartups.md +72 -0
  679. package/skills/startup-launcher/references/platforms/toolfolio.md +291 -0
  680. package/skills/startup-launcher/references/platforms/trustmrr.md +194 -0
  681. package/skills/startup-launcher/references/platforms/trylaunch.md +531 -0
  682. package/skills/startup-launcher/references/platforms/uneed.md +57 -0
  683. package/skills/startup-launcher/references/templates/asset-checklist.md +218 -0
  684. package/skills/startup-launcher/references/templates/launch-tracker.md +195 -0
  685. package/skills/startup-launcher/references/templates/product-brief.md +98 -0
  686. package/skills/summarize/.skill-meta.json +13 -0
  687. package/skills/summarize/SKILL.md +170 -0
  688. package/skills/summarize/rules/install.md +157 -0
  689. package/skills/summarize/rules/usage.md +207 -0
  690. package/skills/topical-authority/SKILL.md +307 -0
  691. package/skills/topical-authority/references/jake-ward-framework.md +201 -0
  692. package/skills/topical-authority/references/serp-clustering-algorithm.md +325 -0
  693. package/skills/topical-authority/scripts/orchestrator.js +367 -0
  694. package/skills/topical-authority/scripts/seo/analyze-competitor-coverage.js +3 -0
  695. package/skills/topical-authority/scripts/seo/audit-internal-links.js +3 -0
  696. package/skills/topical-authority/scripts/seo/build-clusters-from-serp.js +3 -0
  697. package/skills/topical-authority/scripts/seo/extract-keywords-from-forums.js +3 -0
  698. package/skills/topical-authority/scripts/seo/extract-paa-questions.js +3 -0
  699. package/skills/topical-authority/scripts/seo/fetch-serp-urls.js +3 -0
  700. package/skills/topical-authority/scripts/seo/gemini-serp-clustering.js +3 -0
  701. package/skills/topical-authority/scripts/seo/identify-content-gaps.js +3 -0
  702. package/skills/topical-authority/scripts/seo/identify-research-gaps.js +3 -0
  703. package/skills/topical-authority/scripts/seo/scrape-quora-questions.js +3 -0
  704. package/skills/topical-authority/scripts/seo/scrape-reddit-pain-points.js +3 -0
  705. package/skills/topical-authority/scripts/seo/weave-internal-links.js +3 -0
  706. package/skills/visual-style/SKILL.md +199 -0
  707. package/skills/voice-brand-alignment/SKILL.md +70 -0
  708. package/skills/voice-brand-alignment/references/ai-slop-patterns.md +405 -0
  709. package/skills/voice-brand-alignment/scripts/validation/gemini-voice-validator.js +96 -0
  710. package/skills/voice-extraction/SKILL.md +234 -0
  711. package/skills/voice-extraction/references/extraction-prompt.md +155 -0
  712. package/skills/voice-extraction/references/voice-dna-template.md +168 -0
  713. package/skills/voice-extraction/references/voice-elements.md +259 -0
  714. package/skills/voice-extraction/scripts/build-corpus.js +120 -0
  715. package/skills/voice-extraction/scripts/collect-sources.sh +168 -0
  716. package/skills/warm-outbound/SKILL.md +299 -0
  717. package/skills/warm-outbound/templates/warm-message.md +39 -0
  718. package/skills/weekly-newsletter-digest/SKILL.md +78 -0
  719. package/skills/x-playwriter/SKILL.md +428 -0
  720. package/skills/x-playwriter/scripts/x-browser.js +185 -0
  721. package/skills/x-post-generator-openclaw/SKILL.md +138 -0
  722. package/skills/x-post-generator-openclaw/scripts/generate-x-post.js +537 -0
  723. package/skills/x-posting-playwriter/SKILL.md +203 -0
  724. package/skills/x-research/SKILL.md +129 -0
  725. package/skills/x-research/x-research.sh +85 -0
  726. package/skills/youtube-summarize/SKILL.md +90 -0
  727. package/skills/youtube-summarize/scripts/youtube-summarize.sh +143 -0
  728. package/src/cli.js +347 -172
  729. package/src/harness/deepseek-adapter.js +102 -0
  730. package/src/harness/deepseek.js +225 -0
  731. package/src/harness/opencode-adapter.js +174 -0
  732. package/src/harness/pi-adapter.js +172 -0
  733. package/src/harness/registry.js +121 -0
  734. package/src/harness/telemetry.js +102 -0
  735. package/src/harness/types.js +165 -0
  736. package/src/interview.js +36 -4
  737. package/src/postinstall.js +10 -12
  738. package/templates/AGENTS.md +17 -17
  739. package/templates/ORG.md +1 -1
  740. package/templates/TOOLS.md +7 -2
  741. package/templates/opencode.json +26 -29
  742. package/wiki/doctor.md +54 -0
  743. package/wiki/inkstone.md +4 -4
  744. package/wiki/opencode.md +3 -3
  745. package/skills/curated.txt +0 -68
@@ -0,0 +1,1168 @@
1
+ import { GENERIC_FONTS, OVERUSED_FONTS, EM_DASH_FLOOR, EM_DASH_CHARS_PER_DASH } from '../../shared/constants.mjs';
2
+ import { isNeutralColor } from '../../shared/color.mjs';
3
+ import { extractGoogleFontFamilies } from '../../shared/fonts.mjs';
4
+ import { checkSourceDesignSystem } from '../../design-system.mjs';
5
+ import { scanCssTextForGlow, scanCssTextForGridBackground, scanCssTextForMarquee, scanCssTextForPseudoStripe, scanCssTextForRadialHalo } from '../../rules/checks.mjs';
6
+ import { isFullPage } from '../../shared/page.mjs';
7
+ import { applyInlineIgnores } from '../../shared/inline-ignores.mjs';
8
+ import { finding } from '../../findings.mjs';
9
+ import { profileFindings, profileStep } from '../../profile/profiler.mjs';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Regex fallback (non-HTML files: CSS, JSX, TSX, etc.)
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const hasRounded = (line) =>
16
+ /\brounded(?:-\w+)?\b/.test(line.replace(/\brounded-none\b/g, ''));
17
+ const hasBorderRadius = (line) => /border-radius/i.test(line);
18
+ const isSafeElement = (line) => /<(?:blockquote|nav[\s>]|pre[\s>]|code[\s>]|a\s|input[\s>]|span[\s>])/i.test(line);
19
+
20
+
21
+ /** Strip HTML to plain text — drops script/style/comments/tags so
22
+ * content-text analyzers don't false-positive on code or CSS. */
23
+ function stripHtmlToText(html) {
24
+ return html
25
+ .replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
26
+ .replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
27
+ .replace(/<!--[\s\S]*?-->/g, ' ')
28
+ .replace(/<[^>]+>/g, ' ')
29
+ .replace(/\s+/g, ' ');
30
+ }
31
+
32
+ const PAGE_ANALYZER_EXTS = new Set(['.html', '.htm', '.astro', '.vue', '.svelte']);
33
+
34
+ function extFromFilePath(filePath) {
35
+ return filePath ? (filePath.match(/\.\w+$/)?.[0] || '').toLowerCase() : '';
36
+ }
37
+
38
+ function shouldRunPageAnalyzers(content, filePath) {
39
+ if (!isFullPage(content)) return false;
40
+ const ext = extFromFilePath(filePath);
41
+ return !ext || PAGE_ANALYZER_EXTS.has(ext);
42
+ }
43
+
44
+ const JS_SOURCE_EXTS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
45
+ const REGEX_PREFIX_KEYWORDS = new Set(['await', 'case', 'default', 'delete', 'do', 'else', 'in', 'instanceof', 'new', 'of', 'return', 'throw', 'typeof', 'void', 'yield']);
46
+ const BLOCK_BRACE_PREFIX_KEYWORDS = new Set(['do', 'else', 'finally', 'try']);
47
+
48
+ function isInsideOpeningJsxTag(source) {
49
+ const tagStart = source.lastIndexOf('<');
50
+ if (tagStart === -1 || !/^<[A-Za-z][\w.:-]*/.test(source.slice(tagStart))) return false;
51
+
52
+ let quote = '';
53
+ for (let cursor = tagStart + 1; cursor < source.length; cursor++) {
54
+ const char = source[cursor];
55
+ if (quote) {
56
+ if (char === '\\') cursor++;
57
+ else if (char === quote) quote = '';
58
+ } else if (char === "'" || char === '"') {
59
+ quote = char;
60
+ } else if (char === '>') {
61
+ return false;
62
+ }
63
+ }
64
+ return true;
65
+ }
66
+
67
+ /**
68
+ * Blank JavaScript comments without moving any following source. Regex
69
+ * findings keep their original line numbers, while prose examples inside
70
+ * comments cannot masquerade as rendered markup.
71
+ */
72
+ function stripJsComments(content, options = {}) {
73
+ let state = 'code';
74
+ let output = '';
75
+ let lastSignificant = '';
76
+ let previousSignificant = '';
77
+ let antePreviousSignificant = '';
78
+ let currentWord = '';
79
+ let currentWordPrefix = '';
80
+ let wordSeparated = false;
81
+ let regexCharClass = false;
82
+ let jsxExpressionDepth = 0;
83
+ let lastClosedBraceKind = '';
84
+ const braceKinds = [];
85
+ const templateExpressionDepths = [];
86
+
87
+ const braceKind = (startsJsxExpression = false) => (
88
+ !startsJsxExpression && (
89
+ !lastSignificant ||
90
+ lastSignificant === ')' ||
91
+ lastSignificant === ';' ||
92
+ lastSignificant === '}' ||
93
+ (previousSignificant === '=' && lastSignificant === '>') ||
94
+ BLOCK_BRACE_PREFIX_KEYWORDS.has(currentWord)
95
+ ) ? 'block' : 'expression'
96
+ );
97
+
98
+ const recordSignificant = (char) => {
99
+ if (/\s/.test(char)) {
100
+ wordSeparated = true;
101
+ return;
102
+ }
103
+ const isWordChar = /[\w$]/.test(char);
104
+ if (isWordChar && (wordSeparated || !currentWord)) {
105
+ currentWord = '';
106
+ currentWordPrefix = lastSignificant;
107
+ } else if (!isWordChar) {
108
+ currentWordPrefix = '';
109
+ }
110
+ wordSeparated = false;
111
+ antePreviousSignificant = previousSignificant;
112
+ previousSignificant = lastSignificant;
113
+ lastSignificant = char;
114
+ currentWord = isWordChar ? currentWord + char : '';
115
+ };
116
+
117
+ for (let i = 0; i < content.length; i++) {
118
+ const char = content[i];
119
+ const next = content[i + 1];
120
+
121
+ if (state === 'line-comment') {
122
+ if (char === '\n') {
123
+ output += char;
124
+ state = 'code';
125
+ } else {
126
+ output += ' ';
127
+ }
128
+ continue;
129
+ }
130
+
131
+ if (state === 'block-comment') {
132
+ if (char === '*' && next === '/') {
133
+ output += ' ';
134
+ i++;
135
+ state = 'code';
136
+ } else {
137
+ output += char === '\n' ? '\n' : ' ';
138
+ }
139
+ continue;
140
+ }
141
+
142
+ if (state === 'regex') {
143
+ output += char;
144
+ if (char === '\\' && next) {
145
+ output += next;
146
+ i++;
147
+ } else if (char === '[') {
148
+ regexCharClass = true;
149
+ } else if (char === ']') {
150
+ regexCharClass = false;
151
+ } else if (char === '/' && !regexCharClass) {
152
+ state = 'code';
153
+ recordSignificant('/');
154
+ }
155
+ continue;
156
+ }
157
+
158
+ if (state === 'template' && char === '$' && next === '{') {
159
+ output += '${';
160
+ i++;
161
+ recordSignificant('$');
162
+ recordSignificant('{');
163
+ templateExpressionDepths.push(1);
164
+ braceKinds.push('expression');
165
+ if (jsxExpressionDepth) jsxExpressionDepth++;
166
+ state = 'code';
167
+ continue;
168
+ }
169
+
170
+ if (state !== 'code') {
171
+ output += char;
172
+ if (char === '\\' && next) {
173
+ output += next;
174
+ i++;
175
+ } else if (
176
+ (state === 'single-quote' && char === "'") ||
177
+ (state === 'double-quote' && char === '"') ||
178
+ (state === 'template' && char === '`')
179
+ ) {
180
+ state = 'code';
181
+ recordSignificant(char);
182
+ }
183
+ continue;
184
+ }
185
+
186
+ const jsxUrlSeparator = options.jsx && char === '/' && next === '/' &&
187
+ jsxExpressionDepth === 0 &&
188
+ (output.endsWith('http:') ||
189
+ output.endsWith('https:') ||
190
+ (/<[A-Za-z](?:[^>]*[^/])?>[^<]*$/.test(output.slice(output.lastIndexOf('\n') + 1)) &&
191
+ /^[\w.-]+\.[A-Za-z]{2,}(?=[:/?#\s<]|$)/.test(content.slice(i + 2))));
192
+ const afterPostfixUpdate = (lastSignificant === '+' || lastSignificant === '-') &&
193
+ previousSignificant === lastSignificant &&
194
+ antePreviousSignificant !== lastSignificant;
195
+ if (char === '/' && next === '/' && jsxUrlSeparator) {
196
+ output += '//';
197
+ i++;
198
+ recordSignificant('/');
199
+ recordSignificant('/');
200
+ } else if (char === '/' && next === '/') {
201
+ output += ' ';
202
+ i++;
203
+ state = 'line-comment';
204
+ } else if (char === '/' && next === '*') {
205
+ output += ' ';
206
+ i++;
207
+ state = 'block-comment';
208
+ } else if (templateExpressionDepths.length && char === '{') {
209
+ output += char;
210
+ templateExpressionDepths[templateExpressionDepths.length - 1]++;
211
+ braceKinds.push(braceKind());
212
+ if (jsxExpressionDepth) jsxExpressionDepth++;
213
+ recordSignificant(char);
214
+ } else if (templateExpressionDepths.length && char === '}') {
215
+ output += char;
216
+ const depthIndex = templateExpressionDepths.length - 1;
217
+ templateExpressionDepths[depthIndex]--;
218
+ lastClosedBraceKind = braceKinds.pop() || '';
219
+ if (jsxExpressionDepth) jsxExpressionDepth--;
220
+ recordSignificant(char);
221
+ if (templateExpressionDepths[depthIndex] === 0) {
222
+ templateExpressionDepths.pop();
223
+ state = 'template';
224
+ }
225
+ } else if (
226
+ char === '/' &&
227
+ (!lastSignificant ||
228
+ (/[=([{!?:;,&|+\-*%^~<>]/.test(lastSignificant) && !afterPostfixUpdate) ||
229
+ (lastSignificant === '}' && lastClosedBraceKind === 'block') ||
230
+ (previousSignificant === '=' && lastSignificant === '>') ||
231
+ (currentWordPrefix !== '.' && REGEX_PREFIX_KEYWORDS.has(currentWord)))
232
+ ) {
233
+ output += char;
234
+ state = 'regex';
235
+ regexCharClass = false;
236
+ } else {
237
+ output += char;
238
+ const startsJsxExpression = options.jsx && char === '{' && jsxExpressionDepth === 0 &&
239
+ (/<[A-Za-z](?:[^>]*[^/])?>[^<]*$/.test(output.slice(output.lastIndexOf('\n') + 1, -1)) ||
240
+ isInsideOpeningJsxTag(output.slice(0, -1)));
241
+ if (char === '{') braceKinds.push(braceKind(startsJsxExpression));
242
+ else if (char === '}') lastClosedBraceKind = braceKinds.pop() || '';
243
+ if (char === '{' && (jsxExpressionDepth || startsJsxExpression)) jsxExpressionDepth++;
244
+ else if (char === '}' && jsxExpressionDepth) jsxExpressionDepth--;
245
+ recordSignificant(char);
246
+ if (char === "'") state = 'single-quote';
247
+ else if (char === '"') state = 'double-quote';
248
+ else if (char === '`') state = 'template';
249
+ }
250
+ }
251
+
252
+ return output;
253
+ }
254
+
255
+ function stripCssComments(content) {
256
+ return content.replace(/\/\*[\s\S]*?\*\//g, comment => comment.replace(/[^\n]/g, ' '));
257
+ }
258
+
259
+ function firstOverusedGoogleFont(text) {
260
+ return extractGoogleFontFamilies(text).find(f => OVERUSED_FONTS.has(f)) || '';
261
+ }
262
+
263
+ // CSS named colors whose channels are equal (achromatic). Anything outside
264
+ // this set falls through to the format parsers, and an unrecognized spelling
265
+ // stays non-neutral so a real accent is never skipped.
266
+ const NEUTRAL_COLOR_KEYWORDS = new Set([
267
+ 'transparent', 'currentcolor',
268
+ 'black', 'white', 'gray', 'grey', 'silver',
269
+ 'dimgray', 'dimgrey', 'darkgray', 'darkgrey', 'lightgray', 'lightgrey',
270
+ 'gainsboro', 'whitesmoke',
271
+ ]);
272
+
273
+ function hexChannels(color) {
274
+ const long = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})(?:[0-9a-f]{2})?$/i);
275
+ if (long) return [parseInt(long[1], 16), parseInt(long[2], 16), parseInt(long[3], 16)];
276
+ const short = color.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])(?:[0-9a-f])?$/i);
277
+ if (short) return [1, 2, 3].map((i) => parseInt(short[i] + short[i], 16));
278
+ return null;
279
+ }
280
+
281
+ /**
282
+ * Split one box-shadow layer into top-level tokens.
283
+ *
284
+ * Whitespace inside parens does not separate tokens: `rgb(0 0 0)` and
285
+ * `var(--x, 4px)` are each a single value, and splitting them on spaces would
286
+ * read their innards as separate lengths.
287
+ */
288
+ function tokenizeShadowLayer(layer) {
289
+ const tokens = [];
290
+ let depth = 0;
291
+ let current = '';
292
+ for (const char of String(layer || '')) {
293
+ if (char === '(') depth++;
294
+ else if (char === ')') depth--;
295
+ else if (depth === 0 && /\s/.test(char)) {
296
+ if (current) tokens.push(current);
297
+ current = '';
298
+ continue;
299
+ }
300
+ current += char;
301
+ }
302
+ if (current) tokens.push(current);
303
+ return tokens;
304
+ }
305
+
306
+ function lastMatch(text, re) {
307
+ const all = [...String(text || '').matchAll(re)];
308
+ return all.length ? all[all.length - 1] : null;
309
+ }
310
+
311
+ function isShadowLength(token) {
312
+ return /^-?\d*\.?\d+(?:px)?$/i.test(String(token || ''));
313
+ }
314
+
315
+ /**
316
+ * Neutrality test for colors as written in source CSS.
317
+ *
318
+ * shared/color.mjs's isNeutralColor only parses the computed function forms a
319
+ * browser or jsdom emits (rgb/oklch/lab/...) and deliberately reports every
320
+ * other spelling as chromatic so an unknown format is never silently skipped.
321
+ * That default is wrong for authored CSS, where `#000` and `black` are the
322
+ * normal spellings: calling it directly reports a plain black hairline as a
323
+ * colored stripe. Handle hex and named neutrals here, then defer.
324
+ */
325
+ function isNeutralAuthoredColor(rawColor) {
326
+ const c = String(rawColor || '').trim().toLowerCase();
327
+ if (!c) return false;
328
+ if (NEUTRAL_COLOR_KEYWORDS.has(c)) return true;
329
+ // Modern rgb() takes space-separated channels (`rgb(0 0 0)`). shared/color.mjs
330
+ // parses only the comma form a browser's getComputedStyle emits, so authored
331
+ // space-separated neutrals fell through it and reported as chromatic — the
332
+ // exemption this function exists for, missed. Normalize before delegating.
333
+ if (/^rgba?\(/i.test(c)) {
334
+ const channels = c.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);
335
+ if (channels) {
336
+ const values = [1, 2, 3].map((i) => Number(channels[i]));
337
+ return (Math.max(...values) - Math.min(...values)) < 30;
338
+ }
339
+ return isNeutralColor(c);
340
+ }
341
+ if (/^(?:hsla?|oklch|oklab|lab|lch|hwb)\(/i.test(c)) return isNeutralColor(c);
342
+ const channels = hexChannels(c);
343
+ if (channels) return (Math.max(...channels) - Math.min(...channels)) < 30;
344
+ return false;
345
+ }
346
+
347
+ function isNeutralBorderColor(str) {
348
+ const m = str.match(/solid\s+((?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color)\([^)]*\)|#[0-9a-f]{3,8}\b|[a-z]+)/i);
349
+ if (!m) return false;
350
+ return isNeutralAuthoredColor(m[1]);
351
+ }
352
+
353
+ const REGEX_MATCHERS = [
354
+ // --- Side-tab ---
355
+ { id: 'side-tab', regex: /\bborder-[lrse]-(\d+)\b/g,
356
+ test: (m, line) => { const n = +m[1]; return hasRounded(line) ? n >= 2 : n >= 4; },
357
+ fmt: (m) => m[0] },
358
+ { id: 'side-tab', regex: /border-(?:left|right)\s*:\s*(\d+)px\s+solid[^;]*/gi,
359
+ test: (m, line) => { if (isSafeElement(line)) return false; if (isNeutralBorderColor(m[0])) return false; const n = +m[1]; return hasBorderRadius(line) ? n >= 2 : n >= 3; },
360
+ fmt: (m) => m[0].replace(/\s*;?\s*$/, '') },
361
+ { id: 'side-tab', regex: /border-(?:left|right)-width\s*:\s*(\d+)px/gi,
362
+ test: (m, line) => !isSafeElement(line) && +m[1] >= 3,
363
+ fmt: (m) => m[0] },
364
+ { id: 'side-tab', regex: /border-inline-(?:start|end)\s*:\s*(\d+)px\s+solid/gi,
365
+ test: (m, line) => !isSafeElement(line) && +m[1] >= 3,
366
+ fmt: (m) => m[0] },
367
+ { id: 'side-tab', regex: /border-inline-(?:start|end)-width\s*:\s*(\d+)px/gi,
368
+ test: (m, line) => !isSafeElement(line) && +m[1] >= 3,
369
+ fmt: (m) => m[0] },
370
+ { id: 'side-tab', regex: /border(?:Left|Right)\s*[:=]\s*["'`](\d+)px\s+solid/g,
371
+ test: (m) => +m[1] >= 3,
372
+ fmt: (m) => m[0] },
373
+ // --- Border accent on rounded ---
374
+ { id: 'border-accent-on-rounded', regex: /\bborder-[tb]-(\d+)\b/g,
375
+ test: (m, line) => hasRounded(line) && +m[1] >= 1,
376
+ fmt: (m) => m[0] },
377
+ { id: 'border-accent-on-rounded', regex: /border-(?:top|bottom)\s*:\s*(\d+)px\s+solid/gi,
378
+ test: (m, line) => +m[1] >= 3 && hasBorderRadius(line),
379
+ fmt: (m) => m[0] },
380
+ // --- Overused font ---
381
+ { id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans|Instrument Serif)\b/gi,
382
+ test: () => true,
383
+ fmt: (m) => m[0] },
384
+ { id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?[^"'\s)<>]*/gi,
385
+ test: (m) => {
386
+ m.overusedGoogleFont = firstOverusedGoogleFont(m[0]);
387
+ return Boolean(m.overusedGoogleFont);
388
+ },
389
+ fmt: (m) => `Google Fonts: ${m.overusedGoogleFont || firstOverusedGoogleFont(m[0])}` },
390
+ // --- Gradient text ---
391
+ { id: 'gradient-text', regex: /background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/gi,
392
+ test: (m, line) => /gradient/i.test(line),
393
+ fmt: () => 'background-clip: text + gradient' },
394
+ // --- Gradient text (Tailwind) ---
395
+ { id: 'gradient-text', regex: /\bbg-clip-text\b/g,
396
+ test: (m, line) => /\bbg-gradient-to-/i.test(line),
397
+ fmt: () => 'bg-clip-text + bg-gradient' },
398
+ // --- Tailwind gray on colored bg ---
399
+ { id: 'gray-on-color', regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
400
+ test: (m, line) => /\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/.test(line),
401
+ fmt: (m, line) => { const bg = line.match(/\bbg-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+\b/); return `${m[0]} on ${bg?.[0] || '?'}`; } },
402
+ // --- Tailwind AI palette ---
403
+ { id: 'ai-color-palette', regex: /\btext-(?:purple|violet|indigo)-(\d+)\b/g,
404
+ test: (m, line) => /\btext-(?:[2-9]xl|[3-9]xl)\b|<h[1-3]/i.test(line),
405
+ fmt: (m) => `${m[0]} on heading` },
406
+ { id: 'ai-color-palette', regex: /\bfrom-(?:purple|violet|indigo)-(\d+)\b/g,
407
+ test: (m, line) => /\bto-(?:purple|violet|indigo|blue|cyan|pink|fuchsia)-\d+\b/.test(line),
408
+ fmt: (m) => `${m[0]} gradient` },
409
+ // --- Bounce/elastic easing ---
410
+ { id: 'bounce-easing', regex: /\banimate-bounce\b/g,
411
+ test: () => true,
412
+ fmt: () => 'animate-bounce (Tailwind)' },
413
+ { id: 'bounce-easing', regex: /animation(?:-name)?\s*:\s*([^;{}]*(?:bounce|elastic|wobble|jiggle|spring)[^;{}]*)/gi,
414
+ test: () => true,
415
+ fmt: (m) => {
416
+ const token = m[1]
417
+ .split(/[,\s]+/)
418
+ .find((part) => /bounce|elastic|wobble|jiggle|spring/i.test(part));
419
+ return `animation: ${token || m[1].trim()}`;
420
+ } },
421
+ { id: 'bounce-easing', regex: /cubic-bezier\(\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*,\s*([\d.-]+)\s*\)/g,
422
+ test: (m) => {
423
+ const y1 = parseFloat(m[2]), y2 = parseFloat(m[4]);
424
+ return y1 < -0.1 || y1 > 1.1 || y2 < -0.1 || y2 > 1.1;
425
+ },
426
+ fmt: (m) => `cubic-bezier(${m[1]}, ${m[2]}, ${m[3]}, ${m[4]})` },
427
+ // --- Layout property transition ---
428
+ { id: 'layout-transition', regex: /transition\s*:\s*([^;{}]+)/gi,
429
+ test: (m) => {
430
+ const val = m[1].toLowerCase();
431
+ if (/\ball\b/.test(val)) return false;
432
+ return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
433
+ },
434
+ fmt: (m) => {
435
+ const found = m[1].match(/\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi);
436
+ return `transition: ${found ? found.join(', ') : m[1].trim()}`;
437
+ } },
438
+ { id: 'layout-transition', regex: /transition-property\s*:\s*([^;{}]+)/gi,
439
+ test: (m) => {
440
+ const val = m[1].toLowerCase();
441
+ if (/\ball\b/.test(val)) return false;
442
+ return /\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding\b|\bmargin\b/.test(val);
443
+ },
444
+ fmt: (m) => {
445
+ const found = m[1].match(/\b(?:(?:max|min)-)?(?:width|height)\b|\bpadding(?:-(?:top|right|bottom|left))?\b|\bmargin(?:-(?:top|right|bottom|left))?\b/gi);
446
+ return `transition-property: ${found ? found.join(', ') : m[1].trim()}`;
447
+ } },
448
+ // --- Broken image: src="" or src="#" or src=" " ---
449
+ { id: 'broken-image', regex: /<img\b[^>]*?\bsrc\s*=\s*(?:""|''|"\s+"|'\s+'|"#"|'#')/gi,
450
+ test: () => true,
451
+ fmt: (m) => m[0].slice(0, 100) },
452
+ // --- Broken image: <img> with no src attribute at all ---
453
+ { id: 'broken-image', regex: /<img\b(?:(?!\bsrc\s*=)[^>])*>/gi,
454
+ test: (m) => !/\bsrc\s*=/i.test(m[0]),
455
+ fmt: (m) => m[0].slice(0, 100) },
456
+ ];
457
+
458
+ const REGEX_ANALYZERS = [
459
+ // Flat type hierarchy
460
+ (content, filePath) => {
461
+ const sizes = new Set();
462
+ const REM = 16;
463
+ let m;
464
+ const sizeRe = /font-size\s*:\s*([\d.]+)(px|rem|em)\b/gi;
465
+ while ((m = sizeRe.exec(content)) !== null) {
466
+ const px = m[2] === 'px' ? +m[1] : +m[1] * REM;
467
+ if (px > 0 && px < 200) sizes.add(Math.round(px * 10) / 10);
468
+ }
469
+ const clampRe = /font-size\s*:\s*clamp\(\s*([\d.]+)(px|rem|em)\s*,\s*[^,]+,\s*([\d.]+)(px|rem|em)\s*\)/gi;
470
+ while ((m = clampRe.exec(content)) !== null) {
471
+ sizes.add(Math.round((m[2] === 'px' ? +m[1] : +m[1] * REM) * 10) / 10);
472
+ sizes.add(Math.round((m[4] === 'px' ? +m[3] : +m[3] * REM) * 10) / 10);
473
+ }
474
+ const TW = { 'text-xs': 12, 'text-sm': 14, 'text-base': 16, 'text-lg': 18, 'text-xl': 20, 'text-2xl': 24, 'text-3xl': 30, 'text-4xl': 36, 'text-5xl': 48, 'text-6xl': 60, 'text-7xl': 72, 'text-8xl': 96, 'text-9xl': 128 };
475
+ for (const [cls, px] of Object.entries(TW)) { if (new RegExp(`\\b${cls}\\b`).test(content)) sizes.add(px); }
476
+ if (sizes.size < 3) return [];
477
+ const sorted = [...sizes].sort((a, b) => a - b);
478
+ const ratio = sorted[sorted.length - 1] / sorted[0];
479
+ if (ratio >= 2.0) return [];
480
+ const lines = content.split('\n');
481
+ let line = 1;
482
+ for (let i = 0; i < lines.length; i++) { if (/font-size/i.test(lines[i]) || /\btext-(?:xs|sm|base|lg|xl|\d)/i.test(lines[i])) { line = i + 1; break; } }
483
+ return [finding('flat-type-hierarchy', filePath, `Sizes: ${sorted.map(s => s + 'px').join(', ')} (ratio ${ratio.toFixed(1)}:1)`, line)];
484
+ },
485
+ // Monotonous spacing (regex)
486
+ (content, filePath) => {
487
+ const vals = [];
488
+ let m;
489
+ const pxRe = /(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*(\d+)px/gi;
490
+ while ((m = pxRe.exec(content)) !== null) { const v = +m[1]; if (v > 0 && v < 200) vals.push(v); }
491
+ const remRe = /(?:padding|margin)(?:-(?:top|right|bottom|left))?\s*:\s*([\d.]+)rem/gi;
492
+ while ((m = remRe.exec(content)) !== null) { const v = Math.round(parseFloat(m[1]) * 16); if (v > 0 && v < 200) vals.push(v); }
493
+ const gapRe = /gap\s*:\s*(\d+)px/gi;
494
+ while ((m = gapRe.exec(content)) !== null) vals.push(+m[1]);
495
+ const twRe = /\b(?:p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap)-(\d+)\b/g;
496
+ while ((m = twRe.exec(content)) !== null) vals.push(+m[1] * 4);
497
+ const rounded = vals.map(v => Math.round(v / 4) * 4);
498
+ if (rounded.length < 10) return [];
499
+ const counts = {};
500
+ for (const v of rounded) counts[v] = (counts[v] || 0) + 1;
501
+ const maxCount = Math.max(...Object.values(counts));
502
+ const pct = maxCount / rounded.length;
503
+ const unique = [...new Set(rounded)].filter(v => v > 0);
504
+ if (pct <= 0.6 || unique.length > 3) return [];
505
+ const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];
506
+ return [finding('monotonous-spacing', filePath, `~${dominant}px used ${maxCount}/${rounded.length} times (${Math.round(pct * 100)}%)`)];
507
+ },
508
+ // Em-dash overuse (ADVISORY): the AI cadence tell is em-dash *saturation*,
509
+ // not the occasional dash. Humans use em-dashes legitimately, so this rule is
510
+ // advisory (surfaced separately, never a failure, hook-skipped by default) and
511
+ // its threshold is deliberately conservative. Two gates must both hold:
512
+ // 1. Absolute floor of EM_DASH_FLOOR (8) dashes — a page with a handful
513
+ // never fires, no matter how short.
514
+ // 2. Density: at least one dash per EM_DASH_CHARS_PER_DASH (500) characters
515
+ // of body text, so a long article that uses eight across several thousand
516
+ // words is left alone while a short, dash-per-clause landing page is not.
517
+ // Raised from the old flat 5-dash floor, which fired on ordinary long prose.
518
+ //
519
+ // stripHtmlToText drops tags but leaves character-entity escapes intact, so
520
+ // a model that writes `&mdash;`, `&#8212;`, or `&#x2014;` renders an em-dash
521
+ // the counter never saw. Decode the em-dash entities (named, zero-padded
522
+ // decimal, upper/lower hex) to the literal glyph first. En-dash entities are
523
+ // deliberately left alone: the rule counts em-dashes, and the literal `–`
524
+ // was never counted either.
525
+ (content, filePath) => {
526
+ const text = stripHtmlToText(content)
527
+ .replace(/&mdash;|&#0*8212;|&#x0*2014;/gi, '—');
528
+ let count = 0;
529
+ const re = /[—]|--(?=\S)/g;
530
+ while (re.exec(text) !== null) count++;
531
+ if (count < EM_DASH_FLOOR) return [];
532
+ // Saturation gate: dashes must be dense in the prose, not sprinkled through
533
+ // a long document. textLength <= count * chars-per-dash means the density is
534
+ // at or above the threshold.
535
+ if (text.length > count * EM_DASH_CHARS_PER_DASH) return [];
536
+ return [finding('em-dash-overuse', filePath, `${count} em-dashes in body text`)];
537
+ },
538
+ // Marketing buzzwords: SaaS phrase list
539
+ (content, filePath) => {
540
+ const text = stripHtmlToText(content);
541
+ const lower = text.toLowerCase();
542
+ const BUZZWORDS = [
543
+ 'streamline your', 'empower your', 'supercharge your',
544
+ 'unleash your', 'unleash the power', 'leverage the power',
545
+ 'built for the modern', 'trusted by leading', 'trusted by the world',
546
+ 'best-in-class', 'industry-leading', 'world-class', 'enterprise-grade',
547
+ 'next-generation', 'cutting-edge', 'transform your business',
548
+ 'revolutionize', 'game-changer', 'game changing',
549
+ 'mission-critical', 'best of breed', 'future-proof', 'future proof',
550
+ 'seamless experience', 'seamlessly integrate',
551
+ 'drive engagement', 'drive growth', 'drive results',
552
+ 'harness the power',
553
+ ];
554
+ let count = 0;
555
+ let firstSample = '';
556
+ for (const phrase of BUZZWORDS) {
557
+ let from = 0;
558
+ while (true) {
559
+ const idx = lower.indexOf(phrase, from);
560
+ if (idx === -1) break;
561
+ count++;
562
+ if (!firstSample) {
563
+ firstSample = text.slice(Math.max(0, idx - 12), Math.min(text.length, idx + phrase.length + 12)).trim();
564
+ }
565
+ from = idx + phrase.length;
566
+ }
567
+ }
568
+ if (count === 0) return [];
569
+ return [finding('marketing-buzzword', filePath, `${count} buzzword phrase${count === 1 ? '' : 's'}: "${firstSample}"`)];
570
+ },
571
+ // Aphoristic cadence: manufactured-contrast + short-rebuttal
572
+ (content, filePath) => {
573
+ const text = stripHtmlToText(content);
574
+ const NOT_A_RE = /\bNot an? [a-z][^.!?]{1,40}[.!]\s+[A-Z][^.!?]{1,60}[.!]/g;
575
+ const SHORT_REBUTTAL_RE = /\b[A-Z][^.!?]{4,80}[.!]\s+(No|Just)\s+[a-z][^.!?]{2,60}[.!]/g;
576
+ let count = 0;
577
+ let firstSample = '';
578
+ let m;
579
+ NOT_A_RE.lastIndex = 0;
580
+ while ((m = NOT_A_RE.exec(text)) !== null) {
581
+ count++;
582
+ if (!firstSample) firstSample = m[0].trim().slice(0, 80);
583
+ }
584
+ SHORT_REBUTTAL_RE.lastIndex = 0;
585
+ while ((m = SHORT_REBUTTAL_RE.exec(text)) !== null) {
586
+ count++;
587
+ if (!firstSample) firstSample = m[0].trim().slice(0, 80);
588
+ }
589
+ if (count < 3) return [];
590
+ return [finding('aphoristic-cadence', filePath, `${count} aphoristic constructions: "${firstSample}"`)];
591
+ },
592
+ // Dark glow / chromatic halo shadows (page-level). Shared scanner handles
593
+ // any color format, single-level var() resolution, zero-offset halos on
594
+ // any background, and text-shadow glows.
595
+ (content, filePath) => {
596
+ const hits = scanCssTextForGlow(content);
597
+ if (hits.length === 0) return [];
598
+ const lines = content.substring(0, hits[0].index).split('\n');
599
+ return [finding('dark-glow', filePath, hits[0].snippet, lines.length)];
600
+ },
601
+ // Radial-gradient background halo on a dark page (the gradient sibling
602
+ // of the dark-glow shadow tell).
603
+ (content, filePath) => {
604
+ const hits = scanCssTextForRadialHalo(content);
605
+ if (hits.length === 0) return [];
606
+ const lines = content.substring(0, hits[0].index).split('\n');
607
+ return [finding('radial-halo', filePath, hits[0].snippet, lines.length)];
608
+ },
609
+ // Auto-scrolling marquees (<marquee> or infinite horizontal loop
610
+ // animations).
611
+ (content, filePath) => scanCssTextForMarquee(content).map(hit => finding('marquee', filePath, hit.snippet)),
612
+ ];
613
+
614
+ // ---------------------------------------------------------------------------
615
+ // Structural CSS checks used by source files whose styles are not parsed by
616
+ // the static HTML engine.
617
+ // ---------------------------------------------------------------------------
618
+
619
+ const CHROMATIC_SHADOW_TOKEN_RE = /(?:^|-)(?:accent|kinpaku|patina|gold|red|orange|amber|yellow|lime|green|emerald|teal|cyan|blue|indigo|violet|purple|magenta|pink|rose|coral|aqua|mint|burgundy|crimson|scarlet)(?:-|$)/i;
620
+
621
+ function insetStripeColorIsChromatic(rawColor) {
622
+ const color = String(rawColor || '').trim().replace(/\s*!important\s*$/i, '');
623
+ if (/^(?:currentcolor|transparent|inherit|unset)$/i.test(color)) return false;
624
+ const variable = color.match(/^var\(\s*(--[\w-]+)/i);
625
+ if (variable) return CHROMATIC_SHADOW_TOKEN_RE.test(variable[1]);
626
+ if (!/^(?:#|rgba?\(|hsla?\(|hwb\(|oklch\(|oklab\(|lch\(|lab\(|color\(|[a-z]+$)/i.test(color)) return false;
627
+ return !isNeutralAuthoredColor(color);
628
+ }
629
+
630
+ /**
631
+ * Blank out comment bodies while preserving every byte offset (and therefore
632
+ * every line number) so commented-out CSS is not scanned as live rules.
633
+ */
634
+ function blankCssComments(css) {
635
+ return css.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, ' '));
636
+ }
637
+
638
+ function scanInsetStripeCss(rawContent, filePath, lineOffset = 0) {
639
+ const content = blankCssComments(rawContent);
640
+ const findings = [];
641
+ const ruleRe = /([^{};]+)\{([^{}]*)\}/g;
642
+ let match;
643
+ // Deriving each line with content.slice(0, offset).split('\n') re-scans the
644
+ // whole prefix per rule, which is O(n^2) on a large stylesheet. Rule matches
645
+ // arrive in source order, so carry a monotonic cursor instead: one pass total.
646
+ let scanOffset = 0;
647
+ let scanLine = 1;
648
+ const lineAtOffset = (offset) => {
649
+ while (scanOffset < offset) {
650
+ if (content[scanOffset] === '\n') scanLine++;
651
+ scanOffset++;
652
+ }
653
+ return scanLine;
654
+ };
655
+ while ((match = ruleRe.exec(content)) !== null) {
656
+ // The selector group is `[^{};]+`, which greedily absorbs the whitespace and
657
+ // newlines trailing the previous rule. Advance past that run before deriving
658
+ // the line, or every rule after the first reports the preceding line.
659
+ const selectorStart = match.index + (match[1].length - match[1].trimStart().length);
660
+ const selector = match[1].trim().replace(/\s+/g, ' ');
661
+ if (!selector) continue;
662
+ if (/:(?:hover|focus|focus-visible|focus-within|active|checked|target)\b/i.test(selector)) continue;
663
+ if (/\[aria-selected\s*[*^$|~]?=\s*["']?true/i.test(selector)) continue;
664
+ if (/\[aria-current(?!\s*[*^$|~]?=\s*["']?false)/i.test(selector)) continue;
665
+ if (/(?:^|[\s._[-])(?:active|current|selected)(?![\w])/i.test(selector)) continue;
666
+ if (/(?:^|[\s>+~,(])(?:button|hr|tr|td|th|table|blockquote|pre|code)(?![\w-])/i.test(selector)) continue;
667
+
668
+ // Read the last of a repeated declaration, not the first: that is what the
669
+ // cascade paints. Taking the first both flagged stripes that a later
670
+ // `box-shadow: none` had cancelled and missed stripes that overrode an
671
+ // earlier value, and mis-skipped rules whose narrow width was overridden.
672
+ const width = lastMatch(match[2], /(?:^|;)\s*(?:width|inline-size)\s*:\s*(\d+(?:\.\d+)?)px/gi);
673
+ if (width && Number(width[1]) <= 40) continue;
674
+ const declaration = lastMatch(match[2], /(?:^|;)\s*box-shadow\s*:\s*([^;]+)/gi);
675
+ if (!declaration || !/\binset\b/i.test(declaration[1])) continue;
676
+ // `!important` qualifies the declaration, not the shadow value, so strip it
677
+ // before the layers are read. Tokenizing split it into its own token, which
678
+ // made the color count wrong and silently stopped flagging stripes declared
679
+ // with it — a shape the previous regex handled.
680
+ const shadowValue = declaration[1].replace(/\s*!\s*important\s*$/i, '').trim();
681
+
682
+ for (const rawLayer of shadowValue.split(/,(?![^(]*\))/)) {
683
+ const layer = rawLayer.trim();
684
+ // Parse the layer by its grammar rather than by one spelling of it.
685
+ // A box-shadow layer is `inset? && <length>{2,4} && <color>?` in any
686
+ // order, so `inset 4px 0 red`, `4px 0 0 red inset`, and `red 4px 0 inset`
687
+ // all paint the same stripe. Matching a fixed token order missed three
688
+ // valid spellings in a row; enumerate the tokens instead. Tokenizing must
689
+ // respect parens: `rgb(0 0 0)` is one color token, and splitting it on
690
+ // whitespace would read its channels as lengths.
691
+ const tokens = tokenizeShadowLayer(layer);
692
+ if (!tokens.some((token) => /^inset$/i.test(token))) continue;
693
+ const rest = tokens.filter((token) => !/^inset$/i.test(token));
694
+ const lengths = rest.filter(isShadowLength);
695
+ const colors = rest.filter((token) => !isShadowLength(token));
696
+ // Only the two offsets are required; omitted blur/spread default to 0,
697
+ // which is exactly the stripe shape. More than one non-length token is a
698
+ // layer shape we do not claim to understand, so leave it alone.
699
+ if (lengths.length < 2 || lengths.length > 4 || colors.length !== 1) continue;
700
+ const values = lengths.map((token) => ({
701
+ n: Number(token.replace(/px$/i, '')),
702
+ hasPx: /px$/i.test(token),
703
+ }));
704
+ const x = values[0];
705
+ const y = values[1];
706
+ const blur = values[2] ? values[2].n : 0;
707
+ const spread = values[3] ? values[3].n : 0;
708
+ if ((x.n !== 0 && !x.hasPx) || (y.n !== 0 && !y.hasPx) || blur !== 0 || spread !== 0) continue;
709
+ const ax = Math.abs(x.n);
710
+ const ay = Math.abs(y.n);
711
+ if (!((ax >= 3 && ax <= 12 && ay === 0) || (ay >= 3 && ay <= 12 && ax === 0))) continue;
712
+ if (!insetStripeColorIsChromatic(colors[0])) continue;
713
+ const edge = ay === 0 ? (x.n > 0 ? 'left' : 'right') : (y.n > 0 ? 'top' : 'bottom');
714
+ const line = lineOffset + lineAtOffset(selectorStart);
715
+ findings.push(finding('side-tab', filePath, `${selector} — inset box-shadow ${ay === 0 ? ax : ay}px stripe (${edge})`, line));
716
+ break;
717
+ }
718
+ }
719
+ return findings;
720
+ }
721
+
722
+ // ---------------------------------------------------------------------------
723
+ // Style block extraction (Astro/Vue/Svelte <style> blocks)
724
+ // ---------------------------------------------------------------------------
725
+
726
+ function extractStyleBlocks(content, ext) {
727
+ ext = ext.toLowerCase();
728
+ if (ext !== '.astro' && ext !== '.vue' && ext !== '.svelte') return [];
729
+ const blocks = [];
730
+ const re = /<style[^>]*>([\s\S]*?)<\/style>/gi;
731
+ let m;
732
+ while ((m = re.exec(content)) !== null) {
733
+ const before = content.substring(0, m.index);
734
+ const startLine = before.split('\n').length + 1;
735
+ blocks.push({ content: m[1], startLine });
736
+ }
737
+ return blocks;
738
+ }
739
+
740
+ // ---------------------------------------------------------------------------
741
+ // CSS-in-JS extraction (styled-components, emotion)
742
+ // ---------------------------------------------------------------------------
743
+
744
+ const CSS_IN_JS_EXTENSIONS = new Set(['.js', '.ts', '.jsx', '.tsx']);
745
+
746
+ function findQuotedStringEnd(content, start, quote) {
747
+ for (let cursor = start + 1; cursor < content.length; cursor++) {
748
+ if (content[cursor] === '\\') cursor++;
749
+ else if (content[cursor] === quote) return cursor;
750
+ }
751
+ return -1;
752
+ }
753
+
754
+ function findRegexLiteralEnd(content, start) {
755
+ let inCharacterClass = false;
756
+ for (let cursor = start + 1; cursor < content.length; cursor++) {
757
+ const char = content[cursor];
758
+ if (char === '\\') {
759
+ cursor++;
760
+ } else if (char === '[') {
761
+ inCharacterClass = true;
762
+ } else if (char === ']') {
763
+ inCharacterClass = false;
764
+ } else if (char === '/' && !inCharacterClass) {
765
+ while (/[A-Za-z]/.test(content[cursor + 1] || '')) cursor++;
766
+ return cursor;
767
+ } else if (char === '\n' || char === '\r') {
768
+ return -1;
769
+ }
770
+ }
771
+ return -1;
772
+ }
773
+
774
+ function findTemplateExpressionEnd(content, start) {
775
+ let depth = 1;
776
+ let lastSignificant = '';
777
+ let previousSignificant = '';
778
+ let antePreviousSignificant = '';
779
+ let currentWord = '';
780
+ let currentWordPrefix = '';
781
+ let wordSeparated = false;
782
+ let lastClosedBraceKind = '';
783
+ const braceKinds = [];
784
+
785
+ const braceKind = () => (
786
+ lastSignificant === ')' ||
787
+ lastSignificant === ';' ||
788
+ lastSignificant === '}' ||
789
+ (previousSignificant === '=' && lastSignificant === '>') ||
790
+ BLOCK_BRACE_PREFIX_KEYWORDS.has(currentWord)
791
+ ? 'block'
792
+ : 'expression'
793
+ );
794
+
795
+ const recordSignificant = (char) => {
796
+ if (/\s/.test(char)) {
797
+ wordSeparated = true;
798
+ return;
799
+ }
800
+ const isWordChar = /[\w$]/.test(char);
801
+ if (isWordChar && (wordSeparated || !currentWord)) {
802
+ currentWord = '';
803
+ currentWordPrefix = lastSignificant;
804
+ } else if (!isWordChar) {
805
+ currentWordPrefix = '';
806
+ }
807
+ wordSeparated = false;
808
+ antePreviousSignificant = previousSignificant;
809
+ previousSignificant = lastSignificant;
810
+ lastSignificant = char;
811
+ currentWord = isWordChar ? currentWord + char : '';
812
+ };
813
+
814
+ for (let cursor = start; cursor < content.length; cursor++) {
815
+ const char = content[cursor];
816
+ const next = content[cursor + 1];
817
+ const afterPostfixUpdate = (lastSignificant === '+' || lastSignificant === '-') &&
818
+ previousSignificant === lastSignificant &&
819
+ antePreviousSignificant !== lastSignificant;
820
+ if (char === "'" || char === '"') {
821
+ cursor = findQuotedStringEnd(content, cursor, char);
822
+ if (cursor === -1) return -1;
823
+ recordSignificant(')');
824
+ } else if (char === '/' && next === '/') {
825
+ const lineEnd = content.indexOf('\n', cursor + 2);
826
+ if (lineEnd === -1) return -1;
827
+ cursor = lineEnd;
828
+ } else if (char === '/' && next === '*') {
829
+ const commentEnd = content.indexOf('*/', cursor + 2);
830
+ if (commentEnd === -1) return -1;
831
+ cursor = commentEnd + 1;
832
+ } else if (
833
+ char === '/' &&
834
+ (!lastSignificant ||
835
+ (/[=([{!?:;,&|+\-*%^~<>]/.test(lastSignificant) && !afterPostfixUpdate) ||
836
+ (lastSignificant === '}' && lastClosedBraceKind === 'block') ||
837
+ (previousSignificant === '=' && lastSignificant === '>') ||
838
+ (currentWordPrefix !== '.' && REGEX_PREFIX_KEYWORDS.has(currentWord)))
839
+ ) {
840
+ cursor = findRegexLiteralEnd(content, cursor);
841
+ if (cursor === -1) return -1;
842
+ recordSignificant(')');
843
+ } else if (char === '`') {
844
+ cursor = findTemplateLiteralEnd(content, cursor);
845
+ if (cursor === -1) return -1;
846
+ recordSignificant(')');
847
+ } else if (char === '{') {
848
+ depth++;
849
+ braceKinds.push(braceKind());
850
+ recordSignificant(char);
851
+ } else if (char === '}') {
852
+ depth--;
853
+ if (depth === 0) return cursor;
854
+ lastClosedBraceKind = braceKinds.pop() || '';
855
+ recordSignificant(char);
856
+ } else {
857
+ recordSignificant(char);
858
+ }
859
+ }
860
+ return -1;
861
+ }
862
+
863
+ function findTemplateLiteralEnd(content, start) {
864
+ for (let cursor = start + 1; cursor < content.length; cursor++) {
865
+ const char = content[cursor];
866
+ if (char === '\\') {
867
+ cursor++;
868
+ } else if (char === '`') {
869
+ return cursor;
870
+ } else if (char === '$' && content[cursor + 1] === '{') {
871
+ cursor = findTemplateExpressionEnd(content, cursor + 2);
872
+ if (cursor === -1) return -1;
873
+ }
874
+ }
875
+ return -1;
876
+ }
877
+
878
+ function findCSSinJSTemplates(content) {
879
+ const templates = [];
880
+ const tagRe = /\b(?:styled(?:\.\w+|\([^)]+\))|css)/g;
881
+ let match;
882
+ while ((match = tagRe.exec(content)) !== null) {
883
+ let cursor = match.index + match[0].length;
884
+ while (/\s/.test(content[cursor] || '')) cursor++;
885
+
886
+ if (content[cursor] === '<') {
887
+ let depth = 0;
888
+ while (cursor < content.length) {
889
+ const char = content[cursor];
890
+ if (char === '<') depth++;
891
+ else if (char === '>' && content[cursor - 1] !== '=') depth--;
892
+ cursor++;
893
+ if (depth === 0) break;
894
+ }
895
+ if (depth !== 0) continue;
896
+ while (/\s/.test(content[cursor] || '')) cursor++;
897
+ }
898
+
899
+ if (content[cursor] !== '`') continue;
900
+ const contentStart = cursor + 1;
901
+ cursor = findTemplateLiteralEnd(content, cursor);
902
+ if (cursor === -1) continue;
903
+
904
+ templates.push({
905
+ tagStart: match.index,
906
+ contentStart,
907
+ contentEnd: cursor,
908
+ });
909
+ tagRe.lastIndex = cursor + 1;
910
+ }
911
+ return templates;
912
+ }
913
+
914
+ function extractCSSinJS(content, ext) {
915
+ ext = ext.toLowerCase();
916
+ if (!CSS_IN_JS_EXTENSIONS.has(ext)) return [];
917
+ return findCSSinJSTemplates(content).map((template) => {
918
+ const before = content.substring(0, template.tagStart);
919
+ const startLine = before.split('\n').length;
920
+ return {
921
+ content: content.slice(template.contentStart, template.contentEnd),
922
+ startLine,
923
+ };
924
+ });
925
+ }
926
+
927
+ function stripCssInJsComments(content, ext) {
928
+ if (!CSS_IN_JS_EXTENSIONS.has(ext.toLowerCase())) return content;
929
+ const templates = findCSSinJSTemplates(content);
930
+ let output = '';
931
+ let cursor = 0;
932
+ for (const template of templates) {
933
+ output += content.slice(cursor, template.contentStart);
934
+ output += stripCssComments(content.slice(template.contentStart, template.contentEnd));
935
+ cursor = template.contentEnd;
936
+ }
937
+ return output + content.slice(cursor);
938
+ }
939
+
940
+ function runRegexMatchers(lines, filePath, lineOffset = 0, blockContext = null, options = {}) {
941
+ const { profile, phase = 'regex-matchers' } = options || {};
942
+ const findings = [];
943
+ if (!profile) {
944
+ for (const matcher of REGEX_MATCHERS) {
945
+ for (let i = 0; i < lines.length; i++) {
946
+ const line = lines[i];
947
+ matcher.regex.lastIndex = 0;
948
+ let m;
949
+ while ((m = matcher.regex.exec(line)) !== null) {
950
+ // For extracted blocks, use nearby lines as context for multi-line CSS patterns
951
+ const context = blockContext
952
+ ? lines.slice(Math.max(0, i - 3), Math.min(lines.length, i + 4)).join(' ')
953
+ : line;
954
+ if (matcher.test(m, context)) {
955
+ findings.push(finding(matcher.id, filePath, matcher.fmt(m, context), i + 1 + lineOffset));
956
+ }
957
+ }
958
+ }
959
+ }
960
+ return findings;
961
+ }
962
+
963
+ for (const matcher of REGEX_MATCHERS) {
964
+ const matcherFindings = profileFindings(profile, {
965
+ engine: 'regex',
966
+ phase,
967
+ ruleId: matcher.id,
968
+ target: filePath,
969
+ }, () => {
970
+ const matches = [];
971
+ for (let i = 0; i < lines.length; i++) {
972
+ const line = lines[i];
973
+ matcher.regex.lastIndex = 0;
974
+ let m;
975
+ while ((m = matcher.regex.exec(line)) !== null) {
976
+ // For extracted blocks, use nearby lines as context for multi-line CSS patterns
977
+ const context = blockContext
978
+ ? lines.slice(Math.max(0, i - 3), Math.min(lines.length, i + 4)).join(' ')
979
+ : line;
980
+ if (matcher.test(m, context)) {
981
+ matches.push(finding(matcher.id, filePath, matcher.fmt(m, context), i + 1 + lineOffset));
982
+ }
983
+ }
984
+ }
985
+ return matches;
986
+ });
987
+ findings.push(...matcherFindings);
988
+ }
989
+ return findings;
990
+ }
991
+
992
+ /** Page-level analyzers that scan rendered text content (em-dash use,
993
+ * buzzword phrases, aphoristic cadence).
994
+ * These are detector-agnostic — they work on any HTML/text source
995
+ * and don't need a parsed DOM. Exported so detectHtml can call them
996
+ * for `.html` files (which otherwise skip the regex engine). */
997
+ const TEXT_CONTENT_ANALYZER_IDS = [
998
+ 'em-dash-overuse',
999
+ 'marketing-buzzword',
1000
+ 'aphoristic-cadence',
1001
+ ];
1002
+
1003
+ function runTextContentAnalyzers(content, filePath, options = {}) {
1004
+ const profile = options?.profile;
1005
+ if (!shouldRunPageAnalyzers(content, filePath)) return [];
1006
+ // The 3 text-content analyzers are at indices 2-4 in REGEX_ANALYZERS
1007
+ // (single-font's removal on 2026-07-29 shifted every index down one).
1008
+ const findings = [];
1009
+ for (let i = 0; i < TEXT_CONTENT_ANALYZER_IDS.length; i++) {
1010
+ const analyzer = REGEX_ANALYZERS[2 + i];
1011
+ const ruleId = TEXT_CONTENT_ANALYZER_IDS[i];
1012
+ findings.push(...profileFindings(profile, {
1013
+ engine: 'regex',
1014
+ phase: 'text-content',
1015
+ ruleId,
1016
+ target: filePath,
1017
+ }, () => analyzer(content, filePath)));
1018
+ }
1019
+ return findings;
1020
+ }
1021
+
1022
+ function detectText(content, filePath, options = {}) {
1023
+ const profile = options?.profile;
1024
+ const findings = [];
1025
+ const ext = extFromFilePath(filePath);
1026
+ const commentStrippedSource = JS_SOURCE_EXTS.has(ext) ? stripJsComments(content, {
1027
+ jsx: ext === '.js' || ext === '.jsx' || ext === '.tsx',
1028
+ }) : content;
1029
+ const source = stripCssInJsComments(commentStrippedSource, ext);
1030
+ const lines = source.split('\n');
1031
+
1032
+ // Run regex matchers on the full file content (catches Tailwind classes, inline styles)
1033
+ // Enable block context for CSS files where related properties span multiple lines
1034
+ const cssLike = new Set(['.css', '.scss', '.sass', '.less']);
1035
+ findings.push(...runRegexMatchers(lines, filePath, 0, cssLike.has(ext) || null, {
1036
+ profile,
1037
+ phase: 'source',
1038
+ }));
1039
+ // Pseudo-element stripes (::before/::after absolute bars) carry the same
1040
+ // side-tab silhouette without any border token, so the line matchers can't
1041
+ // see them (issue #394). The shared scanner already runs on full HTML pages
1042
+ // via checkHtmlPatterns; give standalone stylesheets, component style
1043
+ // blocks, and CSS-in-JS templates the same coverage. Each hit carries the
1044
+ // rule's source offset, so the finding gets a real line and line-scoped
1045
+ // inline ignores keep working.
1046
+ const pseudoStripeFindings = (text, lineOffset) =>
1047
+ scanCssTextForPseudoStripe(text).map(hit =>
1048
+ finding(hit.id, filePath, hit.snippet, lineOffset + text.slice(0, hit.index).split('\n').length));
1049
+
1050
+ if (cssLike.has(ext)) {
1051
+ findings.push(...scanInsetStripeCss(content, filePath));
1052
+ findings.push(...pseudoStripeFindings(content, 0));
1053
+ }
1054
+
1055
+ // Block-level CSS checks that need multiple declarations must run over the
1056
+ // complete source, not line-by-line. This covers standalone stylesheets,
1057
+ // component style blocks, inline styles, and CSS-in-JS templates.
1058
+ findings.push(...profileFindings(profile, {
1059
+ engine: 'regex',
1060
+ phase: 'source',
1061
+ ruleId: 'codex-grid-background',
1062
+ target: filePath,
1063
+ }, () => scanCssTextForGridBackground(source).map(hit => {
1064
+ const line = source.substring(0, hit.index).split('\n').length;
1065
+ return finding('codex-grid-background', filePath, hit.snippet, line);
1066
+ })));
1067
+
1068
+ // Extract and scan <style> blocks from Astro/Vue/Svelte components.
1069
+ const styleBlocks = profile
1070
+ ? profileStep(profile, {
1071
+ engine: 'regex',
1072
+ phase: 'extract',
1073
+ ruleId: 'style-blocks',
1074
+ target: filePath,
1075
+ }, () => extractStyleBlocks(content, ext))
1076
+ : extractStyleBlocks(content, ext);
1077
+ for (const block of styleBlocks) {
1078
+ const blockLines = block.content.split('\n');
1079
+ findings.push(...runRegexMatchers(blockLines, filePath, block.startLine - 1, true, {
1080
+ profile,
1081
+ phase: 'style-block',
1082
+ }));
1083
+ // block.startLine is the first line *after* the <style> tag, but block.content
1084
+ // begins at the character right after that tag — so its own line 1 sits on the
1085
+ // tag's line, whether or not a newline follows immediately. lineAtOffset is
1086
+ // 1-based, so the offset is startLine - 2; startLine - 1 double-counted and
1087
+ // reported every selector one line low. runRegexMatchers keeps startLine - 1
1088
+ // because it indexes its split lines from zero.
1089
+ findings.push(...scanInsetStripeCss(block.content, filePath, block.startLine - 2));
1090
+ findings.push(...pseudoStripeFindings(block.content, block.startLine - 2));
1091
+ }
1092
+
1093
+ // Extract and scan CSS-in-JS template literals
1094
+ const cssJsBlocks = profile
1095
+ ? profileStep(profile, {
1096
+ engine: 'regex',
1097
+ phase: 'extract',
1098
+ ruleId: 'css-in-js',
1099
+ target: filePath,
1100
+ }, () => extractCSSinJS(source, ext))
1101
+ : extractCSSinJS(source, ext);
1102
+ for (const block of cssJsBlocks) {
1103
+ const blockContent = stripCssComments(block.content);
1104
+ const blockLines = blockContent.split('\n');
1105
+ findings.push(...runRegexMatchers(blockLines, filePath, block.startLine - 1, true, {
1106
+ profile,
1107
+ phase: 'css-in-js',
1108
+ }));
1109
+ findings.push(...scanInsetStripeCss(blockContent, filePath, block.startLine - 1));
1110
+ findings.push(...pseudoStripeFindings(blockContent, block.startLine - 1));
1111
+ }
1112
+
1113
+ if (options?.designSystem) {
1114
+ findings.push(...profileFindings(profile, {
1115
+ engine: 'regex',
1116
+ phase: 'source',
1117
+ ruleId: 'design-system',
1118
+ target: filePath,
1119
+ }, () => checkSourceDesignSystem(content, filePath, { designSystem: options.designSystem })));
1120
+ }
1121
+
1122
+ // Deduplicate findings (same antipattern + similar snippet, within 2 lines)
1123
+ const deduped = [];
1124
+ for (const f of findings) {
1125
+ const isDupe = deduped.some(d =>
1126
+ d.antipattern === f.antipattern &&
1127
+ d.snippet === f.snippet &&
1128
+ Math.abs(d.line - f.line) <= 2
1129
+ );
1130
+ if (!isDupe) deduped.push(f);
1131
+ }
1132
+
1133
+ // Page-level analyzers only run on full pages
1134
+ if (shouldRunPageAnalyzers(content, filePath)) {
1135
+ const analyzerIds = [
1136
+ 'flat-type-hierarchy',
1137
+ 'monotonous-spacing',
1138
+ 'em-dash-overuse',
1139
+ 'marketing-buzzword',
1140
+ 'aphoristic-cadence',
1141
+ 'dark-glow',
1142
+ ];
1143
+ for (let i = 0; i < REGEX_ANALYZERS.length; i++) {
1144
+ const analyzer = REGEX_ANALYZERS[i];
1145
+ deduped.push(...profileFindings(profile, {
1146
+ engine: 'regex',
1147
+ phase: 'page-analyzer',
1148
+ ruleId: analyzerIds[i] || `analyzer-${i + 1}`,
1149
+ target: filePath,
1150
+ }, () => analyzer(content, filePath)));
1151
+ }
1152
+ }
1153
+
1154
+ // Inline `impeccable-disable*` waivers travel with the file; honor them unless
1155
+ // explicitly bypassed (`--no-config` / `--no-inline-ignores`).
1156
+ return options?.inlineIgnores === false ? deduped : applyInlineIgnores(deduped, content);
1157
+ }
1158
+
1159
+ export {
1160
+ REGEX_MATCHERS,
1161
+ REGEX_ANALYZERS,
1162
+ TEXT_CONTENT_ANALYZER_IDS,
1163
+ extractStyleBlocks,
1164
+ extractCSSinJS,
1165
+ runRegexMatchers,
1166
+ runTextContentAnalyzers,
1167
+ detectText,
1168
+ };