@jack200714/mafw 4.5.1 → 4.5.2

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 (320) hide show
  1. package/README.md +5 -2
  2. package/bin/mafw.js +16 -14
  3. package/package.json +1 -7
  4. package/.opencode/mafw/.backup-v61/parametric/base-skill-manifest.yaml +0 -2
  5. package/.opencode/mafw/STATUS.md +0 -6
  6. package/.opencode/mafw/ledger.md +0 -20
  7. package/.opencode/mafw/memory/.harmonic_index.json +0 -28
  8. package/.opencode/mafw/memory/.review_queue.json +0 -1
  9. package/.opencode/mafw/memory/tier3/parametric.json +0 -13
  10. package/.opencode/mafw/memory/tier3/test-goal.json +0 -19
  11. package/.opencode/mafw/memory-index.json +0 -6
  12. package/.opencode/mafw/parametric/base-skill-manifest.yaml +0 -2
  13. package/.opencode/mafw/waves.json +0 -4
  14. package/.opencode/opencode.json +0 -3
  15. package/.opencode/plugins/mafw-plugin.ts +0 -8
  16. package/.opencode/skills/banner-design/SKILL.md +0 -196
  17. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +0 -118
  18. package/.opencode/skills/brand/SKILL.md +0 -97
  19. package/.opencode/skills/brand/references/approval-checklist.md +0 -169
  20. package/.opencode/skills/brand/references/asset-organization.md +0 -157
  21. package/.opencode/skills/brand/references/brand-guideline-template.md +0 -140
  22. package/.opencode/skills/brand/references/color-palette-management.md +0 -186
  23. package/.opencode/skills/brand/references/consistency-checklist.md +0 -94
  24. package/.opencode/skills/brand/references/logo-usage-rules.md +0 -185
  25. package/.opencode/skills/brand/references/messaging-framework.md +0 -85
  26. package/.opencode/skills/brand/references/typography-specifications.md +0 -214
  27. package/.opencode/skills/brand/references/update.md +0 -118
  28. package/.opencode/skills/brand/references/visual-identity.md +0 -96
  29. package/.opencode/skills/brand/references/voice-framework.md +0 -88
  30. package/.opencode/skills/brand/scripts/extract-colors.cjs +0 -341
  31. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +0 -349
  32. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +0 -248
  33. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +0 -52
  34. package/.opencode/skills/brand/scripts/validate-asset.cjs +0 -387
  35. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +0 -275
  36. package/.opencode/skills/design/SKILL.md +0 -313
  37. package/.opencode/skills/design/data/cip/deliverables.csv +0 -51
  38. package/.opencode/skills/design/data/cip/industries.csv +0 -21
  39. package/.opencode/skills/design/data/cip/mockup-contexts.csv +0 -21
  40. package/.opencode/skills/design/data/cip/styles.csv +0 -21
  41. package/.opencode/skills/design/data/icon/styles.csv +0 -16
  42. package/.opencode/skills/design/data/logo/colors.csv +0 -56
  43. package/.opencode/skills/design/data/logo/industries.csv +0 -56
  44. package/.opencode/skills/design/data/logo/styles.csv +0 -56
  45. package/.opencode/skills/design/references/banner-sizes-and-styles.md +0 -118
  46. package/.opencode/skills/design/references/cip-deliverable-guide.md +0 -95
  47. package/.opencode/skills/design/references/cip-design.md +0 -121
  48. package/.opencode/skills/design/references/cip-prompt-engineering.md +0 -84
  49. package/.opencode/skills/design/references/cip-style-guide.md +0 -68
  50. package/.opencode/skills/design/references/design-routing.md +0 -207
  51. package/.opencode/skills/design/references/icon-design.md +0 -122
  52. package/.opencode/skills/design/references/logo-color-psychology.md +0 -101
  53. package/.opencode/skills/design/references/logo-design.md +0 -92
  54. package/.opencode/skills/design/references/logo-prompt-engineering.md +0 -158
  55. package/.opencode/skills/design/references/logo-style-guide.md +0 -109
  56. package/.opencode/skills/design/references/slides-copywriting-formulas.md +0 -84
  57. package/.opencode/skills/design/references/slides-create.md +0 -4
  58. package/.opencode/skills/design/references/slides-html-template.md +0 -295
  59. package/.opencode/skills/design/references/slides-layout-patterns.md +0 -137
  60. package/.opencode/skills/design/references/slides-strategies.md +0 -94
  61. package/.opencode/skills/design/references/slides.md +0 -42
  62. package/.opencode/skills/design/references/social-photos-design.md +0 -329
  63. package/.opencode/skills/design/scripts/cip/core.py +0 -215
  64. package/.opencode/skills/design/scripts/cip/generate.py +0 -484
  65. package/.opencode/skills/design/scripts/cip/render-html.py +0 -424
  66. package/.opencode/skills/design/scripts/cip/search.py +0 -127
  67. package/.opencode/skills/design/scripts/icon/generate.py +0 -487
  68. package/.opencode/skills/design/scripts/logo/core.py +0 -175
  69. package/.opencode/skills/design/scripts/logo/generate.py +0 -362
  70. package/.opencode/skills/design/scripts/logo/search.py +0 -114
  71. package/.opencode/skills/design-system/SKILL.md +0 -244
  72. package/.opencode/skills/design-system/data/slide-backgrounds.csv +0 -11
  73. package/.opencode/skills/design-system/data/slide-charts.csv +0 -26
  74. package/.opencode/skills/design-system/data/slide-color-logic.csv +0 -14
  75. package/.opencode/skills/design-system/data/slide-copy.csv +0 -26
  76. package/.opencode/skills/design-system/data/slide-layout-logic.csv +0 -16
  77. package/.opencode/skills/design-system/data/slide-layouts.csv +0 -26
  78. package/.opencode/skills/design-system/data/slide-strategies.csv +0 -16
  79. package/.opencode/skills/design-system/data/slide-typography.csv +0 -15
  80. package/.opencode/skills/design-system/references/component-specs.md +0 -236
  81. package/.opencode/skills/design-system/references/component-tokens.md +0 -214
  82. package/.opencode/skills/design-system/references/primitive-tokens.md +0 -203
  83. package/.opencode/skills/design-system/references/semantic-tokens.md +0 -215
  84. package/.opencode/skills/design-system/references/states-and-variants.md +0 -241
  85. package/.opencode/skills/design-system/references/tailwind-integration.md +0 -251
  86. package/.opencode/skills/design-system/references/token-architecture.md +0 -224
  87. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +0 -99
  88. package/.opencode/skills/design-system/scripts/fetch-background.py +0 -317
  89. package/.opencode/skills/design-system/scripts/generate-slide.py +0 -770
  90. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +0 -205
  91. package/.opencode/skills/design-system/scripts/html-token-validator.py +0 -327
  92. package/.opencode/skills/design-system/scripts/search-slides.py +0 -218
  93. package/.opencode/skills/design-system/scripts/slide-token-validator.py +0 -35
  94. package/.opencode/skills/design-system/scripts/slide_search_core.py +0 -453
  95. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +0 -48
  96. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +0 -246
  97. package/.opencode/skills/design-system/templates/design-tokens-starter.json +0 -143
  98. package/.opencode/skills/frontend-design/LICENSE.txt +0 -21
  99. package/.opencode/skills/frontend-design/SKILL.md +0 -115
  100. package/.opencode/skills/mafw-automation-interview/SKILL.md +0 -43
  101. package/.opencode/skills/mafw-cli-scanner/SKILL.md +0 -37
  102. package/.opencode/skills/mafw-compression-verifier/SKILL.md +0 -52
  103. package/.opencode/skills/mafw-desktop-inspector/SKILL.md +0 -170
  104. package/.opencode/skills/mafw-execute/SKILL.md +0 -62
  105. package/.opencode/skills/mafw-gateway-restart/SKILL.md +0 -87
  106. package/.opencode/skills/mafw-github-scanner/SKILL.md +0 -38
  107. package/.opencode/skills/mafw-goal/SKILL.md +0 -76
  108. package/.opencode/skills/mafw-interview/SKILL.md +0 -68
  109. package/.opencode/skills/mafw-memory-extractor/SKILL.md +0 -65
  110. package/.opencode/skills/mafw-plan/SKILL.md +0 -58
  111. package/.opencode/skills/mafw-review/SKILL.md +0 -85
  112. package/.opencode/skills/runtime-plugin-authoring/SKILL.md +0 -791
  113. package/.opencode/skills/slides/SKILL.md +0 -40
  114. package/.opencode/skills/slides/references/copywriting-formulas.md +0 -84
  115. package/.opencode/skills/slides/references/create.md +0 -4
  116. package/.opencode/skills/slides/references/html-template.md +0 -295
  117. package/.opencode/skills/slides/references/layout-patterns.md +0 -137
  118. package/.opencode/skills/slides/references/slide-strategies.md +0 -94
  119. package/.opencode/skills/ui-styling/LICENSE.txt +0 -202
  120. package/.opencode/skills/ui-styling/SKILL.md +0 -324
  121. package/.opencode/skills/ui-styling/references/canvas-design-system.md +0 -320
  122. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +0 -471
  123. package/.opencode/skills/ui-styling/references/shadcn-components.md +0 -424
  124. package/.opencode/skills/ui-styling/references/shadcn-theming.md +0 -373
  125. package/.opencode/skills/ui-styling/references/tailwind-customization.md +0 -483
  126. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +0 -382
  127. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +0 -455
  128. package/.opencode/skills/ui-styling/scripts/requirements.txt +0 -17
  129. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +0 -308
  130. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +0 -473
  131. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +0 -1
  132. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +0 -3
  133. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +0 -266
  134. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +0 -394
  135. package/.opencode/skills/ui-ux-pro-max/SKILL.md +0 -388
  136. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +0 -31
  137. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +0 -26
  138. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +0 -193
  139. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +0 -1924
  140. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +0 -106
  141. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +0 -35
  142. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +0 -17
  143. package/.opencode/skills/ui-ux-pro-max/data/products.csv +0 -193
  144. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +0 -45
  145. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +0 -51
  146. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +0 -54
  147. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +0 -57
  148. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  149. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  150. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +0 -76
  151. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  152. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +0 -51
  153. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  154. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -71
  155. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  156. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  157. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
  158. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  159. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  160. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  161. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +0 -54
  162. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +0 -60
  163. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +0 -56
  164. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  165. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +0 -60
  166. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +0 -57
  167. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +0 -85
  168. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +0 -75
  169. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +0 -162
  170. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  171. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +0 -464
  172. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +0 -1479
  173. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +0 -162
  174. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_core.py +0 -134
  175. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_design_system_mode.py +0 -159
  176. package/.opencode/skills/ui-ux-pro-max/scripts/validate_data.py +0 -114
  177. package/dist/hooks/bash-python-guide.d.ts +0 -4
  178. package/dist/hooks/bash-python-guide.d.ts.map +0 -1
  179. package/dist/hooks/bash-python-guide.js +0 -67
  180. package/dist/hooks/bash-python-guide.js.map +0 -1
  181. package/dist/hooks/handoff.d.ts +0 -9
  182. package/dist/hooks/handoff.d.ts.map +0 -1
  183. package/dist/hooks/handoff.js +0 -8
  184. package/dist/hooks/handoff.js.map +0 -1
  185. package/dist/hooks/hook-manager.d.ts +0 -32
  186. package/dist/hooks/hook-manager.d.ts.map +0 -1
  187. package/dist/hooks/hook-manager.js +0 -91
  188. package/dist/hooks/hook-manager.js.map +0 -1
  189. package/dist/hooks/llm-after.d.ts +0 -8
  190. package/dist/hooks/llm-after.d.ts.map +0 -1
  191. package/dist/hooks/llm-after.js +0 -8
  192. package/dist/hooks/llm-after.js.map +0 -1
  193. package/dist/hooks/media-ingest.d.ts +0 -43
  194. package/dist/hooks/media-ingest.d.ts.map +0 -1
  195. package/dist/hooks/media-ingest.js +0 -269
  196. package/dist/hooks/media-ingest.js.map +0 -1
  197. package/dist/hooks/memory-guide.d.ts +0 -3
  198. package/dist/hooks/memory-guide.d.ts.map +0 -1
  199. package/dist/hooks/memory-guide.js +0 -47
  200. package/dist/hooks/memory-guide.js.map +0 -1
  201. package/dist/hooks/observation-capture.d.ts +0 -17
  202. package/dist/hooks/observation-capture.d.ts.map +0 -1
  203. package/dist/hooks/observation-capture.js +0 -20
  204. package/dist/hooks/observation-capture.js.map +0 -1
  205. package/dist/hooks/session-compacting.d.ts +0 -6
  206. package/dist/hooks/session-compacting.d.ts.map +0 -1
  207. package/dist/hooks/session-compacting.js +0 -6
  208. package/dist/hooks/session-compacting.js.map +0 -1
  209. package/dist/hooks/session-ending.d.ts +0 -20
  210. package/dist/hooks/session-ending.d.ts.map +0 -1
  211. package/dist/hooks/session-ending.js +0 -113
  212. package/dist/hooks/session-ending.js.map +0 -1
  213. package/dist/hooks/session-recall.d.ts +0 -2
  214. package/dist/hooks/session-recall.d.ts.map +0 -1
  215. package/dist/hooks/session-recall.js +0 -103
  216. package/dist/hooks/session-recall.js.map +0 -1
  217. package/dist/hooks/session-start.d.ts +0 -9
  218. package/dist/hooks/session-start.d.ts.map +0 -1
  219. package/dist/hooks/session-start.js +0 -42
  220. package/dist/hooks/session-start.js.map +0 -1
  221. package/dist/hooks/tool-before.d.ts +0 -10
  222. package/dist/hooks/tool-before.d.ts.map +0 -1
  223. package/dist/hooks/tool-before.js +0 -6
  224. package/dist/hooks/tool-before.js.map +0 -1
  225. package/dist/hooks/tool-executed.d.ts +0 -19
  226. package/dist/hooks/tool-executed.d.ts.map +0 -1
  227. package/dist/hooks/tool-executed.js +0 -12
  228. package/dist/hooks/tool-executed.js.map +0 -1
  229. package/dist/hooks/user-profile.d.ts +0 -2
  230. package/dist/hooks/user-profile.d.ts.map +0 -1
  231. package/dist/hooks/user-profile.js +0 -30
  232. package/dist/hooks/user-profile.js.map +0 -1
  233. package/dist/hooks/user-prompt.d.ts +0 -9
  234. package/dist/hooks/user-prompt.d.ts.map +0 -1
  235. package/dist/hooks/user-prompt.js +0 -8
  236. package/dist/hooks/user-prompt.js.map +0 -1
  237. package/dist/hooks/voice-guide.d.ts +0 -8
  238. package/dist/hooks/voice-guide.d.ts.map +0 -1
  239. package/dist/hooks/voice-guide.js +0 -101
  240. package/dist/hooks/voice-guide.js.map +0 -1
  241. package/dist/plugin.d.ts +0 -55
  242. package/dist/plugin.d.ts.map +0 -1
  243. package/dist/plugin.js +0 -342
  244. package/dist/plugin.js.map +0 -1
  245. package/dist/tools/add-memory.d.ts +0 -17
  246. package/dist/tools/add-memory.d.ts.map +0 -1
  247. package/dist/tools/add-memory.js +0 -60
  248. package/dist/tools/add-memory.js.map +0 -1
  249. package/dist/tools/media-ask.d.ts +0 -14
  250. package/dist/tools/media-ask.d.ts.map +0 -1
  251. package/dist/tools/media-ask.js +0 -224
  252. package/dist/tools/media-ask.js.map +0 -1
  253. package/dist/tools/media-speak.d.ts +0 -16
  254. package/dist/tools/media-speak.d.ts.map +0 -1
  255. package/dist/tools/media-speak.js +0 -78
  256. package/dist/tools/media-speak.js.map +0 -1
  257. package/dist/tools/media-upload.d.ts +0 -14
  258. package/dist/tools/media-upload.d.ts.map +0 -1
  259. package/dist/tools/media-upload.js +0 -155
  260. package/dist/tools/media-upload.js.map +0 -1
  261. package/dist/tools/python-exec.d.ts +0 -13
  262. package/dist/tools/python-exec.d.ts.map +0 -1
  263. package/dist/tools/python-exec.js +0 -99
  264. package/dist/tools/python-exec.js.map +0 -1
  265. package/dist/tools/python-restart.d.ts +0 -11
  266. package/dist/tools/python-restart.d.ts.map +0 -1
  267. package/dist/tools/python-restart.js +0 -33
  268. package/dist/tools/python-restart.js.map +0 -1
  269. package/dist/utils/circuit-breaker.d.ts +0 -23
  270. package/dist/utils/circuit-breaker.d.ts.map +0 -1
  271. package/dist/utils/circuit-breaker.js +0 -75
  272. package/dist/utils/circuit-breaker.js.map +0 -1
  273. package/dist/utils/config-loader.d.ts +0 -20
  274. package/dist/utils/config-loader.d.ts.map +0 -1
  275. package/dist/utils/config-loader.js +0 -194
  276. package/dist/utils/config-loader.js.map +0 -1
  277. package/dist/utils/fallback.d.ts +0 -7
  278. package/dist/utils/fallback.d.ts.map +0 -1
  279. package/dist/utils/fallback.js +0 -20
  280. package/dist/utils/fallback.js.map +0 -1
  281. package/dist/utils/git.d.ts +0 -24
  282. package/dist/utils/git.d.ts.map +0 -1
  283. package/dist/utils/git.js +0 -71
  284. package/dist/utils/git.js.map +0 -1
  285. package/dist/utils/github.d.ts +0 -37
  286. package/dist/utils/github.d.ts.map +0 -1
  287. package/dist/utils/github.js +0 -53
  288. package/dist/utils/github.js.map +0 -1
  289. package/dist/utils/global-path.d.ts +0 -2
  290. package/dist/utils/global-path.d.ts.map +0 -1
  291. package/dist/utils/global-path.js +0 -32
  292. package/dist/utils/global-path.js.map +0 -1
  293. package/dist/utils/logger.d.ts +0 -6
  294. package/dist/utils/logger.d.ts.map +0 -1
  295. package/dist/utils/logger.js +0 -69
  296. package/dist/utils/logger.js.map +0 -1
  297. package/dist/utils/obs-capture.d.ts +0 -7
  298. package/dist/utils/obs-capture.d.ts.map +0 -1
  299. package/dist/utils/obs-capture.js +0 -49
  300. package/dist/utils/obs-capture.js.map +0 -1
  301. package/dist/utils/retry.d.ts +0 -11
  302. package/dist/utils/retry.d.ts.map +0 -1
  303. package/dist/utils/retry.js +0 -45
  304. package/dist/utils/retry.js.map +0 -1
  305. package/dist/utils/self-wiring.d.ts +0 -13
  306. package/dist/utils/self-wiring.d.ts.map +0 -1
  307. package/dist/utils/self-wiring.js +0 -86
  308. package/dist/utils/self-wiring.js.map +0 -1
  309. package/dist/utils/state.d.ts +0 -106
  310. package/dist/utils/state.d.ts.map +0 -1
  311. package/dist/utils/state.js +0 -166
  312. package/dist/utils/state.js.map +0 -1
  313. package/dist/utils/status.d.ts +0 -47
  314. package/dist/utils/status.d.ts.map +0 -1
  315. package/dist/utils/status.js +0 -147
  316. package/dist/utils/status.js.map +0 -1
  317. package/dist/utils/ttl-map.d.ts +0 -17
  318. package/dist/utils/ttl-map.d.ts.map +0 -1
  319. package/dist/utils/ttl-map.js +0 -67
  320. package/dist/utils/ttl-map.js.map +0 -1
