dsh-hyperframes 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/README.en.md +4 -3
  2. package/README.md +5 -4
  3. package/lib/index.d.ts +1 -1
  4. package/lib/index.js +1 -0
  5. package/package.json +2 -2
  6. package/skills/embedded-captions/SKILL.md +28 -3
  7. package/skills/embedded-captions/references/aesthetic-principles.md +2 -3
  8. package/skills/embedded-captions/references/rail.md +1 -1
  9. package/skills/embedded-captions/scripts/check-occlusion.cjs +4 -1
  10. package/skills/embedded-captions/scripts/check-overflow.cjs +1 -0
  11. package/skills/embedded-captions/scripts/check-rail-climax.cjs +5 -1
  12. package/skills/embedded-captions/scripts/fixtures/heroless/theme.json +11 -0
  13. package/skills/embedded-captions/scripts/hf-cli.cjs +33 -0
  14. package/skills/embedded-captions/scripts/make-theme.cjs +95 -57
  15. package/skills/embedded-captions/scripts/make-theme.test.mjs +131 -0
  16. package/skills/embedded-captions/scripts/matte.cjs +1 -13
  17. package/skills/embedded-captions/scripts/measure-layout.cjs +2 -1
  18. package/skills/embedded-captions/scripts/preview-frames.cjs +39 -11
  19. package/skills/embedded-captions/scripts/preview-frames.test.mjs +24 -0
  20. package/skills/embedded-captions/scripts/render-and-composite.sh +8 -52
  21. package/skills/embedded-captions/scripts/safe-zones.cjs +4 -1
  22. package/skills/embedded-captions/scripts/transcribe.cjs +5 -14
  23. package/skills/embedded-captions/scripts/transcribe.test.mjs +41 -0
  24. package/skills/faceless-explainer/SKILL.md +20 -16
  25. package/skills/faceless-explainer/references/story-design.md +1 -1
  26. package/skills/faceless-explainer/scripts/assemble-index.mjs +6 -4
  27. package/skills/faceless-explainer/scripts/assemble-index.test.mjs +57 -2
  28. package/skills/faceless-explainer/scripts/audio.mjs +2 -0
  29. package/skills/faceless-explainer/scripts/build-frame.mjs +6 -3
  30. package/skills/faceless-explainer/scripts/captions.mjs +7 -4
  31. package/skills/faceless-explainer/scripts/frame-packets.mjs +4 -4
  32. package/skills/faceless-explainer/scripts/lib/bgm-volume.mjs +4 -0
  33. package/skills/faceless-explainer/scripts/lib/captured-fonts.mjs +58 -0
  34. package/skills/{hyperframes-core → faceless-explainer}/scripts/lib/frame-packets-core.mjs +5 -23
  35. package/skills/faceless-explainer/scripts/lib/storyboard.mjs +1 -1
  36. package/skills/faceless-explainer/scripts/lib/transitions.json +3 -3
  37. package/skills/faceless-explainer/scripts/transitions.mjs +1 -0
  38. package/skills/faceless-explainer/scripts/transitions.test.mjs +2 -0
  39. package/skills/faceless-explainer/sub-agents/frame-worker.md +1 -1
  40. package/skills/figma/SKILL.md +2 -0
  41. package/skills/general-video/SKILL.md +13 -11
  42. package/skills/general-video/scripts/frame-packets.mjs +4 -4
  43. package/skills/general-video/scripts/lib/frame-packets-core.mjs +194 -0
  44. package/skills/general-video/sub-agents/frame-worker.md +1 -1
  45. package/skills/hyperframes/SKILL.md +40 -30
  46. package/skills/{hyperframes-core → hyperframes}/references/brief-contract.md +15 -15
  47. package/skills/{hyperframes-core → hyperframes}/references/brief-format.md +8 -8
  48. package/skills/hyperframes/references/capability-menu.md +2 -2
  49. package/skills/{hyperframes-core → hyperframes}/references/frame-worker-core.md +1 -1
  50. package/skills/hyperframes/references/intent-interview.md +16 -16
  51. package/skills/hyperframes/references/plugin-installation.md +43 -0
  52. package/skills/{hyperframes-core → hyperframes}/references/production-loop.md +11 -11
  53. package/skills/hyperframes/references/review-loop.md +37 -0
  54. package/skills/hyperframes/references/route-briefs.md +1 -1
  55. package/skills/hyperframes/references/routes/motion-graphics.md +1 -1
  56. package/skills/{hyperframes-core → hyperframes}/references/script-format.md +3 -3
  57. package/skills/hyperframes/references/skill-lifecycle.md +2 -0
  58. package/skills/{hyperframes-core → hyperframes}/references/storyboard-format.md +3 -34
  59. package/skills/hyperframes/scripts/lib/frame-packets-core.mjs +194 -0
  60. package/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  61. package/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  62. package/skills/hyperframes-animation/SKILL.md +5 -1
  63. package/skills/hyperframes-animation/adapters/gsap.md +1 -1
  64. package/skills/hyperframes-animation/adapters/lottie.md +15 -2
  65. package/skills/hyperframes-animation/adapters/three.md +14 -0
  66. package/skills/hyperframes-animation/blueprints/titlecard-reveal.md +1 -1
  67. package/skills/hyperframes-animation/references/motion-blur.md +133 -0
  68. package/skills/hyperframes-animation/rules/cursor-click-ripple.md +1 -1
  69. package/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +1 -1
  70. package/skills/hyperframes-animation/rules/hacker-flip-3d.md +1 -1
  71. package/skills/hyperframes-animation/rules/kinetic-beat-slam.md +2 -2
  72. package/skills/hyperframes-animation/rules/motion-blur-streak.md +1 -1
  73. package/skills/hyperframes-animation/rules/vertical-spring-ticker.md +1 -1
  74. package/skills/hyperframes-animation/scripts/package-loader.mjs +47 -16
  75. package/skills/hyperframes-animation/scripts/package-loader.test.mjs +52 -0
  76. package/skills/hyperframes-animation/techniques.md +1 -1
  77. package/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +3 -3
  78. package/skills/hyperframes-animation/transitions/css-push.md +4 -4
  79. package/skills/hyperframes-audio/SKILL.md +30 -22
  80. package/skills/hyperframes-audio/scripts/carve.mjs +6 -3
  81. package/skills/hyperframes-audio/scripts/carve.test.mjs +16 -1
  82. package/skills/hyperframes-cli/SKILL.md +44 -35
  83. package/skills/hyperframes-cli/references/init-and-scaffold.md +8 -6
  84. package/skills/hyperframes-cli/references/lint-validate-inspect.md +3 -3
  85. package/skills/hyperframes-cli/references/preview-render.md +35 -32
  86. package/skills/hyperframes-cli/references/upgrade-info-misc.md +50 -1
  87. package/skills/hyperframes-core/SKILL.md +30 -24
  88. package/skills/hyperframes-core/references/composition-patterns.md +3 -42
  89. package/skills/hyperframes-core/references/creator-editing-recipes.md +131 -7
  90. package/skills/hyperframes-core/references/data-attributes.md +11 -11
  91. package/skills/hyperframes-core/references/determinism-rules.md +2 -19
  92. package/skills/hyperframes-core/references/full-screen-motion.md +0 -9
  93. package/skills/hyperframes-core/references/minimal-composition.md +2 -2
  94. package/skills/hyperframes-core/references/sub-compositions.md +3 -66
  95. package/skills/hyperframes-core/references/tailwind.md +1 -20
  96. package/skills/hyperframes-core/references/tracks-and-clips.md +2 -6
  97. package/skills/hyperframes-core/references/variables-and-media.md +3 -3
  98. package/skills/hyperframes-creative/SKILL.md +2 -0
  99. package/skills/hyperframes-creative/references/house-style.md +15 -15
  100. package/skills/hyperframes-creative/references/story-spine.md +3 -1
  101. package/skills/hyperframes-creative/references/storyboard-recipe.md +57 -0
  102. package/skills/hyperframes-creative/references/typography.md +1 -1
  103. package/skills/hyperframes-creative/scripts/package-loader.mjs +47 -16
  104. package/skills/hyperframes-creative/scripts/package-loader.test.mjs +52 -0
  105. package/skills/hyperframes-keyframes/SKILL.md +4 -2
  106. package/skills/hyperframes-registry/SKILL.md +3 -1
  107. package/skills/hyperframes-registry/references/contributing.md +8 -2
  108. package/skills/hyperframes-registry/references/discovery.md +20 -34
  109. package/skills/hyperframes-studio/SKILL.md +77 -0
  110. package/skills/media-use/SKILL.md +14 -12
  111. package/skills/media-use/audio/references/captions/authoring.md +4 -0
  112. package/skills/media-use/audio/references/remove-background.md +1 -1
  113. package/skills/media-use/audio/references/tts-to-captions.md +11 -2
  114. package/skills/media-use/audio/references/tts.md +84 -6
  115. package/skills/media-use/audio/scripts/audio.mjs +15 -6
  116. package/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +113 -0
  117. package/skills/media-use/audio/scripts/lib/audio-meta.mjs +39 -0
  118. package/skills/media-use/audio/scripts/lib/audio-meta.test.mjs +136 -0
  119. package/skills/media-use/audio/scripts/lib/bgm-volume.mjs +4 -0
  120. package/skills/media-use/audio/scripts/lib/bgm.mjs +2 -6
  121. package/skills/media-use/audio/scripts/lib/gemini-auth.mjs +43 -0
  122. package/skills/media-use/audio/scripts/lib/gemini-auth.py +52 -0
  123. package/skills/media-use/audio/scripts/lib/gemini-auth.test.mjs +86 -0
  124. package/skills/media-use/audio/scripts/lib/gemini-auth_test.py +90 -0
  125. package/skills/media-use/audio/scripts/lib/gemini-tts.mjs +122 -0
  126. package/skills/media-use/audio/scripts/lib/gemini-tts.test.mjs +243 -0
  127. package/skills/media-use/audio/scripts/lib/heygen.mjs +2 -1
  128. package/skills/media-use/audio/scripts/lib/tts.mjs +15 -3
  129. package/skills/media-use/audio/scripts/wait-bgm.mjs +8 -4
  130. package/skills/media-use/audio/scripts/wait-bgm.test.mjs +65 -0
  131. package/skills/media-use/references/audio.md +3 -2
  132. package/skills/media-use/references/grading.md +7 -7
  133. package/skills/media-use/references/media-treatment-recipes.md +1 -1
  134. package/skills/media-use/references/memory.md +2 -2
  135. package/skills/media-use/references/meta.md +2 -2
  136. package/skills/media-use/references/operations.md +15 -13
  137. package/skills/media-use/references/resolve.md +22 -11
  138. package/skills/media-use/references/setup-providers.md +11 -11
  139. package/skills/media-use/scripts/audio-duck.mjs +19 -27
  140. package/skills/media-use/scripts/compatibility.test.mjs +96 -0
  141. package/skills/media-use/scripts/lib/config-lock.mjs +45 -0
  142. package/skills/media-use/scripts/lib/duck.mjs +16 -10
  143. package/skills/media-use/scripts/lib/media-fetch.mjs +85 -0
  144. package/skills/media-use/scripts/lib/media-home.mjs +20 -0
  145. package/skills/media-use/scripts/lib/npx-sync.mjs +28 -2
  146. package/skills/media-use/scripts/lib/prefs-store.mjs +2 -2
  147. package/skills/media-use/scripts/lib/recipe-store.mjs +2 -2
  148. package/skills/media-use/scripts/lib/telemetry.mjs +29 -41
  149. package/skills/media-use/scripts/lib/transcriptCutFade.mjs +21 -0
  150. package/skills/media-use/scripts/prefs.mjs +1 -1
  151. package/skills/media-use/scripts/recipe.mjs +1 -1
  152. package/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  153. package/skills/media-use/scripts/resolve.mjs +27 -1267
  154. package/skills/media-use/scripts/transcribe.mjs +39 -25
  155. package/skills/media-use/scripts/transcribe.test.mjs +112 -0
  156. package/skills/media-use/scripts/transcript-cut.mjs +39 -8
  157. package/skills/media-use/scripts/transcript-cut.test.mjs +117 -0
  158. package/skills/motion-graphics/SKILL.md +3 -1
  159. package/skills/motion-graphics/agents/builder.md +1 -0
  160. package/skills/motion-graphics/agents/director.md +2 -1
  161. package/skills/motion-graphics/catalog-map.md +25 -13
  162. package/skills/motion-graphics/categories/charts/module.md +2 -1
  163. package/skills/motion-graphics/categories/lower-thirds/module.md +4 -0
  164. package/skills/motion-graphics/categories/news/module.md +1 -1
  165. package/skills/motion-graphics/references/motion-vocabulary.md +4 -0
  166. package/skills/music-to-video/SKILL.md +8 -6
  167. package/skills/music-to-video/references/montage.md +3 -6
  168. package/skills/music-to-video/references/motion-primitives/3d-card-flip/index.html +10 -111
  169. package/skills/music-to-video/references/motion-primitives/3d-card-flip/scene.html +119 -0
  170. package/skills/music-to-video/references/motion-primitives/bg-flow-field/index.html +11 -284
  171. package/skills/music-to-video/references/motion-primitives/bg-flow-field/scene.html +294 -0
  172. package/skills/music-to-video/references/motion-primitives/binary-decrypt/index.html +10 -76
  173. package/skills/music-to-video/references/motion-primitives/binary-decrypt/scene.html +85 -0
  174. package/skills/music-to-video/references/motion-primitives/blur-resolve/index.html +10 -52
  175. package/skills/music-to-video/references/motion-primitives/blur-resolve/scene.html +60 -0
  176. package/skills/music-to-video/references/motion-primitives/braam-punch/index.html +10 -92
  177. package/skills/music-to-video/references/motion-primitives/braam-punch/scene.html +100 -0
  178. package/skills/music-to-video/references/motion-primitives/chromatic-split/index.html +10 -103
  179. package/skills/music-to-video/references/motion-primitives/chromatic-split/scene.html +111 -0
  180. package/skills/music-to-video/references/motion-primitives/chrome-sweep/index.html +10 -60
  181. package/skills/music-to-video/references/motion-primitives/chrome-sweep/scene.html +68 -0
  182. package/skills/music-to-video/references/motion-primitives/counting-punch/index.html +10 -110
  183. package/skills/music-to-video/references/motion-primitives/counting-punch/scene.html +119 -0
  184. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/index.html +10 -112
  185. package/skills/music-to-video/references/motion-primitives/crash-zoom-in/scene.html +120 -0
  186. package/skills/music-to-video/references/motion-primitives/datamosh-smear/index.html +10 -121
  187. package/skills/music-to-video/references/motion-primitives/datamosh-smear/scene.html +131 -0
  188. package/skills/music-to-video/references/motion-primitives/directional-fill/index.html +10 -90
  189. package/skills/music-to-video/references/motion-primitives/directional-fill/scene.html +103 -0
  190. package/skills/music-to-video/references/motion-primitives/dolly-zoom/index.html +10 -94
  191. package/skills/music-to-video/references/motion-primitives/dolly-zoom/scene.html +102 -0
  192. package/skills/music-to-video/references/motion-primitives/electric-arc/index.html +10 -100
  193. package/skills/music-to-video/references/motion-primitives/electric-arc/scene.html +105 -0
  194. package/skills/music-to-video/references/motion-primitives/flash-cut/index.html +10 -89
  195. package/skills/music-to-video/references/motion-primitives/flash-cut/scene.html +97 -0
  196. package/skills/music-to-video/references/motion-primitives/gooey-metaball/index.html +10 -197
  197. package/skills/music-to-video/references/motion-primitives/gooey-metaball/scene.html +206 -0
  198. package/skills/music-to-video/references/motion-primitives/hard-cut/index.html +10 -74
  199. package/skills/music-to-video/references/motion-primitives/hard-cut/scene.html +82 -0
  200. package/skills/music-to-video/references/motion-primitives/hypercut-whip/index.html +10 -36
  201. package/skills/music-to-video/references/motion-primitives/hypercut-whip/scene.html +44 -0
  202. package/skills/music-to-video/references/motion-primitives/iris-open/index.html +10 -61
  203. package/skills/music-to-video/references/motion-primitives/iris-open/scene.html +69 -0
  204. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/index.html +10 -55
  205. package/skills/music-to-video/references/motion-primitives/kinetic-letter-in/scene.html +64 -0
  206. package/skills/music-to-video/references/motion-primitives/liquid-morph/index.html +10 -88
  207. package/skills/music-to-video/references/motion-primitives/liquid-morph/scene.html +96 -0
  208. package/skills/music-to-video/references/motion-primitives/mask-reveal/index.html +10 -66
  209. package/skills/music-to-video/references/motion-primitives/mask-reveal/scene.html +74 -0
  210. package/skills/music-to-video/references/motion-primitives/mosaic-pack/index.html +10 -81
  211. package/skills/music-to-video/references/motion-primitives/mosaic-pack/scene.html +89 -0
  212. package/skills/music-to-video/references/motion-primitives/neon-flicker/index.html +10 -65
  213. package/skills/music-to-video/references/motion-primitives/neon-flicker/scene.html +73 -0
  214. package/skills/music-to-video/references/motion-primitives/outline-to-fill/index.html +10 -74
  215. package/skills/music-to-video/references/motion-primitives/outline-to-fill/scene.html +82 -0
  216. package/skills/music-to-video/references/motion-primitives/palette-flip/index.html +10 -89
  217. package/skills/music-to-video/references/motion-primitives/palette-flip/scene.html +97 -0
  218. package/skills/music-to-video/references/motion-primitives/particle-burst/index.html +10 -98
  219. package/skills/music-to-video/references/motion-primitives/particle-burst/scene.html +106 -0
  220. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/index.html +10 -78
  221. package/skills/music-to-video/references/motion-primitives/pixel-dissolve/scene.html +86 -0
  222. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/index.html +10 -117
  223. package/skills/music-to-video/references/motion-primitives/radial-burst-lines/scene.html +130 -0
  224. package/skills/music-to-video/references/motion-primitives/screen-shake/index.html +10 -70
  225. package/skills/music-to-video/references/motion-primitives/screen-shake/scene.html +78 -0
  226. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/index.html +10 -87
  227. package/skills/music-to-video/references/motion-primitives/slot-machine-reveal/scene.html +100 -0
  228. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/index.html +10 -94
  229. package/skills/music-to-video/references/motion-primitives/spotlight-sweep/scene.html +102 -0
  230. package/skills/music-to-video/references/motion-primitives/staggered-exit/index.html +10 -74
  231. package/skills/music-to-video/references/motion-primitives/staggered-exit/scene.html +82 -0
  232. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/index.html +11 -271
  233. package/skills/music-to-video/references/motion-primitives/text-spectral-rays/scene.html +280 -0
  234. package/skills/music-to-video/references/motion-primitives/text-wave-distort/index.html +10 -92
  235. package/skills/music-to-video/references/motion-primitives/text-wave-distort/scene.html +106 -0
  236. package/skills/music-to-video/references/motion-primitives/tile-mosaic/index.html +10 -101
  237. package/skills/music-to-video/references/motion-primitives/tile-mosaic/scene.html +109 -0
  238. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/index.html +10 -79
  239. package/skills/music-to-video/references/motion-primitives/typewriter-reveal/scene.html +91 -0
  240. package/skills/music-to-video/references/motion-primitives/word-grid-burst/index.html +10 -83
  241. package/skills/music-to-video/references/motion-primitives/word-grid-burst/scene.html +96 -0
  242. package/skills/music-to-video/references/planning.md +2 -2
  243. package/skills/music-to-video/scripts/lib/storyboard.mjs +1 -1
  244. package/skills/music-to-video/sub-agents/frame-worker.md +2 -2
  245. package/skills/pr-to-video/SKILL.md +20 -16
  246. package/skills/pr-to-video/references/code-vocabulary.md +14 -12
  247. package/skills/pr-to-video/references/story-design.md +1 -1
  248. package/skills/pr-to-video/scripts/assemble-index.mjs +6 -4
  249. package/skills/pr-to-video/scripts/assemble-index.test.mjs +57 -2
  250. package/skills/pr-to-video/scripts/audio.mjs +2 -0
  251. package/skills/pr-to-video/scripts/build-frame.mjs +6 -3
  252. package/skills/pr-to-video/scripts/captions.mjs +7 -4
  253. package/skills/pr-to-video/scripts/captions.test.mjs +41 -0
  254. package/skills/pr-to-video/scripts/frame-packets.mjs +4 -4
  255. package/skills/pr-to-video/scripts/lib/bgm-volume.mjs +4 -0
  256. package/skills/pr-to-video/scripts/lib/captured-fonts.mjs +58 -0
  257. package/skills/pr-to-video/scripts/lib/frame-packets-core.mjs +194 -0
  258. package/skills/pr-to-video/scripts/lib/storyboard.mjs +1 -1
  259. package/skills/pr-to-video/scripts/lib/transitions.json +3 -3
  260. package/skills/pr-to-video/scripts/transitions.mjs +7 -5
  261. package/skills/pr-to-video/sub-agents/frame-worker.md +1 -1
  262. package/skills/product-launch-video/SKILL.md +20 -16
  263. package/skills/product-launch-video/references/story-design.md +1 -1
  264. package/skills/product-launch-video/scripts/assemble-index.mjs +6 -4
  265. package/skills/product-launch-video/scripts/assemble-index.test.mjs +57 -2
  266. package/skills/product-launch-video/scripts/audio.mjs +2 -0
  267. package/skills/product-launch-video/scripts/audio.test.mjs +16 -0
  268. package/skills/product-launch-video/scripts/build-frame-subsets.test.mjs +152 -0
  269. package/skills/product-launch-video/scripts/build-frame.mjs +6 -3
  270. package/skills/product-launch-video/scripts/captions.mjs +7 -4
  271. package/skills/product-launch-video/scripts/frame-packets.mjs +4 -4
  272. package/skills/product-launch-video/scripts/lib/bgm-volume.mjs +4 -0
  273. package/skills/product-launch-video/scripts/lib/captured-fonts.mjs +58 -0
  274. package/skills/product-launch-video/scripts/lib/frame-packets-core.mjs +194 -0
  275. package/skills/product-launch-video/scripts/lib/storyboard.mjs +1 -1
  276. package/skills/product-launch-video/scripts/lib/transitions.json +3 -3
  277. package/skills/product-launch-video/scripts/transitions.mjs +7 -5
  278. package/skills/product-launch-video/scripts/transitions.test.mjs +68 -0
  279. package/skills/product-launch-video/sub-agents/frame-worker.md +1 -1
  280. package/skills/remotion-to-hyperframes/SKILL.md +7 -4
  281. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json +5 -5
  282. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/compositions/title.html +31 -0
  283. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html +10 -25
  284. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md +8 -8
  285. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/expected.json +9 -9
  286. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-1.html +35 -0
  287. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-2.html +35 -0
  288. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/compositions/scene-3.html +36 -0
  289. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/hf-src/index.html +28 -71
  290. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/expected.json +3 -3
  291. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-1.html +52 -0
  292. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-2.html +99 -0
  293. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/compositions/scene-3.html +55 -0
  294. package/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven/hf-src/index.html +13 -212
  295. package/skills/remotion-to-hyperframes/references/api-map.md +16 -16
  296. package/skills/remotion-to-hyperframes/references/limitations.md +7 -7
  297. package/skills/remotion-to-hyperframes/references/lottie.md +5 -7
  298. package/skills/remotion-to-hyperframes/references/media.md +3 -3
  299. package/skills/remotion-to-hyperframes/references/parameters.md +5 -0
  300. package/skills/remotion-to-hyperframes/references/sequencing.md +71 -18
  301. package/skills/slideshow/SKILL.md +4 -1
  302. package/skills/slideshow/references/standalone-harness.md +0 -3
  303. package/skills/talking-head-recut/SKILL.md +10 -1
  304. package/skills/hyperframes-core/references/review-loop.md +0 -41
  305. package/skills/media-use/scripts/lib/adopt.mjs +0 -128
  306. package/skills/media-use/scripts/lib/adopt.test.mjs +0 -95
  307. package/skills/media-use/scripts/lib/bgm-provider.mjs +0 -20
  308. package/skills/media-use/scripts/lib/brand-provider.mjs +0 -59
  309. package/skills/media-use/scripts/lib/bundled-sfx-provider.mjs +0 -108
  310. package/skills/media-use/scripts/lib/bundled-sfx-provider.test.mjs +0 -85
  311. package/skills/media-use/scripts/lib/cache.mjs +0 -150
  312. package/skills/media-use/scripts/lib/candidates.mjs +0 -91
  313. package/skills/media-use/scripts/lib/candidates.test.mjs +0 -155
  314. package/skills/media-use/scripts/lib/codex-provider.mjs +0 -161
  315. package/skills/media-use/scripts/lib/codex-provider.test.mjs +0 -34
  316. package/skills/media-use/scripts/lib/coverage.test.mjs +0 -117
  317. package/skills/media-use/scripts/lib/cube-build.mjs +0 -181
  318. package/skills/media-use/scripts/lib/cube-build.test.mjs +0 -80
  319. package/skills/media-use/scripts/lib/cube-validate.mjs +0 -198
  320. package/skills/media-use/scripts/lib/cube-validate.test.mjs +0 -125
  321. package/skills/media-use/scripts/lib/cutlist.test.mjs +0 -148
  322. package/skills/media-use/scripts/lib/duck.test.mjs +0 -118
  323. package/skills/media-use/scripts/lib/error-diffusion.test.mjs +0 -102
  324. package/skills/media-use/scripts/lib/freeze.mjs +0 -70
  325. package/skills/media-use/scripts/lib/freeze.test.mjs +0 -46
  326. package/skills/media-use/scripts/lib/grade-analyzer.mjs +0 -264
  327. package/skills/media-use/scripts/lib/grade-analyzer.test.mjs +0 -285
  328. package/skills/media-use/scripts/lib/heygen-cli.mjs +0 -191
  329. package/skills/media-use/scripts/lib/heygen-cli.test.mjs +0 -301
  330. package/skills/media-use/scripts/lib/heygen-search.mjs +0 -45
  331. package/skills/media-use/scripts/lib/heygen-search.test.mjs +0 -49
  332. package/skills/media-use/scripts/lib/heygen-video-provider.mjs +0 -127
  333. package/skills/media-use/scripts/lib/heygen-video-provider.test.mjs +0 -344
  334. package/skills/media-use/scripts/lib/image-provider.mjs +0 -44
  335. package/skills/media-use/scripts/lib/local-models.mjs +0 -318
  336. package/skills/media-use/scripts/lib/local-models.test.mjs +0 -220
  337. package/skills/media-use/scripts/lib/local-run.mjs +0 -78
  338. package/skills/media-use/scripts/lib/local-run.test.mjs +0 -121
  339. package/skills/media-use/scripts/lib/logo-provider.mjs +0 -222
  340. package/skills/media-use/scripts/lib/logo-provider.test.mjs +0 -137
  341. package/skills/media-use/scripts/lib/ltx-video-provider.mjs +0 -94
  342. package/skills/media-use/scripts/lib/ltx-video-provider.test.mjs +0 -316
  343. package/skills/media-use/scripts/lib/lut-preset-provider.mjs +0 -266
  344. package/skills/media-use/scripts/lib/lut-preset-provider.test.mjs +0 -176
  345. package/skills/media-use/scripts/lib/manifest.test.mjs +0 -388
  346. package/skills/media-use/scripts/lib/match.mjs +0 -47
  347. package/skills/media-use/scripts/lib/mflux-provider.mjs +0 -128
  348. package/skills/media-use/scripts/lib/mflux-provider.test.mjs +0 -174
  349. package/skills/media-use/scripts/lib/misses.mjs +0 -49
  350. package/skills/media-use/scripts/lib/misses.test.mjs +0 -78
  351. package/skills/media-use/scripts/lib/npx-sync.test.mjs +0 -50
  352. package/skills/media-use/scripts/lib/parakeet-words.test.mjs +0 -44
  353. package/skills/media-use/scripts/lib/prefs-store.test.mjs +0 -189
  354. package/skills/media-use/scripts/lib/probe.mjs +0 -39
  355. package/skills/media-use/scripts/lib/probe.test.mjs +0 -31
  356. package/skills/media-use/scripts/lib/providers.mjs +0 -5
  357. package/skills/media-use/scripts/lib/recipe-store.test.mjs +0 -367
  358. package/skills/media-use/scripts/lib/registry.mjs +0 -218
  359. package/skills/media-use/scripts/lib/registry.test.mjs +0 -261
  360. package/skills/media-use/scripts/lib/search.mjs +0 -24
  361. package/skills/media-use/scripts/lib/search.test.mjs +0 -32
  362. package/skills/media-use/scripts/lib/sfx-provider.mjs +0 -23
  363. package/skills/media-use/scripts/lib/specs.mjs +0 -132
  364. package/skills/media-use/scripts/lib/specs.test.mjs +0 -157
  365. package/skills/media-use/scripts/lib/stats.mjs +0 -117
  366. package/skills/media-use/scripts/lib/stats.test.mjs +0 -165
  367. package/skills/media-use/scripts/lib/telemetry.test.mjs +0 -373
  368. package/skills/media-use/scripts/lib/tts-local-provider.mjs +0 -92
  369. package/skills/media-use/scripts/lib/tts-local-provider.test.mjs +0 -72
  370. package/skills/media-use/scripts/lib/usage.mjs +0 -57
  371. package/skills/media-use/scripts/lib/usage.test.mjs +0 -54
  372. package/skills/media-use/scripts/lib/voice-provider.mjs +0 -57
  373. package/skills/media-use/scripts/lib/voice-provider.test.mjs +0 -46
  374. package/skills/media-use/scripts/resolve.test.mjs +0 -1120
  375. /package/skills/{hyperframes-core → hyperframes}/references/subagent-dispatch.md +0 -0
