@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,47 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The cheap half of what the removed sign-off barrier was doing, kept without
|
|
5
|
+
* the wait.
|
|
6
|
+
*
|
|
7
|
+
* The QGuard plan was 33 KB and read as thorough. Two of the three defects the
|
|
8
|
+
* first human viewing found were not mistakes IN it - they were subjects it
|
|
9
|
+
* never mentioned. It bound colour, vertical layout, theme-per-section and
|
|
10
|
+
* motion density, and said nothing about typographic case, emphasis, or whether
|
|
11
|
+
* the copy in a fixed-size panel fits it. Seven scenes then each decided those
|
|
12
|
+
* locally, seven ways, and no scene file could see the divergence.
|
|
13
|
+
*
|
|
14
|
+
* So the pass is not "read it again", which finds nothing. It is one adversarial
|
|
15
|
+
* question - *what does this plan leave to per-scene judgement?* - because a
|
|
16
|
+
* plan that permits a judgement will get that judgement made once per scene, and
|
|
17
|
+
* the drift is invisible from inside any single one of them.
|
|
18
|
+
*
|
|
19
|
+
* Checklist, from the rulebook's own declare-shaped rules, plus the two the
|
|
20
|
+
* QGuard round found missing:
|
|
21
|
+
*
|
|
22
|
+
* - colour (`tokens-not-raw-hexes`)
|
|
23
|
+
* - vertical layout (`declare-a-vertical-zone-map`)
|
|
24
|
+
* - theme per section (`theme-per-section`)
|
|
25
|
+
* - seam per cut, and the object that carries it
|
|
26
|
+
* (`geometric-seams-not-crossfades`)
|
|
27
|
+
* - typographic case and emphasis per text tier
|
|
28
|
+
* - a fit budget for every fixed-size panel that holds copy
|
|
29
|
+
* - which kit component and variant each section leans on
|
|
30
|
+
* (`vary-kit-choreography-across-sections`)
|
|
31
|
+
*
|
|
32
|
+
* A subject not on the plan is not automatically a defect - it is a decision
|
|
33
|
+
* that has to be made somewhere, and the plan is the only place it can be made
|
|
34
|
+
* once.
|
|
35
|
+
*/
|
|
36
|
+
export const selfReviewThePlanForGaps: Rule = {
|
|
37
|
+
id: "self-review-the-plan-for-gaps",
|
|
38
|
+
claim: "Before building, run one adversarial pass over the scene-flow plan asking what it leaves undeclared, and bind anything it leaves to per-scene judgement.",
|
|
39
|
+
why: "A plan can be detailed and still be silent: the QGuard plan bound colour, layout, theme and motion density but never mentioned typographic case or panel fit, so seven scenes each decided those separately and both defects reached the viewer.",
|
|
40
|
+
learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
|
|
41
|
+
tier: "process",
|
|
42
|
+
scope: { kind: "pipeline" },
|
|
43
|
+
enforcement: {
|
|
44
|
+
kind: "prose",
|
|
45
|
+
anchor: { file: "packages/video-cli/src/planmd.ts" },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/superpowers/specs/2026-07-31-defect-radar-design.md: "a
|
|
5
|
+
* browsed-and-gone URL is not a source". The recon dir's own honesty is the
|
|
6
|
+
* gate a brief's claims can be checked against later - a source that was
|
|
7
|
+
* read but never saved leaves every downstream fidelity check (thesis
|
|
8
|
+
* derivation, scene-serves-thesis) with nothing but the brief's own word for
|
|
9
|
+
* what the source said.
|
|
10
|
+
*/
|
|
11
|
+
export const sourceArchivedBeforeBrief: Rule = {
|
|
12
|
+
id: "source-archived-before-brief",
|
|
13
|
+
claim: "The recon dir contains a verbatim-saved copy of the source material (article text, transcript, etc.) before the brief is written, not only screenshots of it.",
|
|
14
|
+
why: "A brief written against a browsed-and-gone source cannot be checked against anything once the tab is closed; screenshots preserve a layout, not the words a thesis-fidelity read needs to compare against.",
|
|
15
|
+
learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
|
|
16
|
+
tier: "gate",
|
|
17
|
+
verify: "`video radar:plan --project <dir>` (check: `brief-structure`)",
|
|
18
|
+
owner: "brief-structure",
|
|
19
|
+
scope: { kind: "pipeline" },
|
|
20
|
+
enforcement: {
|
|
21
|
+
kind: "prose",
|
|
22
|
+
unanchorable: "The rule is about what an operator saves into a recon dir before writing a brief; no single code location expresses it.",
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-craft-video.md: boss feedback "we
|
|
5
|
+
* say something- 5%, what is that. 5% of their day or what, it's confusing".
|
|
6
|
+
*/
|
|
7
|
+
export const statisticsSelfExplanatory: Rule = {
|
|
8
|
+
id: "statistics-self-explanatory",
|
|
9
|
+
claim: "Every statistic on screen or in the voiceover names its denominator in context ('5% of their day', not '5%'); orphan numbers are a defect.",
|
|
10
|
+
why: "An unexplained '5%' reached the boss review before anyone caught it; a number without its base confuses exactly the executive audience the video targets.",
|
|
11
|
+
learnedFrom: "claude-sessions-digest/revola-craft-video.md",
|
|
12
|
+
tier: "craft",
|
|
13
|
+
scope: { kind: "pipeline" },
|
|
14
|
+
enforcement: {
|
|
15
|
+
kind: "prose",
|
|
16
|
+
anchor: { file: "packages/video-cli/src/voice.ts" },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md ("authored from values are treated as
|
|
5
|
+
* relative spacing and rescaled to fill the scene ... a re-measure, not a
|
|
6
|
+
* re-authoring of every stop"), merged with act854-video/BUILD_NOTES.md
|
|
7
|
+
* ("Re-timing is now a 5-number change. The stops inside each walk scene
|
|
8
|
+
* AUTO-DISTRIBUTE to fill the scene duration").
|
|
9
|
+
*/
|
|
10
|
+
export const stopsAsRelativeSpacing: Rule = {
|
|
11
|
+
id: "stops-as-relative-spacing",
|
|
12
|
+
claim: "Author walkthrough stop from values as relative spacing and rescale them with distributeStops; never as absolute frame positions.",
|
|
13
|
+
why: "Absolute stop frames make every VO re-record a re-authoring of every stop instead of a re-measure; with relative spacing a re-time collapses to changing the per-section durations.",
|
|
14
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
anchor: { file: "packages/video-kit/src/pure/walk.ts", symbol: "distributeStops" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/style/CREATIVE_EDGE.md directive 6 ("the whole scene only
|
|
5
|
+
* takes the middle of the screen - take this scene up a notch", "make the
|
|
6
|
+
* SVGs bigger, they look so small compared to their containers", "make the
|
|
7
|
+
* text bigger"). hero-fills-the-frame owns hero scale; this rule owns the
|
|
8
|
+
* rest of the canvas and the type floor.
|
|
9
|
+
*/
|
|
10
|
+
export const theWholeCanvasWorks: Rule = {
|
|
11
|
+
id: "the-whole-canvas-works",
|
|
12
|
+
claim: "Compose across the whole frame: a scene that lives only in the middle band with the outer thirds idle reads as low effort, small art never sits inside a big container, and type scales up whenever legibility is questioned - at 1080p, text under roughly 15px is a defect.",
|
|
13
|
+
why: "'The whole scene only takes the middle of the screen' and 'make the text bigger' recurred even when the hero itself passed its size check; hero scale alone did not catch compositions that centered everything in a small band of the canvas.",
|
|
14
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
unanchorable: "A whole-frame composition judgment per shot; no single code location expresses it.",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md: "Theme | dark · light · mixed (act854:
|
|
5
|
+
* dark intro/outro + light pages) | Must be per-section, token-driven".
|
|
6
|
+
* Token modules are the mechanism that makes per-section theming a data
|
|
7
|
+
* change.
|
|
8
|
+
*/
|
|
9
|
+
export const themePerSection: Rule = {
|
|
10
|
+
id: "theme-per-section",
|
|
11
|
+
claim: "Treat theme (dark or light) as a per-section, token-driven variable, never a per-video constant.",
|
|
12
|
+
why: "Real briefs mix themes within one video (act854: dark intro/outro plus light pages); a per-video theme assumption forces a structural rewrite mid-build.",
|
|
13
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
14
|
+
tier: "craft",
|
|
15
|
+
exceptions: [
|
|
16
|
+
{
|
|
17
|
+
project: "qse-qguard-video",
|
|
18
|
+
decision:
|
|
19
|
+
"client chose one dark canvas across all seven sections for the QGuard restyle; light survives as panel surfaces inside the dark shell, not as a per-section world",
|
|
20
|
+
recordedIn: "qse-qguard-video/docs/motion-brand-spec.md",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
scope: { kind: "pipeline" },
|
|
24
|
+
enforcement: {
|
|
25
|
+
kind: "prose",
|
|
26
|
+
anchor: { file: "revola-video/src/tokens.ts" },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/superpowers/specs/2026-07-31-defect-radar-design.md: the
|
|
5
|
+
* Klaviyo round-4 escaped defect ("the v1 brief transcribed the source's
|
|
6
|
+
* facts but buried its thesis") cost a rewritten scene, a full VO regen, and
|
|
7
|
+
* a re-render, because the misread was authored before any scene was built.
|
|
8
|
+
* `brief-structure`'s `missing-thesis` finding is the check that would have
|
|
9
|
+
* caught it: a brief with no `Thesis:` line has, structurally, no argument to
|
|
10
|
+
* check every scene against.
|
|
11
|
+
*/
|
|
12
|
+
export const thesisDeclaredInBrief: Rule = {
|
|
13
|
+
id: "thesis-declared-in-brief",
|
|
14
|
+
claim: "Every brief opens with a \"Thesis: <one sentence>\" line stating the video's one-sentence argument, before any step-by-step content.",
|
|
15
|
+
why: "A brief that transcribes the source's facts without stating its own argument reads as complete while leaving every scene to guess what it is supposed to prove; the Klaviyo v1 plan walked the UI and never argued the payoff, discovered only after a full build.",
|
|
16
|
+
learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
|
|
17
|
+
tier: "gate",
|
|
18
|
+
verify: "`video radar:plan --project <dir>` (check: `brief-structure`)",
|
|
19
|
+
owner: "brief-structure",
|
|
20
|
+
scope: { kind: "pipeline" },
|
|
21
|
+
enforcement: {
|
|
22
|
+
kind: "prose",
|
|
23
|
+
unanchorable: "The rule is a document-structure judgment over prose the brief author writes; no single code location expresses it.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md: "Recurrence across videos makes a
|
|
5
|
+
* component a Tier A CANDIDATE, never an automatic port ... a fork must buy
|
|
6
|
+
* something props cannot." The anchor is the Tier A home the rule gates
|
|
7
|
+
* entry to.
|
|
8
|
+
*/
|
|
9
|
+
export const tierARequiresPropUnreachableEdit: Rule = {
|
|
10
|
+
id: "tier-a-requires-prop-unreachable-edit",
|
|
11
|
+
claim: "Promote a component to Tier A only when it needs an internal edit that props, CSS vars, or the variant API cannot reach; recurrence across videos alone never justifies a port.",
|
|
12
|
+
why: "Unnecessary ports create owned forks that must be manually rebased forever, for zero gain, since props already brand a Tier B component with zero source changes.",
|
|
13
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
14
|
+
tier: "process",
|
|
15
|
+
scope: { kind: "pipeline" },
|
|
16
|
+
enforcement: {
|
|
17
|
+
kind: "prose",
|
|
18
|
+
anchor: { file: "packages/video-kit/src/motion/index.ts" },
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "Body copy stays as fine
|
|
5
|
+
* ruled lines (real text is noise at that size); everything else is genuine
|
|
6
|
+
* content." The exemplar is DocStrip in S1Graveyard.tsx.
|
|
7
|
+
*/
|
|
8
|
+
export const tinyBodyCopyAsRuledLines: Rule = {
|
|
9
|
+
id: "tiny-body-copy-as-ruled-lines",
|
|
10
|
+
claim: "In miniature document props, render body copy as fine ruled lines and reserve real text for headings, numbers, and callouts.",
|
|
11
|
+
why: "Real body text at that scale is visual noise; the hero PDF reads as a genuine dense document precisely because the unreadable parts are abstracted to lines while every scannable element is real content.",
|
|
12
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
13
|
+
tier: "craft",
|
|
14
|
+
scope: { kind: "pipeline" },
|
|
15
|
+
enforcement: {
|
|
16
|
+
kind: "prose",
|
|
17
|
+
anchor: { file: "revola-video/src/scenes/craft/S1Graveyard.tsx", symbol: "DocStrip" },
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-usecase-win-booth.md ("when
|
|
5
|
+
* zoomed in, the text overflows the screen"; the locked taste summary:
|
|
6
|
+
* "titles pinned outside camera push") and portfolio-reels.md's overflow
|
|
7
|
+
* family ("Everything is overflowing at the sides"). Narrower than
|
|
8
|
+
* cross-cut-elements-live-in-the-hud-layer, which covers seam contracts at
|
|
9
|
+
* cuts; this covers titles during in-scene camera travel.
|
|
10
|
+
*/
|
|
11
|
+
export const titlesPinnedOutsideCameraPush: Rule = {
|
|
12
|
+
id: "titles-pinned-outside-camera-push",
|
|
13
|
+
claim: "Render titles and captions in screen space outside the camera transform, so a camera push can never scale them off screen; verify framing with stills at the camera's extreme positions.",
|
|
14
|
+
why: "Titles placed inside the camera transform overflowed the frame at zoom peaks in two videos; screen-space pinning removes the whole defect class instead of re-tuning per zoom.",
|
|
15
|
+
learnedFrom: "claude-sessions-digest/revola-usecase-win-booth.md",
|
|
16
|
+
tier: "craft",
|
|
17
|
+
scope: { kind: "pipeline" },
|
|
18
|
+
enforcement: {
|
|
19
|
+
kind: "prose",
|
|
20
|
+
anchor: { file: "packages/video-kit/src/react/motion.ts" },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
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 { tokensNotRawHexes } from "./tokensNotRawHexes";
|
|
6
|
+
|
|
7
|
+
const check = () => {
|
|
8
|
+
if (tokensNotRawHexes.enforcement.kind !== "source") throw new Error("expected a source rule");
|
|
9
|
+
return tokensNotRawHexes.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("tokensNotRawHexes", () => {
|
|
18
|
+
test("fires once per raw palette hex, in strings and templates, skipping white/black", () => {
|
|
19
|
+
const findings = run("raw-hex-scene.tsx");
|
|
20
|
+
expect(findings).toHaveLength(2);
|
|
21
|
+
expect(findings[0].line).toBe(6);
|
|
22
|
+
expect(findings[0].match).toBe("#0E1420");
|
|
23
|
+
expect(findings[1].line).toBe(7);
|
|
24
|
+
expect(findings[1].match).toBe("#9C33FF");
|
|
25
|
+
for (const f of findings) expect(f.message).toMatch(/hex/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("does not fire on token references or rgba() strings", () => {
|
|
29
|
+
expect(run("token-scene.tsx")).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("scopes to the films that adopted the tokens doctrine", () => {
|
|
33
|
+
// Older projects and revola-video's pre-craft scenes are frozen shipped
|
|
34
|
+
// films; sweeping them in floods the check with pre-doctrine code
|
|
35
|
+
// (corpus calibration hit ~150 findings there). New projects enroll from
|
|
36
|
+
// day one, scenes only - the token module itself lives outside scenes/.
|
|
37
|
+
expect(check().include).toEqual([
|
|
38
|
+
"revola-video/src/scenes/craft/**/*.tsx",
|
|
39
|
+
"rcp-fund7-video/src/scenes/**/*.{ts,tsx}",
|
|
40
|
+
"expanse-video/src/scenes/**/*.{ts,tsx}",
|
|
41
|
+
"reverence-video/src/scenes/**/*.{ts,tsx}",
|
|
42
|
+
"workday-dple-video/src/scenes/**/*.{ts,tsx}",
|
|
43
|
+
]);
|
|
44
|
+
// Rebuilt product-UI mocks carry their own in-file demo palettes by
|
|
45
|
+
// design (sample-brand-from-product content, not film styling).
|
|
46
|
+
expect(check().exclude).toEqual([
|
|
47
|
+
"revola-video/src/scenes/craft/NorthwindUI.tsx",
|
|
48
|
+
"revola-video/src/scenes/craft/RestyleScenes.tsx",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { SyntaxKind } from "ts-morph";
|
|
2
|
+
import type { Rule, SourceFinding } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md ("Palette centralized in CK
|
|
6
|
+
* (src/tokensCraft.ts) - scenes reference only CK tokens, no raw hexes") and
|
|
7
|
+
* HOW_I_BUILD_VIDEOS.md ("Tokens everywhere. One theme.js per look; scenes
|
|
8
|
+
* never hard-code a color. Dark/light/brand then costs one file.").
|
|
9
|
+
*
|
|
10
|
+
* Scoped to the craft film only: that is the build that adopted the tokens
|
|
11
|
+
* doctrine. Older projects AND revola-video's pre-craft scenes are frozen
|
|
12
|
+
* shipped films; corpus calibration proved sweeping them in floods the check
|
|
13
|
+
* with ~150 pre-doctrine findings. rgba()/hsl() strings are deliberately not
|
|
14
|
+
* flagged - hex only. Pure white/black (#fff, #000) is also not flagged:
|
|
15
|
+
* those are specular/glint constants, not palette - the dark regrade shipped
|
|
16
|
+
* with dozens of them unchanged, which is the rule's own test of what
|
|
17
|
+
* "palette" means.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const HEX = /#[0-9a-fA-F]{3,8}\b/g;
|
|
21
|
+
|
|
22
|
+
const WHITE_BLACK = new Set(["#fff", "#ffffff", "#000", "#000000"]);
|
|
23
|
+
|
|
24
|
+
/** The palette hexes (not pure white/black) in the text, in order. */
|
|
25
|
+
function paletteHexes(text: string): string[] {
|
|
26
|
+
return [...text.matchAll(HEX)].map((m) => m[0]).filter((h) => !WHITE_BLACK.has(h.toLowerCase()));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Template literals with substitutions are scanned through their text chunks
|
|
30
|
+
// (head/middle/tail), never as the whole TemplateExpression: scanning the
|
|
31
|
+
// whole expression would double-count a hex that sits inside a substitution's
|
|
32
|
+
// own string literal.
|
|
33
|
+
const LITERAL_KINDS = [
|
|
34
|
+
SyntaxKind.StringLiteral,
|
|
35
|
+
SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
36
|
+
SyntaxKind.TemplateHead,
|
|
37
|
+
SyntaxKind.TemplateMiddle,
|
|
38
|
+
SyntaxKind.TemplateTail,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
export const tokensNotRawHexes: Rule = {
|
|
42
|
+
id: "tokens-not-raw-hexes",
|
|
43
|
+
claim: "Never hard-code a hex color in a scene file; every color comes from the token module.",
|
|
44
|
+
why: "A regrade (light-to-dark) is a one-file change when the palette is centralized; scattered hexes make every restyle a hunt. Melisa's dark-regrade was one file because of this.",
|
|
45
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
46
|
+
tier: "gate",
|
|
47
|
+
// The old wording promised "machine-checked over scene and kit source". It
|
|
48
|
+
// never covered kit source (no packages/ glob has ever been in `include`),
|
|
49
|
+
// and after the 2026-08-24 extraction its four film roots stayed upstream,
|
|
50
|
+
// so in this repo it reaches zero files while still reading as an enforced
|
|
51
|
+
// gate. `includeCoverage.test.ts` now fails on any source rule that matches
|
|
52
|
+
// nothing, so this cannot go quiet again without a declared reason.
|
|
53
|
+
verify: "`vitest run packages/video-craft` - machine-checked over projects carrying a committed token module; the fixture projects in this repo have none, so it currently reaches no file",
|
|
54
|
+
scope: { kind: "pipeline" },
|
|
55
|
+
enforcement: {
|
|
56
|
+
kind: "source",
|
|
57
|
+
check: {
|
|
58
|
+
include: ["revola-video/src/scenes/craft/**/*.tsx", "rcp-fund7-video/src/scenes/**/*.{ts,tsx}", "expanse-video/src/scenes/**/*.{ts,tsx}", "reverence-video/src/scenes/**/*.{ts,tsx}", "workday-dple-video/src/scenes/**/*.{ts,tsx}"],
|
|
59
|
+
exclude: [
|
|
60
|
+
// Rebuilt product-UI mocks: their palettes are scene CONTENT (an
|
|
61
|
+
// in-file light/dark/warm theme demo, a restyle showcase), sampled
|
|
62
|
+
// per sample-brand-from-product, not the film's own styling.
|
|
63
|
+
"revola-video/src/scenes/craft/NorthwindUI.tsx",
|
|
64
|
+
"revola-video/src/scenes/craft/RestyleScenes.tsx",
|
|
65
|
+
],
|
|
66
|
+
run: (file, relPath): SourceFinding[] => {
|
|
67
|
+
const findings: SourceFinding[] = [];
|
|
68
|
+
for (const kind of LITERAL_KINDS) {
|
|
69
|
+
for (const literal of file.getDescendantsOfKind(kind)) {
|
|
70
|
+
// One finding per hex occurrence, not per literal: a gradient
|
|
71
|
+
// string with two palette hexes is two findings, so a second hex
|
|
72
|
+
// is never silently carried by an exception for the first.
|
|
73
|
+
for (const hex of paletteHexes(literal.getText())) {
|
|
74
|
+
findings.push({
|
|
75
|
+
file: relPath,
|
|
76
|
+
line: literal.getStartLineNumber(),
|
|
77
|
+
match: hex,
|
|
78
|
+
message:
|
|
79
|
+
"raw hex color in a scene file. Move it into the token module and reference the token; " +
|
|
80
|
+
"a centralized palette makes a regrade a one-file change.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return findings;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from revola-video/BUILD_NOTES_CRAFT.md: "ink() helper defaulted
|
|
5
|
+
* stroke = CK.ink; CK is as const so TS narrowed the param to that one
|
|
6
|
+
* literal hex and rejected every other colour. Typed stroke: string." Same
|
|
7
|
+
* class as the earlier row.n literal-union guard - it recurs whenever
|
|
8
|
+
* as-const tokens seed defaults.
|
|
9
|
+
*/
|
|
10
|
+
export const typeTokenDefaultedParamsAsString: Rule = {
|
|
11
|
+
id: "type-token-defaulted-params-as-string",
|
|
12
|
+
claim: "Explicitly type any parameter whose default is a token from an as-const object (e.g. stroke: string = CK.ink).",
|
|
13
|
+
why: "Without the annotation TS narrows the parameter to that single literal and rejects every other value; the ink() helper's defaulted stroke accepted exactly one hex until typed string.",
|
|
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/tokensCraft.ts", symbol: "CK" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The machine-checked companion of distinct-transition-per-beat and
|
|
5
|
+
* one-punch-per-moment-class: those govern transitions and punches as prose;
|
|
6
|
+
* this covers only what they do not, component/variant repetition across a
|
|
7
|
+
* plan's sections, and is enforced by lint (adjacent-section and
|
|
8
|
+
* share-of-sections warnings over uses[] component+variant pairs).
|
|
9
|
+
*/
|
|
10
|
+
export const varyKitChoreographyAcrossSections: Rule = {
|
|
11
|
+
id: "vary-kit-choreography-across-sections",
|
|
12
|
+
claim: "Never lean on the same kit component+variant pair in adjacent sections or in more than half of a video's sections; vary the variant or the component so each beat reads authored.",
|
|
13
|
+
why: "Kit primitives make repetition frictionless, and a video whose every section opens with the same odometer or the same bar-grow reads as template output; the WS3 spec makes this the plan-stage de-sameness gate.",
|
|
14
|
+
learnedFrom: "docs/superpowers/specs/2026-07-24-ws3-theme-variant-api-design.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
anchor: { file: "packages/video-cli/src/lint.ts", symbol: "lint" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/style/CREATIVE_EDGE.md directive 5 ("research any new
|
|
5
|
+
* packages on react that will look good for visualizations. like heatmaps,
|
|
6
|
+
* 3d stuff", "use different visualizations, be creative", "professional
|
|
7
|
+
* custom SVG animations that represent maturing debt and new lending
|
|
8
|
+
* properly" - never "just some lines and shapes"). The harbor-map pattern
|
|
9
|
+
* is the approved shape for external packages: pull data at authoring time.
|
|
10
|
+
*/
|
|
11
|
+
export const varyTheVisualizationGrammar: Rule = {
|
|
12
|
+
id: "vary-the-visualization-grammar",
|
|
13
|
+
claim: "Every concept gets its own visual form - heatmap, real-geography map, dial, ledger, odometer, glyph field, architectural elevation - that genuinely depicts the concept; never two adjacent beats in the same grammar, never abstract lines-and-shapes filler, and external visualization packages are sanctioned with their data and assets pulled at authoring time so renders stay deterministic.",
|
|
14
|
+
why: "'Use different visualizations, be creative' and the demand for SVG animations that 'represent maturing debt and new lending properly' recurred across builds; same-grammar adjacent beats and abstract filler both drew the template verdict.",
|
|
15
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
16
|
+
tier: "craft",
|
|
17
|
+
scope: { kind: "pipeline" },
|
|
18
|
+
enforcement: {
|
|
19
|
+
kind: "prose",
|
|
20
|
+
unanchorable: "A concept-to-form mapping judgment across beats; no single code location expresses it.",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md: "Verify cheap, render dear. esbuild
|
|
5
|
+
* bundle + typecheck as the CI-style gate ... full render only on request."
|
|
6
|
+
*/
|
|
7
|
+
export const verifyCheapRenderDear: Rule = {
|
|
8
|
+
id: "verify-cheap-render-dear",
|
|
9
|
+
claim: "Gate every change with the cheap checks (bundle + typecheck, Studio scrub, per-beat stills) and run the full render only on demand, last.",
|
|
10
|
+
why: "A full render costs minutes of CPU; using it as a routine check turns every iteration loop from seconds into minutes and starves the machine.",
|
|
11
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
12
|
+
tier: "process",
|
|
13
|
+
scope: { kind: "pipeline" },
|
|
14
|
+
enforcement: {
|
|
15
|
+
kind: "prose",
|
|
16
|
+
anchor: { file: "packages/video-cli/src/typecheckAll.ts" },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from claude-sessions-digest/revola-craft-video.md: "why do you say
|
|
5
|
+
* we send them a long pdf,.. it should be 'you send them a long pdf'".
|
|
6
|
+
*/
|
|
7
|
+
export const voAddressesTheViewer: Rule = {
|
|
8
|
+
id: "vo-addresses-the-viewer",
|
|
9
|
+
claim: "The voiceover addresses the viewer as 'you'; the pain is something you experience, never something 'we' do to them.",
|
|
10
|
+
why: "A 'we send them a long pdf' line made the vendor the villain of its own pitch; point of view is a one-word bug with a whole-script blast radius.",
|
|
11
|
+
learnedFrom: "claude-sessions-digest/revola-craft-video.md",
|
|
12
|
+
tier: "craft",
|
|
13
|
+
scope: { kind: "pipeline" },
|
|
14
|
+
enforcement: {
|
|
15
|
+
kind: "prose",
|
|
16
|
+
anchor: { file: "packages/video-cli/src/voice.ts" },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from docs/style/CREATIVE_EDGE.md directive 17 ("consistent and
|
|
5
|
+
* confident throughout", "don't use [calm], it sounds boring", never
|
|
6
|
+
* "fluctuating like describing a fairytale"). Full takes were re-recorded
|
|
7
|
+
* over tone alone; the wording rules live in vo-script-locks-spoken-words
|
|
8
|
+
* and vo-addresses-the-viewer - this rule owns the performance.
|
|
9
|
+
*/
|
|
10
|
+
export const voIsAPerformance: Rule = {
|
|
11
|
+
id: "vo-is-a-performance",
|
|
12
|
+
claim: "The read is steady, confident, and energetic - never robotic, never fairytale-fluctuating, never tagged [calm] - and the VO never reads the screen: it adds the aside and the why, and deliberately goes silent under hero visuals.",
|
|
13
|
+
why: "'Don't use [calm], it sounds boring' and 'consistent and confident throughout' triggered full re-records on tone alone; a technically clean take with the wrong performance cost the same round a broken one would.",
|
|
14
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
15
|
+
tier: "craft",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
unanchorable: "A performance judgment on the recorded read; no single code location expresses it.",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from HOW_I_BUILD_VIDEOS.md ("Write the script so acronyms are spelled
|
|
5
|
+
* the way you want them spoken ... the recording bakes it in"), merged with
|
|
6
|
+
* act854-video/BUILD_NOTES.md's recorded instance ("speech 9 was recorded
|
|
7
|
+
* from the 'Bill of Materials' spelled-out variant - re-record from v6 if you
|
|
8
|
+
* want the acronyms spoken"). Covers acronym spelling AND final wording.
|
|
9
|
+
*/
|
|
10
|
+
export const voScriptLocksSpokenWords: Rule = {
|
|
11
|
+
id: "vo-script-locks-spoken-words",
|
|
12
|
+
claim: "Finalize the exact spoken wording in the VO script before recording, including spelling acronyms the way they must be spoken.",
|
|
13
|
+
why: "The recording bakes the words in: audio that says 'Bill of Materials' when the client expects the acronym spoken is discovered only after the build is wired to that WAV, and the fix is a re-record.",
|
|
14
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
15
|
+
tier: "process",
|
|
16
|
+
scope: { kind: "pipeline" },
|
|
17
|
+
enforcement: {
|
|
18
|
+
kind: "prose",
|
|
19
|
+
anchor: { file: "packages/video-cli/src/voice.ts" },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Rule } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mined from the product-room build: swapping hand-built cards for a shared
|
|
5
|
+
* GlassPanel broke two invariants at once. Padding moved to the outer element
|
|
6
|
+
* (so callers' spacing vanished) and the content wrapper had no height (so a
|
|
7
|
+
* child at `inset: 24` collapsed and an entire micro-walkthrough rendered as
|
|
8
|
+
* an empty rectangle). Wrapper components are exactly where a single edit
|
|
9
|
+
* reaches every scene, so their geometry contract has to be explicit.
|
|
10
|
+
*/
|
|
11
|
+
export const wrappersPreserveChildGeometry: Rule = {
|
|
12
|
+
id: "wrappers-preserve-child-geometry",
|
|
13
|
+
claim: "A shared surface or wrapper component must preserve its children's geometry: keep the padding semantics its callers expect and give its content box the panel's full size so absolutely positioned children still resolve against it.",
|
|
14
|
+
why: "A glass wrapper introduced mid-build put padding on the outer box and wrapped children in a zero-height div, which silently removed every panel's padding and collapsed every absolutely positioned child across all five scenes at once - one component, whole-film blast radius.",
|
|
15
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
16
|
+
tier: "craft",
|
|
17
|
+
scope: { kind: "pipeline" },
|
|
18
|
+
enforcement: {
|
|
19
|
+
kind: "prose",
|
|
20
|
+
anchor: { file: "revola-video/src/productroom/Glass.tsx" },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { SyntaxKind } from "ts-morph";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
5
|
+
import { checkFile, runSourceCheck } from "./sourceCheck";
|
|
6
|
+
import type { SourceCheck } from "./types";
|
|
7
|
+
|
|
8
|
+
const countJsxElements: SourceCheck = {
|
|
9
|
+
include: ["packages/video-craft/src/fixtures/*.tsx"],
|
|
10
|
+
run: (file, relPath) =>
|
|
11
|
+
file.getDescendantsOfKind(SyntaxKind.JsxOpeningElement).map((node) => ({
|
|
12
|
+
file: relPath,
|
|
13
|
+
line: node.getStartLineNumber(),
|
|
14
|
+
match: node.getText().slice(0, 60),
|
|
15
|
+
message: "element",
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe("checkFile", () => {
|
|
20
|
+
test("reports findings with the repo-relative path, not the absolute one", () => {
|
|
21
|
+
const rel = "packages/video-craft/src/fixtures/nested-filters-fixed.tsx";
|
|
22
|
+
const findings = checkFile(countJsxElements, join(repoRoot, rel), rel, repoRoot);
|
|
23
|
+
expect(findings.length).toBeGreaterThan(0);
|
|
24
|
+
expect(findings.every((f) => f.file === rel)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("runSourceCheck", () => {
|
|
29
|
+
test("visits every file matched by the globs", () => {
|
|
30
|
+
const files = new Set(runSourceCheck(countJsxElements, repoRoot).map((f) => f.file));
|
|
31
|
+
expect(files).toContain("packages/video-craft/src/fixtures/nested-filters-broken.tsx");
|
|
32
|
+
expect(files).toContain("packages/video-craft/src/fixtures/nested-filters-fixed.tsx");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("returns nothing when the globs match nothing", () => {
|
|
36
|
+
expect(runSourceCheck({ ...countJsxElements, include: ["no/such/dir/*.tsx"] }, repoRoot)).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("never descends into node_modules", () => {
|
|
40
|
+
const check: SourceCheck = { ...countJsxElements, include: ["**/*.tsx"] };
|
|
41
|
+
expect(runSourceCheck(check, repoRoot).every((f) => !f.file.includes("node_modules"))).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("honors a per-check exclude glob", () => {
|
|
45
|
+
const check: SourceCheck = { ...countJsxElements, exclude: ["**/nested-filters-broken.tsx"] };
|
|
46
|
+
const files = new Set(runSourceCheck(check, repoRoot).map((f) => f.file));
|
|
47
|
+
expect(files).not.toContain("packages/video-craft/src/fixtures/nested-filters-broken.tsx");
|
|
48
|
+
expect(files).toContain("packages/video-craft/src/fixtures/nested-filters-fixed.tsx");
|
|
49
|
+
});
|
|
50
|
+
});
|