@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.
- package/README.md +56 -0
- package/dist/anchors.js +69 -0
- package/dist/corpus.js +91 -0
- package/dist/projectDoc.js +83 -0
- package/dist/projectDocSync.js +39 -0
- package/dist/projectMeta.js +37 -0
- package/dist/provenance.js +41 -0
- package/dist/radar-check-ids.json +34 -0
- package/dist/registry.js +307 -0
- package/dist/rules/animatedBarChartTightBox.js +15 -0
- package/dist/rules/animatedBarChartWhenNotToUse.js +15 -0
- package/dist/rules/animatedBarChartWhenToUse.js +15 -0
- package/dist/rules/animatedLineChartWhenNotToUse.js +15 -0
- package/dist/rules/animatedLineChartWhenToUse.js +15 -0
- package/dist/rules/approvedSfxPaletteOnly.js +15 -0
- package/dist/rules/atmosphereGradientsFadeAtBothEnds.js +15 -0
- package/dist/rules/audioIsTheClock.js +16 -0
- package/dist/rules/beatsDeriveFromTheirOwnSequence.js +15 -0
- package/dist/rules/bestSceneSetsTheBar.js +15 -0
- package/dist/rules/bumpBeforeRefreeze.js +15 -0
- package/dist/rules/cameraIsTheNarrator.js +15 -0
- package/dist/rules/checkPinBeforeShip.js +16 -0
- package/dist/rules/coldViewerClarity.js +15 -0
- package/dist/rules/commitOrCutEffects.js +29 -0
- package/dist/rules/convertFramesAcrossClockBoundary.js +15 -0
- package/dist/rules/crossCutElementsLiveInTheHudLayer.js +15 -0
- package/dist/rules/cursorClicksRealControls.js +15 -0
- package/dist/rules/cutOnRealAudioPauses.js +15 -0
- package/dist/rules/darkButNeverDim.js +15 -0
- package/dist/rules/dataRadarTagsDeclared.js +193 -0
- package/dist/rules/declareAVerticalZoneMap.js +15 -0
- package/dist/rules/deleteRejectedRigsCompletely.js +15 -0
- package/dist/rules/deriveSeamsDontMeasure.js +17 -0
- package/dist/rules/deterministicSceneSource.js +65 -0
- package/dist/rules/diegeticSoundForOnscreenActions.js +15 -0
- package/dist/rules/distinctTransitionPerBeat.js +15 -0
- package/dist/rules/downloadOrDrawRichAssets.js +15 -0
- package/dist/rules/elementOnscreenForItsVoSentence.js +17 -0
- package/dist/rules/emitBuildNotes.js +15 -0
- package/dist/rules/energyFromTimingNotPartCount.js +15 -0
- package/dist/rules/evidenceBeforeClaimingFixed.js +15 -0
- package/dist/rules/exactPeerLockstep.js +15 -0
- package/dist/rules/firstPassIsADraft.js +15 -0
- package/dist/rules/fixCrowdingByRebandingNotNudging.js +15 -0
- package/dist/rules/fixesPreserveApprovedElements.js +15 -0
- package/dist/rules/floatingNotBoxed.js +15 -0
- package/dist/rules/focusBeatsRecedeTheContext.js +15 -0
- package/dist/rules/fontVarsBrandRemocn.js +15 -0
- package/dist/rules/geometricSeamsNotCrossfades.js +23 -0
- package/dist/rules/geometrySidecarExportsTyped.js +36 -0
- package/dist/rules/glowIsAFocalAccent.js +22 -0
- package/dist/rules/heroFillsTheFrame.js +17 -0
- package/dist/rules/htmlMasksForAnimatedCounters.js +15 -0
- package/dist/rules/impactStackOnPayoffs.js +15 -0
- package/dist/rules/interactiveTargetsAreDeclaredData.js +15 -0
- package/dist/rules/interpolateLiteralKeysMonotonic.js +79 -0
- package/dist/rules/latenessIsStructuralOverlapTheBeats.js +15 -0
- package/dist/rules/lessTextMoreVisualizations.js +15 -0
- package/dist/rules/liveLinkDuplicateReactAlias.js +15 -0
- package/dist/rules/make2dElements3d.js +15 -0
- package/dist/rules/masterMixLoudnessTarget.js +17 -0
- package/dist/rules/measureAlignmentDontEyeball.js +15 -0
- package/dist/rules/motionCarriesWeightAndMeaning.js +15 -0
- package/dist/rules/motionDensityFloor.js +15 -0
- package/dist/rules/neverEditTierBVendored.js +15 -0
- package/dist/rules/neverRestateDerivedFrameNumbersInComments.js +15 -0
- package/dist/rules/noAiGradientLook.js +15 -0
- package/dist/rules/noColoredFlashToHideACut.js +22 -0
- package/dist/rules/noDoubleFrameShift.js +126 -0
- package/dist/rules/noDuplicateMotionPrimitives.js +15 -0
- package/dist/rules/noLinearMotion.js +22 -0
- package/dist/rules/noNestedSvgFilters.js +93 -0
- package/dist/rules/noRepeatedAssetAcrossShots.js +15 -0
- package/dist/rules/noShorthandWithUndefinedLonghand.js +93 -0
- package/dist/rules/noStackedFadesAtACut.js +24 -0
- package/dist/rules/noStaticHolds.js +17 -0
- package/dist/rules/occlusionVisibleElements.js +17 -0
- package/dist/rules/oneContinuousVoTrack.js +15 -0
- package/dist/rules/onePunchPerMomentClass.js +15 -0
- package/dist/rules/oneRenderPerRound.js +15 -0
- package/dist/rules/openStaticAfterAHardCut.js +22 -0
- package/dist/rules/openingsAndClosingsCarryMore.js +15 -0
- package/dist/rules/persistentDeviceFrames.js +15 -0
- package/dist/rules/pixelDiffAnEmptyBandFirst.js +15 -0
- package/dist/rules/planIsPublishedNotGating.js +15 -0
- package/dist/rules/poweredByRevolaWatermark.js +15 -0
- package/dist/rules/predrawContentPresentAtTheCut.js +17 -0
- package/dist/rules/premiumSurfacesNotFlatBorders.js +15 -0
- package/dist/rules/radarDispositionsAreHumanOnly.js +17 -0
- package/dist/rules/realAssetsNotPlaceholders.js +15 -0
- package/dist/rules/rebuildUiFromReconNotScreenshots.js +15 -0
- package/dist/rules/reconStrandsRunInParallel.js +15 -0
- package/dist/rules/registryBeforeHandrolling.js +15 -0
- package/dist/rules/remapRateEndpointFormula.js +15 -0
- package/dist/rules/remocnContentTailwindChrome.js +15 -0
- package/dist/rules/retimeOnlyViaPlaybackOrDesignTokens.js +15 -0
- package/dist/rules/reviewTheRenderByFrameSweep.js +15 -0
- package/dist/rules/rollingNumberBoundedSequence.js +17 -0
- package/dist/rules/rollingNumberSequenceLayoutNone.js +16 -0
- package/dist/rules/rollingNumberTightBoxAlignment.js +15 -0
- package/dist/rules/rollingNumberWhenNotToUse.js +17 -0
- package/dist/rules/rollingNumberWhenToUse.js +17 -0
- package/dist/rules/sameFamilySameProject.js +15 -0
- package/dist/rules/sampleBrandFromProduct.js +15 -0
- package/dist/rules/sampleVelocityFromPreviousFramePosition.js +15 -0
- package/dist/rules/sceneRootTagDeclared.js +41 -0
- package/dist/rules/scrubDemoDataToGenericRoles.js +15 -0
- package/dist/rules/selfReviewThePlanForGaps.js +15 -0
- package/dist/rules/sourceArchivedBeforeBrief.js +17 -0
- package/dist/rules/statisticsSelfExplanatory.js +15 -0
- package/dist/rules/stopsAsRelativeSpacing.js +15 -0
- package/dist/rules/theWholeCanvasWorks.js +15 -0
- package/dist/rules/themePerSection.js +22 -0
- package/dist/rules/thesisDeclaredInBrief.js +17 -0
- package/dist/rules/tierARequiresPropUnreachableEdit.js +15 -0
- package/dist/rules/tinyBodyCopyAsRuledLines.js +15 -0
- package/dist/rules/titlesPinnedOutsideCameraPush.js +15 -0
- package/dist/rules/tokensNotRawHexes.js +60 -0
- package/dist/rules/typeTokenDefaultedParamsAsString.js +15 -0
- package/dist/rules/varyKitChoreographyAcrossSections.js +15 -0
- package/dist/rules/varyTheVisualizationGrammar.js +15 -0
- package/dist/rules/verifyCheapRenderDear.js +15 -0
- package/dist/rules/voAddressesTheViewer.js +15 -0
- package/dist/rules/voIsAPerformance.js +15 -0
- package/dist/rules/voScriptLocksSpokenWords.js +15 -0
- package/dist/rules/wrappersPreserveChildGeometry.js +15 -0
- package/dist/sourceCheck.js +47 -0
- package/dist/sourceCitations.js +68 -0
- package/dist/stageProjection.js +69 -0
- package/dist/stages.js +163 -0
- package/dist/testSupport.js +6 -0
- package/dist/types.js +0 -0
- package/dist/variants.js +8 -0
- package/dist/waiverProjection.js +20 -0
- package/package.json +32 -0
- package/src/anchors.test.ts +119 -0
- package/src/anchors.ts +110 -0
- package/src/corpus.test.ts +96 -0
- package/src/corpus.ts +149 -0
- package/src/corpusProject.test.ts +103 -0
- package/src/creativeEdge.test.ts +58 -0
- package/src/distBuild.test.ts +86 -0
- package/src/docStatus.test.ts +81 -0
- package/src/drift.test.ts +124 -0
- package/src/fixtures/data-radar-dynamic/Branches.tsx +14 -0
- package/src/fixtures/data-radar-dynamic/plan.fixture.yaml +16 -0
- package/src/fixtures/data-radar-forwarded/Scene.tsx +19 -0
- package/src/fixtures/data-radar-forwarded/plan.fixture.yaml +13 -0
- package/src/fixtures/data-radar-opaque/Scene.tsx +15 -0
- package/src/fixtures/data-radar-opaque/plan.fixture.yaml +10 -0
- package/src/fixtures/data-radar-tags/SceneClean.tsx +10 -0
- package/src/fixtures/data-radar-tags/SceneNullBinding.tsx +12 -0
- package/src/fixtures/data-radar-tags/SceneWithGap.tsx +11 -0
- package/src/fixtures/data-radar-tags/plan.fixture.yaml +29 -0
- package/src/fixtures/deterministic-scene.tsx +15 -0
- package/src/fixtures/double-shift/aliased.tsx +13 -0
- package/src/fixtures/double-shift/assigned.tsx +13 -0
- package/src/fixtures/double-shift/clean.tsx +19 -0
- package/src/fixtures/double-shift/destructured.tsx +12 -0
- package/src/fixtures/double-shift/elementAccess.tsx +12 -0
- package/src/fixtures/double-shift/layout.ts +24 -0
- package/src/fixtures/double-shift/mechanism-prefix.tsx.txt +458 -0
- package/src/fixtures/double-shift/wrapped.tsx +15 -0
- package/src/fixtures/geometry-sidecar-missing-export.ts +10 -0
- package/src/fixtures/geometry-sidecar-missing-type.ts +7 -0
- package/src/fixtures/geometry-sidecar-valid.ts +6 -0
- package/src/fixtures/interpolate-ascending.tsx +6 -0
- package/src/fixtures/interpolate-descending.tsx +8 -0
- package/src/fixtures/interpolate-identifier-keys.tsx +9 -0
- package/src/fixtures/nested-filters-broken.tsx +11 -0
- package/src/fixtures/nested-filters-fixed.tsx +8 -0
- package/src/fixtures/nested-filters-html-wrapper.tsx +14 -0
- package/src/fixtures/nested-filters-html.tsx +7 -0
- package/src/fixtures/nested-filters-siblings.tsx +12 -0
- package/src/fixtures/nondeterministic-scene.tsx +14 -0
- package/src/fixtures/nondeterministic-variants.tsx +16 -0
- package/src/fixtures/raw-hex-scene.tsx +10 -0
- package/src/fixtures/scene-root-declared/SceneHook.tsx +9 -0
- package/src/fixtures/scene-root-declared/plan.fixture.yaml +10 -0
- package/src/fixtures/scene-root-missing/SceneHook.tsx +11 -0
- package/src/fixtures/scene-root-missing/plan.fixture.yaml +10 -0
- package/src/fixtures/scene-root-no-names/SceneHook.tsx +6 -0
- package/src/fixtures/scene-root-no-names/plan.fixture.yaml +6 -0
- package/src/fixtures/scene-root-shell/SceneHook.tsx +8 -0
- package/src/fixtures/scene-root-shell/Shell.tsx +9 -0
- package/src/fixtures/scene-root-shell/plan.fixture.yaml +10 -0
- package/src/fixtures/shorthand-undefined-clean.tsx +23 -0
- package/src/fixtures/shorthand-undefined-longhand.tsx +17 -0
- package/src/fixtures/token-scene.tsx +9 -0
- package/src/includeCoverage.test.ts +176 -0
- package/src/learnedFrom.test.ts +32 -0
- package/src/projectDoc.test.ts +230 -0
- package/src/projectDoc.ts +152 -0
- package/src/projectDocSync.test.ts +152 -0
- package/src/projectDocSync.ts +79 -0
- package/src/projectMeta.test.ts +75 -0
- package/src/projectMeta.ts +53 -0
- package/src/provenance.ts +85 -0
- package/src/radar-check-ids.json +34 -0
- package/src/registry.test.ts +207 -0
- package/src/registry.ts +352 -0
- package/src/rules/animatedBarChartTightBox.ts +14 -0
- package/src/rules/animatedBarChartWhenNotToUse.ts +14 -0
- package/src/rules/animatedBarChartWhenToUse.ts +14 -0
- package/src/rules/animatedLineChartWhenNotToUse.ts +14 -0
- package/src/rules/animatedLineChartWhenToUse.ts +14 -0
- package/src/rules/approvedSfxPaletteOnly.ts +23 -0
- package/src/rules/atmosphereGradientsFadeAtBothEnds.ts +21 -0
- package/src/rules/audioIsTheClock.ts +22 -0
- package/src/rules/beatsDeriveFromTheirOwnSequence.ts +21 -0
- package/src/rules/bestSceneSetsTheBar.ts +21 -0
- package/src/rules/bumpBeforeRefreeze.ts +20 -0
- package/src/rules/cameraIsTheNarrator.ts +21 -0
- package/src/rules/checkPinBeforeShip.ts +22 -0
- package/src/rules/coldViewerClarity.ts +21 -0
- package/src/rules/commitOrCutEffects.ts +36 -0
- package/src/rules/convertFramesAcrossClockBoundary.ts +19 -0
- package/src/rules/crossCutElementsLiveInTheHudLayer.ts +21 -0
- package/src/rules/cursorClicksRealControls.ts +22 -0
- package/src/rules/cutOnRealAudioPauses.ts +20 -0
- package/src/rules/darkButNeverDim.ts +21 -0
- package/src/rules/dataRadarTagsDeclared.test.ts +60 -0
- package/src/rules/dataRadarTagsDeclared.ts +341 -0
- package/src/rules/declareAVerticalZoneMap.ts +19 -0
- package/src/rules/deleteRejectedRigsCompletely.ts +19 -0
- package/src/rules/deriveSeamsDontMeasure.ts +23 -0
- package/src/rules/deterministicSceneSource.test.ts +58 -0
- package/src/rules/deterministicSceneSource.ts +82 -0
- package/src/rules/diegeticSoundForOnscreenActions.ts +21 -0
- package/src/rules/distinctTransitionPerBeat.ts +21 -0
- package/src/rules/downloadOrDrawRichAssets.ts +22 -0
- package/src/rules/elementOnscreenForItsVoSentence.ts +25 -0
- package/src/rules/emitBuildNotes.ts +19 -0
- package/src/rules/energyFromTimingNotPartCount.ts +20 -0
- package/src/rules/evidenceBeforeClaimingFixed.ts +23 -0
- package/src/rules/exactPeerLockstep.ts +19 -0
- package/src/rules/firstPassIsADraft.ts +20 -0
- package/src/rules/fixCrowdingByRebandingNotNudging.ts +20 -0
- package/src/rules/fixesPreserveApprovedElements.ts +24 -0
- package/src/rules/floatingNotBoxed.ts +21 -0
- package/src/rules/focusBeatsRecedeTheContext.ts +20 -0
- package/src/rules/fontVarsBrandRemocn.ts +20 -0
- package/src/rules/geometricSeamsNotCrossfades.ts +28 -0
- package/src/rules/geometrySidecarExportsTyped.test.ts +37 -0
- package/src/rules/geometrySidecarExportsTyped.ts +59 -0
- package/src/rules/glowIsAFocalAccent.ts +28 -0
- package/src/rules/heroFillsTheFrame.ts +24 -0
- package/src/rules/htmlMasksForAnimatedCounters.ts +21 -0
- package/src/rules/impactStackOnPayoffs.ts +26 -0
- package/src/rules/interactiveTargetsAreDeclaredData.ts +21 -0
- package/src/rules/interpolateLiteralKeysMonotonic.test.ts +49 -0
- package/src/rules/interpolateLiteralKeysMonotonic.ts +98 -0
- package/src/rules/latenessIsStructuralOverlapTheBeats.ts +20 -0
- package/src/rules/lessTextMoreVisualizations.ts +22 -0
- package/src/rules/liveLinkDuplicateReactAlias.ts +22 -0
- package/src/rules/make2dElements3d.ts +21 -0
- package/src/rules/masterMixLoudnessTarget.ts +22 -0
- package/src/rules/measureAlignmentDontEyeball.ts +20 -0
- package/src/rules/motionCarriesWeightAndMeaning.ts +21 -0
- package/src/rules/motionDensityFloor.ts +20 -0
- package/src/rules/neverEditTierBVendored.ts +20 -0
- package/src/rules/neverRestateDerivedFrameNumbersInComments.ts +20 -0
- package/src/rules/noAiGradientLook.ts +22 -0
- package/src/rules/noColoredFlashToHideACut.ts +29 -0
- package/src/rules/noDoubleFrameShift.test.ts +95 -0
- package/src/rules/noDoubleFrameShift.ts +193 -0
- package/src/rules/noDuplicateMotionPrimitives.ts +19 -0
- package/src/rules/noLinearMotion.ts +29 -0
- package/src/rules/noNestedSvgFilters.test.ts +47 -0
- package/src/rules/noNestedSvgFilters.ts +138 -0
- package/src/rules/noRepeatedAssetAcrossShots.ts +19 -0
- package/src/rules/noShorthandWithUndefinedLonghand.test.ts +35 -0
- package/src/rules/noShorthandWithUndefinedLonghand.ts +140 -0
- package/src/rules/noStackedFadesAtACut.ts +30 -0
- package/src/rules/noStaticHolds.ts +24 -0
- package/src/rules/occlusionVisibleElements.ts +26 -0
- package/src/rules/oneContinuousVoTrack.ts +20 -0
- package/src/rules/onePunchPerMomentClass.ts +20 -0
- package/src/rules/oneRenderPerRound.ts +46 -0
- package/src/rules/openStaticAfterAHardCut.ts +29 -0
- package/src/rules/openingsAndClosingsCarryMore.ts +21 -0
- package/src/rules/persistentDeviceFrames.ts +20 -0
- package/src/rules/pixelDiffAnEmptyBandFirst.ts +20 -0
- package/src/rules/planIsPublishedNotGating.ts +32 -0
- package/src/rules/poweredByRevolaWatermark.ts +19 -0
- package/src/rules/predrawContentPresentAtTheCut.ts +21 -0
- package/src/rules/premiumSurfacesNotFlatBorders.ts +20 -0
- package/src/rules/radarDispositionsAreHumanOnly.ts +28 -0
- package/src/rules/realAssetsNotPlaceholders.ts +21 -0
- package/src/rules/rebuildUiFromReconNotScreenshots.ts +21 -0
- package/src/rules/reconStrandsRunInParallel.ts +63 -0
- package/src/rules/registryBeforeHandrolling.ts +20 -0
- package/src/rules/remapRateEndpointFormula.ts +19 -0
- package/src/rules/remocnContentTailwindChrome.ts +19 -0
- package/src/rules/retimeOnlyViaPlaybackOrDesignTokens.ts +18 -0
- package/src/rules/reviewTheRenderByFrameSweep.ts +23 -0
- package/src/rules/rollingNumberBoundedSequence.ts +21 -0
- package/src/rules/rollingNumberSequenceLayoutNone.ts +22 -0
- package/src/rules/rollingNumberTightBoxAlignment.ts +19 -0
- package/src/rules/rollingNumberWhenNotToUse.ts +24 -0
- package/src/rules/rollingNumberWhenToUse.ts +23 -0
- package/src/rules/sameFamilySameProject.ts +20 -0
- package/src/rules/sampleBrandFromProduct.ts +19 -0
- package/src/rules/sampleVelocityFromPreviousFramePosition.ts +21 -0
- package/src/rules/sceneRootTagDeclared.test.ts +43 -0
- package/src/rules/sceneRootTagDeclared.ts +78 -0
- package/src/rules/scrubDemoDataToGenericRoles.ts +19 -0
- package/src/rules/selfReviewThePlanForGaps.ts +47 -0
- package/src/rules/sourceArchivedBeforeBrief.ts +24 -0
- package/src/rules/statisticsSelfExplanatory.ts +18 -0
- package/src/rules/stopsAsRelativeSpacing.ts +21 -0
- package/src/rules/theWholeCanvasWorks.ts +21 -0
- package/src/rules/themePerSection.ts +28 -0
- package/src/rules/thesisDeclaredInBrief.ts +25 -0
- package/src/rules/tierARequiresPropUnreachableEdit.ts +20 -0
- package/src/rules/tinyBodyCopyAsRuledLines.ts +19 -0
- package/src/rules/titlesPinnedOutsideCameraPush.ts +22 -0
- package/src/rules/tokensNotRawHexes.test.ts +51 -0
- package/src/rules/tokensNotRawHexes.ts +89 -0
- package/src/rules/typeTokenDefaultedParamsAsString.ts +21 -0
- package/src/rules/varyKitChoreographyAcrossSections.ts +21 -0
- package/src/rules/varyTheVisualizationGrammar.ts +22 -0
- package/src/rules/verifyCheapRenderDear.ts +18 -0
- package/src/rules/voAddressesTheViewer.ts +18 -0
- package/src/rules/voIsAPerformance.ts +21 -0
- package/src/rules/voScriptLocksSpokenWords.ts +21 -0
- package/src/rules/wrappersPreserveChildGeometry.ts +22 -0
- package/src/sourceCheck.test.ts +50 -0
- package/src/sourceCheck.ts +55 -0
- package/src/sourceCitations.test.ts +747 -0
- package/src/sourceCitations.ts +217 -0
- package/src/stageProjection.ts +112 -0
- package/src/stages.test.ts +68 -0
- package/src/stages.ts +207 -0
- package/src/testSupport.ts +16 -0
- package/src/types.ts +181 -0
- package/src/variants.test.ts +24 -0
- package/src/variants.ts +13 -0
- package/src/waiverProjection.test.ts +59 -0
- package/src/waiverProjection.ts +45 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/style/CREATIVE_EDGE.md directive 9's counter-failure ("it
|
|
5
|
+
* looks overwhelming, I wanted to do it more with the camera", "still
|
|
6
|
+
* AI-generated - less detailed, smoother"): the same corpus that demanded
|
|
7
|
+
* motion density also rejected scenes that answered it with more objects.
|
|
8
|
+
*/
|
|
9
|
+
export const energyFromTimingNotPartCount: Rule = {
|
|
10
|
+
id: "energy-from-timing-not-part-count",
|
|
11
|
+
claim: "Scene energy comes from timing and material, never from part count: one subject performing beautifully beats ten things wiggling, and a beat where several subjects compete for the eye at once is as much a defect as a static hold.",
|
|
12
|
+
why: "'It looks overwhelming, I wanted to do it more with the camera' arrived in the same corpus as the too-static notes; answering flatness by adding elements produced the opposite failure, so density must be bought with overlapping beats in time, not with simultaneous objects.",
|
|
13
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
unanchorable: "An attention-competition judgment per beat; no single code location expresses it.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/portfolio-reels.md and
|
|
5
|
+
* act854-qpa-video.md: "it's still there" (the purple cursor ripple survived
|
|
6
|
+
* its own fix), "you didn't add it in the animation???" (BOMs edited into
|
|
7
|
+
* the script but not the scene), "I cannot hear any typewriter sound
|
|
8
|
+
* effect". The companion to verify-cheap-render-dear: that rule says which
|
|
9
|
+
* checks are cheap; this one says a fix is not done until a check confirmed
|
|
10
|
+
* it.
|
|
11
|
+
*/
|
|
12
|
+
export const evidenceBeforeClaimingFixed: Rule = {
|
|
13
|
+
id: "evidence-before-claiming-fixed",
|
|
14
|
+
claim: "Never report a fix without evidence from the artifact itself: a still at the affected frames, a grep proving the removed effect is gone, or an audio probe proving the sound is present.",
|
|
15
|
+
why: "Three separate 'fixes' shipped unverified and bounced with 'it's still there'; each unverified claim costs a full human review round, the most expensive resource in the loop.",
|
|
16
|
+
learnedFrom: "claude-sessions-digest/portfolio-reels.md",
|
|
17
|
+
tier: "process",
|
|
18
|
+
scope: { kind: "pipeline" },
|
|
19
|
+
enforcement: {
|
|
20
|
+
kind: "prose",
|
|
21
|
+
unanchorable: "The rule prescribes review-loop discipline; no repo code implements evidence capture to point at.",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md: "Exact peers, lockstep releases. The kit
|
|
5
|
+
* pins its peers exactly; a project that bumps Remotion needs a matching kit
|
|
6
|
+
* release before its next freeze."
|
|
7
|
+
*/
|
|
8
|
+
export const exactPeerLockstep: Rule = {
|
|
9
|
+
id: "exact-peer-lockstep",
|
|
10
|
+
claim: "The kit pins its peers exactly; a project that bumps Remotion must get a matching kit release before its next freeze.",
|
|
11
|
+
why: "A peer-range mismatch lets a project silently render against a Remotion version the kit was never tested with, or blocks the freeze at the worst moment.",
|
|
12
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
13
|
+
tier: "process",
|
|
14
|
+
scope: { kind: "pipeline" },
|
|
15
|
+
enforcement: {
|
|
16
|
+
kind: "prose",
|
|
17
|
+
anchor: { file: "packages/video-cli/src/kitShell.ts" },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-craft-video.md: the identical
|
|
5
|
+
* "this needs to blow people away ... this looks too basic, like a template
|
|
6
|
+
* almost" upgrade prompt was issued for ALL five scenes; no first-pass scene
|
|
7
|
+
* ever shipped. The amplification pass is structural, not a correction.
|
|
8
|
+
*/
|
|
9
|
+
export const firstPassIsADraft: Rule = {
|
|
10
|
+
id: "first-pass-is-a-draft",
|
|
11
|
+
claim: "Treat every scene's first build as a draft: a dedicated amplification pass (faster, more detailed, more layered motion; unnecessary text removed) is a mandatory pipeline stage, not an optional polish.",
|
|
12
|
+
why: "In the best-documented build, all five scenes received the same 'too basic, blow people away' round; budgeting the second pass up front converts a guaranteed feedback round into a pipeline stage.",
|
|
13
|
+
learnedFrom: "claude-sessions-digest/revola-craft-video.md",
|
|
14
|
+
tier: "process",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
unanchorable: "The rule prescribes a pipeline stage; no code location expresses it yet.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "Root cause was layout, not
|
|
5
|
+
* size - the spine was at y660, so every station was squeezed into the 360px
|
|
6
|
+
* between the copy and the line while the bottom ~240px of frame sat empty.
|
|
7
|
+
* Fixed by moving the whole stage down, not by nudging."
|
|
8
|
+
*/
|
|
9
|
+
export const fixCrowdingByRebandingNotNudging: Rule = {
|
|
10
|
+
id: "fix-crowding-by-rebanding-not-nudging",
|
|
11
|
+
claim: "When a layout reads crowded or crumbled, fix the band allocation by moving the whole stage, not by nudging individual elements.",
|
|
12
|
+
why: "The S3 spine at y660 squeezed every station into 360px while the bottom 240px of frame sat empty; the root cause was layout allocation, not element size, and shrinking elements would not have fixed it.",
|
|
13
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
anchor: { file: "revola-video/src/scenes/craft/S3Line.tsx" },
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-usecase-book-meeting.md ("nooo,
|
|
5
|
+
* the particle animation and the room appearing in a circle were amazing,
|
|
6
|
+
* why did you remove them? I meant that when the room is opened, the text on
|
|
7
|
+
* it and the animations looked bad") and portfolio-reels.md ("Yeah, but now
|
|
8
|
+
* it doesn't scroll the sidebar. Why are you acting like you're in a time
|
|
9
|
+
* crunch? Just have both."). The inverse of
|
|
10
|
+
* delete-rejected-rigs-completely: rejected work is deleted entirely,
|
|
11
|
+
* approved work is never deleted incidentally.
|
|
12
|
+
*/
|
|
13
|
+
export const fixesPreserveApprovedElements: Rule = {
|
|
14
|
+
id: "fixes-preserve-approved-elements",
|
|
15
|
+
claim: "A fix scoped to one complaint is a minimal diff against the approved baseline: additive unless removal was requested, and previously-praised elements are never deleted while fixing a neighbour.",
|
|
16
|
+
why: "Twice a scoped complaint was 'fixed' by deleting elements the reviewer loved, costing a restoration round each time; feedback on one aspect of a scene is not license to rebuild the scene.",
|
|
17
|
+
learnedFrom: "claude-sessions-digest/revola-usecase-book-meeting.md",
|
|
18
|
+
tier: "process",
|
|
19
|
+
scope: { kind: "pipeline" },
|
|
20
|
+
enforcement: {
|
|
21
|
+
kind: "prose",
|
|
22
|
+
unanchorable: "The rule prescribes diff discipline for feedback rounds; no code location expresses it.",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/style/CREATIVE_EDGE.md directive 7 ("i prefer floating
|
|
5
|
+
* svgs instead of containers and i want everything to be bigger and more
|
|
6
|
+
* polished", "remove the ugly bg colors from the SVGs - let them float",
|
|
7
|
+
* "what if the card didn't have a bg and the content sat floating with a
|
|
8
|
+
* nice abstract hue in the bg").
|
|
9
|
+
*/
|
|
10
|
+
export const floatingNotBoxed: Rule = {
|
|
11
|
+
id: "floating-not-boxed",
|
|
12
|
+
claim: "Content floats free on the canvas, grounded by soft contact shadows, instead of sitting in boxed panels; every container must earn its chrome - a filler background rectangle behind an SVG is a defect, and when a panel does exist it is a premium layered surface.",
|
|
13
|
+
why: "'I prefer floating svgs instead of containers' and 'remove the ugly bg colors from the SVGs - let them float' were standing preferences across rounds; boxed content read as template while floating, shadow-grounded elements read as designed.",
|
|
14
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
unanchorable: "A does-this-container-earn-its-chrome judgment per element; no single code location expresses it.",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from the product-room build: the 'product brain' beat overlaid
|
|
5
|
+
* traces and a label on the live room. Feedback was that it 'covers the
|
|
6
|
+
* product room and overlaps highly with it so it's not very clear'. The fix
|
|
7
|
+
* was not to move the label - it was to make the beat its own shot.
|
|
8
|
+
*/
|
|
9
|
+
export const focusBeatsRecedeTheContext: Rule = {
|
|
10
|
+
id: "focus-beats-recede-the-context",
|
|
11
|
+
claim: "When a beat introduces a new subject over existing UI, recede the context behind it - dim, blur, scale back, scrim - and give the new subject cleared space; never draw the new layer on top of live content.",
|
|
12
|
+
why: "A diagram drawn over a fully lit room put its label character-for-character on top of a card's caption and its connector lines read as scratches across the UI; receding the room turned the same beat into a clean shot with no geometry changes.",
|
|
13
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
unanchorable: "A composition discipline spanning scene and overlay layers; no single symbol owns it.",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md: "Point those vars at the fonts the
|
|
5
|
+
* project already loads ... and every remocn component is branded at once,
|
|
6
|
+
* with zero source changes." A .css file cannot hold a ts-morph symbol, so
|
|
7
|
+
* the anchor is file-only by construction.
|
|
8
|
+
*/
|
|
9
|
+
export const fontVarsBrandRemocn: Rule = {
|
|
10
|
+
id: "font-vars-brand-remocn",
|
|
11
|
+
claim: "Brand all remocn components at once by pointing --font-geist-sans and --font-geist-mono at the project's loaded fonts in CSS; never touch font settings in component source.",
|
|
12
|
+
why: "Editing fonts per-component forfeits Tier B update-safety and reintroduces per-file drift; the CSS vars brand ~110 components with zero source changes.",
|
|
13
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
anchor: { file: "revola-video/src/index.css" },
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adopted from the reels and craft sessions ('cards physically combine into
|
|
5
|
+
* the next scene like a puzzle'; scenes overlap 12 frames). Applied in the
|
|
6
|
+
* product-room build by having the clicked intro card expand to fill the
|
|
7
|
+
* viewport and hand off to the explore grid.
|
|
8
|
+
*/
|
|
9
|
+
export const geometricSeamsNotCrossfades: Rule = {
|
|
10
|
+
id: "geometric-seams-not-crossfades",
|
|
11
|
+
claim: "Make a seam physical: the clicked element becomes the next scene, or the camera travels through it; overlap adjacent sections by a few frames so the handoff happens mid-air rather than as a dissolve between slides.",
|
|
12
|
+
why: "Praised cuts in the reference films are all motivated - a card expands into the next shot, the camera flies through a control - while crossfades read as slides advancing; the geometric handoff is also what makes a cut survive a re-time.",
|
|
13
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
14
|
+
tier: "gate",
|
|
15
|
+
// Added by the codex red team round (2026-08-01): the r2 sudden-seams class
|
|
16
|
+
// (5 of 6 seams, BUILD_NOTES round 2 feedback item 1, bead
|
|
17
|
+
// remotion-videos-t0s) escaped every prior check because the two seam bans
|
|
18
|
+
// this rule states are satisfiable by doing nothing - `seam-continuity` is
|
|
19
|
+
// the check that verifies the CONSTRUCTIVE half: every seam carries either
|
|
20
|
+
// a shared geometry-sidecar carrier or a declared overlap of >=4 frames.
|
|
21
|
+
verify: "`video radar:film --project <dir> --video <mp4> --check seam-continuity`",
|
|
22
|
+
owner: "seam-continuity",
|
|
23
|
+
scope: { kind: "pipeline" },
|
|
24
|
+
enforcement: {
|
|
25
|
+
kind: "prose",
|
|
26
|
+
anchor: { file: "revola-video/src/productroom/PRColdOpen.tsx" },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { toolchainRepoRoot as repoRoot } from "../testSupport";
|
|
4
|
+
import { checkFile } from "../sourceCheck";
|
|
5
|
+
import { geometrySidecarExportsTyped } from "./geometrySidecarExportsTyped";
|
|
6
|
+
|
|
7
|
+
const check = () => {
|
|
8
|
+
if (geometrySidecarExportsTyped.enforcement.kind !== "source") throw new Error("expected a source rule");
|
|
9
|
+
return geometrySidecarExportsTyped.enforcement.check;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const run = (name: string) => {
|
|
13
|
+
const rel = `packages/video-craft/src/fixtures/${name}`;
|
|
14
|
+
return checkFile(check(), join(repoRoot, rel), rel, repoRoot);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("geometrySidecarExportsTyped", () => {
|
|
18
|
+
test("does not fire on a sidecar exporting a typed geometry", () => {
|
|
19
|
+
expect(run("geometry-sidecar-valid.ts")).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("fires when the export is missing a `: SceneGeometry` type annotation", () => {
|
|
23
|
+
const findings = run("geometry-sidecar-missing-type.ts");
|
|
24
|
+
expect(findings).toHaveLength(1);
|
|
25
|
+
expect(findings[0].message).toMatch(/`: SceneGeometry`/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("fires when there is no `export const geometry` at all", () => {
|
|
29
|
+
const findings = run("geometry-sidecar-missing-export.ts");
|
|
30
|
+
expect(findings).toHaveLength(1);
|
|
31
|
+
expect(findings[0].message).toMatch(/`export const geometry`/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("owns the sidecar convention's include glob", () => {
|
|
35
|
+
expect(check().include).toEqual(["*/src/scenes/scene.*.geometry.ts"]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Rule, SourceFinding } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Task 6 of the task-graph phase-1 plan
|
|
5
|
+
* (docs/superpowers/plans/2026-07-30-video-task-graph-phase1.md) introduces
|
|
6
|
+
* `SceneGeometry` (packages/video-kit/src/pure/geometry.ts) as the typed
|
|
7
|
+
* seam-pose contract a scene's cross-cut sidecar carries, so that the
|
|
8
|
+
* `compose:registry` generator (Task 11) and film assembly (Task 18) can
|
|
9
|
+
* import a seam's exported geometry and have the compiler - not a render -
|
|
10
|
+
* catch a missing or mistyped export. The spec's decision 1 is "typed
|
|
11
|
+
* geometry so the compiler checks seam consumers": that only holds if every
|
|
12
|
+
* sidecar actually exports the typed shape, which is what this rule checks.
|
|
13
|
+
*
|
|
14
|
+
* The sidecar naming convention (`src/scenes/scene.<id>.geometry.ts`, one per
|
|
15
|
+
* project directory) is declared once, in `geometrySidecarPath()` in the same
|
|
16
|
+
* geometry.ts module this rule's `why` cites - this file enforces the
|
|
17
|
+
* convention, it does not restate it.
|
|
18
|
+
*/
|
|
19
|
+
export const geometrySidecarExportsTyped: Rule = {
|
|
20
|
+
id: "geometry-sidecar-exports-typed",
|
|
21
|
+
claim: "A scene's geometry sidecar exports `const geometry` typed as `SceneGeometry`.",
|
|
22
|
+
why:
|
|
23
|
+
"The compose:registry generator and film assembly import a seam's geometry sidecar by convention " +
|
|
24
|
+
"(src/scenes/scene.<id>.geometry.ts) and read its `geometry` export at compile time; an untyped or " +
|
|
25
|
+
"missing export turns that compile-time check into a silent `undefined` at runtime, at the exact " +
|
|
26
|
+
"moment - a scene cut - where the seam contract is supposed to be enforced.",
|
|
27
|
+
learnedFrom: "docs/superpowers/plans/2026-07-30-video-task-graph-phase1.md",
|
|
28
|
+
tier: "gate",
|
|
29
|
+
verify: "`vitest run packages/video-craft` - machine-checked over every geometry sidecar file",
|
|
30
|
+
scope: { kind: "pipeline" },
|
|
31
|
+
enforcement: {
|
|
32
|
+
kind: "source",
|
|
33
|
+
check: {
|
|
34
|
+
// One level deep under each project directory, matching
|
|
35
|
+
// geometrySidecarPath()'s convention exactly: <project>/src/scenes/scene.<id>.geometry.ts.
|
|
36
|
+
include: ["*/src/scenes/scene.*.geometry.ts"],
|
|
37
|
+
run(file, relPath): SourceFinding[] {
|
|
38
|
+
const text = file.getFullText();
|
|
39
|
+
const hasExport = text.includes("export const geometry");
|
|
40
|
+
const hasType = text.includes(": SceneGeometry");
|
|
41
|
+
if (hasExport && hasType) return [];
|
|
42
|
+
|
|
43
|
+
const missing = [
|
|
44
|
+
!hasExport && "`export const geometry`",
|
|
45
|
+
!hasType && "`: SceneGeometry`",
|
|
46
|
+
].filter((x): x is string => !!x).join(" and ");
|
|
47
|
+
|
|
48
|
+
return [{
|
|
49
|
+
file: relPath,
|
|
50
|
+
line: 1,
|
|
51
|
+
match: relPath.split("/").pop() ?? relPath,
|
|
52
|
+
message:
|
|
53
|
+
`geometry sidecar is missing ${missing}; a sidecar at this path must read ` +
|
|
54
|
+
`\`export const geometry: SceneGeometry = { ... }\` (packages/video-kit/src/pure/geometry.ts)`,
|
|
55
|
+
}];
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/portfolio-reels.md: uniform glows and
|
|
5
|
+
* boxShadow on every panel were called out as part of the "AI-generated and
|
|
6
|
+
* cheap" read; the fixes that worked removed glow everywhere except focal
|
|
7
|
+
* moments, keeping frosted glass as the liked signature surface.
|
|
8
|
+
*/
|
|
9
|
+
export const glowIsAFocalAccent: Rule = {
|
|
10
|
+
id: "glow-is-a-focal-accent",
|
|
11
|
+
claim: "Reserve glow and drop-shadow for one or two focal moments per scene; a uniform glow on every panel is banned, frosted-glass surfaces are the sanctioned depth cue.",
|
|
12
|
+
why: "Uniform glows read as cheap template styling; de-glowing was part of every de-AI-ification round in the reels.",
|
|
13
|
+
learnedFrom: "claude-sessions-digest/portfolio-reels.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
exceptions: [
|
|
16
|
+
{
|
|
17
|
+
project: "qse-qguard-video",
|
|
18
|
+
decision:
|
|
19
|
+
"client chose ACT854's breathing accent bloom on every panel and glyph tile for the QGuard restyle; the bloom lives on rings and tiles, never behind body copy",
|
|
20
|
+
recordedIn: "qse-qguard-video/docs/motion-brand-spec.md",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
scope: { kind: "pipeline" },
|
|
24
|
+
enforcement: {
|
|
25
|
+
kind: "prose",
|
|
26
|
+
unanchorable: "The rule is a styling-restraint judgment across scenes; no single code location expresses it.",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-usecase-win-booth.md ("the
|
|
5
|
+
* animations are too small ... it's big, cinematic, and engaging .. these
|
|
6
|
+
* are too small, hardly legible. play big") and
|
|
7
|
+
* revola-usecase-book-meeting.md ("the second animation only takes 1/3 of
|
|
8
|
+
* the screen"; "they are still tiny"). "Bigger" requests routinely took two
|
|
9
|
+
* rounds because the first enlargement undershot.
|
|
10
|
+
*/
|
|
11
|
+
export const heroFillsTheFrame: Rule = {
|
|
12
|
+
id: "hero-fills-the-frame",
|
|
13
|
+
claim: "The hero element of a shot fills roughly two thirds of the frame or more, legible at 1080p; a hero occupying a third of the screen is a defect, and when enlarging, overshoot rather than nudge.",
|
|
14
|
+
why: "Undersized heroes were flagged in five separate rounds across three videos, and every timid enlargement needed a second round; 'play big' is the single most repeated scale note in the corpus.",
|
|
15
|
+
learnedFrom: "claude-sessions-digest/revola-usecase-win-booth.md",
|
|
16
|
+
tier: "gate",
|
|
17
|
+
verify: "`video radar:scene --project <dir> --comp <id> --check hero-size`",
|
|
18
|
+
owner: "hero-size",
|
|
19
|
+
scope: { kind: "pipeline" },
|
|
20
|
+
enforcement: {
|
|
21
|
+
kind: "prose",
|
|
22
|
+
unanchorable: "The rule is a composition judgment per shot; no single code location expresses it.",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "odometer rebuilt in HTML
|
|
5
|
+
* (SVG clipPath was unreliable in headless renders)". Narrowed to ANIMATED
|
|
6
|
+
* mask geometry on the extractor's own evidence: ink.tsx's static clipPath
|
|
7
|
+
* (InkFill) renders fine in the same pipeline, so the failure is specific to
|
|
8
|
+
* animated clipPath geometry, not clipPath per se.
|
|
9
|
+
*/
|
|
10
|
+
export const htmlMasksForAnimatedCounters: Rule = {
|
|
11
|
+
id: "html-masks-for-animated-counters",
|
|
12
|
+
claim: "Build animated sliding-mask counters (odometers, digit rolls) in HTML/CSS rather than SVG clipPath; animated clipPath geometry is unreliable in headless renders.",
|
|
13
|
+
why: "The S3 odometer built on SVG clipPath rendered inconsistently in headless Chrome and had to be rebuilt in HTML; static clipPath (e.g. InkFill) remains fine.",
|
|
14
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
anchor: { file: "revola-video/src/scenes/craft/S3Line.tsx" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reconciles with one-punch-per-moment-class rather than contradicting it:
|
|
5
|
+
* that rule bans repeating the SAME punch across instances within a class
|
|
6
|
+
* (every keypress, every card); this one says stack several signals on a
|
|
7
|
+
* payoff and vary the stack BETWEEN classes. Four payoffs of four classes
|
|
8
|
+
* satisfy both.
|
|
9
|
+
*
|
|
10
|
+
* Adopted from the reels sessions, where every beat frame layers punchScale
|
|
11
|
+
* + chromaKick + beatFlash + RingPulse + LightSweep plus a foley cue. The
|
|
12
|
+
* product-room first pass had payoffs that merely appeared, and read flat
|
|
13
|
+
* even though the underlying content was right.
|
|
14
|
+
*/
|
|
15
|
+
export const impactStackOnPayoffs: Rule = {
|
|
16
|
+
id: "impact-stack-on-payoffs",
|
|
17
|
+
claim: "Land each payoff with several simultaneous signals on the same frame - ring pulse, scale punch, light sweep, one approved foley hit - and vary which signals each class of payoff uses.",
|
|
18
|
+
why: "A single fade or count-up reads as a value changing; the stacked signal reads as a moment landing, and varying the stack per payoff class is what keeps four payoffs in one film from reading as the same template beat.",
|
|
19
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
20
|
+
tier: "craft",
|
|
21
|
+
scope: { kind: "pipeline" },
|
|
22
|
+
enforcement: {
|
|
23
|
+
kind: "prose",
|
|
24
|
+
unanchorable: "A choreography discipline composed per beat from several primitives; it has no single owning symbol.",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from the product-room build, and consistent with the digest note
|
|
5
|
+
* that hit geometry must be derived from layout constants: 'on a film with
|
|
6
|
+
* Pixel-perfect. on screen, that is the one mistake you cannot ship'. Two
|
|
7
|
+
* sources of truth (the control's flow position and the cursor's hardcoded
|
|
8
|
+
* coordinate) will always drift; one shared constant cannot.
|
|
9
|
+
*/
|
|
10
|
+
export const interactiveTargetsAreDeclaredData: Rule = {
|
|
11
|
+
id: "interactive-targets-are-declared-data",
|
|
12
|
+
claim: "A control the cursor clicks and the cursor path that clicks it must read the same declared coordinate; if a target is measured off a render instead, record the measurement and re-verify it after any layout change.",
|
|
13
|
+
why: "Cursor targets positioned by eye drifted out from under their controls as the layout changed - one click landed 122px right and 155px below its button while the button lit up on its own - and nothing in the build could detect it.",
|
|
14
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
anchor: { file: "revola-video/src/components/Cursor.tsx" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { toolchainRepoRoot as repoRoot } from "../testSupport";
|
|
4
|
+
import { checkFile } from "../sourceCheck";
|
|
5
|
+
import { interpolateLiteralKeysMonotonic } from "./interpolateLiteralKeysMonotonic";
|
|
6
|
+
|
|
7
|
+
const check = () => {
|
|
8
|
+
if (interpolateLiteralKeysMonotonic.enforcement.kind !== "source") {
|
|
9
|
+
throw new Error("expected a source rule");
|
|
10
|
+
}
|
|
11
|
+
return interpolateLiteralKeysMonotonic.enforcement.check;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const run = (name: string) => {
|
|
15
|
+
const rel = `packages/video-craft/src/fixtures/${name}`;
|
|
16
|
+
return checkFile(check(), join(repoRoot, rel), rel, repoRoot);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("interpolateLiteralKeysMonotonic", () => {
|
|
20
|
+
test("fires on non-ascending all-literal ranges, including unary minus", () => {
|
|
21
|
+
const findings = run("interpolate-descending.tsx");
|
|
22
|
+
expect(findings).toHaveLength(2);
|
|
23
|
+
expect(findings[0].line).toBe(7);
|
|
24
|
+
expect(findings[0].match).toBe("[0, 136, 134, 200]");
|
|
25
|
+
expect(findings[1].line).toBe(8);
|
|
26
|
+
expect(findings[1].match).toBe("[-10, -20]");
|
|
27
|
+
for (const f of findings) expect(f.message).toMatch(/ascending/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("does not fire on strictly ascending literal ranges", () => {
|
|
31
|
+
expect(run("interpolate-ascending.tsx")).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("does not fire on identifier-composed ranges (out of scope)", () => {
|
|
35
|
+
expect(run("interpolate-identifier-keys.tsx")).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("covers all project layouts plus the kit, including plain .ts/.js helpers", () => {
|
|
39
|
+
// interpolate() also lives in non-JSX helpers feeding scenes
|
|
40
|
+
// (spec-videos/src/lib/transitions.ts, packages/video-kit/src/react/motion.ts,
|
|
41
|
+
// qse-investor-video/03_scenes/_lib/motion.js), so this rule scans
|
|
42
|
+
// .js/.ts too, unlike the JSX-only source rules.
|
|
43
|
+
expect(check().include).toContain("qpa-video/03_scenes/**/*.{js,jsx,ts,tsx}");
|
|
44
|
+
expect(check().include).toContain("revola-video/src/scenes/**/*.{js,jsx,ts,tsx}");
|
|
45
|
+
expect(check().include).toContain("spec-videos/**/*.{js,jsx,ts,tsx}");
|
|
46
|
+
expect(check().include).toContain("qse-investor-video/**/*.{js,jsx,ts,tsx}");
|
|
47
|
+
expect(check().include).toContain("packages/video-kit/src/**/*.{ts,tsx}");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from "ts-morph";
|
|
2
|
+
import type { Rule, SourceFinding } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "HERO_AT (134) is now
|
|
6
|
+
* *earlier* than TILT_END (136), so it can no longer be a CAM_KEYS entry -
|
|
7
|
+
* interpolate() needs a monotonic input range and will throw."
|
|
8
|
+
*
|
|
9
|
+
* Deliberate limitation: only ranges whose elements are ALL numeric literals
|
|
10
|
+
* (including unary minus) are evaluated. Identifier-composed arrays would
|
|
11
|
+
* need constant folding to prove monotonic, so they are out of scope; any
|
|
12
|
+
* literal hit is a real latent crash by construction.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The numeric value of a literal element, or undefined when it is not one. */
|
|
16
|
+
function literalValue(element: Node): number | undefined {
|
|
17
|
+
if (Node.isNumericLiteral(element)) return element.getLiteralValue();
|
|
18
|
+
if (Node.isPrefixUnaryExpression(element) && element.getOperatorToken() === SyntaxKind.MinusToken) {
|
|
19
|
+
const operand = element.getOperand();
|
|
20
|
+
if (Node.isNumericLiteral(operand)) return -operand.getLiteralValue();
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const interpolateLiteralKeysMonotonic: Rule = {
|
|
26
|
+
id: "interpolate-literal-keys-monotonic",
|
|
27
|
+
claim: "A literal input-range array passed to interpolate() must be strictly ascending.",
|
|
28
|
+
why: "interpolate() throws at runtime on a non-monotonic range; a literal non-ascending array is a latent crash that renders fine until that frame is reached. The HERO_AT(134)/TILT_END(136) retime would have thrown.",
|
|
29
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
30
|
+
tier: "gate",
|
|
31
|
+
verify: "`vitest run packages/video-craft` - machine-checked over scene and kit source",
|
|
32
|
+
scope: { kind: "pipeline" },
|
|
33
|
+
enforcement: {
|
|
34
|
+
kind: "source",
|
|
35
|
+
check: {
|
|
36
|
+
// Unlike the JSX-only checks, this one also scans plain .ts/.js:
|
|
37
|
+
// interpolate() lives in non-JSX helpers feeding scenes
|
|
38
|
+
// (spec-videos/src/lib/transitions.ts, packages/video-kit/src/react/
|
|
39
|
+
// motion.ts, qse-investor-video/03_scenes/_lib/motion.js), and a
|
|
40
|
+
// non-monotonic literal range crashes wherever it is written.
|
|
41
|
+
include: [
|
|
42
|
+
"qpa-video/03_scenes/**/*.{js,jsx,ts,tsx}",
|
|
43
|
+
"reverence-video/src/scenes/**/*.{js,jsx,ts,tsx}", "workday-dple-video/src/scenes/**/*.{js,jsx,ts,tsx}",
|
|
44
|
+
"revola-video/src/scenes/**/*.{js,jsx,ts,tsx}",
|
|
45
|
+
"datapeak-video/**/*.{js,jsx,ts,tsx}",
|
|
46
|
+
"datapeak-usecases/**/*.{js,jsx,ts,tsx}",
|
|
47
|
+
"qse-video/**/*.{js,jsx,ts,tsx}",
|
|
48
|
+
"qse-investor-video/**/*.{js,jsx,ts,tsx}",
|
|
49
|
+
"qse-qguard-video/src/**/*.{ts,tsx}",
|
|
50
|
+
"rcp-fund7-video/src/**/*.{ts,tsx}",
|
|
51
|
+
"expanse-video/src/**/*.{ts,tsx}",
|
|
52
|
+
"reverence-video/src/**/*.{ts,tsx}",
|
|
53
|
+
"spec-videos/**/*.{js,jsx,ts,tsx}",
|
|
54
|
+
// Added Task 23: klaviyo-segments-video and radar-fixture-video were
|
|
55
|
+
// silently excluded - the same gap deterministic-scene-source had.
|
|
56
|
+
"klaviyo-segments-video/src/**/*.{js,jsx,ts,tsx}",
|
|
57
|
+
"radar-fixture-video/src/**/*.{js,jsx,ts,tsx}",
|
|
58
|
+
"packages/video-kit/src/**/*.{ts,tsx}",
|
|
59
|
+
],
|
|
60
|
+
run: (file, relPath): SourceFinding[] => {
|
|
61
|
+
const findings: SourceFinding[] = [];
|
|
62
|
+
|
|
63
|
+
for (const call of file.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
64
|
+
if (call.getExpression().getText() !== "interpolate") continue;
|
|
65
|
+
|
|
66
|
+
const range = call.getArguments()[1];
|
|
67
|
+
if (!range || !Node.isArrayLiteralExpression(range)) continue;
|
|
68
|
+
|
|
69
|
+
const values: number[] = [];
|
|
70
|
+
let allLiteral = true;
|
|
71
|
+
for (const element of range.getElements()) {
|
|
72
|
+
const value = literalValue(element);
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
allLiteral = false;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
values.push(value);
|
|
78
|
+
}
|
|
79
|
+
if (!allLiteral) continue;
|
|
80
|
+
|
|
81
|
+
const ascending = values.every((v, i) => i === 0 || values[i - 1] < v);
|
|
82
|
+
if (ascending) continue;
|
|
83
|
+
|
|
84
|
+
findings.push({
|
|
85
|
+
file: relPath,
|
|
86
|
+
line: call.getStartLineNumber(),
|
|
87
|
+
match: range.getText().replace(/\s+/g, " ").slice(0, 60),
|
|
88
|
+
message:
|
|
89
|
+
`interpolate() input range [${values.join(", ")}] is not strictly ascending; ` +
|
|
90
|
+
`interpolate() throws at runtime when this frame range is evaluated.`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return findings;
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "the delay was structural,
|
|
5
|
+
* not a timing offset: the clock hook, the tilt, and the *entire* field pop
|
|
6
|
+
* all had to finish before the deck could land. Sequential beats. Fix =
|
|
7
|
+
* overlap them."
|
|
8
|
+
*/
|
|
9
|
+
export const latenessIsStructuralOverlapTheBeats: Rule = {
|
|
10
|
+
id: "lateness-is-structural-overlap-the-beats",
|
|
11
|
+
claim: "When a key element arrives late, check whether the delay is structural (strictly sequential prerequisite beats) before shaving durations; the fix is to overlap beats, not to trim them.",
|
|
12
|
+
why: "Two shave passes (420f to 396f, deck 232 to 196) failed to fix a late deck because the hook, tilt, and field pop all had to finish first; overlapping them fixed it in one pass.",
|
|
13
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
anchor: { file: "revola-video/src/scenes/craft/S1Graveyard.tsx" },
|
|
19
|
+
},
|
|
20
|
+
};
|