@@ -1,189 +0,0 @@
1
- import { strict as assert } from "node:assert";
2
- import { test } from "node:test";
3
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import {
7
- mergedPreferences,
8
- preferenceKeyFor,
9
- projectPrefsPath,
10
- recordPreference,
11
- userPrefsPath,
12
- } from "./prefs-store.mjs";
13
-
14
- function sandbox() {
15
- const root = mkdtempSync(join(tmpdir(), "mu-prefs-"));
16
- const home = join(root, "home");
17
- const projectA = join(root, "proj-a");
18
- const projectB = join(root, "proj-b");
19
- mkdirSync(home, { recursive: true });
20
- mkdirSync(projectA, { recursive: true });
21
- mkdirSync(projectB, { recursive: true });
22
- process.env.HOME = home;
23
- return { root, home, projectA, projectB };
24
- }
25
-
26
- function restoreEnv(saved) {
27
- for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
28
- Object.assign(process.env, saved);
29
- }
30
-
31
- function withSandbox(fn) {
32
- const savedEnv = { ...process.env };
33
- const box = sandbox();
34
- try {
35
- fn(box);
36
- } finally {
37
- restoreEnv(savedEnv);
38
- rmSync(box.root, { recursive: true, force: true });
39
- }
40
- }
41
-
42
- test("record writes the project tier and get merges it as source=project", () => {
43
- withSandbox(({ projectA }) => {
44
- const result = recordPreference({ projectDir: projectA, key: "destination", value: "x-feed" });
45
- assert.equal(result.promoted, false);
46
- assert.deepEqual(result.confirmed_in, ["proj-a"]);
47
-
48
- const merged = mergedPreferences(projectA);
49
- assert.equal(merged.destination.value, "x-feed");
50
- assert.equal(merged.destination.source, "project");
51
-
52
- const onDisk = JSON.parse(readFileSync(projectPrefsPath(projectA), "utf8"));
53
- assert.equal(onDisk.preferences.destination.value, "x-feed");
54
- });
55
- });
56
-
57
- test("one project never promotes: a fresh project sees nothing user-tier", () => {
58
- withSandbox(({ projectA, projectB }) => {
59
- recordPreference({ projectDir: projectA, key: "destination", value: "x-feed" });
60
- // proj-b has no project entry, and the value was only confirmed once.
61
- assert.equal(mergedPreferences(projectB).destination, undefined);
62
- });
63
- });
64
-
65
- test("the same value confirmed in two projects promotes to the user tier", () => {
66
- withSandbox(({ projectA, projectB }) => {
67
- recordPreference({ projectDir: projectA, key: "destination", value: "x-feed" });
68
- const second = recordPreference({ projectDir: projectB, key: "destination", value: "x-feed" });
69
- assert.equal(second.promoted, true);
70
-
71
- const userFile = JSON.parse(readFileSync(userPrefsPath(), "utf8"));
72
- assert.deepEqual(userFile.preferences.destination.confirmed_in.sort(), ["proj-a", "proj-b"]);
73
-
74
- // A third project with no local entry now inherits the promoted default.
75
- const projectC = join(projectA, "..", "proj-c");
76
- mkdirSync(projectC, { recursive: true });
77
- const merged = mergedPreferences(projectC);
78
- assert.equal(merged.destination.value, "x-feed");
79
- assert.equal(merged.destination.source, "user");
80
- });
81
- });
82
-
83
- test("record is idempotent per project — confirmed_in stays deduped", () => {
84
- withSandbox(({ projectA }) => {
85
- recordPreference({ projectDir: projectA, key: "language", value: "zh" });
86
- const again = recordPreference({ projectDir: projectA, key: "language", value: "zh" });
87
- assert.deepEqual(again.confirmed_in, ["proj-a"]);
88
- assert.equal(again.promoted, false);
89
- });
90
- });
91
-
92
- test("a changed value restarts provenance instead of inheriting confirmations", () => {
93
- withSandbox(({ projectA, projectB }) => {
94
- recordPreference({ projectDir: projectA, key: "destination", value: "x-feed" });
95
- recordPreference({ projectDir: projectB, key: "destination", value: "x-feed" });
96
- const switched = recordPreference({
97
- projectDir: projectA,
98
- key: "destination",
99
- value: "youtube",
100
- });
101
- assert.deepEqual(switched.confirmed_in, ["proj-a"]);
102
- assert.equal(switched.promoted, false);
103
- // The previously promoted value keeps serving other projects until youtube
104
- // earns its own two confirmations.
105
- const userFile = JSON.parse(readFileSync(userPrefsPath(), "utf8"));
106
- assert.equal(userFile.preferences.destination.value, "x-feed");
107
- });
108
- });
109
-
110
- test("project tier overrides a promoted user-tier value", () => {
111
- withSandbox(({ projectA, projectB }) => {
112
- recordPreference({ projectDir: projectA, key: "storyboard", value: "yes" });
113
- recordPreference({ projectDir: projectB, key: "storyboard", value: "yes" });
114
- recordPreference({ projectDir: projectA, key: "storyboard", value: "no" });
115
- const merged = mergedPreferences(projectA);
116
- assert.equal(merged.storyboard.value, "no");
117
- assert.equal(merged.storyboard.source, "project");
118
- });
119
- });
120
-
121
- test("the retired mode key is rejected", () => {
122
- withSandbox(({ projectA }) => {
123
- assert.throws(
124
- () => recordPreference({ projectDir: projectA, key: "mode", value: "collaborative" }),
125
- /unknown preference key/,
126
- );
127
- });
128
- });
129
-
130
- test("the run-shape keys record and promote like any field", () => {
131
- withSandbox(({ projectA, projectB }) => {
132
- recordPreference({ projectDir: projectA, key: "flow", value: "automation" });
133
- const promoted = recordPreference({ projectDir: projectB, key: "flow", value: "automation" });
134
- assert.equal(promoted.promoted, true);
135
- recordPreference({ projectDir: projectA, key: "storyboard", value: "yes" });
136
- const merged = mergedPreferences(projectA);
137
- assert.equal(merged.flow.value, "automation");
138
- assert.equal(merged.storyboard.value, "yes");
139
- assert.equal(merged.storyboard.source, "project");
140
- });
141
- });
142
-
143
- test("style_preset is keyed per workflow", () => {
144
- withSandbox(({ projectA }) => {
145
- assert.equal(
146
- preferenceKeyFor("style_preset", "faceless-explainer"),
147
- "style_preset.faceless-explainer",
148
- );
149
- assert.equal(preferenceKeyFor("destination", "faceless-explainer"), "destination");
150
- recordPreference({
151
- projectDir: projectA,
152
- key: "style_preset",
153
- value: "pin-and-paper",
154
- workflow: "faceless-explainer",
155
- });
156
- const merged = mergedPreferences(projectA);
157
- assert.equal(merged["style_preset.faceless-explainer"].value, "pin-and-paper");
158
- assert.equal(merged["style_preset"], undefined);
159
- });
160
- });
161
-
162
- test("style_preset without a workflow is rejected, never stored bare", () => {
163
- withSandbox(({ projectA }) => {
164
- assert.throws(
165
- () => recordPreference({ projectDir: projectA, key: "style_preset", value: "pin-and-paper" }),
166
- /pass --workflow/,
167
- );
168
- assert.deepEqual(mergedPreferences(projectA), {});
169
- });
170
- });
171
-
172
- test("unknown keys and empty values are rejected", () => {
173
- withSandbox(({ projectA }) => {
174
- assert.throws(() => recordPreference({ projectDir: projectA, key: "vibe", value: "x" }));
175
- assert.throws(() => recordPreference({ projectDir: projectA, key: "voice", value: " " }));
176
- });
177
- });
178
-
179
- test("corrupt files are treated as empty instead of crashing", () => {
180
- withSandbox(({ projectA, home }) => {
181
- mkdirSync(join(projectA, ".media"), { recursive: true });
182
- writeFileSync(projectPrefsPath(projectA), "not json");
183
- mkdirSync(join(home, ".media"), { recursive: true });
184
- writeFileSync(userPrefsPath(), '{"preferences": 42}');
185
- assert.deepEqual(mergedPreferences(projectA), {});
186
- const result = recordPreference({ projectDir: projectA, key: "language", value: "zh" });
187
- assert.equal(result.value, "zh");
188
- });
189
- });
@@ -1,39 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { extname } from "node:path";
3
-
4
- const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico"]);
5
-
6
- export function probe(filePath) {
7
- const ext = extname(filePath).toLowerCase();
8
- if (ext === ".svg") return { width: null, height: null, duration: null, codec: "svg" };
9
-
10
- try {
11
- // execFileSync (no shell) so a hostile filename like `"; rm -rf ~; ".png`
12
- // can't break out of the quoting — filePath is passed as a literal argv entry.
13
- const raw = execFileSync(
14
- "ffprobe",
15
- ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "--", filePath],
16
- { encoding: "utf8", timeout: 5000 },
17
- );
18
- const info = JSON.parse(raw);
19
- const stream = info.streams?.[0];
20
- const format = info.format;
21
-
22
- const isImage = IMAGE_EXT.has(ext);
23
- const duration = isImage
24
- ? null
25
- : parseFloat(format?.duration) || parseFloat(stream?.duration) || null;
26
- const width = parseInt(stream?.width, 10) || null;
27
- const height = parseInt(stream?.height, 10) || null;
28
- const codec = stream?.codec_name || null;
29
-
30
- return {
31
- duration: duration != null ? Math.round(duration * 10) / 10 : null,
32
- width,
33
- height,
34
- codec,
35
- };
36
- } catch {
37
- return { duration: null, width: null, height: null, codec: null };
38
- }
39
- }
@@ -1,31 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { mkdtempSync, writeFileSync, existsSync, rmSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { probe } from "./probe.mjs";
7
-
8
- // Regression for the shell-injection fix: probe() must pass the path as a literal
9
- // argv entry, never through a shell. A filename containing shell metacharacters
10
- // must NOT execute. Under the old execSync(`ffprobe ... "${path}"`) the embedded
11
- // `touch` ran and created the marker; under execFileSync it cannot, regardless of
12
- // whether ffprobe is installed (the injected command never reaches a shell).
13
- test("probe does not execute shell metacharacters in a filename", () => {
14
- const dir = mkdtempSync(join(tmpdir(), "probe-inject-"));
15
- const marker = join(dir, "INJECTED");
16
- // Slash-free basename (a real on-disk filename) that breaks out of the old
17
- // double-quoted interpolation and would `touch INJECTED` in the cwd.
18
- const evil = join(dir, `clip"; touch INJECTED; echo ".mp4`);
19
- const prevCwd = process.cwd();
20
- try {
21
- writeFileSync(evil, "not real media");
22
- process.chdir(dir); // so a leaked `touch INJECTED` would land next to `marker`
23
- const meta = probe(evil);
24
- assert.equal(existsSync(marker), false, "injected `touch` must not have run");
25
- // Bogus/unreadable media still returns the null-shaped result, never throws.
26
- assert.deepEqual(Object.keys(meta).sort(), ["codec", "duration", "height", "width"]);
27
- } finally {
28
- process.chdir(prevCwd);
29
- rmSync(dir, { recursive: true, force: true });
30
- }
31
- });
@@ -1,5 +0,0 @@
1
- // Back-compat surface for the v1 provider API. The ordered, capability-based
2
- // registry now lives in registry.mjs; this re-exports the v1 helpers so existing
3
- // callers keep working. New code should import from registry.mjs directly
4
- // (getProviders / runCapability).
5
- export { getProvider, listTypes } from "./registry.mjs";
@@ -1,367 +0,0 @@
1
- import { strict as assert } from "node:assert";
2
- import { test } from "node:test";
3
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import {
7
- freezeRecipe,
8
- listRecipes,
9
- skeletonizeBrief,
10
- skeletonizeStoryboard,
11
- slugifyRecipeName,
12
- useRecipe,
13
- } from "./recipe-store.mjs";
14
- import { recordPreference } from "./prefs-store.mjs";
15
-
16
- const STORYBOARD = `---
17
- format: 1080x1080
18
- message: "Ship a launch video in an afternoon"
19
- arc: Hook → Problem → Solution
20
- audience: indie devs on X
21
- mode: collaborative
22
- music: upbeat tech
23
- ---
24
-
25
- ## Frame 1 — Hook
26
-
27
- - scene: Big type punches in
28
- - duration: 3s
29
- - transition_in: cut
30
- - status: animated
31
- - voiceover: "Ship a launch video in an afternoon."
32
- - src: compositions/frames/01-hook.html
33
-
34
- Open cold on the promise. This is the thesis.
35
-
36
- ## Frame 2 — Proof
37
-
38
- - duration: 4s
39
- - transition_in: crossfade
40
- - status: animated
41
- - asset_candidates: dashboard.png
42
- - src: compositions/frames/02-proof.html
43
-
44
- Real dashboard, real numbers.
45
- Second prose line to collapse.
46
-
47
- ## Video direction
48
-
49
- Punchy, one accent color, hard cuts on the beat.
50
- `;
51
-
52
- const BRIEF = `---
53
- workflow: product-launch-video
54
- flow: automation
55
- storyboard: yes
56
- message: "Ship a launch video in an afternoon"
57
- audience: indie devs on X
58
- destination: x-feed
59
- aspect: 1080x1080
60
- language: en
61
- length: 60s
62
- angle: feature-reveal
63
- ---
64
-
65
- ## Intent
66
-
67
- Sell the afternoon-launch promise to indie devs.
68
-
69
- ## Assets
70
-
71
- - public/dashboard.png — the real dashboard, proof beat.
72
-
73
- ## Notes
74
-
75
- - No stock-photo aesthetics.
76
- `;
77
-
78
- function sandbox() {
79
- const root = mkdtempSync(join(tmpdir(), "mu-recipes-"));
80
- const home = join(root, "home");
81
- const project = join(root, "my-launch");
82
- mkdirSync(home, { recursive: true });
83
- mkdirSync(project, { recursive: true });
84
- writeFileSync(join(project, "frame.md"), "# Frame spec\nbackground: #101014\n");
85
- writeFileSync(join(project, "STORYBOARD.md"), STORYBOARD);
86
- process.env.HOME = home;
87
- return { root, home, project };
88
- }
89
-
90
- function restoreEnv(saved) {
91
- for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
92
- Object.assign(process.env, saved);
93
- }
94
-
95
- function withSandbox(fn) {
96
- const savedEnv = { ...process.env };
97
- const box = sandbox();
98
- try {
99
- fn(box);
100
- } finally {
101
- restoreEnv(savedEnv);
102
- rmSync(box.root, { recursive: true, force: true });
103
- }
104
- }
105
-
106
- test("slugifyRecipeName normalizes and rejects empty", () => {
107
- assert.equal(slugifyRecipeName("Weekly Changelog!"), "weekly-changelog");
108
- assert.equal(slugifyRecipeName(" pr_reveal "), "pr-reveal");
109
- assert.throws(() => slugifyRecipeName("!!!"));
110
- });
111
-
112
- test("skeletonize keeps structure, resets status, blanks content", () => {
113
- const skeleton = skeletonizeStoryboard(STORYBOARD);
114
- // Structure kept.
115
- assert.match(skeleton, /format: 1080x1080/);
116
- assert.match(skeleton, /arc: Hook → Problem → Solution/);
117
- assert.match(skeleton, /music: upbeat tech/);
118
- assert.match(skeleton, /- duration: 3s/);
119
- assert.match(skeleton, /- src: compositions\/frames\/01-hook\.html/);
120
- assert.match(skeleton, /## Video direction/);
121
- assert.match(skeleton, /hard cuts on the beat/);
122
- // Statuses reset.
123
- assert.equal((skeleton.match(/- status: outline/g) ?? []).length, 2);
124
- assert.doesNotMatch(skeleton, /animated/);
125
- // Content blanked.
126
- assert.doesNotMatch(skeleton, /message:/);
127
- assert.doesNotMatch(skeleton, /audience:/);
128
- assert.doesNotMatch(skeleton, /mode:/);
129
- assert.doesNotMatch(skeleton, /voiceover/);
130
- assert.doesNotMatch(skeleton, /asset_candidates/);
131
- assert.doesNotMatch(skeleton, /thesis/);
132
- assert.doesNotMatch(skeleton, /Real dashboard/);
133
- assert.match(skeleton, /<fill in: this video's content for the "Hook" beat/);
134
- assert.match(skeleton, /<fill in: this video's content for the "Proof" beat/);
135
- });
136
-
137
- test("skeletonizeBrief keeps reusable keys, drops run-shape, blanks body sections", () => {
138
- const skeleton = skeletonizeBrief(BRIEF);
139
- // Reusable frontmatter kept.
140
- assert.match(skeleton, /workflow: product-launch-video/);
141
- assert.match(skeleton, /destination: x-feed/);
142
- assert.match(skeleton, /aspect: 1080x1080/);
143
- assert.match(skeleton, /length: 60s/);
144
- assert.match(skeleton, /angle: feature-reveal/);
145
- // Run-shape and content keys dropped.
146
- assert.doesNotMatch(skeleton, /^flow:/m);
147
- assert.doesNotMatch(skeleton, /^storyboard:/m);
148
- assert.doesNotMatch(skeleton, /^message:/m);
149
- assert.doesNotMatch(skeleton, /^audience:/m);
150
- // Body sections kept as headings, prose blanked to placeholders.
151
- assert.match(skeleton, /## Intent/);
152
- assert.match(skeleton, /## Assets/);
153
- assert.match(skeleton, /<fill in: this video's intent/);
154
- assert.match(skeleton, /<fill in: this video's assets/);
155
- assert.doesNotMatch(skeleton, /afternoon-launch promise/);
156
- assert.doesNotMatch(skeleton, /dashboard\.png/);
157
- });
158
-
159
- test("freeze writes the folder, manifest record, and user-tier copy", () => {
160
- withSandbox(({ project, home }) => {
161
- recordPreference({ projectDir: project, key: "destination", value: "x-feed" });
162
- recordPreference({
163
- projectDir: project,
164
- key: "style_preset",
165
- value: "pin-and-paper",
166
- workflow: "product-launch-video",
167
- });
168
- const frozen = freezeRecipe({
169
- projectDir: project,
170
- name: "Weekly Launch",
171
- workflow: "product-launch-video",
172
- blocks: ["data-chart"],
173
- });
174
- assert.equal(frozen.slug, "weekly-launch");
175
- assert.equal(frozen.version, 1);
176
-
177
- const dir = join(project, ".media/recipes/weekly-launch");
178
- const recipe = JSON.parse(readFileSync(join(dir, "recipe.json"), "utf8"));
179
- assert.equal(recipe.workflow, "product-launch-video");
180
- assert.equal(recipe.destination, "x-feed");
181
- assert.equal(recipe.style_preset, "pin-and-paper");
182
- assert.deepEqual(recipe.blocks, ["data-chart"]);
183
- assert.ok(existsSync(join(dir, "frame.md")));
184
- assert.match(readFileSync(join(dir, "storyboard-skeleton.md"), "utf8"), /- status: outline/);
185
-
186
- const manifest = readFileSync(join(project, ".media/manifest.jsonl"), "utf8");
187
- assert.match(manifest, /"type":"recipe"/);
188
- assert.match(manifest, /"entity":"weekly-launch"/);
189
-
190
- assert.ok(existsSync(join(home, ".media/recipes/weekly-launch/recipe.json")));
191
- });
192
- });
193
-
194
- test("freeze with a BRIEF.md carries the brief skeleton; use hands its path back", () => {
195
- withSandbox(({ project, root }) => {
196
- writeFileSync(join(project, "BRIEF.md"), BRIEF);
197
- const frozen = freezeRecipe({
198
- projectDir: project,
199
- name: "promo",
200
- workflow: "product-launch-video",
201
- });
202
- assert.equal(frozen.briefSkeleton, true);
203
- const skeleton = readFileSync(join(project, ".media/recipes/promo/brief-skeleton.md"), "utf8");
204
- assert.match(skeleton, /destination: x-feed/);
205
- assert.doesNotMatch(skeleton, /^flow:/m);
206
-
207
- const fresh = join(root, "fresh-project");
208
- mkdirSync(fresh, { recursive: true });
209
- const used = useRecipe({ projectDir: fresh, name: "promo" });
210
- assert.equal(used.briefSkeletonPath, ".media/recipes/promo/brief-skeleton.md");
211
- assert.ok(existsSync(join(fresh, ".media/recipes/promo/brief-skeleton.md")));
212
- });
213
- });
214
-
215
- test("freeze without a BRIEF.md degrades: no skeleton, use returns no path", () => {
216
- withSandbox(({ project, root }) => {
217
- const frozen = freezeRecipe({
218
- projectDir: project,
219
- name: "promo",
220
- workflow: "product-launch-video",
221
- });
222
- assert.equal(frozen.briefSkeleton, false);
223
- assert.ok(!existsSync(join(project, ".media/recipes/promo/brief-skeleton.md")));
224
-
225
- const fresh = join(root, "fresh-project");
226
- mkdirSync(fresh, { recursive: true });
227
- const used = useRecipe({ projectDir: fresh, name: "promo" });
228
- assert.equal(used.briefSkeletonPath, undefined);
229
- });
230
- });
231
-
232
- test("freeze takes the workflow from BRIEF.md over a contradicting flag", () => {
233
- withSandbox(({ project }) => {
234
- writeFileSync(
235
- join(project, "BRIEF.md"),
236
- BRIEF.replace("workflow: product-launch-video", "workflow: general-video"),
237
- );
238
- const frozen = freezeRecipe({
239
- projectDir: project,
240
- name: "promo",
241
- workflow: "faceless-explainer",
242
- });
243
- assert.equal(frozen.workflow, "general-video");
244
- assert.equal(frozen.workflowOverridden, true);
245
- const recipe = JSON.parse(
246
- readFileSync(join(project, ".media/recipes/promo/recipe.json"), "utf8"),
247
- );
248
- assert.equal(recipe.workflow, "general-video");
249
- });
250
- });
251
-
252
- test("freeze without BRIEF.md falls back to --workflow; with neither it refuses", () => {
253
- withSandbox(({ project }) => {
254
- const frozen = freezeRecipe({
255
- projectDir: project,
256
- name: "promo",
257
- workflow: "product-launch-video",
258
- });
259
- assert.equal(frozen.workflow, "product-launch-video");
260
- assert.equal(frozen.workflowOverridden, false);
261
- assert.throws(() => freezeRecipe({ projectDir: project, name: "other" }), /no workflow found/);
262
- });
263
- });
264
-
265
- test("freeze finds a legacy bare style_preset record; the scoped key wins over it", () => {
266
- withSandbox(({ project }) => {
267
- writeFileSync(join(project, "BRIEF.md"), BRIEF);
268
- // A record made before the store required workflow scoping.
269
- mkdirSync(join(project, ".media"), { recursive: true });
270
- writeFileSync(
271
- join(project, ".media/preferences.json"),
272
- JSON.stringify({
273
- version: 1,
274
- preferences: {
275
- style_preset: {
276
- value: "source-paper-flowchart",
277
- confirmed_in: ["my-launch"],
278
- updated_at: "2026-07-15T00:00:00.000Z",
279
- },
280
- },
281
- sightings: {},
282
- }),
283
- );
284
- freezeRecipe({ projectDir: project, name: "promo" });
285
- const legacy = JSON.parse(
286
- readFileSync(join(project, ".media/recipes/promo/recipe.json"), "utf8"),
287
- );
288
- assert.equal(legacy.style_preset, "source-paper-flowchart");
289
-
290
- recordPreference({
291
- projectDir: project,
292
- key: "style_preset",
293
- value: "pin-and-paper",
294
- workflow: "product-launch-video",
295
- });
296
- freezeRecipe({ projectDir: project, name: "promo" });
297
- const scoped = JSON.parse(
298
- readFileSync(join(project, ".media/recipes/promo/recipe.json"), "utf8"),
299
- );
300
- assert.equal(scoped.style_preset, "pin-and-paper");
301
- });
302
- });
303
-
304
- test("re-freezing bumps the version and archives the old folder", () => {
305
- withSandbox(({ project }) => {
306
- freezeRecipe({ projectDir: project, name: "promo", workflow: "product-launch-video" });
307
- const again = freezeRecipe({
308
- projectDir: project,
309
- name: "promo",
310
- workflow: "product-launch-video",
311
- });
312
- assert.equal(again.version, 2);
313
- assert.ok(existsSync(join(project, ".media/recipes/promo@v1/recipe.json")));
314
- const current = JSON.parse(
315
- readFileSync(join(project, ".media/recipes/promo/recipe.json"), "utf8"),
316
- );
317
- assert.equal(current.version, 2);
318
- });
319
- });
320
-
321
- test("list merges tiers (project wins), filters by workflow, skips archives", () => {
322
- withSandbox(({ project, root }) => {
323
- freezeRecipe({ projectDir: project, name: "promo", workflow: "product-launch-video" });
324
- freezeRecipe({ projectDir: project, name: "promo", workflow: "product-launch-video" });
325
- freezeRecipe({ projectDir: project, name: "explainer", workflow: "faceless-explainer" });
326
-
327
- const fresh = join(root, "fresh-project");
328
- mkdirSync(fresh, { recursive: true });
329
- const all = listRecipes({ projectDir: fresh });
330
- assert.deepEqual(all.map((r) => r.source).sort(), ["user", "user"]);
331
- assert.equal(all.find((r) => r.name === "promo").version, 2);
332
-
333
- const filtered = listRecipes({ projectDir: fresh, workflow: "faceless-explainer" });
334
- assert.deepEqual(
335
- filtered.map((r) => r.name),
336
- ["explainer"],
337
- );
338
- });
339
- });
340
-
341
- test("use imports from the user tier into a fresh project and copies frame.md", () => {
342
- withSandbox(({ project, root }) => {
343
- freezeRecipe({ projectDir: project, name: "promo", workflow: "product-launch-video" });
344
-
345
- const fresh = join(root, "fresh-project");
346
- mkdirSync(fresh, { recursive: true });
347
- const used = useRecipe({ projectDir: fresh, name: "Promo" });
348
- assert.equal(used.recipe.name, "promo");
349
- assert.equal(used.skeletonPath, ".media/recipes/promo/storyboard-skeleton.md");
350
- assert.ok(existsSync(join(fresh, "frame.md")));
351
- assert.ok(existsSync(join(fresh, ".media/recipes/promo/storyboard-skeleton.md")));
352
- assert.match(
353
- readFileSync(join(fresh, ".media/manifest.jsonl"), "utf8"),
354
- /"imported_from":"user-tier"/,
355
- );
356
- });
357
- });
358
-
359
- test("use with an unknown name lists what exists", () => {
360
- withSandbox(({ project }) => {
361
- freezeRecipe({ projectDir: project, name: "promo", workflow: "product-launch-video" });
362
- assert.throws(
363
- () => useRecipe({ projectDir: project, name: "nope" }),
364
- /no recipe named "nope" \(known: promo\)/,
365
- );
366
- });
367
- });