@revola-ai/video-craft 0.2.1

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 (340) hide show
  1. package/README.md +56 -0
  2. package/dist/anchors.js +69 -0
  3. package/dist/corpus.js +91 -0
  4. package/dist/projectDoc.js +83 -0
  5. package/dist/projectDocSync.js +39 -0
  6. package/dist/projectMeta.js +37 -0
  7. package/dist/provenance.js +41 -0
  8. package/dist/radar-check-ids.json +34 -0
  9. package/dist/registry.js +307 -0
  10. package/dist/rules/animatedBarChartTightBox.js +15 -0
  11. package/dist/rules/animatedBarChartWhenNotToUse.js +15 -0
  12. package/dist/rules/animatedBarChartWhenToUse.js +15 -0
  13. package/dist/rules/animatedLineChartWhenNotToUse.js +15 -0
  14. package/dist/rules/animatedLineChartWhenToUse.js +15 -0
  15. package/dist/rules/approvedSfxPaletteOnly.js +15 -0
  16. package/dist/rules/atmosphereGradientsFadeAtBothEnds.js +15 -0
  17. package/dist/rules/audioIsTheClock.js +16 -0
  18. package/dist/rules/beatsDeriveFromTheirOwnSequence.js +15 -0
  19. package/dist/rules/bestSceneSetsTheBar.js +15 -0
  20. package/dist/rules/bumpBeforeRefreeze.js +15 -0
  21. package/dist/rules/cameraIsTheNarrator.js +15 -0
  22. package/dist/rules/checkPinBeforeShip.js +16 -0
  23. package/dist/rules/coldViewerClarity.js +15 -0
  24. package/dist/rules/commitOrCutEffects.js +29 -0
  25. package/dist/rules/convertFramesAcrossClockBoundary.js +15 -0
  26. package/dist/rules/crossCutElementsLiveInTheHudLayer.js +15 -0
  27. package/dist/rules/cursorClicksRealControls.js +15 -0
  28. package/dist/rules/cutOnRealAudioPauses.js +15 -0
  29. package/dist/rules/darkButNeverDim.js +15 -0
  30. package/dist/rules/dataRadarTagsDeclared.js +193 -0
  31. package/dist/rules/declareAVerticalZoneMap.js +15 -0
  32. package/dist/rules/deleteRejectedRigsCompletely.js +15 -0
  33. package/dist/rules/deriveSeamsDontMeasure.js +17 -0
  34. package/dist/rules/deterministicSceneSource.js +65 -0
  35. package/dist/rules/diegeticSoundForOnscreenActions.js +15 -0
  36. package/dist/rules/distinctTransitionPerBeat.js +15 -0
  37. package/dist/rules/downloadOrDrawRichAssets.js +15 -0
  38. package/dist/rules/elementOnscreenForItsVoSentence.js +17 -0
  39. package/dist/rules/emitBuildNotes.js +15 -0
  40. package/dist/rules/energyFromTimingNotPartCount.js +15 -0
  41. package/dist/rules/evidenceBeforeClaimingFixed.js +15 -0
  42. package/dist/rules/exactPeerLockstep.js +15 -0
  43. package/dist/rules/firstPassIsADraft.js +15 -0
  44. package/dist/rules/fixCrowdingByRebandingNotNudging.js +15 -0
  45. package/dist/rules/fixesPreserveApprovedElements.js +15 -0
  46. package/dist/rules/floatingNotBoxed.js +15 -0
  47. package/dist/rules/focusBeatsRecedeTheContext.js +15 -0
  48. package/dist/rules/fontVarsBrandRemocn.js +15 -0
  49. package/dist/rules/geometricSeamsNotCrossfades.js +23 -0
  50. package/dist/rules/geometrySidecarExportsTyped.js +36 -0
  51. package/dist/rules/glowIsAFocalAccent.js +22 -0
  52. package/dist/rules/heroFillsTheFrame.js +17 -0
  53. package/dist/rules/htmlMasksForAnimatedCounters.js +15 -0
  54. package/dist/rules/impactStackOnPayoffs.js +15 -0
  55. package/dist/rules/interactiveTargetsAreDeclaredData.js +15 -0
  56. package/dist/rules/interpolateLiteralKeysMonotonic.js +79 -0
  57. package/dist/rules/latenessIsStructuralOverlapTheBeats.js +15 -0
  58. package/dist/rules/lessTextMoreVisualizations.js +15 -0
  59. package/dist/rules/liveLinkDuplicateReactAlias.js +15 -0
  60. package/dist/rules/make2dElements3d.js +15 -0
  61. package/dist/rules/masterMixLoudnessTarget.js +17 -0
  62. package/dist/rules/measureAlignmentDontEyeball.js +15 -0
  63. package/dist/rules/motionCarriesWeightAndMeaning.js +15 -0
  64. package/dist/rules/motionDensityFloor.js +15 -0
  65. package/dist/rules/neverEditTierBVendored.js +15 -0
  66. package/dist/rules/neverRestateDerivedFrameNumbersInComments.js +15 -0
  67. package/dist/rules/noAiGradientLook.js +15 -0
  68. package/dist/rules/noColoredFlashToHideACut.js +22 -0
  69. package/dist/rules/noDoubleFrameShift.js +126 -0
  70. package/dist/rules/noDuplicateMotionPrimitives.js +15 -0
  71. package/dist/rules/noLinearMotion.js +22 -0
  72. package/dist/rules/noNestedSvgFilters.js +93 -0
  73. package/dist/rules/noRepeatedAssetAcrossShots.js +15 -0
  74. package/dist/rules/noShorthandWithUndefinedLonghand.js +93 -0
  75. package/dist/rules/noStackedFadesAtACut.js +24 -0
  76. package/dist/rules/noStaticHolds.js +17 -0
  77. package/dist/rules/occlusionVisibleElements.js +17 -0
  78. package/dist/rules/oneContinuousVoTrack.js +15 -0
  79. package/dist/rules/onePunchPerMomentClass.js +15 -0
  80. package/dist/rules/oneRenderPerRound.js +15 -0
  81. package/dist/rules/openStaticAfterAHardCut.js +22 -0
  82. package/dist/rules/openingsAndClosingsCarryMore.js +15 -0
  83. package/dist/rules/persistentDeviceFrames.js +15 -0
  84. package/dist/rules/pixelDiffAnEmptyBandFirst.js +15 -0
  85. package/dist/rules/planIsPublishedNotGating.js +15 -0
  86. package/dist/rules/poweredByRevolaWatermark.js +15 -0
  87. package/dist/rules/predrawContentPresentAtTheCut.js +17 -0
  88. package/dist/rules/premiumSurfacesNotFlatBorders.js +15 -0
  89. package/dist/rules/radarDispositionsAreHumanOnly.js +17 -0
  90. package/dist/rules/realAssetsNotPlaceholders.js +15 -0
  91. package/dist/rules/rebuildUiFromReconNotScreenshots.js +15 -0
  92. package/dist/rules/reconStrandsRunInParallel.js +15 -0
  93. package/dist/rules/registryBeforeHandrolling.js +15 -0
  94. package/dist/rules/remapRateEndpointFormula.js +15 -0
  95. package/dist/rules/remocnContentTailwindChrome.js +15 -0
  96. package/dist/rules/retimeOnlyViaPlaybackOrDesignTokens.js +15 -0
  97. package/dist/rules/reviewTheRenderByFrameSweep.js +15 -0
  98. package/dist/rules/rollingNumberBoundedSequence.js +17 -0
  99. package/dist/rules/rollingNumberSequenceLayoutNone.js +16 -0
  100. package/dist/rules/rollingNumberTightBoxAlignment.js +15 -0
  101. package/dist/rules/rollingNumberWhenNotToUse.js +17 -0
  102. package/dist/rules/rollingNumberWhenToUse.js +17 -0
  103. package/dist/rules/sameFamilySameProject.js +15 -0
  104. package/dist/rules/sampleBrandFromProduct.js +15 -0
  105. package/dist/rules/sampleVelocityFromPreviousFramePosition.js +15 -0
  106. package/dist/rules/sceneRootTagDeclared.js +41 -0
  107. package/dist/rules/scrubDemoDataToGenericRoles.js +15 -0
  108. package/dist/rules/selfReviewThePlanForGaps.js +15 -0
  109. package/dist/rules/sourceArchivedBeforeBrief.js +17 -0
  110. package/dist/rules/statisticsSelfExplanatory.js +15 -0
  111. package/dist/rules/stopsAsRelativeSpacing.js +15 -0
  112. package/dist/rules/theWholeCanvasWorks.js +15 -0
  113. package/dist/rules/themePerSection.js +22 -0
  114. package/dist/rules/thesisDeclaredInBrief.js +17 -0
  115. package/dist/rules/tierARequiresPropUnreachableEdit.js +15 -0
  116. package/dist/rules/tinyBodyCopyAsRuledLines.js +15 -0
  117. package/dist/rules/titlesPinnedOutsideCameraPush.js +15 -0
  118. package/dist/rules/tokensNotRawHexes.js +60 -0
  119. package/dist/rules/typeTokenDefaultedParamsAsString.js +15 -0
  120. package/dist/rules/varyKitChoreographyAcrossSections.js +15 -0
  121. package/dist/rules/varyTheVisualizationGrammar.js +15 -0
  122. package/dist/rules/verifyCheapRenderDear.js +15 -0
  123. package/dist/rules/voAddressesTheViewer.js +15 -0
  124. package/dist/rules/voIsAPerformance.js +15 -0
  125. package/dist/rules/voScriptLocksSpokenWords.js +15 -0
  126. package/dist/rules/wrappersPreserveChildGeometry.js +15 -0
  127. package/dist/sourceCheck.js +47 -0
  128. package/dist/sourceCitations.js +68 -0
  129. package/dist/stageProjection.js +69 -0
  130. package/dist/stages.js +163 -0
  131. package/dist/testSupport.js +6 -0
  132. package/dist/types.js +0 -0
  133. package/dist/variants.js +8 -0
  134. package/dist/waiverProjection.js +20 -0
  135. package/package.json +32 -0
  136. package/src/anchors.test.ts +119 -0
  137. package/src/anchors.ts +110 -0
  138. package/src/corpus.test.ts +96 -0
  139. package/src/corpus.ts +149 -0
  140. package/src/corpusProject.test.ts +103 -0
  141. package/src/creativeEdge.test.ts +58 -0
  142. package/src/distBuild.test.ts +86 -0
  143. package/src/docStatus.test.ts +81 -0
  144. package/src/drift.test.ts +124 -0
  145. package/src/fixtures/data-radar-dynamic/Branches.tsx +14 -0
  146. package/src/fixtures/data-radar-dynamic/plan.fixture.yaml +16 -0
  147. package/src/fixtures/data-radar-forwarded/Scene.tsx +19 -0
  148. package/src/fixtures/data-radar-forwarded/plan.fixture.yaml +13 -0
  149. package/src/fixtures/data-radar-opaque/Scene.tsx +15 -0
  150. package/src/fixtures/data-radar-opaque/plan.fixture.yaml +10 -0
  151. package/src/fixtures/data-radar-tags/SceneClean.tsx +10 -0
  152. package/src/fixtures/data-radar-tags/SceneNullBinding.tsx +12 -0
  153. package/src/fixtures/data-radar-tags/SceneWithGap.tsx +11 -0
  154. package/src/fixtures/data-radar-tags/plan.fixture.yaml +29 -0
  155. package/src/fixtures/deterministic-scene.tsx +15 -0
  156. package/src/fixtures/double-shift/aliased.tsx +13 -0
  157. package/src/fixtures/double-shift/assigned.tsx +13 -0
  158. package/src/fixtures/double-shift/clean.tsx +19 -0
  159. package/src/fixtures/double-shift/destructured.tsx +12 -0
  160. package/src/fixtures/double-shift/elementAccess.tsx +12 -0
  161. package/src/fixtures/double-shift/layout.ts +24 -0
  162. package/src/fixtures/double-shift/mechanism-prefix.tsx.txt +458 -0
  163. package/src/fixtures/double-shift/wrapped.tsx +15 -0
  164. package/src/fixtures/geometry-sidecar-missing-export.ts +10 -0
  165. package/src/fixtures/geometry-sidecar-missing-type.ts +7 -0
  166. package/src/fixtures/geometry-sidecar-valid.ts +6 -0
  167. package/src/fixtures/interpolate-ascending.tsx +6 -0
  168. package/src/fixtures/interpolate-descending.tsx +8 -0
  169. package/src/fixtures/interpolate-identifier-keys.tsx +9 -0
  170. package/src/fixtures/nested-filters-broken.tsx +11 -0
  171. package/src/fixtures/nested-filters-fixed.tsx +8 -0
  172. package/src/fixtures/nested-filters-html-wrapper.tsx +14 -0
  173. package/src/fixtures/nested-filters-html.tsx +7 -0
  174. package/src/fixtures/nested-filters-siblings.tsx +12 -0
  175. package/src/fixtures/nondeterministic-scene.tsx +14 -0
  176. package/src/fixtures/nondeterministic-variants.tsx +16 -0
  177. package/src/fixtures/raw-hex-scene.tsx +10 -0
  178. package/src/fixtures/scene-root-declared/SceneHook.tsx +9 -0
  179. package/src/fixtures/scene-root-declared/plan.fixture.yaml +10 -0
  180. package/src/fixtures/scene-root-missing/SceneHook.tsx +11 -0
  181. package/src/fixtures/scene-root-missing/plan.fixture.yaml +10 -0
  182. package/src/fixtures/scene-root-no-names/SceneHook.tsx +6 -0
  183. package/src/fixtures/scene-root-no-names/plan.fixture.yaml +6 -0
  184. package/src/fixtures/scene-root-shell/SceneHook.tsx +8 -0
  185. package/src/fixtures/scene-root-shell/Shell.tsx +9 -0
  186. package/src/fixtures/scene-root-shell/plan.fixture.yaml +10 -0
  187. package/src/fixtures/shorthand-undefined-clean.tsx +23 -0
  188. package/src/fixtures/shorthand-undefined-longhand.tsx +17 -0
  189. package/src/fixtures/token-scene.tsx +9 -0
  190. package/src/includeCoverage.test.ts +176 -0
  191. package/src/learnedFrom.test.ts +32 -0
  192. package/src/projectDoc.test.ts +230 -0
  193. package/src/projectDoc.ts +152 -0
  194. package/src/projectDocSync.test.ts +152 -0
  195. package/src/projectDocSync.ts +79 -0
  196. package/src/projectMeta.test.ts +75 -0
  197. package/src/projectMeta.ts +53 -0
  198. package/src/provenance.ts +85 -0
  199. package/src/radar-check-ids.json +34 -0
  200. package/src/registry.test.ts +207 -0
  201. package/src/registry.ts +352 -0
  202. package/src/rules/animatedBarChartTightBox.ts +14 -0
  203. package/src/rules/animatedBarChartWhenNotToUse.ts +14 -0
  204. package/src/rules/animatedBarChartWhenToUse.ts +14 -0
  205. package/src/rules/animatedLineChartWhenNotToUse.ts +14 -0
  206. package/src/rules/animatedLineChartWhenToUse.ts +14 -0
  207. package/src/rules/approvedSfxPaletteOnly.ts +23 -0
  208. package/src/rules/atmosphereGradientsFadeAtBothEnds.ts +21 -0
  209. package/src/rules/audioIsTheClock.ts +22 -0
  210. package/src/rules/beatsDeriveFromTheirOwnSequence.ts +21 -0
  211. package/src/rules/bestSceneSetsTheBar.ts +21 -0
  212. package/src/rules/bumpBeforeRefreeze.ts +20 -0
  213. package/src/rules/cameraIsTheNarrator.ts +21 -0
  214. package/src/rules/checkPinBeforeShip.ts +22 -0
  215. package/src/rules/coldViewerClarity.ts +21 -0
  216. package/src/rules/commitOrCutEffects.ts +36 -0
  217. package/src/rules/convertFramesAcrossClockBoundary.ts +19 -0
  218. package/src/rules/crossCutElementsLiveInTheHudLayer.ts +21 -0
  219. package/src/rules/cursorClicksRealControls.ts +22 -0
  220. package/src/rules/cutOnRealAudioPauses.ts +20 -0
  221. package/src/rules/darkButNeverDim.ts +21 -0
  222. package/src/rules/dataRadarTagsDeclared.test.ts +60 -0
  223. package/src/rules/dataRadarTagsDeclared.ts +341 -0
  224. package/src/rules/declareAVerticalZoneMap.ts +19 -0
  225. package/src/rules/deleteRejectedRigsCompletely.ts +19 -0
  226. package/src/rules/deriveSeamsDontMeasure.ts +23 -0
  227. package/src/rules/deterministicSceneSource.test.ts +58 -0
  228. package/src/rules/deterministicSceneSource.ts +82 -0
  229. package/src/rules/diegeticSoundForOnscreenActions.ts +21 -0
  230. package/src/rules/distinctTransitionPerBeat.ts +21 -0
  231. package/src/rules/downloadOrDrawRichAssets.ts +22 -0
  232. package/src/rules/elementOnscreenForItsVoSentence.ts +25 -0
  233. package/src/rules/emitBuildNotes.ts +19 -0
  234. package/src/rules/energyFromTimingNotPartCount.ts +20 -0
  235. package/src/rules/evidenceBeforeClaimingFixed.ts +23 -0
  236. package/src/rules/exactPeerLockstep.ts +19 -0
  237. package/src/rules/firstPassIsADraft.ts +20 -0
  238. package/src/rules/fixCrowdingByRebandingNotNudging.ts +20 -0
  239. package/src/rules/fixesPreserveApprovedElements.ts +24 -0
  240. package/src/rules/floatingNotBoxed.ts +21 -0
  241. package/src/rules/focusBeatsRecedeTheContext.ts +20 -0
  242. package/src/rules/fontVarsBrandRemocn.ts +20 -0
  243. package/src/rules/geometricSeamsNotCrossfades.ts +28 -0
  244. package/src/rules/geometrySidecarExportsTyped.test.ts +37 -0
  245. package/src/rules/geometrySidecarExportsTyped.ts +59 -0
  246. package/src/rules/glowIsAFocalAccent.ts +28 -0
  247. package/src/rules/heroFillsTheFrame.ts +24 -0
  248. package/src/rules/htmlMasksForAnimatedCounters.ts +21 -0
  249. package/src/rules/impactStackOnPayoffs.ts +26 -0
  250. package/src/rules/interactiveTargetsAreDeclaredData.ts +21 -0
  251. package/src/rules/interpolateLiteralKeysMonotonic.test.ts +49 -0
  252. package/src/rules/interpolateLiteralKeysMonotonic.ts +98 -0
  253. package/src/rules/latenessIsStructuralOverlapTheBeats.ts +20 -0
  254. package/src/rules/lessTextMoreVisualizations.ts +22 -0
  255. package/src/rules/liveLinkDuplicateReactAlias.ts +22 -0
  256. package/src/rules/make2dElements3d.ts +21 -0
  257. package/src/rules/masterMixLoudnessTarget.ts +22 -0
  258. package/src/rules/measureAlignmentDontEyeball.ts +20 -0
  259. package/src/rules/motionCarriesWeightAndMeaning.ts +21 -0
  260. package/src/rules/motionDensityFloor.ts +20 -0
  261. package/src/rules/neverEditTierBVendored.ts +20 -0
  262. package/src/rules/neverRestateDerivedFrameNumbersInComments.ts +20 -0
  263. package/src/rules/noAiGradientLook.ts +22 -0
  264. package/src/rules/noColoredFlashToHideACut.ts +29 -0
  265. package/src/rules/noDoubleFrameShift.test.ts +95 -0
  266. package/src/rules/noDoubleFrameShift.ts +193 -0
  267. package/src/rules/noDuplicateMotionPrimitives.ts +19 -0
  268. package/src/rules/noLinearMotion.ts +29 -0
  269. package/src/rules/noNestedSvgFilters.test.ts +47 -0
  270. package/src/rules/noNestedSvgFilters.ts +138 -0
  271. package/src/rules/noRepeatedAssetAcrossShots.ts +19 -0
  272. package/src/rules/noShorthandWithUndefinedLonghand.test.ts +35 -0
  273. package/src/rules/noShorthandWithUndefinedLonghand.ts +140 -0
  274. package/src/rules/noStackedFadesAtACut.ts +30 -0
  275. package/src/rules/noStaticHolds.ts +24 -0
  276. package/src/rules/occlusionVisibleElements.ts +26 -0
  277. package/src/rules/oneContinuousVoTrack.ts +20 -0
  278. package/src/rules/onePunchPerMomentClass.ts +20 -0
  279. package/src/rules/oneRenderPerRound.ts +46 -0
  280. package/src/rules/openStaticAfterAHardCut.ts +29 -0
  281. package/src/rules/openingsAndClosingsCarryMore.ts +21 -0
  282. package/src/rules/persistentDeviceFrames.ts +20 -0
  283. package/src/rules/pixelDiffAnEmptyBandFirst.ts +20 -0
  284. package/src/rules/planIsPublishedNotGating.ts +32 -0
  285. package/src/rules/poweredByRevolaWatermark.ts +19 -0
  286. package/src/rules/predrawContentPresentAtTheCut.ts +21 -0
  287. package/src/rules/premiumSurfacesNotFlatBorders.ts +20 -0
  288. package/src/rules/radarDispositionsAreHumanOnly.ts +28 -0
  289. package/src/rules/realAssetsNotPlaceholders.ts +21 -0
  290. package/src/rules/rebuildUiFromReconNotScreenshots.ts +21 -0
  291. package/src/rules/reconStrandsRunInParallel.ts +63 -0
  292. package/src/rules/registryBeforeHandrolling.ts +20 -0
  293. package/src/rules/remapRateEndpointFormula.ts +19 -0
  294. package/src/rules/remocnContentTailwindChrome.ts +19 -0
  295. package/src/rules/retimeOnlyViaPlaybackOrDesignTokens.ts +18 -0
  296. package/src/rules/reviewTheRenderByFrameSweep.ts +23 -0
  297. package/src/rules/rollingNumberBoundedSequence.ts +21 -0
  298. package/src/rules/rollingNumberSequenceLayoutNone.ts +22 -0
  299. package/src/rules/rollingNumberTightBoxAlignment.ts +19 -0
  300. package/src/rules/rollingNumberWhenNotToUse.ts +24 -0
  301. package/src/rules/rollingNumberWhenToUse.ts +23 -0
  302. package/src/rules/sameFamilySameProject.ts +20 -0
  303. package/src/rules/sampleBrandFromProduct.ts +19 -0
  304. package/src/rules/sampleVelocityFromPreviousFramePosition.ts +21 -0
  305. package/src/rules/sceneRootTagDeclared.test.ts +43 -0
  306. package/src/rules/sceneRootTagDeclared.ts +78 -0
  307. package/src/rules/scrubDemoDataToGenericRoles.ts +19 -0
  308. package/src/rules/selfReviewThePlanForGaps.ts +47 -0
  309. package/src/rules/sourceArchivedBeforeBrief.ts +24 -0
  310. package/src/rules/statisticsSelfExplanatory.ts +18 -0
  311. package/src/rules/stopsAsRelativeSpacing.ts +21 -0
  312. package/src/rules/theWholeCanvasWorks.ts +21 -0
  313. package/src/rules/themePerSection.ts +28 -0
  314. package/src/rules/thesisDeclaredInBrief.ts +25 -0
  315. package/src/rules/tierARequiresPropUnreachableEdit.ts +20 -0
  316. package/src/rules/tinyBodyCopyAsRuledLines.ts +19 -0
  317. package/src/rules/titlesPinnedOutsideCameraPush.ts +22 -0
  318. package/src/rules/tokensNotRawHexes.test.ts +51 -0
  319. package/src/rules/tokensNotRawHexes.ts +89 -0
  320. package/src/rules/typeTokenDefaultedParamsAsString.ts +21 -0
  321. package/src/rules/varyKitChoreographyAcrossSections.ts +21 -0
  322. package/src/rules/varyTheVisualizationGrammar.ts +22 -0
  323. package/src/rules/verifyCheapRenderDear.ts +18 -0
  324. package/src/rules/voAddressesTheViewer.ts +18 -0
  325. package/src/rules/voIsAPerformance.ts +21 -0
  326. package/src/rules/voScriptLocksSpokenWords.ts +21 -0
  327. package/src/rules/wrappersPreserveChildGeometry.ts +22 -0
  328. package/src/sourceCheck.test.ts +50 -0
  329. package/src/sourceCheck.ts +55 -0
  330. package/src/sourceCitations.test.ts +747 -0
  331. package/src/sourceCitations.ts +217 -0
  332. package/src/stageProjection.ts +112 -0
  333. package/src/stages.test.ts +68 -0
  334. package/src/stages.ts +207 -0
  335. package/src/testSupport.ts +16 -0
  336. package/src/types.ts +181 -0
  337. package/src/variants.test.ts +24 -0
  338. package/src/variants.ts +13 -0
  339. package/src/waiverProjection.test.ts +59 -0
  340. package/src/waiverProjection.ts +45 -0
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * The house branding default, set for all Revola videos (2026-07-30). Sourced
5
+ * from the shared kit (`RevolaWatermark`) so it is one import, not a per-project
6
+ * reimplementation.
7
+ */
8
+ export const poweredByRevolaWatermark: Rule = {
9
+ id: "powered-by-revola-watermark",
10
+ claim: "Every video carries the 'Powered by Revola' watermark - the kit's RevolaWatermark, rendered in screen space at the film level - unless the video explicitly specifies otherwise.",
11
+ why: "It is the standing brand default for Revola videos; sourcing it from @revola-ai/video-kit keeps it one import instead of a per-project reimplementation, and a film-level screen-space placement holds it fixed through scene camera moves and off the very edge so a minor crop cannot shave it.",
12
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
13
+ tier: "process",
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "packages/video-kit/src/react/bricks/RevolaWatermark.tsx", symbol: "RevolaWatermark" },
18
+ },
19
+ };
@@ -0,0 +1,21 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from revola-video/BUILD_NOTES_CRAFT.md: "Spine now drawn from
5
+ * start={-20}: start=0/dur=1 still evaluates to *zero* progress on frame 0,
6
+ * so the cut landed on a black frame."
7
+ */
8
+ export const predrawContentPresentAtTheCut: Rule = {
9
+ id: "predraw-content-present-at-the-cut",
10
+ claim: "Give anything that must be visible on a scene's first frame a negative draw-on start (e.g. start={-20}); start=0 evaluates to zero progress on frame 0 and the cut lands on an empty frame.",
11
+ why: "The S3 spine with start=0 made the cut land on a black frame; a negative start pre-draws it while frame still advances, keeping the line boil alive.",
12
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
13
+ tier: "gate",
14
+ verify: "`video radar:film --project <dir> --video <mp4> --check dead-frame-at-cut`",
15
+ owner: "dead-frame-at-cut",
16
+ scope: { kind: "pipeline" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ anchor: { file: "revola-video/src/scenes/craft/ink.tsx", symbol: "drawP" },
20
+ },
21
+ };
@@ -0,0 +1,20 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Adopted from spec-videos/src/components/GlassPanel.tsx, whose own source
5
+ * comment names the tell: 'flat borders are the #1 cheap-glass tell'. The
6
+ * product-room draft used flat 1px borders on every panel and read cheap
7
+ * next to the same layout rendered with the layered treatment.
8
+ */
9
+ export const premiumSurfacesNotFlatBorders: Rule = {
10
+ id: "premium-surfaces-not-flat-borders",
11
+ claim: "Give panels a layered surface - contact plus ambient plus long-throw shadow, a top sheen, and a gradient hairline ring - instead of a flat 1px border.",
12
+ why: "Flat borders are the single clearest cheap-glass tell, and the surface treatment is what separated the reference videos from a first draft that used the same layout and the same palette.",
13
+ learnedFrom: "claude-sessions-digest/product-room-video.md",
14
+ tier: "craft",
15
+ scope: { kind: "pipeline" },
16
+ enforcement: {
17
+ kind: "prose",
18
+ anchor: { file: "revola-video/src/productroom/Glass.tsx" },
19
+ },
20
+ };
@@ -0,0 +1,28 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from docs/superpowers/specs/2026-07-31-defect-radar-design.md:
5
+ * "Disposition authority is human-only: the CLI refuses to write a
6
+ * disposition without an `--operator <name>` that doctrine forbids agents to
7
+ * pass, and the CLAUDE.md projection carries the one-liner 'never accept or
8
+ * dismiss a radar claim; surface it and continue.'" Added by the codex red
9
+ * team round (2026-08-01, finding 11): nothing in the CLI can authenticate a
10
+ * human, so the doctrine plus a committed, auditable ledger is the designed
11
+ * trust model - this rule is what puts that one-liner in the registry the
12
+ * projections are generated from, rather than leaving it as prose nobody
13
+ * mines back into CLAUDE.md.
14
+ */
15
+ export const radarDispositionsAreHumanOnly: Rule = {
16
+ id: "radar-dispositions-are-human-only",
17
+ claim: "Never accept or dismiss a radar claim; surface it and continue. Disposition authority is human-only.",
18
+ why: "An agent that quietly disposes its own findings is the exact single-pair-of-eyes failure the radar exists to remove - a judge claim an agent waved through on the operator's behalf is indistinguishable from one nobody ever reviewed.",
19
+ learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
20
+ tier: "gate",
21
+ verify: "`video radar:accept`/`radar:dismiss` refuse without a human `--operator <name>` and refuse outright under an agent process (`CLAUDECODE` set)",
22
+ owner: "coherence",
23
+ scope: { kind: "pipeline" },
24
+ enforcement: {
25
+ kind: "prose",
26
+ anchor: { file: "packages/video-cli/src/cli.ts" },
27
+ },
28
+ };
@@ -0,0 +1,21 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from claude-sessions-digest/portfolio-reels.md ("Replace them with
5
+ * icons, not dots or squares"; "IT SHOULD MIMIC CURSOR EXACTLY AND BE SUPER
6
+ * DETAILED ... NO FLUFF") and revola-usecase-book-meeting.md ("why are the 4
7
+ * cards at the beginning empty"; "why is everything, including the agent
8
+ * answers, so short?? it should be super detailed").
9
+ */
10
+ export const realAssetsNotPlaceholders: Rule = {
11
+ id: "real-assets-not-placeholders",
12
+ claim: "Mock UIs and cards carry real assets and realistic, detailed content: brand SVG logos and icons (never colored dots or gradient squares), full-length plausible copy, no empty placeholder cards.",
13
+ why: "Placeholder shapes and short generic copy are the second half of the AI-template read; every video needed a de-placeholder round, and detail density was the fix that made recreated UIs land.",
14
+ learnedFrom: "claude-sessions-digest/portfolio-reels.md",
15
+ tier: "craft",
16
+ scope: { kind: "pipeline" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ unanchorable: "The rule is a content-density judgment over scene JSX; no single code location expresses it.",
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from act854-video/BUILD_NOTES.md, which records the client bounce
5
+ * that proves the why: "panels rebuilt 1:1 from the live portal. The old
6
+ * CBOM/HBOM/SBOM panels were a stub ... They now reproduce the real Summary
7
+ * Audit Dashboard." Merged with HOW_I_BUILD_VIDEOS.md's doctrine statement
8
+ * ("Screenshots can't be re-themed, animated per-element, or scrubbed").
9
+ */
10
+ export const rebuildUiFromReconNotScreenshots: Rule = {
11
+ id: "rebuild-ui-from-recon-not-screenshots",
12
+ claim: "Rebuild product pages 1:1 as React components from recon captures; never embed screenshots in scenes or ship a generic stub.",
13
+ why: "Screenshots cannot be re-themed, animated per-element, or data-scrubbed, and a stub misrepresents the product: the v1 BOM panels were a one-chart stub and the client bounced them.",
14
+ learnedFrom: "act854-video/BUILD_NOTES.md",
15
+ tier: "craft",
16
+ scope: { kind: "pipeline" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ anchor: { file: "qpa-video/03_scenes/act854/panels.jsx" },
20
+ },
21
+ };
@@ -0,0 +1,63 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Recon is several unrelated investigations wearing one name, and running them
5
+ * in series is the single largest avoidable block of wall clock in a build.
6
+ *
7
+ * Measured on the QGuard build: 17 minutes from kickoff to a finished VO script,
8
+ * all of it sequential - read the rulebook, read the brief, browse the live
9
+ * site, rasterise and pixel-count the PDF, write the brand document, then draft
10
+ * the script. The reference sessions in `claude-sessions-digest/` fire research
11
+ * subagents in the FIRST action of the session and carry on.
12
+ *
13
+ * The strands are genuinely independent:
14
+ *
15
+ * - brand palette, type, logo geometry, from the live product and its
16
+ * collateral (`sample-brand-from-product`)
17
+ * - product the walkthrough capture: screenshots, colours, text dump
18
+ * (`rebuild-ui-from-recon-not-screenshots`)
19
+ * - artifacts making what appears on screen true - real header shapes, real
20
+ * policy syntax, real log lines, the mechanics of whatever the
21
+ * film claims
22
+ * - script the VO draft, which depends on the brief and not on any of the
23
+ * above (`vo-script-locks-spoken-words`)
24
+ *
25
+ * Give each strand exactly one declared output path before starting. Two strands
26
+ * wanting the same file is the signal that they were one strand.
27
+ *
28
+ * Each strand still gets its own worktree (`isolation: "worktree"`), and the
29
+ * join collects their outputs. The first version of this rule waived that,
30
+ * arguing disjoint writes cannot collide so the isolation machinery was "pure
31
+ * overhead here". Both halves were wrong:
32
+ *
33
+ * - A worktree costs a few hundred milliseconds against a strand that runs for
34
+ * minutes, and it serialises nothing. There was no overhead to save.
35
+ * - Disjoint OUTPUT paths are narrower than the collision surface. Four agents
36
+ * in one working tree share the git index, and an agent that runs `git add`
37
+ * or `git commit` - which they do reflexively - sweeps up the other three
38
+ * strands' half-written files. `node_modules` and the beads DB are shared
39
+ * the same way. None of those is the declared output path.
40
+ *
41
+ * And the declared path was never enforced by anything. This repo's whole
42
+ * history is declarations without checks drifting: a zone guard handed a number
43
+ * 55px wrong, seventeen captions consistently lowercase, that guard's own remedy
44
+ * text pointing the wrong way for four rounds. `isolation: "worktree"` is a
45
+ * check that already exists and costs nothing to use.
46
+ *
47
+ * The parallelism is the point of this rule and is untouched by any of that.
48
+ *
49
+ * The plan is the join point: it consumes all four and cannot be finished until
50
+ * they land. Everything before the join runs at once.
51
+ */
52
+ export const reconStrandsRunInParallel: Rule = {
53
+ id: "recon-strands-run-in-parallel",
54
+ claim: "Run brand, product, artifact-accuracy and VO-script recon concurrently, each strand in its own worktree and owning exactly one declared output path; the plan is the join, and nothing before it runs in series.",
55
+ why: "The strands share no inputs and no outputs, so serialising them adds their durations instead of taking their maximum - 17 minutes of the QGuard build's 40-minute pre-scene phase was recon done one step at a time.",
56
+ learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
57
+ tier: "process",
58
+ scope: { kind: "pipeline" },
59
+ enforcement: {
60
+ kind: "prose",
61
+ anchor: { file: "packages/video-cli/src/schema.ts" },
62
+ },
63
+ };
@@ -0,0 +1,20 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md: "It is the animation vocabulary going
5
+ * forward for anything generic - reach for the registry before
6
+ * hand-authoring a counter, a bar chart, or a text reveal." The anchor is a
7
+ * vendored registry component, the first stop the rule mandates.
8
+ */
9
+ export const registryBeforeHandrolling: Rule = {
10
+ id: "registry-before-handrolling",
11
+ claim: "Reach for the remocn registry before hand-authoring any generic animated element (counter, bar chart, text reveal, backdrop, transition).",
12
+ why: "Hand-authoring what the registry already provides creates one-off implementations with no upstream, repeating the pre-registry _lib sprawl.",
13
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
14
+ tier: "process",
15
+ scope: { kind: "pipeline" },
16
+ enforcement: {
17
+ kind: "prose",
18
+ anchor: { file: "revola-video/src/components/remocn/backdrop.tsx" },
19
+ },
20
+ };
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from revola-video/BUILD_NOTES_CRAFT.md: "(D-1)/(P-1), not D/P, on
5
+ * purpose: the last playback frame must map to the scene's exact last design
6
+ * frame, because S2's match cut derives off D_S1 - 1."
7
+ */
8
+ export const remapRateEndpointFormula: Rule = {
9
+ id: "remap-rate-endpoint-formula",
10
+ claim: "Compute a scene's playback rate as (D-1)/(P-1), never D/P, so the last playback frame maps exactly onto the last design frame.",
11
+ why: "Seam contracts are derived off a scene's last design frame; D/P leaves an endpoint error and the match cut drifts.",
12
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
13
+ tier: "craft",
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "revola-video/src/tokensCraft.ts", symbol: "rate" },
18
+ },
19
+ };
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md: "remocn animates the content, Tailwind
5
+ * utilities structure the frame." The anchor is the reference scene
6
+ * demonstrating the split.
7
+ */
8
+ export const remocnContentTailwindChrome: Rule = {
9
+ id: "remocn-content-tailwind-chrome",
10
+ claim: "Use remocn components for animated content (counters, charts, text reveals, backdrops, transitions) and hand-built Tailwind for all container chrome (card shells, browser chrome, grids, layout).",
11
+ why: "remocn has no card, spotlight, or panel primitive, so sourcing chrome from the registry stalls the build; hand-animating counters and charts wastes the registry. The split is the norm, not a workaround.",
12
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
13
+ tier: "craft",
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "revola-video/src/scenes/V2Beat4DashboardRemocn.tsx" },
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from revola-video/BUILD_NOTES_CRAFT.md: "Do NOT re-time a scene by
5
+ * editing its internal beats to fit audio; that's what R_Sx is for."
6
+ */
7
+ export const retimeOnlyViaPlaybackOrDesignTokens: Rule = {
8
+ id: "retime-only-via-playback-or-design-tokens",
9
+ claim: "Retime by moving P_* (audio changed) or D_* (choreography changed) and let rates and seams re-derive; never edit a scene's internal beats to fit audio, and shorten a rushed beat in design space.",
10
+ why: "Editing internal beats to fit audio bypasses the remap layer, silently breaking every derived rate, seam contract, and cue anchor.",
11
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
12
+ tier: "craft",
13
+ scope: { kind: "pipeline" },
14
+ enforcement: {
15
+ kind: "prose",
16
+ anchor: { file: "revola-video/src/tokensCraft.ts" },
17
+ },
18
+ };
@@ -0,0 +1,23 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from the product-room build: the author verified each round with
5
+ * per-beat stills and still shipped an empty hero card, unpadded panels and a
6
+ * click that never rendered. A blanket 2fps sweep of the finished mp4 found
7
+ * the first two together (build notes, "Round 3b - frame-sweep audit"); the
8
+ * dead click surfaced two rounds later while verifying something else. So the
9
+ * sweep is necessary and not sufficient - which is why `video review` also
10
+ * gates on staleness and on the render's length matching timing.json.
11
+ */
12
+ export const reviewTheRenderByFrameSweep: Rule = {
13
+ id: "review-the-render-by-frame-sweep",
14
+ claim: "Before calling a round done, sweep the RENDERED file at ~2fps into labelled contact sheets and scan every frame; source review and hand-picked stills do not count.",
15
+ why: "Three defects in one build were invisible in source and obvious in the render - a wrapper that silently wiped every panel's padding, an animated sub-scene rendering as an empty box, and a click beat scheduled past its sequence end so it never drew - and all three survived rounds of per-beat stills because the stills were chosen where the author expected problems, not where they were.",
16
+ learnedFrom: "claude-sessions-digest/product-room-video.md",
17
+ tier: "process",
18
+ scope: { kind: "pipeline" },
19
+ enforcement: {
20
+ kind: "prose",
21
+ anchor: { file: "packages/video-cli/src/review.ts" },
22
+ },
23
+ };
@@ -0,0 +1,21 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md, "Gotchas found wiring it into bespoke
5
+ * scenes", item 2. One of the three gotchas WS1 currently hardcodes in its
6
+ * plan; migrating it here is the proof that projection works end to end.
7
+ */
8
+ export const rollingNumberBoundedSequence: Rule = {
9
+ id: "rolling-number-bounded-sequence",
10
+ claim: "Wrap RollingNumber in a Sequence spanning the whole card window and use the speed prop to settle early.",
11
+ why: "RollingNumber reads useVideoConfig().durationInFrames, so it counts over its Sequence's duration rather than its own. Left unwrapped it rolls for the entire shot; wrapped in a short Sequence it unmounts and the number vanishes mid-card.",
12
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
13
+ tier: "craft",
14
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
18
+ // learned on the vendored Tier B copy in revola-video.
19
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" },
20
+ },
21
+ };
@@ -0,0 +1,22 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md, "Gotchas found wiring it into bespoke
5
+ * scenes": "the default Sequence wraps children in an AbsoluteFill, which
6
+ * breaks inline/flow layout inside a card. Always pass layout=\"none\"...".
7
+ * The default-wrapper behavior itself is documented Remotion behavior; the
8
+ * house rule is the always-pass-it policy when time-shifting in flow layout.
9
+ */
10
+ export const rollingNumberSequenceLayoutNone: Rule = {
11
+ id: "rolling-number-sequence-layout-none",
12
+ claim: "When the Sequence around RollingNumber exists only to shift time, always pass layout=\"none\".",
13
+ why: "The default Sequence wraps children in an AbsoluteFill, which silently breaks inline/flow layout inside a card - elements stack at the container origin.",
14
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
15
+ upstream: "remotion:rules/sequencing.md",
16
+ tier: "craft",
17
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
18
+ enforcement: {
19
+ kind: "prose",
20
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" },
21
+ },
22
+ };
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md, "Gotchas found wiring it into bespoke
5
+ * scenes": "remocn primitives are centered AbsoluteFills ... Give it too wide
6
+ * a box and the text floats to the middle (or wraps mid-word)."
7
+ */
8
+ export const rollingNumberTightBoxAlignment: Rule = {
9
+ id: "rolling-number-tight-box-alignment",
10
+ claim: "To flow-align RollingNumber (or any remocn primitive), wrap it in a position: relative box sized to roughly the content's natural width, never a full-width box.",
11
+ why: "remocn primitives are centered AbsoluteFills filling their nearest positioned ancestor; in a too-wide box the number floats to the middle or wraps mid-word.",
12
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
13
+ tier: "craft",
14
+ scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" },
18
+ },
19
+ };
@@ -0,0 +1,24 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Recovered from the WS1 plan's original hand-authored rolling-number.meta.ts
5
+ * block (deleted by the Task-8 reconciliation without being captured as a
6
+ * rule): "Do not use for multiple simultaneous stats (use a stat grid), for
7
+ * non-numeric emphasis, or when the value must be pixel-aligned to bespoke
8
+ * chrome without a wrapping box."
9
+ */
10
+ export const rollingNumberWhenNotToUse: Rule = {
11
+ id: "rolling-number-when-not-to-use",
12
+ claim:
13
+ "Do not use RollingNumber for multiple simultaneous stats (use a stat grid), for non-numeric emphasis, or when the value must be pixel-aligned to bespoke chrome without a wrapping box.",
14
+ why: "A grid of rolling odometers makes every stat move so nothing reads as focal, and without a wrapping position: relative box its AbsoluteFill centering fights any pixel-alignment to bespoke chrome.",
15
+ learnedFrom: "docs/plans/2026-07-18-ws1-manifest-check-before-build-plan.md",
16
+ tier: "craft",
17
+ scope: { kind: "component", slug: "rolling-number", field: "whenNotToUse" },
18
+ enforcement: {
19
+ kind: "prose",
20
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
21
+ // learned on the vendored Tier B copy in revola-video.
22
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" },
23
+ },
24
+ };
@@ -0,0 +1,23 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Recovered from the WS1 plan's original hand-authored rolling-number.meta.ts
5
+ * block (deleted by the Task-8 reconciliation without being captured as a
6
+ * rule): "An animated odometer for a single headline number (users, revenue,
7
+ * count). Best when the number is the beat's focal point."
8
+ */
9
+ export const rollingNumberWhenToUse: Rule = {
10
+ id: "rolling-number-when-to-use",
11
+ claim:
12
+ "Use RollingNumber as the animated odometer for a single headline number (users, revenue, count) that is the beat's focal point.",
13
+ why: "Skipping it means hand-rolling a counter (a duplicate motion primitive), and animating a number that is not the focal point turns the odometer into background noise competing with the real subject of the beat.",
14
+ learnedFrom: "docs/plans/2026-07-18-ws1-manifest-check-before-build-plan.md",
15
+ tier: "craft",
16
+ scope: { kind: "component", slug: "rolling-number", field: "whenToUse" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ // Anchored to the owned Tier A port (WS1); the guidance was originally
20
+ // learned on the vendored Tier B copy in revola-video.
21
+ anchor: { file: "packages/video-kit/src/motion/rolling-number.tsx" },
22
+ },
23
+ };
@@ -0,0 +1,20 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md: "A new video for the same product family
5
+ * should be a new composition in the existing setup (act854 model), not a
6
+ * fresh repo." / "_lib is forked across ... colors.js sits at three distinct
7
+ * hashes".
8
+ */
9
+ export const sameFamilySameProject: Rule = {
10
+ id: "same-family-same-project",
11
+ claim: "Build a new video for the same product family as a new composition inside the existing npm project, never as a fresh repo.",
12
+ why: "A fresh repo forks _lib, fonts, and node_modules - the exact fork that left colors.js at three hashes and SceneTransition.jsx at five across the old projects.",
13
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
14
+ tier: "process",
15
+ scope: { kind: "pipeline" },
16
+ enforcement: {
17
+ kind: "prose",
18
+ unanchorable: "Repo-layout doctrine; no code symbol encodes it.",
19
+ },
20
+ };
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from HOW_I_BUILD_VIDEOS.md: "Brand | palette, logo files, fonts,
5
+ * accent ... | Sample from the real product, don't guess". The sampled values
6
+ * land in the project's token module.
7
+ */
8
+ export const sampleBrandFromProduct: Rule = {
9
+ id: "sample-brand-from-product",
10
+ claim: "Sample brand colors, fonts, and accents from the real product's recon capture; never guess them.",
11
+ why: "Guessed brand values produce a video the client immediately flags as off-brand, triggering an avoidable feedback round.",
12
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
13
+ tier: "craft",
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "revola-video/src/tokens.ts" },
18
+ },
19
+ };
@@ -0,0 +1,21 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from revola-video/BUILD_NOTES_CRAFT.md: "cursor velocity was sampled
5
+ * in bezier PARAMETER space (travel - 0.035), so the clamped tail reported
6
+ * fake speed and the cursor would have blurred permanently at rest. Now
7
+ * sampled from the previous frame." The corrected pattern is
8
+ * bezier(curTravel(frame - 1), ...) in S2Turn.tsx.
9
+ */
10
+ export const sampleVelocityFromPreviousFramePosition: Rule = {
11
+ id: "sample-velocity-from-previous-frame-position",
12
+ claim: "Compute motion-blur velocity from the previous frame's evaluated position, never from a delta in curve-parameter space.",
13
+ why: "Parameter-space sampling on a clamped bezier reports fake speed at the clamped tail, so the cursor would have blurred permanently while at rest.",
14
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
15
+ tier: "craft",
16
+ scope: { kind: "pipeline" },
17
+ enforcement: {
18
+ kind: "prose",
19
+ anchor: { file: "revola-video/src/scenes/craft/S2Turn.tsx" },
20
+ },
21
+ };
@@ -0,0 +1,43 @@
1
+ import { join } from "node:path";
2
+ import { describe, expect, test } from "vitest";
3
+ import { toolchainRepoRoot as repoRoot } from "../testSupport";
4
+ import { checkFile } from "../sourceCheck";
5
+ import { sceneRootTagDeclared } from "./sceneRootTagDeclared";
6
+
7
+ const check = () => {
8
+ if (sceneRootTagDeclared.enforcement.kind !== "source") throw new Error("expected a source rule");
9
+ return sceneRootTagDeclared.enforcement.check;
10
+ };
11
+
12
+ // Same shape as dataRadarTagsDeclared.test.ts: each fixture directory is its
13
+ // own one-project world (its own plan*.yaml), project-wide scope.
14
+ const runIn = (fixture: string, name: string) => {
15
+ const rel = `packages/video-craft/src/fixtures/${fixture}/${name}`;
16
+ return checkFile(check(), join(repoRoot, rel), rel, repoRoot);
17
+ };
18
+
19
+ describe("sceneRootTagDeclared", () => {
20
+ test("fires when a radar-adopting project carries no data-radar-scene-root anywhere", () => {
21
+ const findings = runIn("scene-root-missing", "SceneHook.tsx");
22
+ expect(findings).toHaveLength(1);
23
+ expect(findings[0].match).toBe("data-radar-scene-root");
24
+ expect(findings[0].message).toMatch(/radar:scene refuses at setup/);
25
+ expect(findings[0].message).toMatch(/data-radar-scene-root="static"/);
26
+ expect(findings[0].message).toMatch(/innermost full-bleed camera layer/);
27
+ // Attributed to the plan, where the names: bindings that make the
28
+ // project radar-adopting live.
29
+ expect(findings[0].file).toMatch(/plan\.fixture\.yaml$/);
30
+ });
31
+
32
+ test("clean when the scene's root declares data-radar-scene-root=\"static\"", () => {
33
+ expect(runIn("scene-root-declared", "SceneHook.tsx")).toEqual([]);
34
+ });
35
+
36
+ test("clean when a SIBLING shell file carries the tag (the qguard shape)", () => {
37
+ expect(runIn("scene-root-shell", "SceneHook.tsx")).toEqual([]);
38
+ });
39
+
40
+ test("vacuous pass for a project whose plan declares no names: bindings", () => {
41
+ expect(runIn("scene-root-no-names", "SceneHook.tsx")).toEqual([]);
42
+ });
43
+ });
@@ -0,0 +1,78 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, relative, sep } from "node:path";
3
+ import type { Rule, SourceFinding } from "../types";
4
+ import { declaredElements, findPlanDir, INCLUDE, isReporterFile, projectSourceFiles } from "./dataRadarTagsDeclared";
5
+
6
+ /**
7
+ * Mined from docs/superpowers/specs/2026-08-08-build2-track-b-round2-report.md
8
+ * (Task B, "Setup refusal"): every geometry check's numbers come from scene
9
+ * space, and the sampler derives scene space from the `[data-radar-scene-root]`
10
+ * element. A radar-adopting scene emitted without the tag makes `radar:scene`
11
+ * refuse at the first sampled frame ("no [data-radar-scene-root] element").
12
+ * Both build2 first passes hit that refusal IDENTICALLY - Claude's 7-scene
13
+ * fan-out (40a7fb5) needed a dedicated fix round (c662df7) before its first
14
+ * sweep, and the Gemini replays reproduced the same absence 6/6 - because the
15
+ * scene-node contract never taught the convention. The contract text now does
16
+ * (HOW_I_BUILD_VIDEOS.md, scene/amplify dispatch); this rule is the free,
17
+ * no-render source check that catches the omission at authoring time.
18
+ *
19
+ * SCOPE: PROJECT-WIDE, like `data-radar-tags-declared`, and for the same
20
+ * reason: the tag routinely lives outside the scene module. qse-qguard-video
21
+ * carries it once in the shared `Shell.tsx` every scene renders inside, so a
22
+ * per-scene-file check would false-fail correct code. The honest source-level
23
+ * claim is "some element in this project's source declares scene space";
24
+ * which element is the RIGHT one (root AbsoluteFill vs innermost camera
25
+ * layer) is not decidable from source and stays with the runtime sampler,
26
+ * which refuses an untransformed-root shape outright and names the fix.
27
+ *
28
+ * A project whose plans declare no `names:` bindings is not radar-adopting:
29
+ * no geometry check will ever run against declared elements there, the
30
+ * contract is not owed, and the check passes vacuously - which is also what
31
+ * keeps the legacy corpus (qpa-video, revola-video, datapeak, qse-*) clean
32
+ * without a single KNOWN_EXCEPTIONS row.
33
+ */
34
+ export const sceneRootTagDeclared: Rule = {
35
+ id: "scene-root-tag-declared",
36
+ claim: "A scene declares its scene-space contract: the root AbsoluteFill carries data-radar-scene-root=\"static\" when the scene has no whole-scene camera transform; when it has one, the innermost full-bleed camera layer carries data-radar-scene-root instead.",
37
+ why: "radar:scene derives every geometry number from the [data-radar-scene-root] element and refuses at setup without it; both build2 first passes (Claude's fan-out and the Gemini replays) shipped scenes missing the tag and cost a dedicated fix round (c662df7) before the first sweep could run.",
38
+ learnedFrom: "docs/superpowers/specs/2026-08-08-build2-track-b-round2-report.md",
39
+ tier: "gate",
40
+ verify: "`vitest run packages/video-craft` (source-level, no render) - at runtime `video radar:scene --project <dir> --comp <id>` refuses at setup naming the missing tag",
41
+ scope: { kind: "pipeline" },
42
+ enforcement: {
43
+ kind: "source",
44
+ check: {
45
+ include: INCLUDE,
46
+ run(_file, relPath, root): SourceFinding[] {
47
+ const absPath = join(root, relPath);
48
+ const planDir = findPlanDir(dirname(absPath), root);
49
+ if (!planDir || !existsSync(planDir)) return [];
50
+ if (sceneRootTagDeclared.enforcement.kind !== "source") return [];
51
+ const selfCheck = sceneRootTagDeclared.enforcement.check;
52
+ if (!isReporterFile(planDir, absPath, selfCheck, root)) return [];
53
+
54
+ const declared = declaredElements(planDir, root);
55
+ if (declared.length === 0) return []; // not a radar-adopting project
56
+
57
+ // Textual containment is the whole check: the attribute NAME is the
58
+ // contract (any value form - "static", a camera declaration - counts),
59
+ // and ts-morph adds nothing over a substring for an attribute name.
60
+ const tagged = projectSourceFiles(planDir, selfCheck, root)
61
+ .some((f) => readFileSync(f, "utf8").includes("data-radar-scene-root"));
62
+ if (tagged) return [];
63
+
64
+ const planFile = declared[0].planFile;
65
+ const projRel = relative(root, planDir).split(sep).join("/");
66
+ return [{
67
+ file: planFile,
68
+ line: 1,
69
+ match: "data-radar-scene-root",
70
+ message: `${projRel} declares names: bindings but no data-radar-scene-root attribute exists anywhere in its `
71
+ + `source, so radar:scene refuses at setup on every scene. Put data-radar-scene-root="static" on the `
72
+ + `scene's root AbsoluteFill when the scene has no whole-scene camera transform; when it has one, tag `
73
+ + `the innermost full-bleed camera layer instead.`,
74
+ }];
75
+ },
76
+ },
77
+ },
78
+ };
@@ -0,0 +1,19 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from act854-video/BUILD_NOTES.md: "Demo data scrubbed to generic
5
+ * roles (A. Admin, A. Auditor, etc.)." The exemplar is the members table in
6
+ * panels.jsx.
7
+ */
8
+ export const scrubDemoDataToGenericRoles: Rule = {
9
+ id: "scrub-demo-data-to-generic-roles",
10
+ claim: "Scrub all demo data visible in rebuilt UI to generic role placeholders (A. Admin, A. Auditor) before render; never carry real names, emails, or org data from recon captures into panels.",
11
+ why: "Recon captures come from a live authenticated portal, so copied-in table rows are real PII by default; unscrubbed panels leak a client's real user data into a marketing video.",
12
+ learnedFrom: "act854-video/BUILD_NOTES.md",
13
+ tier: "craft",
14
+ scope: { kind: "pipeline" },
15
+ enforcement: {
16
+ kind: "prose",
17
+ anchor: { file: "qpa-video/03_scenes/act854/panels.jsx" },
18
+ },
19
+ };