@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,41 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
|
+
import { declaredElements, findPlanDir, INCLUDE, isReporterFile, projectSourceFiles } from "./dataRadarTagsDeclared.js";
|
|
4
|
+
const sceneRootTagDeclared = {
|
|
5
|
+
id: "scene-root-tag-declared",
|
|
6
|
+
claim: 'A scene declares its scene-space contract: the root AbsoluteFill carries data-radar-scene-root="static" when the scene has no whole-scene camera transform; when it has one, the innermost full-bleed camera layer carries data-radar-scene-root instead.',
|
|
7
|
+
why: "radar:scene derives every geometry number from the [data-radar-scene-root] element and refuses at setup without it; both build2 first passes (Claude's fan-out and the Gemini replays) shipped scenes missing the tag and cost a dedicated fix round (c662df7) before the first sweep could run.",
|
|
8
|
+
learnedFrom: "docs/superpowers/specs/2026-08-08-build2-track-b-round2-report.md",
|
|
9
|
+
tier: "gate",
|
|
10
|
+
verify: "`vitest run packages/video-craft` (source-level, no render) - at runtime `video radar:scene --project <dir> --comp <id>` refuses at setup naming the missing tag",
|
|
11
|
+
scope: { kind: "pipeline" },
|
|
12
|
+
enforcement: {
|
|
13
|
+
kind: "source",
|
|
14
|
+
check: {
|
|
15
|
+
include: INCLUDE,
|
|
16
|
+
run(_file, relPath, root) {
|
|
17
|
+
const absPath = join(root, relPath);
|
|
18
|
+
const planDir = findPlanDir(dirname(absPath), root);
|
|
19
|
+
if (!planDir || !existsSync(planDir)) return [];
|
|
20
|
+
if (sceneRootTagDeclared.enforcement.kind !== "source") return [];
|
|
21
|
+
const selfCheck = sceneRootTagDeclared.enforcement.check;
|
|
22
|
+
if (!isReporterFile(planDir, absPath, selfCheck, root)) return [];
|
|
23
|
+
const declared = declaredElements(planDir, root);
|
|
24
|
+
if (declared.length === 0) return [];
|
|
25
|
+
const tagged = projectSourceFiles(planDir, selfCheck, root).some((f) => readFileSync(f, "utf8").includes("data-radar-scene-root"));
|
|
26
|
+
if (tagged) return [];
|
|
27
|
+
const planFile = declared[0].planFile;
|
|
28
|
+
const projRel = relative(root, planDir).split(sep).join("/");
|
|
29
|
+
return [{
|
|
30
|
+
file: planFile,
|
|
31
|
+
line: 1,
|
|
32
|
+
match: "data-radar-scene-root",
|
|
33
|
+
message: `${projRel} declares names: bindings but no data-radar-scene-root attribute exists anywhere in its source, so radar:scene refuses at setup on every scene. Put data-radar-scene-root="static" on the scene's root AbsoluteFill when the scene has no whole-scene camera transform; when it has one, tag the innermost full-bleed camera layer instead.`
|
|
34
|
+
}];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export {
|
|
40
|
+
sceneRootTagDeclared
|
|
41
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const scrubDemoDataToGenericRoles = {
|
|
2
|
+
id: "scrub-demo-data-to-generic-roles",
|
|
3
|
+
claim: "Scrub all demo data visible in rebuilt UI to generic role placeholders (A. Admin, A. Auditor) before render; never carry real names, emails, or org data from recon captures into panels.",
|
|
4
|
+
why: "Recon captures come from a live authenticated portal, so copied-in table rows are real PII by default; unscrubbed panels leak a client's real user data into a marketing video.",
|
|
5
|
+
learnedFrom: "act854-video/BUILD_NOTES.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "qpa-video/03_scenes/act854/panels.jsx" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
scrubDemoDataToGenericRoles
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const selfReviewThePlanForGaps = {
|
|
2
|
+
id: "self-review-the-plan-for-gaps",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "qse-qguard-video/BUILD_NOTES.md",
|
|
6
|
+
tier: "process",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/planmd.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
selfReviewThePlanForGaps
|
|
15
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const sourceArchivedBeforeBrief = {
|
|
2
|
+
id: "source-archived-before-brief",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
|
|
6
|
+
tier: "gate",
|
|
7
|
+
verify: "`video radar:plan --project <dir>` (check: `brief-structure`)",
|
|
8
|
+
owner: "brief-structure",
|
|
9
|
+
scope: { kind: "pipeline" },
|
|
10
|
+
enforcement: {
|
|
11
|
+
kind: "prose",
|
|
12
|
+
unanchorable: "The rule is about what an operator saves into a recon dir before writing a brief; no single code location expresses it."
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
sourceArchivedBeforeBrief
|
|
17
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const statisticsSelfExplanatory = {
|
|
2
|
+
id: "statistics-self-explanatory",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "claude-sessions-digest/revola-craft-video.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/voice.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
statisticsSelfExplanatory
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const stopsAsRelativeSpacing = {
|
|
2
|
+
id: "stops-as-relative-spacing",
|
|
3
|
+
claim: "Author walkthrough stop from values as relative spacing and rescale them with distributeStops; never as absolute frame positions.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-kit/src/pure/walk.ts", symbol: "distributeStops" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
stopsAsRelativeSpacing
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const theWholeCanvasWorks = {
|
|
2
|
+
id: "the-whole-canvas-works",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
unanchorable: "A whole-frame composition judgment per shot; no single code location expresses it."
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
theWholeCanvasWorks
|
|
15
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const themePerSection = {
|
|
2
|
+
id: "theme-per-section",
|
|
3
|
+
claim: "Treat theme (dark or light) as a per-section, token-driven variable, never a per-video constant.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
exceptions: [
|
|
8
|
+
{
|
|
9
|
+
project: "qse-qguard-video",
|
|
10
|
+
decision: "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",
|
|
11
|
+
recordedIn: "qse-qguard-video/docs/motion-brand-spec.md"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
scope: { kind: "pipeline" },
|
|
15
|
+
enforcement: {
|
|
16
|
+
kind: "prose",
|
|
17
|
+
anchor: { file: "revola-video/src/tokens.ts" }
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export {
|
|
21
|
+
themePerSection
|
|
22
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const thesisDeclaredInBrief = {
|
|
2
|
+
id: "thesis-declared-in-brief",
|
|
3
|
+
claim: `Every brief opens with a "Thesis: <one sentence>" line stating the video's one-sentence argument, before any step-by-step content.`,
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/superpowers/specs/2026-07-31-defect-radar-design.md",
|
|
6
|
+
tier: "gate",
|
|
7
|
+
verify: "`video radar:plan --project <dir>` (check: `brief-structure`)",
|
|
8
|
+
owner: "brief-structure",
|
|
9
|
+
scope: { kind: "pipeline" },
|
|
10
|
+
enforcement: {
|
|
11
|
+
kind: "prose",
|
|
12
|
+
unanchorable: "The rule is a document-structure judgment over prose the brief author writes; no single code location expresses it."
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
thesisDeclaredInBrief
|
|
17
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const tierARequiresPropUnreachableEdit = {
|
|
2
|
+
id: "tier-a-requires-prop-unreachable-edit",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
6
|
+
tier: "process",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-kit/src/motion/index.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
tierARequiresPropUnreachableEdit
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const tinyBodyCopyAsRuledLines = {
|
|
2
|
+
id: "tiny-body-copy-as-ruled-lines",
|
|
3
|
+
claim: "In miniature document props, render body copy as fine ruled lines and reserve real text for headings, numbers, and callouts.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "revola-video/src/scenes/craft/S1Graveyard.tsx", symbol: "DocStrip" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
tinyBodyCopyAsRuledLines
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const titlesPinnedOutsideCameraPush = {
|
|
2
|
+
id: "titles-pinned-outside-camera-push",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "claude-sessions-digest/revola-usecase-win-booth.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-kit/src/react/motion.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
titlesPinnedOutsideCameraPush
|
|
15
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SyntaxKind } from "ts-morph";
|
|
2
|
+
const HEX = /#[0-9a-fA-F]{3,8}\b/g;
|
|
3
|
+
const WHITE_BLACK = /* @__PURE__ */ new Set(["#fff", "#ffffff", "#000", "#000000"]);
|
|
4
|
+
function paletteHexes(text) {
|
|
5
|
+
return [...text.matchAll(HEX)].map((m) => m[0]).filter((h) => !WHITE_BLACK.has(h.toLowerCase()));
|
|
6
|
+
}
|
|
7
|
+
const LITERAL_KINDS = [
|
|
8
|
+
SyntaxKind.StringLiteral,
|
|
9
|
+
SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
10
|
+
SyntaxKind.TemplateHead,
|
|
11
|
+
SyntaxKind.TemplateMiddle,
|
|
12
|
+
SyntaxKind.TemplateTail
|
|
13
|
+
];
|
|
14
|
+
const tokensNotRawHexes = {
|
|
15
|
+
id: "tokens-not-raw-hexes",
|
|
16
|
+
claim: "Never hard-code a hex color in a scene file; every color comes from the token module.",
|
|
17
|
+
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.",
|
|
18
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
19
|
+
tier: "gate",
|
|
20
|
+
// The old wording promised "machine-checked over scene and kit source". It
|
|
21
|
+
// never covered kit source (no packages/ glob has ever been in `include`),
|
|
22
|
+
// and after the 2026-08-24 extraction its four film roots stayed upstream,
|
|
23
|
+
// so in this repo it reaches zero files while still reading as an enforced
|
|
24
|
+
// gate. `includeCoverage.test.ts` now fails on any source rule that matches
|
|
25
|
+
// nothing, so this cannot go quiet again without a declared reason.
|
|
26
|
+
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",
|
|
27
|
+
scope: { kind: "pipeline" },
|
|
28
|
+
enforcement: {
|
|
29
|
+
kind: "source",
|
|
30
|
+
check: {
|
|
31
|
+
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}"],
|
|
32
|
+
exclude: [
|
|
33
|
+
// Rebuilt product-UI mocks: their palettes are scene CONTENT (an
|
|
34
|
+
// in-file light/dark/warm theme demo, a restyle showcase), sampled
|
|
35
|
+
// per sample-brand-from-product, not the film's own styling.
|
|
36
|
+
"revola-video/src/scenes/craft/NorthwindUI.tsx",
|
|
37
|
+
"revola-video/src/scenes/craft/RestyleScenes.tsx"
|
|
38
|
+
],
|
|
39
|
+
run: (file, relPath) => {
|
|
40
|
+
const findings = [];
|
|
41
|
+
for (const kind of LITERAL_KINDS) {
|
|
42
|
+
for (const literal of file.getDescendantsOfKind(kind)) {
|
|
43
|
+
for (const hex of paletteHexes(literal.getText())) {
|
|
44
|
+
findings.push({
|
|
45
|
+
file: relPath,
|
|
46
|
+
line: literal.getStartLineNumber(),
|
|
47
|
+
match: hex,
|
|
48
|
+
message: "raw hex color in a scene file. Move it into the token module and reference the token; a centralized palette makes a regrade a one-file change."
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
export {
|
|
59
|
+
tokensNotRawHexes
|
|
60
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const typeTokenDefaultedParamsAsString = {
|
|
2
|
+
id: "type-token-defaulted-params-as-string",
|
|
3
|
+
claim: "Explicitly type any parameter whose default is a token from an as-const object (e.g. stroke: string = CK.ink).",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "revola-video/src/tokensCraft.ts", symbol: "CK" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
typeTokenDefaultedParamsAsString
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const varyKitChoreographyAcrossSections = {
|
|
2
|
+
id: "vary-kit-choreography-across-sections",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/superpowers/specs/2026-07-24-ws3-theme-variant-api-design.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/lint.ts", symbol: "lint" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
varyKitChoreographyAcrossSections
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const varyTheVisualizationGrammar = {
|
|
2
|
+
id: "vary-the-visualization-grammar",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
unanchorable: "A concept-to-form mapping judgment across beats; no single code location expresses it."
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
varyTheVisualizationGrammar
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const verifyCheapRenderDear = {
|
|
2
|
+
id: "verify-cheap-render-dear",
|
|
3
|
+
claim: "Gate every change with the cheap checks (bundle + typecheck, Studio scrub, per-beat stills) and run the full render only on demand, last.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
6
|
+
tier: "process",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/typecheckAll.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
verifyCheapRenderDear
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const voAddressesTheViewer = {
|
|
2
|
+
id: "vo-addresses-the-viewer",
|
|
3
|
+
claim: "The voiceover addresses the viewer as 'you'; the pain is something you experience, never something 'we' do to them.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "claude-sessions-digest/revola-craft-video.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/voice.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
voAddressesTheViewer
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const voIsAPerformance = {
|
|
2
|
+
id: "vo-is-a-performance",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "docs/style/CREATIVE_EDGE.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
unanchorable: "A performance judgment on the recorded read; no single code location expresses it."
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
voIsAPerformance
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const voScriptLocksSpokenWords = {
|
|
2
|
+
id: "vo-script-locks-spoken-words",
|
|
3
|
+
claim: "Finalize the exact spoken wording in the VO script before recording, including spelling acronyms the way they must be spoken.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "HOW_I_BUILD_VIDEOS.md",
|
|
6
|
+
tier: "process",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "packages/video-cli/src/voice.ts" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
voScriptLocksSpokenWords
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const wrappersPreserveChildGeometry = {
|
|
2
|
+
id: "wrappers-preserve-child-geometry",
|
|
3
|
+
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.",
|
|
4
|
+
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.",
|
|
5
|
+
learnedFrom: "claude-sessions-digest/product-room-video.md",
|
|
6
|
+
tier: "craft",
|
|
7
|
+
scope: { kind: "pipeline" },
|
|
8
|
+
enforcement: {
|
|
9
|
+
kind: "prose",
|
|
10
|
+
anchor: { file: "revola-video/src/productroom/Glass.tsx" }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export {
|
|
14
|
+
wrappersPreserveChildGeometry
|
|
15
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { relative, sep } from "node:path";
|
|
2
|
+
import { globSync } from "tinyglobby";
|
|
3
|
+
import { Project, ts } from "ts-morph";
|
|
4
|
+
const toPosix = (p) => p.split(sep).join("/");
|
|
5
|
+
const newProject = () => new Project({
|
|
6
|
+
skipAddingFilesFromTsConfig: true,
|
|
7
|
+
compilerOptions: { allowJs: true, jsx: ts.JsxEmit.ReactJSX }
|
|
8
|
+
});
|
|
9
|
+
function checkFile(check, absPath, relPath, root) {
|
|
10
|
+
return check.run(newProject().addSourceFileAtPath(absPath), relPath, root);
|
|
11
|
+
}
|
|
12
|
+
function matchedFiles(check, root) {
|
|
13
|
+
return globSync(check.include, {
|
|
14
|
+
cwd: root,
|
|
15
|
+
absolute: true,
|
|
16
|
+
ignore: [
|
|
17
|
+
// Test files are not scenes; broad per-project globs would otherwise
|
|
18
|
+
// sweep them into every check's blast radius.
|
|
19
|
+
"**/node_modules/**",
|
|
20
|
+
"**/.worktrees/**",
|
|
21
|
+
"**/04_exports/**",
|
|
22
|
+
"**/*.test.{ts,tsx,jsx}",
|
|
23
|
+
// Webpack output is not scene source either. build/ is gitignored, so
|
|
24
|
+
// whether it exists depends on what was last run on this machine: a
|
|
25
|
+
// locally bundled qse-investor-video put 161 minified-bundle findings
|
|
26
|
+
// into the corpus and turned the suite red only where someone had
|
|
27
|
+
// happened to run `npm run build`. A check whose verdict varies by
|
|
28
|
+
// machine is a flake, not a guard.
|
|
29
|
+
"**/build/**",
|
|
30
|
+
...check.exclude ?? []
|
|
31
|
+
]
|
|
32
|
+
}).sort();
|
|
33
|
+
}
|
|
34
|
+
function runSourceCheck(check, root) {
|
|
35
|
+
const project = newProject();
|
|
36
|
+
const findings = [];
|
|
37
|
+
for (const abs of matchedFiles(check, root)) {
|
|
38
|
+
const file = project.addSourceFileAtPath(abs);
|
|
39
|
+
findings.push(...check.run(file, toPosix(relative(root, abs)), root));
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
checkFile,
|
|
45
|
+
matchedFiles,
|
|
46
|
+
runSourceCheck
|
|
47
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ts } from "ts-morph";
|
|
2
|
+
function scriptKindFor(fileName) {
|
|
3
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
4
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
5
|
+
if (/\.(?:m|c)?js$/.test(fileName)) return ts.ScriptKind.JS;
|
|
6
|
+
return ts.ScriptKind.TS;
|
|
7
|
+
}
|
|
8
|
+
function commentsOnly(text, fileName) {
|
|
9
|
+
const source = ts.createSourceFile(
|
|
10
|
+
fileName,
|
|
11
|
+
text,
|
|
12
|
+
ts.ScriptTarget.Latest,
|
|
13
|
+
/* setParentNodes */
|
|
14
|
+
false,
|
|
15
|
+
scriptKindFor(fileName)
|
|
16
|
+
);
|
|
17
|
+
const diagnostics = source.parseDiagnostics ?? [];
|
|
18
|
+
const first = diagnostics[0];
|
|
19
|
+
if (first) {
|
|
20
|
+
const { line } = source.getLineAndCharacterOfPosition(first.start);
|
|
21
|
+
throw new Error(
|
|
22
|
+
`${fileName}:${line + 1} cannot be parsed as ${ts.ScriptKind[scriptKindFor(fileName)]}: ${ts.flattenDiagnosticMessageText(first.messageText, " ")}. The citation guard reads comments by parsing, so an unparseable file would be scanned as if it had none.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const ranges = [];
|
|
26
|
+
const jsxText = [];
|
|
27
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28
|
+
const take = (pos) => {
|
|
29
|
+
for (const range of [
|
|
30
|
+
...ts.getLeadingCommentRanges(text, pos) ?? [],
|
|
31
|
+
...ts.getTrailingCommentRanges(text, pos) ?? []
|
|
32
|
+
]) {
|
|
33
|
+
if (seen.has(range.pos)) continue;
|
|
34
|
+
seen.add(range.pos);
|
|
35
|
+
ranges.push(range);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const walk = (node) => {
|
|
39
|
+
if (node.kind === ts.SyntaxKind.JsxText) jsxText.push({ pos: node.pos, end: node.end });
|
|
40
|
+
take(node.pos);
|
|
41
|
+
for (const child of node.getChildren(source)) walk(child);
|
|
42
|
+
};
|
|
43
|
+
walk(source);
|
|
44
|
+
return ranges.filter((r) => !jsxText.some((j) => r.pos >= j.pos && r.pos < j.end)).map((r) => text.slice(r.pos, r.end)).join("\n");
|
|
45
|
+
}
|
|
46
|
+
const TRAILING = /[.,;:!?)\]}`'"]+$/;
|
|
47
|
+
const DOC_EXTENSIONS = [".md", ".json", ".yaml", ".yml", ".txt"];
|
|
48
|
+
const DOC_PATTERN = /(?<![\w/.-])docs\/[A-Za-z0-9._/*-]+/g;
|
|
49
|
+
function extractDocCitations(text) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const raw of text.match(DOC_PATTERN) ?? []) {
|
|
52
|
+
const path = raw.replace(TRAILING, "");
|
|
53
|
+
if (path.includes("*")) continue;
|
|
54
|
+
const resolvable = path.endsWith("/") || DOC_EXTENSIONS.some((e) => path.endsWith(e));
|
|
55
|
+
if (!resolvable) continue;
|
|
56
|
+
if (!out.includes(path)) out.push(path);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
const ISSUE_PATTERN = /\b(?:remotion-videos|video-toolchain)-[a-z0-9]{3,4}\b/g;
|
|
61
|
+
function extractIssueCitations(text) {
|
|
62
|
+
return [...new Set(text.match(ISSUE_PATTERN) ?? [])].sort();
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
commentsOnly,
|
|
66
|
+
extractDocCitations,
|
|
67
|
+
extractIssueCitations
|
|
68
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ALL_STAGES } from "./stages.js";
|
|
2
|
+
const exceptionLines = (r) => (r.exceptions ?? []).map(
|
|
3
|
+
(ex) => ` - waived in \`${ex.project}\`: ${ex.decision} (recorded: \`${ex.recordedIn}\`)`
|
|
4
|
+
);
|
|
5
|
+
function renderStageSheet(rules, stages, stage) {
|
|
6
|
+
const mine = rules.filter((r) => r.scope.kind === "pipeline" && (stages[r.id] ?? []).includes(stage)).sort((a, b) => a.id.localeCompare(b.id));
|
|
7
|
+
const of = (tier) => mine.filter((r) => r.tier === tier);
|
|
8
|
+
const section = (heading, rs, withVerify) => rs.length === 0 ? [] : [
|
|
9
|
+
heading,
|
|
10
|
+
``,
|
|
11
|
+
...rs.map(
|
|
12
|
+
(r) => [
|
|
13
|
+
`- ${r.claim} (\`${r.id}\`)`,
|
|
14
|
+
...withVerify && r.verify ? [` - verify: ${r.verify}`] : [],
|
|
15
|
+
...exceptionLines(r)
|
|
16
|
+
].join("\n")
|
|
17
|
+
),
|
|
18
|
+
``
|
|
19
|
+
];
|
|
20
|
+
return [
|
|
21
|
+
`<!-- Generated from packages/video-craft/src/rules/ + stages.ts. Do not edit. -->`,
|
|
22
|
+
``,
|
|
23
|
+
`# Rules for the ${stage} stage`,
|
|
24
|
+
``,
|
|
25
|
+
`These are the registry rules scoped to ${stage}-stage agents. Scoping narrows attention, never authority: every rule in the registry still applies to you. This sheet carries the full claim of each rule in scope; HOW_I_BUILD_VIDEOS.md carries every rule's claim, why and provenance, and is where to read one you meet only as an id (search for it in a \`Rule id:\` line). CLAUDE.md lists the out-of-scope rules by id rather than in full, so do not treat it as the place to go for a claim.`,
|
|
26
|
+
``,
|
|
27
|
+
...section(`## Gates - settled by running the check, not by reading the diff`, of("gate"), true),
|
|
28
|
+
...section(`## Craft rules`, of("craft"), false),
|
|
29
|
+
...section(`## Process rules`, of("process"), false)
|
|
30
|
+
].join("\n").trimEnd();
|
|
31
|
+
}
|
|
32
|
+
function extractEdgeSection(doc, name) {
|
|
33
|
+
const begin = `<!-- edge:begin ${name} -->`;
|
|
34
|
+
const end = `<!-- edge:end ${name} -->`;
|
|
35
|
+
const from = doc.indexOf(begin);
|
|
36
|
+
if (from === -1) throw new Error(`marker "edge:begin ${name}" not found in CREATIVE_EDGE.md`);
|
|
37
|
+
if (doc.indexOf(begin, from + begin.length) !== -1) {
|
|
38
|
+
throw new Error(`marker "edge:begin ${name}" appears more than once in CREATIVE_EDGE.md`);
|
|
39
|
+
}
|
|
40
|
+
const to = doc.indexOf(end, from);
|
|
41
|
+
if (to === -1) throw new Error(`marker "edge:end ${name}" not found in CREATIVE_EDGE.md`);
|
|
42
|
+
if (doc.indexOf(end, to + end.length) !== -1) {
|
|
43
|
+
throw new Error(`marker "edge:end ${name}" appears more than once in CREATIVE_EDGE.md`);
|
|
44
|
+
}
|
|
45
|
+
return doc.slice(from + begin.length, to).trim();
|
|
46
|
+
}
|
|
47
|
+
function renderRuleSheetsJson(rules, stages, creativeEdgeDoc) {
|
|
48
|
+
const sheets = {};
|
|
49
|
+
for (const stage of ALL_STAGES) {
|
|
50
|
+
sheets[stage] = renderStageSheet(rules, stages, stage);
|
|
51
|
+
}
|
|
52
|
+
return `${JSON.stringify(
|
|
53
|
+
{
|
|
54
|
+
stages: sheets,
|
|
55
|
+
creativeEdge: {
|
|
56
|
+
bar: extractEdgeSection(creativeEdgeDoc, "bar"),
|
|
57
|
+
checklist: extractEdgeSection(creativeEdgeDoc, "checklist")
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
null,
|
|
61
|
+
2
|
|
62
|
+
)}
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
extractEdgeSection,
|
|
67
|
+
renderRuleSheetsJson,
|
|
68
|
+
renderStageSheet
|
|
69
|
+
};
|