@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
package/dist/stages.js ADDED
@@ -0,0 +1,163 @@
1
+ const STAGES = {
2
+ // gates
3
+ "audio-is-the-clock": ["vo", "review", "ship"],
4
+ "check-pin-before-ship": ["ship"],
5
+ "data-radar-tags-declared": ["scene", "amplify", "review"],
6
+ "derive-seams-dont-measure": ["scene", "amplify", "review"],
7
+ "deterministic-scene-source": ["scene", "amplify", "review"],
8
+ "element-onscreen-for-its-vo-sentence": ["plan", "scene", "amplify", "review"],
9
+ "geometric-seams-not-crossfades": ["plan", "scene", "review"],
10
+ "geometry-sidecar-exports-typed": ["scene", "amplify", "review"],
11
+ "hero-fills-the-frame": ["scene", "amplify", "review"],
12
+ "interpolate-literal-keys-monotonic": ["scene", "amplify", "review"],
13
+ "master-mix-loudness-target": ["audio", "ship"],
14
+ "no-double-frame-shift": ["scene", "amplify", "review"],
15
+ "no-nested-svg-filters": ["scene", "amplify", "review"],
16
+ "no-shorthand-with-undefined-longhand": ["scene", "amplify", "review"],
17
+ "no-stacked-fades-at-a-cut": ["scene", "review"],
18
+ "no-static-holds": ["scene", "amplify", "review"],
19
+ "occlusion-visible-elements": ["scene", "amplify", "review"],
20
+ "predraw-content-present-at-the-cut": ["scene", "review"],
21
+ "radar-dispositions-are-human-only": ["review", "ship"],
22
+ "scene-root-tag-declared": ["scene", "amplify", "review"],
23
+ "source-archived-before-brief": ["recon", "review"],
24
+ "thesis-declared-in-brief": ["plan", "review"],
25
+ "tokens-not-raw-hexes": ["scene", "amplify", "review"],
26
+ // craft
27
+ "approved-sfx-palette-only": ["audio", "review"],
28
+ "atmosphere-gradients-fade-at-both-ends": ["scene", "amplify"],
29
+ "beats-derive-from-their-own-sequence": ["scene", "amplify"],
30
+ "camera-is-the-narrator": ["plan", "scene", "amplify", "review"],
31
+ "cold-viewer-clarity": ["plan", "scene", "review"],
32
+ "commit-or-cut-effects": ["scene", "amplify", "review"],
33
+ "convert-frames-across-clock-boundary": ["scene", "amplify"],
34
+ "cross-cut-elements-live-in-the-hud-layer": ["scene", "amplify"],
35
+ "cursor-clicks-real-controls": ["scene", "review"],
36
+ "cut-on-real-audio-pauses": ["plan", "vo"],
37
+ "dark-but-never-dim": ["scene", "amplify", "review"],
38
+ "declare-a-vertical-zone-map": ["scene", "amplify"],
39
+ "diegetic-sound-for-onscreen-actions": ["scene", "audio", "review"],
40
+ "distinct-transition-per-beat": ["scene", "amplify", "review"],
41
+ "download-or-draw-rich-assets": ["recon", "scene", "amplify", "review"],
42
+ "energy-from-timing-not-part-count": ["plan", "scene", "amplify", "review"],
43
+ "fix-crowding-by-rebanding-not-nudging": ["scene", "amplify"],
44
+ "floating-not-boxed": ["scene", "amplify", "review"],
45
+ "focus-beats-recede-the-context": ["scene", "amplify"],
46
+ "font-vars-brand-remocn": ["scene"],
47
+ "glow-is-a-focal-accent": ["scene", "amplify", "review"],
48
+ "html-masks-for-animated-counters": ["scene", "amplify"],
49
+ "impact-stack-on-payoffs": ["scene", "amplify"],
50
+ "interactive-targets-are-declared-data": ["plan", "scene"],
51
+ "lateness-is-structural-overlap-the-beats": ["plan", "scene", "amplify"],
52
+ "less-text-more-visualizations": ["plan", "scene", "amplify", "review"],
53
+ "make-2d-elements-3d": ["scene", "amplify", "review"],
54
+ "motion-carries-weight-and-meaning": ["scene", "amplify", "review"],
55
+ "motion-density-floor": ["scene", "amplify", "review"],
56
+ "never-restate-derived-frame-numbers-in-comments": ["scene", "amplify"],
57
+ "no-ai-gradient-look": ["scene", "amplify", "review"],
58
+ "no-colored-flash-to-hide-a-cut": ["scene", "review"],
59
+ "no-linear-motion": ["scene", "amplify", "review"],
60
+ "no-repeated-asset-across-shots": ["plan", "scene", "review"],
61
+ "one-continuous-vo-track": ["vo", "scene"],
62
+ "one-punch-per-moment-class": ["scene", "amplify", "review"],
63
+ "open-static-after-a-hard-cut": ["scene", "review"],
64
+ "openings-and-closings-carry-more": ["plan", "scene", "amplify", "review"],
65
+ "persistent-device-frames": ["plan", "scene"],
66
+ "premium-surfaces-not-flat-borders": ["scene", "amplify"],
67
+ "real-assets-not-placeholders": ["recon", "scene", "review"],
68
+ "rebuild-ui-from-recon-not-screenshots": ["recon", "scene"],
69
+ "remap-rate-endpoint-formula": ["scene"],
70
+ "remocn-content-tailwind-chrome": ["scene", "amplify"],
71
+ "retime-only-via-playback-or-design-tokens": ["scene", "vo"],
72
+ "sample-brand-from-product": ["recon", "scene"],
73
+ "sample-velocity-from-previous-frame-position": ["scene", "amplify"],
74
+ "scrub-demo-data-to-generic-roles": ["recon", "scene", "review"],
75
+ "statistics-self-explanatory": ["plan", "vo", "scene", "review"],
76
+ "stops-as-relative-spacing": ["scene"],
77
+ "the-whole-canvas-works": ["scene", "amplify", "review"],
78
+ "theme-per-section": ["plan", "scene"],
79
+ "tiny-body-copy-as-ruled-lines": ["scene"],
80
+ "titles-pinned-outside-camera-push": ["scene", "review"],
81
+ "type-token-defaulted-params-as-string": ["scene"],
82
+ "vary-kit-choreography-across-sections": ["plan", "scene", "amplify", "review"],
83
+ "vary-the-visualization-grammar": ["plan", "scene", "amplify", "review"],
84
+ "vo-addresses-the-viewer": ["plan", "vo"],
85
+ "vo-is-a-performance": ["vo", "review"],
86
+ "wrappers-preserve-child-geometry": ["scene", "amplify"],
87
+ // process
88
+ "best-scene-sets-the-bar": ["amplify", "review"],
89
+ "bump-before-refreeze": ["ship"],
90
+ "delete-rejected-rigs-completely": ["scene", "amplify"],
91
+ "emit-build-notes": ["review", "ship"],
92
+ // egw3: NOT in scene/amplify. Dispatched scene/fix/amplify agents are
93
+ // author-once - their task bodies command "typecheck only, no render, no
94
+ // stills" - so shipping a render-evidence rule in the same prompt made the
95
+ // two contradict; whichever the worker obeyed, it violated the other. The
96
+ // review/fix-round driver renders, so evidence rules live there.
97
+ "evidence-before-claiming-fixed": ["review"],
98
+ "exact-peer-lockstep": ["ship"],
99
+ "first-pass-is-a-draft": ["plan", "amplify"],
100
+ "fixes-preserve-approved-elements": ["scene", "amplify", "review"],
101
+ "live-link-duplicate-react-alias": ["scene"],
102
+ "measure-alignment-dont-eyeball": ["scene", "amplify", "review"],
103
+ "never-edit-tier-b-vendored": ["scene", "amplify"],
104
+ "no-duplicate-motion-primitives": ["scene"],
105
+ "one-render-per-round": ["review", "ship"],
106
+ "pixel-diff-an-empty-band-first": ["review"],
107
+ "plan-is-published-not-gating": ["plan"],
108
+ "powered-by-revola-watermark": ["plan", "scene", "review"],
109
+ "recon-strands-run-in-parallel": ["recon"],
110
+ "registry-before-handrolling": ["scene"],
111
+ "review-the-render-by-frame-sweep": ["review"],
112
+ "same-family-same-project": ["plan"],
113
+ "self-review-the-plan-for-gaps": ["plan"],
114
+ "tier-a-requires-prop-unreachable-edit": ["scene", "ship"],
115
+ // egw3: NOT in scene/amplify, same contradiction as
116
+ // evidence-before-claiming-fixed above - the rule's "run the full render
117
+ // only on demand" framing presumes a driver that CAN render.
118
+ "verify-cheap-render-dear": ["review", "ship"],
119
+ "vo-script-locks-spoken-words": ["plan", "vo"]
120
+ };
121
+ function validateStages(rules, stages) {
122
+ const errors = [];
123
+ const pipelineIds = new Set(rules.filter((r) => r.scope.kind === "pipeline").map((r) => r.id));
124
+ for (const rule of rules) {
125
+ if (rule.scope.kind !== "pipeline") continue;
126
+ const assigned = stages[rule.id];
127
+ if (!assigned || assigned.length === 0) {
128
+ errors.push(`pipeline rule "${rule.id}" has no stage assignment`);
129
+ continue;
130
+ }
131
+ for (const s of assigned) {
132
+ if (!ALL_STAGES.includes(s)) {
133
+ errors.push(`rule "${rule.id}" carries unknown stage "${s}"`);
134
+ }
135
+ }
136
+ if (rule.tier === "gate" && !assigned.includes("review") && !assigned.includes("ship")) {
137
+ errors.push(`gate "${rule.id}" carries neither "review" nor "ship"`);
138
+ }
139
+ }
140
+ for (const id of Object.keys(stages)) {
141
+ if (!pipelineIds.has(id)) {
142
+ errors.push(`stage entry "${id}" matches no pipeline rule`);
143
+ }
144
+ }
145
+ return errors;
146
+ }
147
+ const ALL_STAGES = [
148
+ "recon",
149
+ "plan",
150
+ "scene",
151
+ "amplify",
152
+ "review",
153
+ "vo",
154
+ "audio",
155
+ "ship"
156
+ ];
157
+ const _allStagesExhaustive = true;
158
+ void _allStagesExhaustive;
159
+ export {
160
+ ALL_STAGES,
161
+ STAGES,
162
+ validateStages
163
+ };
@@ -0,0 +1,6 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ const toolchainRepoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
4
+ export {
5
+ toolchainRepoRoot
6
+ };
package/dist/types.js ADDED
File without changes
@@ -0,0 +1,8 @@
1
+ const COMPONENT_VARIANTS = {
2
+ "rolling-number": ["odometer", "slam"],
3
+ "animated-bar-chart": ["grow", "cascade"],
4
+ "animated-line-chart": ["draw", "sweep"]
5
+ };
6
+ export {
7
+ COMPONENT_VARIANTS
8
+ };
@@ -0,0 +1,20 @@
1
+ function renderWaivers(rules) {
2
+ return rules.flatMap(
3
+ (rule) => (rule.exceptions ?? []).map((ex) => ({
4
+ ruleId: rule.id,
5
+ project: ex.project,
6
+ ...ex.element !== void 0 ? { element: ex.element } : {},
7
+ ...ex.seam !== void 0 ? { seam: ex.seam } : {},
8
+ recordedIn: ex.recordedIn
9
+ }))
10
+ );
11
+ }
12
+ const sortKey = (w) => `${w.ruleId ?? w.checkId ?? ""}:${w.project}:${w.element ?? ""}:${w.seam ?? ""}`;
13
+ function renderWaiversJson(rules) {
14
+ const sorted = [...renderWaivers(rules)].sort((a, b) => sortKey(a) < sortKey(b) ? -1 : sortKey(a) > sortKey(b) ? 1 : 0);
15
+ return JSON.stringify(sorted, null, 2) + "\n";
16
+ }
17
+ export {
18
+ renderWaivers,
19
+ renderWaiversJson
20
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@revola-ai/video-craft",
3
+ "version": "0.2.1",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "exports": {
7
+ "./corpus": "./src/corpus.ts",
8
+ "./projectDocSync": "./src/projectDocSync.ts",
9
+ "./types": "./src/types.ts",
10
+ "./dist/*": "./dist/*"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "dist"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20.10"
18
+ },
19
+ "scripts": {
20
+ "build": "node scripts/build-dist.mjs",
21
+ "prepack": "npm run build",
22
+ "prepublishOnly": "node -e \"if (process.env.REVOLA_RELEASE !== '1') { console.error('publish only through npm run release:publish'); process.exit(1) }\""
23
+ },
24
+ "dependencies": {
25
+ "ts-morph": "^24.0.0",
26
+ "tinyglobby": "^0.2.10",
27
+ "yaml": "^2.7.0"
28
+ },
29
+ "devDependencies": {
30
+ "esbuild": "0.28.1"
31
+ }
32
+ }
@@ -0,0 +1,119 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { verifyAnchors } from "./anchors";
3
+ import { toolchainRepoRoot } from "./testSupport";
4
+ import { RULES } from "./registry";
5
+ import type { Rule } from "./types";
6
+
7
+ const prose = (anchor: Rule["enforcement"]): Rule => ({
8
+ id: "a-rule",
9
+ claim: "Do the thing.",
10
+ why: "Otherwise the other thing breaks.",
11
+ learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
12
+ tier: "craft",
13
+ scope: { kind: "pipeline" },
14
+ enforcement: anchor,
15
+ });
16
+
17
+ describe("verifyAnchors", () => {
18
+ test("accepts an anchor whose file and symbol both exist", () => {
19
+ const rule = prose({
20
+ kind: "prose",
21
+ anchor: { file: "packages/video-craft/src/provenance.ts", symbol: "isUpstreamProvenance" },
22
+ });
23
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
24
+ });
25
+
26
+ test("accepts a file-only anchor", () => {
27
+ const rule = prose({
28
+ kind: "prose",
29
+ anchor: { file: "packages/video-craft/src/provenance.ts" },
30
+ });
31
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
32
+ });
33
+
34
+ test("reports a missing file", () => {
35
+ const rule = prose({
36
+ kind: "prose",
37
+ anchor: { file: "packages/video-craft/src/Deleted.tsx" },
38
+ });
39
+ expect(verifyAnchors([rule], toolchainRepoRoot)[0]).toMatch(
40
+ /rule "a-rule": anchor file "packages\/video-craft\/src\/Deleted.tsx" does not exist/,
41
+ );
42
+ });
43
+
44
+ test("reports a missing symbol in a file that still exists", () => {
45
+ const rule = prose({
46
+ kind: "prose",
47
+ anchor: { file: "packages/video-craft/src/provenance.ts", symbol: "renamedAway" },
48
+ });
49
+ expect(verifyAnchors([rule], toolchainRepoRoot)[0]).toMatch(
50
+ /rule "a-rule": anchor symbol "renamedAway" no longer exists in "packages\/video-craft\/src\/provenance.ts"/,
51
+ );
52
+ });
53
+
54
+ test("ignores unanchorable rules", () => {
55
+ const rule = prose({ kind: "prose", unanchorable: "process guidance" });
56
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
57
+ });
58
+
59
+ test("reports a learnedFrom file that does not exist", () => {
60
+ const rule = {
61
+ ...prose({ kind: "prose", unanchorable: "process guidance" }),
62
+ learnedFrom: "packages/video-craft/DELETED_NOTES.md",
63
+ };
64
+ expect(verifyAnchors([rule], toolchainRepoRoot)[0]).toMatch(
65
+ /rule "a-rule": learnedFrom "packages\/video-craft\/DELETED_NOTES.md" does not exist/,
66
+ );
67
+ });
68
+
69
+ test("ignores source rules", () => {
70
+ const rule: Rule = {
71
+ ...prose({ kind: "prose", unanchorable: "x" }),
72
+ enforcement: { kind: "source", check: { include: [], run: () => [] } },
73
+ };
74
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
75
+ });
76
+ });
77
+
78
+ describe("upstream provenance (extracted from remotion-videos)", () => {
79
+ test("accepts a learnedFrom citing an upstream project without touching disk", () => {
80
+ // The prose helper's learnedFrom already cites revola-video, which stayed
81
+ // upstream; a clean run proves it is treated as a citation, not a path.
82
+ const rule = prose({ kind: "prose", unanchorable: "process guidance" });
83
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
84
+ });
85
+
86
+ test("accepts an upstream anchor file and skips its symbol", () => {
87
+ const rule = prose({
88
+ kind: "prose",
89
+ anchor: { file: "revola-video/src/scenes/craft/ink.tsx", symbol: "wobbled" },
90
+ });
91
+ expect(verifyAnchors([rule], toolchainRepoRoot)).toEqual([]);
92
+ });
93
+
94
+ test("rejects a malformed upstream citation", () => {
95
+ const rule = {
96
+ ...prose({ kind: "prose", unanchorable: "process guidance" }),
97
+ learnedFrom: "revola-video/../secrets.md",
98
+ };
99
+ expect(verifyAnchors([rule], toolchainRepoRoot)[0]).toMatch(
100
+ /rule "a-rule": learnedFrom "revola-video\/\.\.\/secrets.md" is a malformed upstream citation/,
101
+ );
102
+ });
103
+
104
+ test("qse-qguard-video moved with this repo, so its paths must still resolve", () => {
105
+ const rule = {
106
+ ...prose({ kind: "prose", unanchorable: "process guidance" }),
107
+ learnedFrom: "qse-qguard-video/DELETED_NOTES.md",
108
+ };
109
+ expect(verifyAnchors([rule], toolchainRepoRoot)[0]).toMatch(
110
+ /rule "a-rule": learnedFrom "qse-qguard-video\/DELETED_NOTES.md" does not exist/,
111
+ );
112
+ });
113
+ });
114
+
115
+ describe("the real registry", () => {
116
+ test("every prose anchor resolves", () => {
117
+ expect(verifyAnchors(RULES, toolchainRepoRoot)).toEqual([]);
118
+ });
119
+ });
package/src/anchors.ts ADDED
@@ -0,0 +1,110 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { Project, ts } from "ts-morph";
4
+ import { isUpstreamProvenance, upstreamCitationError } from "./provenance";
5
+ import type { Rule } from "./types";
6
+
7
+ /**
8
+ * Resolves every prose rule's anchor, and every rule's learnedFrom provenance.
9
+ * A dangling anchor means the rule is making a claim about code that no longer
10
+ * exists, which is the exact failure mode that turned BUILD_NOTES into a
11
+ * document you cannot trust. A dangling learnedFrom means the rule cites an
12
+ * origin that is gone - and a rule with no origin is an opinion.
13
+ *
14
+ * Paths under UPSTREAM_PROVENANCE_ROOTS cite the pre-split remotion-videos
15
+ * repo and are format-checked instead of resolved on disk; everything else
16
+ * must exist here, and a missing local path is still a failure.
17
+ */
18
+
19
+ // A provenance path either cites upstream (format check only) or must
20
+ // resolve locally. Returns the failure reason, or undefined when the path
21
+ // is acceptable.
22
+ function provenanceError(path: string, root: string): string | undefined {
23
+ if (isUpstreamProvenance(path)) {
24
+ const malformed = upstreamCitationError(path);
25
+ return malformed ? `is a malformed upstream citation (${malformed})` : undefined;
26
+ }
27
+ return existsSync(join(root, path)) ? undefined : "does not exist";
28
+ }
29
+
30
+ export function verifyAnchors(rules: Rule[], root: string): string[] {
31
+ const errors: string[] = [];
32
+
33
+ // One Project for the whole pass; adding a file is cheap, constructing a
34
+ // compiler host is not.
35
+ const project = new Project({
36
+ useInMemoryFileSystem: false,
37
+ skipAddingFilesFromTsConfig: true,
38
+ compilerOptions: { allowJs: true, jsx: ts.JsxEmit.ReactJSX },
39
+ });
40
+
41
+ for (const rule of rules) {
42
+ if (rule.learnedFrom) {
43
+ const bad = provenanceError(rule.learnedFrom, root);
44
+ if (bad) {
45
+ errors.push(`rule "${rule.id}": learnedFrom "${rule.learnedFrom}" ${bad}`);
46
+ }
47
+ }
48
+
49
+ // Same failure mode as a dangling learnedFrom: a waiver citing a decision
50
+ // record that is gone is an opinion wearing a client's name.
51
+ for (const ex of rule.exceptions ?? []) {
52
+ const badRecord = provenanceError(ex.recordedIn, root);
53
+ if (badRecord) {
54
+ errors.push(
55
+ `rule "${rule.id}": exception recordedIn "${ex.recordedIn}" ${badRecord}`,
56
+ );
57
+ }
58
+ const badProject = provenanceError(ex.project, root);
59
+ if (badProject) {
60
+ errors.push(`rule "${rule.id}": exception project "${ex.project}" ${badProject}`);
61
+ }
62
+ }
63
+
64
+ if (rule.enforcement.kind !== "prose") continue;
65
+ if (!("anchor" in rule.enforcement)) continue;
66
+
67
+ const { file, symbol } = rule.enforcement.anchor;
68
+
69
+ // An upstream anchor file cannot be opened here, so its symbol cannot be
70
+ // resolved either; the citation's shape is the whole check.
71
+ if (isUpstreamProvenance(file)) {
72
+ const malformed = upstreamCitationError(file);
73
+ if (malformed) {
74
+ errors.push(
75
+ `rule "${rule.id}": anchor file "${file}" is a malformed upstream citation (${malformed})`,
76
+ );
77
+ }
78
+ continue;
79
+ }
80
+
81
+ const abs = join(root, file);
82
+
83
+ if (!existsSync(abs)) {
84
+ errors.push(`rule "${rule.id}": anchor file "${file}" does not exist`);
85
+ continue;
86
+ }
87
+ if (!symbol) continue;
88
+
89
+ // Add-or-get: two rules may anchor into the same file, and re-adding a
90
+ // path must not depend on ts-morph's re-add semantics.
91
+ const source = project.getSourceFile(abs) ?? project.addSourceFileAtPath(abs);
92
+ // Exported declarations cover the single-source-of-truth exports these
93
+ // rules point at. A top-level non-exported name is also accepted, because
94
+ // some anchors are internal invariants rather than public surface.
95
+ const exported = source.getExportedDeclarations().has(symbol);
96
+ const local = source.getVariableDeclaration(symbol) !== undefined
97
+ || source.getFunction(symbol) !== undefined
98
+ || source.getInterface(symbol) !== undefined
99
+ || source.getTypeAlias(symbol) !== undefined;
100
+
101
+ if (!exported && !local) {
102
+ errors.push(
103
+ `rule "${rule.id}": anchor symbol "${symbol}" no longer exists in "${file}"` +
104
+ ` - re-anchor the rule to where the evidence now lives; do not flip it to unanchorable`,
105
+ );
106
+ }
107
+ }
108
+
109
+ return errors;
110
+ }
@@ -0,0 +1,96 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { corpusFindings, KNOWN_EXCEPTIONS } from "./corpus";
3
+ import { isUpstreamProvenance } from "./provenance";
4
+ import { RULES } from "./registry";
5
+ import { matchedFiles } from "./sourceCheck";
6
+ import { toolchainRepoRoot } from "./testSupport";
7
+
8
+ const key = (f: { rule: string; file: string; match: string }) => `${f.rule}|${f.file}|${f.match}`;
9
+
10
+ const REMEDIATE =
11
+ "Narrow the rule, add a justified KNOWN_EXCEPTIONS entry in src/corpus.ts naming a recorded " +
12
+ "follow-up, or fix the flagged video in a separate change. See the package README.";
13
+
14
+ /**
15
+ * Rule ids allowed to match zero files today. This test's whole purpose is
16
+ * catching a typo'd include glob that silently checks nothing and looks like
17
+ * coverage, so this list must stay tiny and each entry must be a genuine
18
+ * forward-declared convention, not a typo in hiding.
19
+ *
20
+ * `geometry-sidecar-exports-typed` (Task 6 of
21
+ * docs/superpowers/plans/2026-07-30-video-task-graph-phase1.md) checks each
22
+ * project's "src/scenes/scene.<id>.geometry.ts" sidecars (see the rule's own
23
+ * `include`) - a convention this same task introduces
24
+ * (packages/video-kit/src/pure/geometry.ts's `geometrySidecarPath`) with no
25
+ * adopters yet; scene authoring against it is later work (Tasks 3, 11, 18).
26
+ * Remove this entry once any project ships its first sidecar.
27
+ */
28
+ const NOT_YET_ADOPTED = new Set(["geometry-sidecar-exports-typed"]);
29
+
30
+ /** The top-level directory a glob reaches. */
31
+ const globHead = (glob: string): string => {
32
+ // "docs/superpowers"-style upstream roots are two segments deep, so match
33
+ // against the provenance list with the longest prefix the glob spells out
34
+ // literally (a `*`/`**` head is never upstream - it reaches local files).
35
+ const segments = glob.split("/");
36
+ return segments[0] === "docs" && segments.length > 1 ? `${segments[0]}/${segments[1]}` : segments[0]!;
37
+ };
38
+
39
+ /** A rule whose every include glob points into upstream project roots has
40
+ * nothing to scan in THIS repo - its corpus stayed in remotion-videos at the
41
+ * 2026-08-24 extraction. It is not a typo'd include list; scanning those
42
+ * files again in consumer repos is the `craft:check --project` seam. */
43
+ const upstreamOnlyRule = (include: string[]): boolean =>
44
+ include.length > 0 && include.every((glob) => isUpstreamProvenance(globHead(glob)));
45
+
46
+ describe("source checks against the existing scene corpus", () => {
47
+ test("every source rule's globs match at least one file", () => {
48
+ // A typo'd include list silently checks nothing and looks like coverage.
49
+ const empty = RULES.filter(
50
+ (r) =>
51
+ r.enforcement.kind === "source" &&
52
+ !NOT_YET_ADOPTED.has(r.id) &&
53
+ !upstreamOnlyRule(r.enforcement.check.include) &&
54
+ matchedFiles(r.enforcement.check, toolchainRepoRoot).length === 0,
55
+ );
56
+ expect(empty.map((r) => r.id)).toEqual([]);
57
+ });
58
+
59
+ test("every finding is a justified known exception", () => {
60
+ const excused = new Set(KNOWN_EXCEPTIONS.map(key));
61
+ const unexplained = corpusFindings(toolchainRepoRoot).filter((f) => !excused.has(key(f)));
62
+
63
+ // Printed in full so a failure tells you what to look at rather than just
64
+ // that a count moved.
65
+ expect(
66
+ unexplained.map((f) => `${f.rule} ${f.file}:${f.line} [${f.match}] - ${f.message}`),
67
+ REMEDIATE,
68
+ ).toEqual([]);
69
+ // corpusFindings(toolchainRepoRoot) parses every source-check rule's matched files with a
70
+ // fresh ts-morph Project (one per rule) across ~10 project layouts;
71
+ // vitest's 30s default is comfortably enough on an idle machine but flakes
72
+ // under concurrent load (observed 40-90s wall time with other heavy
73
+ // processes running). Generous, like the browser/spawning tests
74
+ // (global-constraints.md) - this is real ts-morph work, not a hang.
75
+ }, 120_000);
76
+
77
+ test("every known exception carries a justification", () => {
78
+ expect(KNOWN_EXCEPTIONS.filter((e) => !e.justification.trim())).toEqual([]);
79
+ });
80
+
81
+ test("no known exception is stale", () => {
82
+ const live = new Set(corpusFindings(toolchainRepoRoot).map(key));
83
+ // Exceptions keyed to upstream files cannot yield findings here (the
84
+ // files stayed in remotion-videos), so staleness is only checkable for
85
+ // local files. The upstream entries stay in the data on purpose: the
86
+ // rules travel to consumer repos where those files DO exist, and the
87
+ // excuses must travel with them.
88
+ const stale = KNOWN_EXCEPTIONS.filter(
89
+ (e) => !isUpstreamProvenance(e.file) && !live.has(key(e)),
90
+ );
91
+ expect(
92
+ stale.map((e) => `${e.rule} ${e.file} [${e.match}]`),
93
+ "A stale entry usually means the matched text was fixed or rewritten - delete the entry or re-key it to the new text.",
94
+ ).toEqual([]);
95
+ }, 120_000); // same corpusFindings(toolchainRepoRoot) sweep cost as the test above
96
+ });