@@ -1,453 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Slide Search Core - BM25 search engine for slide design databases
5
- """
6
-
7
- import csv
8
- import re
9
- from pathlib import Path
10
- from math import log
11
- from collections import defaultdict
12
-
13
- # ============ CONFIGURATION ============
14
- DATA_DIR = Path(__file__).parent.parent / "data"
15
- MAX_RESULTS = 3
16
-
17
- CSV_CONFIG = {
18
- "strategy": {
19
- "file": "slide-strategies.csv",
20
- "search_cols": ["strategy_name", "keywords", "goal", "audience", "narrative_arc"],
21
- "output_cols": ["strategy_name", "keywords", "slide_count", "structure", "goal", "audience", "tone", "narrative_arc", "sources"]
22
- },
23
- "layout": {
24
- "file": "slide-layouts.csv",
25
- "search_cols": ["layout_name", "keywords", "use_case", "recommended_for"],
26
- "output_cols": ["layout_name", "keywords", "use_case", "content_zones", "visual_weight", "cta_placement", "recommended_for", "avoid_for", "css_structure"]
27
- },
28
- "copy": {
29
- "file": "slide-copy.csv",
30
- "search_cols": ["formula_name", "keywords", "use_case", "emotion_trigger", "slide_type"],
31
- "output_cols": ["formula_name", "keywords", "components", "use_case", "example_template", "emotion_trigger", "slide_type", "source"]
32
- },
33
- "chart": {
34
- "file": "slide-charts.csv",
35
- "search_cols": ["chart_type", "keywords", "best_for", "when_to_use", "slide_context"],
36
- "output_cols": ["chart_type", "keywords", "best_for", "data_type", "when_to_use", "when_to_avoid", "max_categories", "slide_context", "css_implementation", "accessibility_notes"]
37
- }
38
- }
39
-
40
- AVAILABLE_DOMAINS = list(CSV_CONFIG.keys())
41
-
42
-
43
- # ============ BM25 IMPLEMENTATION ============
44
- class BM25:
45
- """BM25 ranking algorithm for text search"""
46
-
47
- def __init__(self, k1=1.5, b=0.75):
48
- self.k1 = k1
49
- self.b = b
50
- self.corpus = []
51
- self.doc_lengths = []
52
- self.avgdl = 0
53
- self.idf = {}
54
- self.doc_freqs = defaultdict(int)
55
- self.N = 0
56
-
57
- def tokenize(self, text):
58
- """Lowercase, split, remove punctuation, filter short words"""
59
- text = re.sub(r'[^\w\s]', ' ', str(text).lower())
60
- return [w for w in text.split() if len(w) > 2]
61
-
62
- def fit(self, documents):
63
- """Build BM25 index from documents"""
64
- self.corpus = [self.tokenize(doc) for doc in documents]
65
- self.N = len(self.corpus)
66
- if self.N == 0:
67
- return
68
- self.doc_lengths = [len(doc) for doc in self.corpus]
69
- self.avgdl = sum(self.doc_lengths) / self.N
70
-
71
- for doc in self.corpus:
72
- seen = set()
73
- for word in doc:
74
- if word not in seen:
75
- self.doc_freqs[word] += 1
76
- seen.add(word)
77
-
78
- for word, freq in self.doc_freqs.items():
79
- self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
80
-
81
- def score(self, query):
82
- """Score all documents against query"""
83
- query_tokens = self.tokenize(query)
84
- scores = []
85
-
86
- for idx, doc in enumerate(self.corpus):
87
- score = 0
88
- doc_len = self.doc_lengths[idx]
89
- term_freqs = defaultdict(int)
90
- for word in doc:
91
- term_freqs[word] += 1
92
-
93
- for token in query_tokens:
94
- if token in self.idf:
95
- tf = term_freqs[token]
96
- idf = self.idf[token]
97
- numerator = tf * (self.k1 + 1)
98
- denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
99
- score += idf * numerator / denominator
100
-
101
- scores.append((idx, score))
102
-
103
- return sorted(scores, key=lambda x: x[1], reverse=True)
104
-
105
-
106
- # ============ SEARCH FUNCTIONS ============
107
- def _load_csv(filepath):
108
- """Load CSV and return list of dicts"""
109
- with open(filepath, 'r', encoding='utf-8') as f:
110
- return list(csv.DictReader(f))
111
-
112
-
113
- def _search_csv(filepath, search_cols, output_cols, query, max_results):
114
- """Core search function using BM25"""
115
- if not filepath.exists():
116
- return []
117
-
118
- data = _load_csv(filepath)
119
-
120
- # Build documents from search columns
121
- documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
122
-
123
- # BM25 search
124
- bm25 = BM25()
125
- bm25.fit(documents)
126
- ranked = bm25.score(query)
127
-
128
- # Get top results with score > 0
129
- results = []
130
- for idx, score in ranked[:max_results]:
131
- if score > 0:
132
- row = data[idx]
133
- results.append({col: row.get(col, "") for col in output_cols if col in row})
134
-
135
- return results
136
-
137
-
138
- def detect_domain(query):
139
- """Auto-detect the most relevant domain from query"""
140
- query_lower = query.lower()
141
-
142
- domain_keywords = {
143
- "strategy": ["pitch", "deck", "investor", "yc", "seed", "series", "demo", "sales", "webinar",
144
- "conference", "board", "qbr", "all-hands", "duarte", "kawasaki", "structure"],
145
- "layout": ["slide", "layout", "grid", "column", "title", "hero", "section", "cta",
146
- "screenshot", "quote", "timeline", "comparison", "pricing", "team"],
147
- "copy": ["headline", "copy", "formula", "aida", "pas", "hook", "cta", "benefit",
148
- "objection", "proof", "testimonial", "urgency", "scarcity"],
149
- "chart": ["chart", "graph", "bar", "line", "pie", "funnel", "metrics", "data",
150
- "visualization", "kpi", "trend", "comparison", "heatmap", "gauge"]
151
- }
152
-
153
- scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
154
- best = max(scores, key=scores.get)
155
- return best if scores[best] > 0 else "strategy"
156
-
157
-
158
- def search(query, domain=None, max_results=MAX_RESULTS):
159
- """Main search function with auto-domain detection"""
160
- if domain is None:
161
- domain = detect_domain(query)
162
-
163
- config = CSV_CONFIG.get(domain, CSV_CONFIG["strategy"])
164
- filepath = DATA_DIR / config["file"]
165
-
166
- if not filepath.exists():
167
- return {"error": f"File not found: {filepath}", "domain": domain}
168
-
169
- results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
170
-
171
- return {
172
- "domain": domain,
173
- "query": query,
174
- "file": config["file"],
175
- "count": len(results),
176
- "results": results
177
- }
178
-
179
-
180
- def search_all(query, max_results=2):
181
- """Search across all domains for comprehensive results"""
182
- all_results = {}
183
-
184
- for domain in AVAILABLE_DOMAINS:
185
- result = search(query, domain, max_results)
186
- if result.get("count", 0) > 0:
187
- all_results[domain] = result
188
-
189
- return all_results
190
-
191
-
192
- # ============ CONTEXTUAL SEARCH (Premium Slide System) ============
193
-
194
- # New CSV configurations for decision system
195
- DECISION_CSV_CONFIG = {
196
- "layout-logic": {
197
- "file": "slide-layout-logic.csv",
198
- "key_col": "goal"
199
- },
200
- "typography": {
201
- "file": "slide-typography.csv",
202
- "key_col": "content_type"
203
- },
204
- "color-logic": {
205
- "file": "slide-color-logic.csv",
206
- "key_col": "emotion"
207
- },
208
- "backgrounds": {
209
- "file": "slide-backgrounds.csv",
210
- "key_col": "slide_type"
211
- }
212
- }
213
-
214
-
215
- def _load_decision_csv(csv_type):
216
- """Load a decision CSV and return as dict keyed by primary column."""
217
- config = DECISION_CSV_CONFIG.get(csv_type)
218
- if not config:
219
- return {}
220
-
221
- filepath = DATA_DIR / config["file"]
222
- if not filepath.exists():
223
- return {}
224
-
225
- data = _load_csv(filepath)
226
- return {row[config["key_col"]]: row for row in data if config["key_col"] in row}
227
-
228
-
229
- def get_layout_for_goal(goal, previous_emotion=None):
230
- """
231
- Get layout recommendation based on slide goal.
232
- Uses slide-layout-logic.csv for decision.
233
- """
234
- layouts = _load_decision_csv("layout-logic")
235
- row = layouts.get(goal, layouts.get("features", {}))
236
-
237
- result = dict(row) if row else {}
238
-
239
- # Apply pattern-breaking logic
240
- if result.get("break_pattern") == "true" and previous_emotion:
241
- result["_pattern_break"] = True
242
- result["_contrast_with"] = previous_emotion
243
-
244
- return result
245
-
246
-
247
- def get_typography_for_slide(slide_type, has_metrics=False, has_quote=False):
248
- """
249
- Get typography recommendation based on slide content.
250
- Uses slide-typography.csv for decision.
251
- """
252
- typography = _load_decision_csv("typography")
253
-
254
- if has_metrics:
255
- return typography.get("metric-callout", {})
256
- if has_quote:
257
- return typography.get("quote-block", {})
258
-
259
- # Map slide types to typography
260
- type_map = {
261
- "hero": "hero-statement",
262
- "hook": "hero-statement",
263
- "title": "title-only",
264
- "problem": "subtitle-heavy",
265
- "agitation": "metric-callout",
266
- "solution": "subtitle-heavy",
267
- "features": "feature-grid",
268
- "proof": "metric-callout",
269
- "traction": "data-insight",
270
- "social": "quote-block",
271
- "testimonial": "testimonial",
272
- "pricing": "pricing",
273
- "team": "team",
274
- "cta": "cta-action",
275
- "comparison": "comparison",
276
- "timeline": "timeline",
277
- }
278
-
279
- content_type = type_map.get(slide_type, "feature-grid")
280
- return typography.get(content_type, {})
281
-
282
-
283
- def get_color_for_emotion(emotion):
284
- """
285
- Get color treatment based on emotional beat.
286
- Uses slide-color-logic.csv for decision.
287
- """
288
- colors = _load_decision_csv("color-logic")
289
- return colors.get(emotion, colors.get("clarity", {}))
290
-
291
-
292
- def get_background_config(slide_type):
293
- """
294
- Get background image configuration.
295
- Uses slide-backgrounds.csv for decision.
296
- """
297
- backgrounds = _load_decision_csv("backgrounds")
298
- return backgrounds.get(slide_type, {})
299
-
300
-
301
- def should_use_full_bleed(slide_index, total_slides, emotion):
302
- """
303
- Determine if slide should use full-bleed background.
304
- Premium decks use 2-3 full-bleed slides strategically.
305
-
306
- Rules:
307
- 1. Never consecutive full-bleed
308
- 2. One in first third, one in middle, one at end
309
- 3. Reserved for high-emotion beats (hope, urgency, fear)
310
- """
311
- high_emotion_beats = ["hope", "urgency", "fear", "curiosity"]
312
-
313
- if emotion not in high_emotion_beats:
314
- return False
315
-
316
- if total_slides < 3:
317
- return False
318
-
319
- third = total_slides // 3
320
- strategic_positions = [1, third, third * 2, total_slides - 1]
321
-
322
- return slide_index in strategic_positions
323
-
324
-
325
- def calculate_pattern_break(slide_index, total_slides, previous_emotion=None):
326
- """
327
- Determine if this slide should break the visual pattern.
328
- Used for emotional contrast (Duarte Sparkline technique).
329
- """
330
- # Pattern breaks at strategic positions
331
- if total_slides < 5:
332
- return False
333
-
334
- # Break at 1/3 and 2/3 points
335
- third = total_slides // 3
336
- if slide_index in [third, third * 2]:
337
- return True
338
-
339
- # Break when switching between frustration and hope
340
- contrasting_emotions = {
341
- "frustration": ["hope", "relief"],
342
- "hope": ["frustration", "fear"],
343
- "fear": ["hope", "relief"],
344
- }
345
-
346
- if previous_emotion in contrasting_emotions:
347
- return True
348
-
349
- return False
350
-
351
-
352
- def search_with_context(query, slide_position=1, total_slides=9, previous_emotion=None):
353
- """
354
- Enhanced search that considers deck context.
355
-
356
- Args:
357
- query: Search query
358
- slide_position: Current slide index (1-based)
359
- total_slides: Total slides in deck
360
- previous_emotion: Emotion of previous slide (for contrast)
361
-
362
- Returns:
363
- Search results enriched with contextual recommendations
364
- """
365
- # Get base results from existing BM25 search
366
- base_results = search_all(query, max_results=2)
367
-
368
- # Detect likely slide goal from query
369
- goal = detect_domain(query.lower())
370
- if "problem" in query.lower():
371
- goal = "problem"
372
- elif "solution" in query.lower():
373
- goal = "solution"
374
- elif "cta" in query.lower() or "call to action" in query.lower():
375
- goal = "cta"
376
- elif "hook" in query.lower() or "title" in query.lower():
377
- goal = "hook"
378
- elif "traction" in query.lower() or "metric" in query.lower():
379
- goal = "traction"
380
-
381
- # Enrich with contextual recommendations
382
- context = {
383
- "slide_position": slide_position,
384
- "total_slides": total_slides,
385
- "previous_emotion": previous_emotion,
386
- "inferred_goal": goal,
387
- }
388
-
389
- # Get layout recommendation
390
- layout = get_layout_for_goal(goal, previous_emotion)
391
- if layout:
392
- context["recommended_layout"] = layout.get("layout_pattern")
393
- context["layout_direction"] = layout.get("direction")
394
- context["visual_weight"] = layout.get("visual_weight")
395
- context["use_background_image"] = layout.get("use_bg_image") == "true"
396
-
397
- # Get typography recommendation
398
- typography = get_typography_for_slide(goal)
399
- if typography:
400
- context["typography"] = {
401
- "primary_size": typography.get("primary_size"),
402
- "secondary_size": typography.get("secondary_size"),
403
- "weight_contrast": typography.get("weight_contrast"),
404
- }
405
-
406
- # Get color treatment
407
- emotion = layout.get("emotion", "clarity") if layout else "clarity"
408
- color = get_color_for_emotion(emotion)
409
- if color:
410
- context["color_treatment"] = {
411
- "background": color.get("background"),
412
- "text_color": color.get("text_color"),
413
- "accent_usage": color.get("accent_usage"),
414
- "card_style": color.get("card_style"),
415
- }
416
-
417
- # Calculate pattern breaking
418
- context["should_break_pattern"] = calculate_pattern_break(
419
- slide_position, total_slides, previous_emotion
420
- )
421
- context["should_use_full_bleed"] = should_use_full_bleed(
422
- slide_position, total_slides, emotion
423
- )
424
-
425
- # Get background config if needed
426
- if context.get("use_background_image"):
427
- bg_config = get_background_config(goal)
428
- if bg_config:
429
- context["background"] = {
430
- "image_category": bg_config.get("image_category"),
431
- "overlay_style": bg_config.get("overlay_style"),
432
- "search_keywords": bg_config.get("search_keywords"),
433
- }
434
-
435
- # Suggested animation classes
436
- animation_map = {
437
- "hook": "animate-fade-up",
438
- "problem": "animate-fade-up",
439
- "agitation": "animate-count animate-stagger",
440
- "solution": "animate-scale",
441
- "features": "animate-stagger",
442
- "traction": "animate-chart animate-count",
443
- "proof": "animate-stagger-scale",
444
- "social": "animate-fade-up",
445
- "cta": "animate-pulse",
446
- }
447
- context["animation_class"] = animation_map.get(goal, "animate-fade-up")
448
-
449
- return {
450
- "query": query,
451
- "context": context,
452
- "base_results": base_results,
453
- }
@@ -1,48 +0,0 @@
1
- """Regression tests for validate-tokens.cjs.
2
-
3
- The validator used to skip any line containing ``var(--`` outright, so a
4
- hardcoded value sharing a line with a token reference (extremely common in
5
- real CSS, and universal in minified CSS where everything is one line) went
6
- undetected. These tests drive the CLI via ``node`` and assert it flags such
7
- cases. They are pytest-based so the repository's existing pytest CI runs them.
8
- """
9
-
10
- import shutil
11
- import subprocess
12
- from pathlib import Path
13
-
14
- import pytest
15
-
16
- SCRIPT = Path(__file__).resolve().parent.parent / "validate-tokens.cjs"
17
-
18
-
19
- def _run(tmp_path: Path, css: str) -> subprocess.CompletedProcess:
20
- node = shutil.which("node")
21
- if not node:
22
- pytest.skip("node not available")
23
- (tmp_path / "sample.css").write_text(css)
24
- return subprocess.run(
25
- [node, str(SCRIPT), "--dir", str(tmp_path)],
26
- capture_output=True,
27
- text=True,
28
- )
29
-
30
-
31
- def test_flags_hardcoded_hex_sharing_line_with_token(tmp_path):
32
- """A hardcoded hex on the same line as a var() token is still a violation."""
33
- result = _run(
34
- tmp_path,
35
- ".btn { background: #FF6B6B; color: var(--color-primary); }\n",
36
- )
37
- assert "#FF6B6B" in result.stdout, result.stdout
38
- assert result.returncode == 1
39
-
40
-
41
- def test_token_only_line_reports_no_violation(tmp_path):
42
- """A line that references only tokens produces no false positives."""
43
- result = _run(
44
- tmp_path,
45
- ".btn { background: var(--color-bg); color: var(--color-primary); }\n",
46
- )
47
- assert "No token violations" in result.stdout, result.stdout
48
- assert result.returncode == 0