@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,207 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { RULES, validateRegistry } from "./registry";
3
+ import type { Rule } from "./types";
4
+
5
+ const base: Rule = {
6
+ id: "a-rule",
7
+ claim: "Do the thing.",
8
+ why: "Otherwise the other thing breaks.",
9
+ learnedFrom: "HOW_I_BUILD_VIDEOS.md",
10
+ tier: "craft",
11
+ scope: { kind: "pipeline" },
12
+ enforcement: { kind: "prose", unanchorable: "process guidance, no code to point at" },
13
+ };
14
+
15
+ describe("validateRegistry", () => {
16
+ test("accepts a well-formed registry", () => {
17
+ expect(validateRegistry([base])).toEqual([]);
18
+ });
19
+
20
+ test("rejects duplicate ids", () => {
21
+ const errors = validateRegistry([base, { ...base, claim: "Different." }]);
22
+ expect(errors).toHaveLength(1);
23
+ expect(errors[0]).toMatch(/duplicate rule id "a-rule"/);
24
+ });
25
+
26
+ test("rejects a non-kebab-case id", () => {
27
+ const errors = validateRegistry([{ ...base, id: "aRule" }]);
28
+ expect(errors[0]).toMatch(/id "aRule" is not kebab-case/);
29
+ });
30
+
31
+ test("rejects an empty learnedFrom", () => {
32
+ const errors = validateRegistry([{ ...base, learnedFrom: "" }]);
33
+ expect(errors[0]).toMatch(/rule "a-rule" has no learnedFrom/);
34
+ });
35
+
36
+ test("rejects two whenToUse rules for one component", () => {
37
+ const scope = { kind: "component", slug: "rolling-number", field: "whenToUse" } as const;
38
+ const errors = validateRegistry([
39
+ { ...base, id: "one", scope },
40
+ { ...base, id: "two", scope },
41
+ ]);
42
+ expect(errors).toHaveLength(1);
43
+ expect(errors[0]).toMatch(/component "rolling-number" has 2 whenToUse rules, expected at most 1/);
44
+ });
45
+
46
+ test("allows many gotcha rules for one component", () => {
47
+ const scope = { kind: "component", slug: "rolling-number", field: "gotcha" } as const;
48
+ expect(validateRegistry([
49
+ { ...base, id: "one", scope },
50
+ { ...base, id: "two", scope },
51
+ ])).toEqual([]);
52
+ });
53
+
54
+ test("rejects an empty claim", () => {
55
+ const errors = validateRegistry([{ ...base, claim: " " }]);
56
+ expect(errors[0]).toMatch(/rule "a-rule" has an empty claim/);
57
+ });
58
+
59
+ test("rejects an empty why", () => {
60
+ const errors = validateRegistry([{ ...base, why: "" }]);
61
+ expect(errors[0]).toMatch(/rule "a-rule" has an empty why/);
62
+ });
63
+
64
+ test("rejects an empty unanchorable reason", () => {
65
+ const errors = validateRegistry([
66
+ { ...base, enforcement: { kind: "prose", unanchorable: " " } },
67
+ ]);
68
+ expect(errors[0]).toMatch(/rule "a-rule" has an empty unanchorable reason/);
69
+ });
70
+
71
+ test("rejects an anchor path that is not repo-relative POSIX", () => {
72
+ const errors = validateRegistry([
73
+ { ...base, enforcement: { kind: "prose", anchor: { file: "/abs/path.tsx" } } },
74
+ { ...base, id: "b-rule", enforcement: { kind: "prose", anchor: { file: "a\\b.tsx" } } },
75
+ ]);
76
+ expect(errors).toHaveLength(2);
77
+ expect(errors[0]).toMatch(/anchor file "\/abs\/path.tsx" is not a repo-relative POSIX path/);
78
+ });
79
+
80
+ test("rejects a source rule with no include globs", () => {
81
+ const errors = validateRegistry([
82
+ { ...base, enforcement: { kind: "source", check: { include: [], run: () => [] } } },
83
+ ]);
84
+ expect(errors[0]).toMatch(/rule "a-rule" has no include globs/);
85
+ });
86
+
87
+ test("rejects a non-kebab-case component slug", () => {
88
+ const errors = validateRegistry([
89
+ { ...base, scope: { kind: "component", slug: "RollingNumber", field: "gotcha" } },
90
+ ]);
91
+ expect(errors[0]).toMatch(/component slug "RollingNumber" is not kebab-case/);
92
+ });
93
+
94
+ test("rejects a prose rule carrying both an anchor and an unanchorable reason", () => {
95
+ const both = {
96
+ kind: "prose",
97
+ anchor: { file: "HOW_I_BUILD_VIDEOS.md" },
98
+ unanchorable: "also this",
99
+ } as Rule["enforcement"];
100
+ const errors = validateRegistry([{ ...base, enforcement: both }]);
101
+ expect(errors[0]).toMatch(/rule "a-rule" carries both an anchor and an unanchorable reason/);
102
+ });
103
+ });
104
+
105
+ describe("the real registry", () => {
106
+ test("is valid", () => {
107
+ expect(validateRegistry(RULES)).toEqual([]);
108
+ });
109
+
110
+ test("rolling-number carries usage guidance, not just gotchas", () => {
111
+ // WS1 builds its manifest from the generated sidecar; empty
112
+ // whenToUse/whenNotToUse would ship a component with no usage guidance.
113
+ const fields = RULES
114
+ .filter((r) => r.scope.kind === "component" && r.scope.slug === "rolling-number")
115
+ .map((r) => (r.scope as { field: string }).field);
116
+ expect(fields).toContain("whenToUse");
117
+ expect(fields).toContain("whenNotToUse");
118
+ });
119
+ });
120
+
121
+ // A gate is a claim that a violation blocks the ship. If nothing can discharge
122
+ // it, it is a wish. `hero-fills-the-frame` states "a hero occupying a third of
123
+ // the screen is a defect" and was violated by a hero occupying a third of the
124
+ // screen, for five rounds, because no artifact ever tested it.
125
+ describe("tier and verify", () => {
126
+ test("accepts a gate that says how it is settled", () => {
127
+ expect(validateRegistry([{ ...base, tier: "gate", verify: "video render-twice --comp x" }])).toEqual([]);
128
+ });
129
+
130
+ test("rejects a gate with no verify", () => {
131
+ const errors = validateRegistry([{ ...base, tier: "gate" }]);
132
+ expect(errors).toHaveLength(1);
133
+ expect(errors[0]).toMatch(/rule "a-rule" is a gate with no verify/);
134
+ });
135
+
136
+ test("rejects a gate whose verify is blank", () => {
137
+ expect(validateRegistry([{ ...base, tier: "gate", verify: " " }])[0])
138
+ .toMatch(/rule "a-rule" is a gate with no verify/);
139
+ });
140
+
141
+ test("rejects an unknown tier", () => {
142
+ expect(validateRegistry([{ ...base, tier: "urgent" as unknown as Rule["tier"] }])[0])
143
+ .toMatch(/rule "a-rule" has an unknown tier "urgent"/);
144
+ });
145
+
146
+ test("lets a craft rule carry a verify without demanding one", () => {
147
+ expect(validateRegistry([{ ...base, verify: "video stills --at all" }])).toEqual([]);
148
+ expect(validateRegistry([base])).toEqual([]);
149
+ });
150
+
151
+ test("every shipped rule declares a tier", () => {
152
+ expect(RULES.filter((r) => !r.tier).map((r) => r.id)).toEqual([]);
153
+ });
154
+ });
155
+
156
+ // Task 23: every rule the radar discharges names its owner - a radar check id
157
+ // (crossing the package boundary via the generated radar-check-ids.json, since
158
+ // craft cannot import video-cli) or the literal "coherence" for a rule the
159
+ // live coherence agent discharges instead of an automated check.
160
+ describe("owner", () => {
161
+ test("accepts an owner that is a known radar check id", () => {
162
+ expect(validateRegistry([{ ...base, owner: "hero-size" }], ["hero-size", "static-hold"])).toEqual([]);
163
+ });
164
+
165
+ test("accepts the literal coherence owner without consulting the check-id list", () => {
166
+ expect(validateRegistry([{ ...base, owner: "coherence" }], [])).toEqual([]);
167
+ });
168
+
169
+ test("rejects an owner id that is not in the committed check-id list", () => {
170
+ const errors = validateRegistry([{ ...base, owner: "not-a-real-check" }], ["hero-size"]);
171
+ expect(errors).toHaveLength(1);
172
+ expect(errors[0]).toMatch(/rule "a-rule" owner "not-a-real-check" is not a known radar check id or "coherence"/);
173
+ });
174
+
175
+ test("rejects an owner naming more than one check (a compound value is not a single owner)", () => {
176
+ const errors = validateRegistry([{ ...base, owner: "hero-size,static-hold" }], ["hero-size", "static-hold"]);
177
+ expect(errors).toHaveLength(1);
178
+ expect(errors[0]).toMatch(/owner "hero-size,static-hold" is not a known radar check id or "coherence"/);
179
+ });
180
+
181
+ test("allows two different rules to share one check as their owner", () => {
182
+ // brief-structure discharges several distinct claims from one run
183
+ // (missing thesis, missing audience, unarchived source) - one check
184
+ // legitimately owns more than one craft rule's obligation.
185
+ expect(validateRegistry(
186
+ [
187
+ { ...base, id: "one", owner: "brief-structure" },
188
+ { ...base, id: "two", owner: "brief-structure" },
189
+ ],
190
+ ["brief-structure"],
191
+ )).toEqual([]);
192
+ });
193
+
194
+ test("allows many rules to share the coherence owner", () => {
195
+ expect(validateRegistry(
196
+ [
197
+ { ...base, id: "one", owner: "coherence" },
198
+ { ...base, id: "two", owner: "coherence" },
199
+ ],
200
+ [],
201
+ )).toEqual([]);
202
+ });
203
+
204
+ test("a rule with no owner is unaffected", () => {
205
+ expect(validateRegistry([base], [])).toEqual([]);
206
+ });
207
+ });
@@ -0,0 +1,352 @@
1
+ import { animatedBarChartTightBox } from "./rules/animatedBarChartTightBox";
2
+ import { animatedBarChartWhenNotToUse } from "./rules/animatedBarChartWhenNotToUse";
3
+ import { animatedBarChartWhenToUse } from "./rules/animatedBarChartWhenToUse";
4
+ import { animatedLineChartWhenNotToUse } from "./rules/animatedLineChartWhenNotToUse";
5
+ import { animatedLineChartWhenToUse } from "./rules/animatedLineChartWhenToUse";
6
+ import { approvedSfxPaletteOnly } from "./rules/approvedSfxPaletteOnly";
7
+ import { atmosphereGradientsFadeAtBothEnds } from "./rules/atmosphereGradientsFadeAtBothEnds";
8
+ import { audioIsTheClock } from "./rules/audioIsTheClock";
9
+ import { beatsDeriveFromTheirOwnSequence } from "./rules/beatsDeriveFromTheirOwnSequence";
10
+ import { bestSceneSetsTheBar } from "./rules/bestSceneSetsTheBar";
11
+ import { bumpBeforeRefreeze } from "./rules/bumpBeforeRefreeze";
12
+ import { cameraIsTheNarrator } from "./rules/cameraIsTheNarrator";
13
+ import { checkPinBeforeShip } from "./rules/checkPinBeforeShip";
14
+ import { coldViewerClarity } from "./rules/coldViewerClarity";
15
+ import { commitOrCutEffects } from "./rules/commitOrCutEffects";
16
+ import { convertFramesAcrossClockBoundary } from "./rules/convertFramesAcrossClockBoundary";
17
+ import { crossCutElementsLiveInTheHudLayer } from "./rules/crossCutElementsLiveInTheHudLayer";
18
+ import { cursorClicksRealControls } from "./rules/cursorClicksRealControls";
19
+ import { cutOnRealAudioPauses } from "./rules/cutOnRealAudioPauses";
20
+ import { darkButNeverDim } from "./rules/darkButNeverDim";
21
+ import { dataRadarTagsDeclared } from "./rules/dataRadarTagsDeclared";
22
+ import { declareAVerticalZoneMap } from "./rules/declareAVerticalZoneMap";
23
+ import { deleteRejectedRigsCompletely } from "./rules/deleteRejectedRigsCompletely";
24
+ import { deriveSeamsDontMeasure } from "./rules/deriveSeamsDontMeasure";
25
+ import { deterministicSceneSource } from "./rules/deterministicSceneSource";
26
+ import { diegeticSoundForOnscreenActions } from "./rules/diegeticSoundForOnscreenActions";
27
+ import { distinctTransitionPerBeat } from "./rules/distinctTransitionPerBeat";
28
+ import { downloadOrDrawRichAssets } from "./rules/downloadOrDrawRichAssets";
29
+ import { elementOnscreenForItsVoSentence } from "./rules/elementOnscreenForItsVoSentence";
30
+ import { emitBuildNotes } from "./rules/emitBuildNotes";
31
+ import { energyFromTimingNotPartCount } from "./rules/energyFromTimingNotPartCount";
32
+ import { evidenceBeforeClaimingFixed } from "./rules/evidenceBeforeClaimingFixed";
33
+ import { exactPeerLockstep } from "./rules/exactPeerLockstep";
34
+ import { firstPassIsADraft } from "./rules/firstPassIsADraft";
35
+ import { fixCrowdingByRebandingNotNudging } from "./rules/fixCrowdingByRebandingNotNudging";
36
+ import { fixesPreserveApprovedElements } from "./rules/fixesPreserveApprovedElements";
37
+ import { floatingNotBoxed } from "./rules/floatingNotBoxed";
38
+ import { focusBeatsRecedeTheContext } from "./rules/focusBeatsRecedeTheContext";
39
+ import { fontVarsBrandRemocn } from "./rules/fontVarsBrandRemocn";
40
+ import { geometricSeamsNotCrossfades } from "./rules/geometricSeamsNotCrossfades";
41
+ import { geometrySidecarExportsTyped } from "./rules/geometrySidecarExportsTyped";
42
+ import { glowIsAFocalAccent } from "./rules/glowIsAFocalAccent";
43
+ import { heroFillsTheFrame } from "./rules/heroFillsTheFrame";
44
+ import { htmlMasksForAnimatedCounters } from "./rules/htmlMasksForAnimatedCounters";
45
+ import { impactStackOnPayoffs } from "./rules/impactStackOnPayoffs";
46
+ import { interactiveTargetsAreDeclaredData } from "./rules/interactiveTargetsAreDeclaredData";
47
+ import { interpolateLiteralKeysMonotonic } from "./rules/interpolateLiteralKeysMonotonic";
48
+ import { latenessIsStructuralOverlapTheBeats } from "./rules/latenessIsStructuralOverlapTheBeats";
49
+ import { lessTextMoreVisualizations } from "./rules/lessTextMoreVisualizations";
50
+ import { liveLinkDuplicateReactAlias } from "./rules/liveLinkDuplicateReactAlias";
51
+ import { make2dElements3d } from "./rules/make2dElements3d";
52
+ import { masterMixLoudnessTarget } from "./rules/masterMixLoudnessTarget";
53
+ import { measureAlignmentDontEyeball } from "./rules/measureAlignmentDontEyeball";
54
+ import { motionCarriesWeightAndMeaning } from "./rules/motionCarriesWeightAndMeaning";
55
+ import { motionDensityFloor } from "./rules/motionDensityFloor";
56
+ import { neverEditTierBVendored } from "./rules/neverEditTierBVendored";
57
+ import { neverRestateDerivedFrameNumbersInComments } from "./rules/neverRestateDerivedFrameNumbersInComments";
58
+ import { noAiGradientLook } from "./rules/noAiGradientLook";
59
+ import { noColoredFlashToHideACut } from "./rules/noColoredFlashToHideACut";
60
+ import { noDoubleFrameShift } from "./rules/noDoubleFrameShift";
61
+ import { noDuplicateMotionPrimitives } from "./rules/noDuplicateMotionPrimitives";
62
+ import { noLinearMotion } from "./rules/noLinearMotion";
63
+ import { noNestedSvgFilters } from "./rules/noNestedSvgFilters";
64
+ import { noRepeatedAssetAcrossShots } from "./rules/noRepeatedAssetAcrossShots";
65
+ import { noShorthandWithUndefinedLonghand } from "./rules/noShorthandWithUndefinedLonghand";
66
+ import { noStackedFadesAtACut } from "./rules/noStackedFadesAtACut";
67
+ import { noStaticHolds } from "./rules/noStaticHolds";
68
+ import { occlusionVisibleElements } from "./rules/occlusionVisibleElements";
69
+ import { oneContinuousVoTrack } from "./rules/oneContinuousVoTrack";
70
+ import { onePunchPerMomentClass } from "./rules/onePunchPerMomentClass";
71
+ import { oneRenderPerRound } from "./rules/oneRenderPerRound";
72
+ import { openingsAndClosingsCarryMore } from "./rules/openingsAndClosingsCarryMore";
73
+ import { openStaticAfterAHardCut } from "./rules/openStaticAfterAHardCut";
74
+ import { persistentDeviceFrames } from "./rules/persistentDeviceFrames";
75
+ import { pixelDiffAnEmptyBandFirst } from "./rules/pixelDiffAnEmptyBandFirst";
76
+ import { planIsPublishedNotGating } from "./rules/planIsPublishedNotGating";
77
+ import { poweredByRevolaWatermark } from "./rules/poweredByRevolaWatermark";
78
+ import { predrawContentPresentAtTheCut } from "./rules/predrawContentPresentAtTheCut";
79
+ import { premiumSurfacesNotFlatBorders } from "./rules/premiumSurfacesNotFlatBorders";
80
+ import { radarDispositionsAreHumanOnly } from "./rules/radarDispositionsAreHumanOnly";
81
+ import { realAssetsNotPlaceholders } from "./rules/realAssetsNotPlaceholders";
82
+ import { rebuildUiFromReconNotScreenshots } from "./rules/rebuildUiFromReconNotScreenshots";
83
+ import { reconStrandsRunInParallel } from "./rules/reconStrandsRunInParallel";
84
+ import { registryBeforeHandrolling } from "./rules/registryBeforeHandrolling";
85
+ import { remapRateEndpointFormula } from "./rules/remapRateEndpointFormula";
86
+ import { remocnContentTailwindChrome } from "./rules/remocnContentTailwindChrome";
87
+ import { retimeOnlyViaPlaybackOrDesignTokens } from "./rules/retimeOnlyViaPlaybackOrDesignTokens";
88
+ import { reviewTheRenderByFrameSweep } from "./rules/reviewTheRenderByFrameSweep";
89
+ import { rollingNumberBoundedSequence } from "./rules/rollingNumberBoundedSequence";
90
+ import { rollingNumberSequenceLayoutNone } from "./rules/rollingNumberSequenceLayoutNone";
91
+ import { rollingNumberTightBoxAlignment } from "./rules/rollingNumberTightBoxAlignment";
92
+ import { rollingNumberWhenNotToUse } from "./rules/rollingNumberWhenNotToUse";
93
+ import { rollingNumberWhenToUse } from "./rules/rollingNumberWhenToUse";
94
+ import { sameFamilySameProject } from "./rules/sameFamilySameProject";
95
+ import { sampleBrandFromProduct } from "./rules/sampleBrandFromProduct";
96
+ import { sampleVelocityFromPreviousFramePosition } from "./rules/sampleVelocityFromPreviousFramePosition";
97
+ import { sceneRootTagDeclared } from "./rules/sceneRootTagDeclared";
98
+ import { scrubDemoDataToGenericRoles } from "./rules/scrubDemoDataToGenericRoles";
99
+ import { selfReviewThePlanForGaps } from "./rules/selfReviewThePlanForGaps";
100
+ import { sourceArchivedBeforeBrief } from "./rules/sourceArchivedBeforeBrief";
101
+ import { statisticsSelfExplanatory } from "./rules/statisticsSelfExplanatory";
102
+ import { stopsAsRelativeSpacing } from "./rules/stopsAsRelativeSpacing";
103
+ import { themePerSection } from "./rules/themePerSection";
104
+ import { thesisDeclaredInBrief } from "./rules/thesisDeclaredInBrief";
105
+ import { theWholeCanvasWorks } from "./rules/theWholeCanvasWorks";
106
+ import { tierARequiresPropUnreachableEdit } from "./rules/tierARequiresPropUnreachableEdit";
107
+ import { tinyBodyCopyAsRuledLines } from "./rules/tinyBodyCopyAsRuledLines";
108
+ import { titlesPinnedOutsideCameraPush } from "./rules/titlesPinnedOutsideCameraPush";
109
+ import { tokensNotRawHexes } from "./rules/tokensNotRawHexes";
110
+ import { typeTokenDefaultedParamsAsString } from "./rules/typeTokenDefaultedParamsAsString";
111
+ import { varyKitChoreographyAcrossSections } from "./rules/varyKitChoreographyAcrossSections";
112
+ import { varyTheVisualizationGrammar } from "./rules/varyTheVisualizationGrammar";
113
+ import { verifyCheapRenderDear } from "./rules/verifyCheapRenderDear";
114
+ import { voAddressesTheViewer } from "./rules/voAddressesTheViewer";
115
+ import { voIsAPerformance } from "./rules/voIsAPerformance";
116
+ import { voScriptLocksSpokenWords } from "./rules/voScriptLocksSpokenWords";
117
+ import { wrappersPreserveChildGeometry } from "./rules/wrappersPreserveChildGeometry";
118
+ import type { Rule } from "./types";
119
+ // The check-id list crossing the video-cli/video-craft package boundary
120
+ // (Task 23): generated by `packages/video-cli/scripts/generate-radar-docs.ts`
121
+ // from CHECKS, drift-tested there, and read here as a plain JSON import - a
122
+ // static data import, not a runtime dependency on video-cli. Craft must never
123
+ // import video-cli (ts-morph has no place in the CLI, and the reverse
124
+ // boundary - waiverProjection.ts's waivers.generated.json - already runs the
125
+ // same mechanism the other direction).
126
+ // The `with { type: "json" }` attribute is load-bearing for the installed
127
+ // world: the dist transpile keeps this import as-is, and plain node refuses a
128
+ // JSON module without the attribute (tsx and vitest tolerate both forms).
129
+ import RADAR_CHECK_IDS from "./radar-check-ids.json" with { type: "json" };
130
+
131
+ /**
132
+ * Every durable craft rule. Rules are added by mining build notes, never by
133
+ * inventing them: a rule with no `learnedFrom` is an opinion, not knowledge.
134
+ */
135
+ export const RULES: Rule[] = [
136
+ animatedBarChartTightBox,
137
+ animatedBarChartWhenNotToUse,
138
+ animatedBarChartWhenToUse,
139
+ animatedLineChartWhenNotToUse,
140
+ animatedLineChartWhenToUse,
141
+ approvedSfxPaletteOnly,
142
+ atmosphereGradientsFadeAtBothEnds,
143
+ audioIsTheClock,
144
+ bestSceneSetsTheBar,
145
+ bumpBeforeRefreeze,
146
+ cameraIsTheNarrator,
147
+ checkPinBeforeShip,
148
+ coldViewerClarity,
149
+ commitOrCutEffects,
150
+ convertFramesAcrossClockBoundary,
151
+ crossCutElementsLiveInTheHudLayer,
152
+ cursorClicksRealControls,
153
+ cutOnRealAudioPauses,
154
+ darkButNeverDim,
155
+ dataRadarTagsDeclared,
156
+ declareAVerticalZoneMap,
157
+ deleteRejectedRigsCompletely,
158
+ deriveSeamsDontMeasure,
159
+ deterministicSceneSource,
160
+ diegeticSoundForOnscreenActions,
161
+ distinctTransitionPerBeat,
162
+ downloadOrDrawRichAssets,
163
+ elementOnscreenForItsVoSentence,
164
+ emitBuildNotes,
165
+ energyFromTimingNotPartCount,
166
+ evidenceBeforeClaimingFixed,
167
+ exactPeerLockstep,
168
+ firstPassIsADraft,
169
+ fixCrowdingByRebandingNotNudging,
170
+ fixesPreserveApprovedElements,
171
+ floatingNotBoxed,
172
+ fontVarsBrandRemocn,
173
+ glowIsAFocalAccent,
174
+ heroFillsTheFrame,
175
+ htmlMasksForAnimatedCounters,
176
+ interpolateLiteralKeysMonotonic,
177
+ latenessIsStructuralOverlapTheBeats,
178
+ lessTextMoreVisualizations,
179
+ liveLinkDuplicateReactAlias,
180
+ make2dElements3d,
181
+ masterMixLoudnessTarget,
182
+ motionCarriesWeightAndMeaning,
183
+ neverEditTierBVendored,
184
+ neverRestateDerivedFrameNumbersInComments,
185
+ noAiGradientLook,
186
+ noColoredFlashToHideACut,
187
+ noDoubleFrameShift,
188
+ noDuplicateMotionPrimitives,
189
+ noLinearMotion,
190
+ noNestedSvgFilters,
191
+ noRepeatedAssetAcrossShots,
192
+ noStackedFadesAtACut,
193
+ noStaticHolds,
194
+ occlusionVisibleElements,
195
+ oneContinuousVoTrack,
196
+ onePunchPerMomentClass,
197
+ oneRenderPerRound,
198
+ openingsAndClosingsCarryMore,
199
+ openStaticAfterAHardCut,
200
+ persistentDeviceFrames,
201
+ pixelDiffAnEmptyBandFirst,
202
+ planIsPublishedNotGating,
203
+ poweredByRevolaWatermark,
204
+ predrawContentPresentAtTheCut,
205
+ radarDispositionsAreHumanOnly,
206
+ realAssetsNotPlaceholders,
207
+ rebuildUiFromReconNotScreenshots,
208
+ reconStrandsRunInParallel,
209
+ registryBeforeHandrolling,
210
+ remapRateEndpointFormula,
211
+ remocnContentTailwindChrome,
212
+ retimeOnlyViaPlaybackOrDesignTokens,
213
+ rollingNumberBoundedSequence,
214
+ rollingNumberSequenceLayoutNone,
215
+ rollingNumberTightBoxAlignment,
216
+ rollingNumberWhenNotToUse,
217
+ rollingNumberWhenToUse,
218
+ sameFamilySameProject,
219
+ sampleBrandFromProduct,
220
+ sampleVelocityFromPreviousFramePosition,
221
+ sceneRootTagDeclared,
222
+ scrubDemoDataToGenericRoles,
223
+ selfReviewThePlanForGaps,
224
+ sourceArchivedBeforeBrief,
225
+ statisticsSelfExplanatory,
226
+ stopsAsRelativeSpacing,
227
+ themePerSection,
228
+ thesisDeclaredInBrief,
229
+ theWholeCanvasWorks,
230
+ tierARequiresPropUnreachableEdit,
231
+ tinyBodyCopyAsRuledLines,
232
+ titlesPinnedOutsideCameraPush,
233
+ tokensNotRawHexes,
234
+ typeTokenDefaultedParamsAsString,
235
+ varyKitChoreographyAcrossSections,
236
+ varyTheVisualizationGrammar,
237
+ verifyCheapRenderDear,
238
+ voAddressesTheViewer,
239
+ voIsAPerformance,
240
+ voScriptLocksSpokenWords,
241
+ beatsDeriveFromTheirOwnSequence,
242
+ focusBeatsRecedeTheContext,
243
+ geometricSeamsNotCrossfades,
244
+ geometrySidecarExportsTyped,
245
+ impactStackOnPayoffs,
246
+ interactiveTargetsAreDeclaredData,
247
+ measureAlignmentDontEyeball,
248
+ motionDensityFloor,
249
+ noShorthandWithUndefinedLonghand,
250
+ premiumSurfacesNotFlatBorders,
251
+ reviewTheRenderByFrameSweep,
252
+ wrappersPreserveChildGeometry,
253
+ ];
254
+
255
+ const TIERS = new Set<Rule["tier"]>(["gate", "craft", "process"]);
256
+ const KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
257
+
258
+ /** The literal owner value for a rule the live coherence agent discharges by
259
+ * judgement, rather than an automated radar check (e.g.
260
+ * `motion-density-floor`). Exempt from both the known-check-id check (it is
261
+ * never a check id) and the one-rule-per-owner uniqueness check (many rules
262
+ * can legitimately be "discharged by coherence review"). */
263
+ const COHERENCE_OWNER = "coherence";
264
+
265
+ export function validateRegistry(rules: Rule[], checkIds: string[] = RADAR_CHECK_IDS): string[] {
266
+ const errors: string[] = [];
267
+
268
+ const seen = new Set<string>();
269
+ for (const rule of rules) {
270
+ if (seen.has(rule.id)) errors.push(`duplicate rule id "${rule.id}"`);
271
+ seen.add(rule.id);
272
+
273
+ if (!KEBAB.test(rule.id)) errors.push(`id "${rule.id}" is not kebab-case`);
274
+ if (!rule.learnedFrom) errors.push(`rule "${rule.id}" has no learnedFrom`);
275
+ if (!rule.claim.trim()) errors.push(`rule "${rule.id}" has an empty claim`);
276
+ if (!rule.why.trim()) errors.push(`rule "${rule.id}" has an empty why`);
277
+
278
+ if (!TIERS.has(rule.tier)) {
279
+ errors.push(`rule "${rule.id}" has an unknown tier "${rule.tier}"`);
280
+ }
281
+ // A gate asserts that a violation blocks the ship, so something has to be
282
+ // able to discharge it. A manual procedure counts; nothing at all does not,
283
+ // because an unverifiable gate is indistinguishable from a strongly worded
284
+ // craft rule and gets treated as one.
285
+ if (rule.tier === "gate" && !rule.verify?.trim()) {
286
+ errors.push(`rule "${rule.id}" is a gate with no verify`);
287
+ }
288
+
289
+ if (rule.scope.kind === "component" && !KEBAB.test(rule.scope.slug)) {
290
+ errors.push(`component slug "${rule.scope.slug}" is not kebab-case`);
291
+ }
292
+
293
+ for (const ex of rule.exceptions ?? []) {
294
+ // An exception with a blank field is a waiver nobody can audit: the
295
+ // project tells you where it applies, the decision tells you what was
296
+ // chosen, and recordedIn tells you where to read the round that chose it.
297
+ if (!ex.project.trim() || !ex.decision.trim() || !ex.recordedIn.trim()) {
298
+ errors.push(`rule "${rule.id}" has an exception with an empty field`);
299
+ }
300
+ if (ex.recordedIn.startsWith("/") || ex.recordedIn.includes("\\")) {
301
+ errors.push(
302
+ `rule "${rule.id}": exception recordedIn "${ex.recordedIn}" is not a repo-relative POSIX path`,
303
+ );
304
+ }
305
+ }
306
+
307
+ const e = rule.enforcement;
308
+ if (e.kind === "prose" && "anchor" in e && "unanchorable" in e) {
309
+ // The two prose variants share a discriminant, so a composed object can
310
+ // carry both; that is ambiguous authorship, not a valid state.
311
+ errors.push(`rule "${rule.id}" carries both an anchor and an unanchorable reason`);
312
+ }
313
+ if (e.kind === "prose" && "unanchorable" in e && !(e as { unanchorable: string }).unanchorable.trim()) {
314
+ errors.push(`rule "${rule.id}" has an empty unanchorable reason`);
315
+ }
316
+ if (e.kind === "prose" && "anchor" in e) {
317
+ const { file } = e.anchor;
318
+ if (file.startsWith("/") || file.includes("\\")) {
319
+ errors.push(`rule "${rule.id}": anchor file "${file}" is not a repo-relative POSIX path`);
320
+ }
321
+ }
322
+ if (e.kind === "source" && e.check.include.length === 0) {
323
+ errors.push(`rule "${rule.id}" has no include globs`);
324
+ }
325
+
326
+ // One radar check can legitimately discharge more than one craft rule's
327
+ // obligation (brief-structure alone produces distinct missing-thesis,
328
+ // missing-audience, and unarchived-source claims), so ownership is
329
+ // validated one rule at a time - not tracked for cross-rule uniqueness.
330
+ if (rule.owner !== undefined && rule.owner !== COHERENCE_OWNER && !checkIds.includes(rule.owner)) {
331
+ errors.push(`rule "${rule.id}" owner "${rule.owner}" is not a known radar check id or "${COHERENCE_OWNER}"`);
332
+ }
333
+ }
334
+
335
+ // whenToUse and whenNotToUse are single strings in WS1's ComponentMeta, so a
336
+ // component can have at most one of each. This is a forcing function toward
337
+ // one crisp sentence rather than a paragraph that grows without review.
338
+ for (const field of ["whenToUse", "whenNotToUse"] as const) {
339
+ const counts = new Map<string, number>();
340
+ for (const rule of rules) {
341
+ if (rule.scope.kind !== "component" || rule.scope.field !== field) continue;
342
+ counts.set(rule.scope.slug, (counts.get(rule.scope.slug) ?? 0) + 1);
343
+ }
344
+ for (const [slug, count] of counts) {
345
+ if (count > 1) {
346
+ errors.push(`component "${slug}" has ${count} ${field} rules, expected at most 1`);
347
+ }
348
+ }
349
+ }
350
+
351
+ return errors;
352
+ }
@@ -0,0 +1,14 @@
1
+ import type { Rule } from "../types";
2
+
3
+ export const animatedBarChartTightBox: Rule = {
4
+ id: "animated-bar-chart-tight-box",
5
+ claim: "AnimatedBarChart fills its nearest positioned ancestor; give it a position: relative box sized to the intended width and height, and pass matching width/height props.",
6
+ why: "The component renders position: absolute inset 0 and centers its fixed-size SVG; in an unsized or oversized box the chart floats to the container's middle instead of sitting where the card design intends.",
7
+ learnedFrom: "revola-video/src/scenes/V2Beat4DashboardRemocn.tsx",
8
+ tier: "craft",
9
+ scope: { kind: "component", slug: "animated-bar-chart", field: "gotcha" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-kit/src/motion/animated-bar-chart.tsx" },
13
+ },
14
+ };
@@ -0,0 +1,14 @@
1
+ import type { Rule } from "../types";
2
+
3
+ export const animatedBarChartWhenNotToUse: Rule = {
4
+ id: "animated-bar-chart-when-not-to-use",
5
+ claim: "Do not use AnimatedBarChart for time-series trends (use the line chart), for a single headline number (use RollingNumber), or when bars must interleave with bespoke chrome inside the plot area.",
6
+ why: "A categorical bar race telling a trend story reads wrong, and the component owns its whole SVG plot area, so bespoke overlays inside it fight the layout.",
7
+ learnedFrom: "docs/superpowers/specs/2026-07-23-ws2-wave1-tier-a-seed-design.md",
8
+ tier: "craft",
9
+ scope: { kind: "component", slug: "animated-bar-chart", field: "whenNotToUse" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-kit/src/motion/animated-bar-chart.tsx" },
13
+ },
14
+ };
@@ -0,0 +1,14 @@
1
+ import type { Rule } from "../types";
2
+
3
+ export const animatedBarChartWhenToUse: Rule = {
4
+ id: "animated-bar-chart-when-to-use",
5
+ claim: "Use AnimatedBarChart for a single categorical comparison beat (attention, volume, ranking) where staggered bar growth IS the story.",
6
+ why: "It is the kit's owned bar chart: themed gradient bars, staggered spring growth, category and value labels; hand-rolling one repeats the pre-registry _lib sprawl.",
7
+ learnedFrom: "docs/superpowers/specs/2026-07-23-ws2-wave1-tier-a-seed-design.md",
8
+ tier: "craft",
9
+ scope: { kind: "component", slug: "animated-bar-chart", field: "whenToUse" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-kit/src/motion/animated-bar-chart.tsx" },
13
+ },
14
+ };
@@ -0,0 +1,14 @@
1
+ import type { Rule } from "../types";
2
+
3
+ export const animatedLineChartWhenNotToUse: Rule = {
4
+ id: "animated-line-chart-when-not-to-use",
5
+ claim: "Do not use AnimatedLineChart for categorical comparisons (use AnimatedBarChart) or a single headline number (use RollingNumber).",
6
+ why: "A trend-line draw-on telling a categorical comparison story reads wrong, and a single headline number does not need a plotted series at all.",
7
+ learnedFrom: "docs/superpowers/specs/2026-07-23-ws2-wave1-tier-a-seed-design.md",
8
+ tier: "craft",
9
+ scope: { kind: "component", slug: "animated-line-chart", field: "whenNotToUse" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-kit/src/motion/animated-line-chart.tsx" },
13
+ },
14
+ };
@@ -0,0 +1,14 @@
1
+ import type { Rule } from "../types";
2
+
3
+ export const animatedLineChartWhenToUse: Rule = {
4
+ id: "animated-line-chart-when-to-use",
5
+ claim: "Use AnimatedLineChart for a continuous trend over time (growth, decline, rate) where the draw-on reveal IS the story.",
6
+ why: "It is the kit's owned line chart: themed gradient stroke, spring-driven draw-on with a leading dot; hand-rolling one repeats the pre-registry _lib sprawl.",
7
+ learnedFrom: "docs/superpowers/specs/2026-07-23-ws2-wave1-tier-a-seed-design.md",
8
+ tier: "craft",
9
+ scope: { kind: "component", slug: "animated-line-chart", field: "whenToUse" },
10
+ enforcement: {
11
+ kind: "prose",
12
+ anchor: { file: "packages/video-kit/src/motion/animated-line-chart.tsx" },
13
+ },
14
+ };
@@ -0,0 +1,23 @@
1
+ import type { Rule } from "../types";
2
+
3
+ /**
4
+ * Mined from claude-sessions-digest/portfolio-reels.md and
5
+ * revola-craft-video.md: synthesized SFX (whoosh, reveal risers, success
6
+ * chimes, bling, fake typewriters) were rejected roughly eight times across
7
+ * projects ("I hate the whoosh sound"; "the success sound is annoying";
8
+ * "This is not the right typewriter effect"); only real recordings survived
9
+ * (asmr-typing.wav, the real keyboard recording, soft plops), with the
10
+ * direction "it should please the ear not irritate".
11
+ */
12
+ export const approvedSfxPaletteOnly: Rule = {
13
+ id: "approved-sfx-palette-only",
14
+ claim: "Source SFX only from the approved library of real recordings (ASMR typing, UI clicks, soft plops); synthesized whooshes, reveal risers, success chimes, and bling tones are banned, no single sound is overused, and SFX sit above the music in the mix.",
15
+ why: "Every synthesized or harsh sound was rejected on first listen, across three videos; the approved recordings were the only sounds that ever survived review.",
16
+ learnedFrom: "claude-sessions-digest/portfolio-reels.md",
17
+ tier: "craft",
18
+ scope: { kind: "pipeline" },
19
+ enforcement: {
20
+ kind: "prose",
21
+ unanchorable: "The rule is an asset-selection judgment over audio files; no code location expresses it.",
22
+ },
23
+ };