@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
package/src/corpus.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { RULES } from "./registry";
|
|
2
|
+
import { matchedFiles, runSourceCheck } from "./sourceCheck";
|
|
3
|
+
import type { SourceFinding } from "./types";
|
|
4
|
+
|
|
5
|
+
export type Exception = {
|
|
6
|
+
rule: string;
|
|
7
|
+
file: string;
|
|
8
|
+
/**
|
|
9
|
+
* The exact matched source text the rule reported (SourceFinding.match),
|
|
10
|
+
* e.g. the hex literal. Keyed on text, not line number: an entry excuses
|
|
11
|
+
* ALL occurrences of that exact text in that file for that rule. That is
|
|
12
|
+
* the deliberate tradeoff - a palette hex restated three times is one
|
|
13
|
+
* decision, not three, and unrelated edits above it can never invalidate
|
|
14
|
+
* the entry or block pushes. A NEW hex or a different violation is never
|
|
15
|
+
* silently excused, which line-keying could not guarantee.
|
|
16
|
+
*/
|
|
17
|
+
match: string;
|
|
18
|
+
/** Why this finding is acceptable. An empty justification is not allowed. */
|
|
19
|
+
justification: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Findings in existing, shipped video that we have looked at and accepted.
|
|
24
|
+
* Every entry is a small admission that a rule is imprecise, so this list
|
|
25
|
+
* staying short is itself a signal. An entry with no justification fails the
|
|
26
|
+
* corpus test.
|
|
27
|
+
*/
|
|
28
|
+
const HEX_RULE = "tokens-not-raw-hexes";
|
|
29
|
+
const PANEL_FILL = "dark panel fill predates the rule; follow-up: tokenize into CK";
|
|
30
|
+
const BG_RESTATED = "raw restatement of CK.bg (#0b0b0d) predates the rule; follow-up: reference the token";
|
|
31
|
+
const HUE_RESTATED = "avatar hue pair restates CK accent tokens raw; predates the rule; follow-up: reference the tokens";
|
|
32
|
+
const LAVENDER = "lavender text tint (#c9b8fb) has no CK token yet; predates the rule; follow-up: tokenize into CK";
|
|
33
|
+
const ROCKET = "rocket illustration fill has no CK token yet; predates the rule; follow-up: tokenize into CK";
|
|
34
|
+
|
|
35
|
+
export const KNOWN_EXCEPTIONS: Exception[] = [
|
|
36
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S3Line.tsx", match: "#15141d", justification: PANEL_FILL },
|
|
37
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S3Line.tsx", match: "#1c1a26", justification: PANEL_FILL },
|
|
38
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S3Line.tsx", match: "#14131c", justification: PANEL_FILL },
|
|
39
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S4Proof.tsx", match: "#16151e", justification: PANEL_FILL },
|
|
40
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S2Turn.tsx", match: "#0b0b0d", justification: BG_RESTATED },
|
|
41
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S4Proof.tsx", match: "#0b0b0d", justification: BG_RESTATED },
|
|
42
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/craftKit.tsx", match: "#0b0b0d", justification: BG_RESTATED },
|
|
43
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#8B5CF6", justification: HUE_RESTATED },
|
|
44
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#5CA4F6", justification: HUE_RESTATED },
|
|
45
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#5fd39a", justification: HUE_RESTATED },
|
|
46
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#f8b8aa", justification: HUE_RESTATED },
|
|
47
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#f5d67a", justification: HUE_RESTATED },
|
|
48
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/S5Mirror.tsx", match: "#c9b8fb", justification: LAVENDER },
|
|
49
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/ink.tsx", match: "#f5c66d", justification: ROCKET },
|
|
50
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/ink.tsx", match: "#2a2833", justification: ROCKET },
|
|
51
|
+
{ rule: HEX_RULE, file: "revola-video/src/scenes/craft/ink.tsx", match: "#bda6ff", justification: ROCKET },
|
|
52
|
+
// v2 (build2) frame-windowed carrier/hero binding: the tag is gated off over
|
|
53
|
+
// the seam-flight/vacate window (so hero-size never measures the shrinking
|
|
54
|
+
// carrier and no post-cue pose trips a geometry check), which a string literal
|
|
55
|
+
// cannot express; the runtime tag coverage (radar:scene tag-coverage, 22/22)
|
|
56
|
+
// still verifies every binding against sampled frames during its cues.
|
|
57
|
+
// Follow-up: remotion-videos-emp (source-evaluable hero/carrier windows).
|
|
58
|
+
{
|
|
59
|
+
rule: "data-radar-tags-declared", file: "rcp-fund7-video/src/scenes/insurers.tsx",
|
|
60
|
+
match: 'data-radar={f < T_VACATE ? "insurer-edifice" : undefined}',
|
|
61
|
+
justification: "frame-windowed carrier tag gated off during the closing travel; runtime tag-coverage (22/22) verifies insurer-edifice during its cues. follow-up: remotion-videos-s300",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
rule: "data-radar-tags-declared", file: "rcp-fund7-video/src/surfaces.tsx",
|
|
65
|
+
match: "data-radar={radarId}",
|
|
66
|
+
justification: "Panel forwards its tag via the radarId prop (call sites pass literals); the gate only traces props named dataRadar. Runtime tag-coverage verifies the bindings. follow-up: remotion-videos-s300 (rename prop to dataRadar)",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export function corpusFindings(root: string): (SourceFinding & { rule: string })[] {
|
|
71
|
+
const out: (SourceFinding & { rule: string })[] = [];
|
|
72
|
+
for (const rule of RULES) {
|
|
73
|
+
if (rule.enforcement.kind !== "source") continue;
|
|
74
|
+
for (const finding of runSourceCheck(rule.enforcement.check, root)) {
|
|
75
|
+
out.push({ ...finding, rule: rule.id });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** How a finding is matched against KNOWN_EXCEPTIONS: exact rule, file, and
|
|
82
|
+
* matched text - deliberately not the line number (see Exception above). */
|
|
83
|
+
export const findingKey = (f: { rule: string; file: string; match: string }): string =>
|
|
84
|
+
`${f.rule}|${f.file}|${f.match}`;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A check's include globs narrowed to one project directory under the root.
|
|
88
|
+
* A glob whose head segment IS the project name is kept verbatim; a glob with
|
|
89
|
+
* a wildcard head (`*` or `**`) reaches any project, so its head is pinned to
|
|
90
|
+
* the named project to keep siblings out of the scan. Everything else is
|
|
91
|
+
* dropped: the include lists name their projects explicitly (see
|
|
92
|
+
* SourceCheck.include's rationale), so a head that names a different project
|
|
93
|
+
* is a claim about that project, not this one.
|
|
94
|
+
*/
|
|
95
|
+
const scopedInclude = (include: string[], projectName: string): string[] =>
|
|
96
|
+
include.flatMap((glob) => {
|
|
97
|
+
const [head, ...rest] = glob.split("/");
|
|
98
|
+
if (head === projectName) return [glob];
|
|
99
|
+
if (head === "*" || head === "**") return [[projectName, ...rest].join("/")];
|
|
100
|
+
return [];
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export type ProjectScan = {
|
|
104
|
+
/** Findings NOT excused by KNOWN_EXCEPTIONS - the set that fails a gate. */
|
|
105
|
+
findings: (SourceFinding & { rule: string })[];
|
|
106
|
+
/** Findings a justified KNOWN_EXCEPTIONS entry excuses. */
|
|
107
|
+
excused: (SourceFinding & { rule: string })[];
|
|
108
|
+
/** Source-rule ids whose include globs reach this project. */
|
|
109
|
+
coveredRules: string[];
|
|
110
|
+
/** Source-rule ids with no include glob into this project: their claims go
|
|
111
|
+
* unchecked here, which the caller must surface rather than read as clean. */
|
|
112
|
+
uncoveredRules: string[];
|
|
113
|
+
/** Distinct files scanned across all covered rules. */
|
|
114
|
+
scannedFiles: number;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The `video craft:check --project <dir>` seam: runs every source-check rule
|
|
119
|
+
* over ONE project directory under an explicit root, so a consumer repo can
|
|
120
|
+
* run the craft gate against the installed package with no vitest wiring of
|
|
121
|
+
* its own. `root` is the directory containing the project; `projectName` is
|
|
122
|
+
* the project directory's name, matched against each include glob's head.
|
|
123
|
+
*/
|
|
124
|
+
export function scanProject(root: string, projectName: string): ProjectScan {
|
|
125
|
+
const findings: (SourceFinding & { rule: string })[] = [];
|
|
126
|
+
const excused: (SourceFinding & { rule: string })[] = [];
|
|
127
|
+
const coveredRules: string[] = [];
|
|
128
|
+
const uncoveredRules: string[] = [];
|
|
129
|
+
const scanned = new Set<string>();
|
|
130
|
+
const excusedKeys = new Set(KNOWN_EXCEPTIONS.map(findingKey));
|
|
131
|
+
|
|
132
|
+
for (const rule of RULES) {
|
|
133
|
+
if (rule.enforcement.kind !== "source") continue;
|
|
134
|
+
const include = scopedInclude(rule.enforcement.check.include, projectName);
|
|
135
|
+
if (include.length === 0) {
|
|
136
|
+
uncoveredRules.push(rule.id);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
coveredRules.push(rule.id);
|
|
140
|
+
const check = { ...rule.enforcement.check, include };
|
|
141
|
+
for (const abs of matchedFiles(check, root)) scanned.add(abs);
|
|
142
|
+
for (const finding of runSourceCheck(check, root)) {
|
|
143
|
+
const f = { ...finding, rule: rule.id };
|
|
144
|
+
(excusedKeys.has(findingKey(f)) ? excused : findings).push(f);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { findings, excused, coveredRules, uncoveredRules, scannedFiles: scanned.size };
|
|
149
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
5
|
+
import { scanProject } from "./corpus";
|
|
6
|
+
|
|
7
|
+
/** A consumer-shaped root: project dirs at the top level, like the upstream
|
|
8
|
+
* repo the include globs were written against. The scene file carries a raw
|
|
9
|
+
* hex, which `tokens-not-raw-hexes` (include head `revola-video/`) flags. */
|
|
10
|
+
const HEX_SCENE = [
|
|
11
|
+
"export const Bad = () => {",
|
|
12
|
+
' const fill = "#ff0000";',
|
|
13
|
+
" return fill;",
|
|
14
|
+
"};",
|
|
15
|
+
"",
|
|
16
|
+
].join("\n");
|
|
17
|
+
|
|
18
|
+
let dirs: string[] = [];
|
|
19
|
+
const consumerRoot = (): string => {
|
|
20
|
+
const d = mkdtempSync(join(tmpdir(), "craft-scan-"));
|
|
21
|
+
dirs.push(d);
|
|
22
|
+
return d;
|
|
23
|
+
};
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
26
|
+
dirs = [];
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const writeScene = (root: string, project: string, rel: string, text: string) => {
|
|
30
|
+
const abs = join(root, project, rel);
|
|
31
|
+
mkdirSync(join(abs, ".."), { recursive: true });
|
|
32
|
+
writeFileSync(abs, text);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe("scanProject", () => {
|
|
36
|
+
test("reports findings from the named project only, in root-relative paths", () => {
|
|
37
|
+
const root = consumerRoot();
|
|
38
|
+
writeScene(root, "revola-video", "src/scenes/craft/bad.tsx", HEX_SCENE);
|
|
39
|
+
// A sibling project with the same defect must stay out of scope.
|
|
40
|
+
writeScene(root, "expanse-video", "src/scenes/bad.tsx", HEX_SCENE);
|
|
41
|
+
|
|
42
|
+
const scan = scanProject(root, "revola-video");
|
|
43
|
+
|
|
44
|
+
expect(scan.coveredRules).toContain("tokens-not-raw-hexes");
|
|
45
|
+
const hexFindings = scan.findings.filter((f) => f.rule === "tokens-not-raw-hexes");
|
|
46
|
+
expect(hexFindings).toHaveLength(1);
|
|
47
|
+
expect(hexFindings[0].file).toBe("revola-video/src/scenes/craft/bad.tsx");
|
|
48
|
+
expect(hexFindings[0].match).toBe("#ff0000");
|
|
49
|
+
expect(scan.findings.every((f) => f.file.startsWith("revola-video/"))).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("scopes wildcard-headed include globs to the named project", () => {
|
|
53
|
+
const root = consumerRoot();
|
|
54
|
+
// geometry-sidecar-exports-typed includes "*/src/scenes/scene.*.geometry.ts":
|
|
55
|
+
// the wildcard head must be pinned to the named project, so the sibling's
|
|
56
|
+
// equally-broken sidecar stays out of this project's scan.
|
|
57
|
+
writeScene(root, "revola-video", "src/scenes/scene.s1.geometry.ts", "export const geometry = {};\n");
|
|
58
|
+
writeScene(root, "expanse-video", "src/scenes/scene.s1.geometry.ts", "export const geometry = {};\n");
|
|
59
|
+
|
|
60
|
+
const scan = scanProject(root, "revola-video");
|
|
61
|
+
|
|
62
|
+
const geo = scan.findings.filter((f) => f.rule === "geometry-sidecar-exports-typed");
|
|
63
|
+
expect(geo.length).toBeGreaterThan(0);
|
|
64
|
+
expect(geo.every((f) => f.file.startsWith("revola-video/"))).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("splits KNOWN_EXCEPTIONS-excused findings out of the failing set", () => {
|
|
68
|
+
const root = consumerRoot();
|
|
69
|
+
// corpus.ts excuses #f5c66d in exactly this file for tokens-not-raw-hexes.
|
|
70
|
+
writeScene(root, "revola-video", "src/scenes/craft/ink.tsx", HEX_SCENE.replace("#ff0000", "#f5c66d"));
|
|
71
|
+
|
|
72
|
+
const scan = scanProject(root, "revola-video");
|
|
73
|
+
|
|
74
|
+
expect(scan.findings.filter((f) => f.rule === "tokens-not-raw-hexes")).toEqual([]);
|
|
75
|
+
const excused = scan.excused.filter((f) => f.rule === "tokens-not-raw-hexes");
|
|
76
|
+
expect(excused).toHaveLength(1);
|
|
77
|
+
expect(excused[0].match).toBe("#f5c66d");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("a project no include glob covers reports zero covered rules, never a silent clean", () => {
|
|
81
|
+
const root = consumerRoot();
|
|
82
|
+
writeScene(root, "acme-video", "src/scenes/craft/bad.tsx", HEX_SCENE);
|
|
83
|
+
|
|
84
|
+
const scan = scanProject(root, "acme-video");
|
|
85
|
+
|
|
86
|
+
// Only the wildcard-headed geometry glob can reach an arbitrary project
|
|
87
|
+
// name; every fixed-head rule is uncovered and must say so.
|
|
88
|
+
expect(scan.coveredRules).not.toContain("tokens-not-raw-hexes");
|
|
89
|
+
expect(scan.uncoveredRules).toContain("tokens-not-raw-hexes");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("a clean covered project scans files and finds nothing", () => {
|
|
93
|
+
const root = consumerRoot();
|
|
94
|
+
writeScene(root, "revola-video", "src/scenes/craft/clean.tsx",
|
|
95
|
+
'import { CK } from "./craftKit";\nexport const Clean = () => CK;\n');
|
|
96
|
+
|
|
97
|
+
const scan = scanProject(root, "revola-video");
|
|
98
|
+
|
|
99
|
+
expect(scan.findings).toEqual([]);
|
|
100
|
+
expect(scan.excused).toEqual([]);
|
|
101
|
+
expect(scan.scannedFiles).toBeGreaterThan(0);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { RULES } from "./registry";
|
|
5
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
6
|
+
import { extractEdgeSection } from "./stageProjection";
|
|
7
|
+
|
|
8
|
+
const DOC = "docs/style/CREATIVE_EDGE.md";
|
|
9
|
+
const doc = readFileSync(join(repoRoot, DOC), "utf8");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CREATIVE_EDGE.md is hand-maintained and links to the registry only through
|
|
13
|
+
* hand-typed ids on its `Codified:` lines. Nothing else verifies them: a
|
|
14
|
+
* renamed or deleted rule would silently orphan its directive, and a typo'd
|
|
15
|
+
* id would send a reviewer citing it into a dead end. This test is the whole
|
|
16
|
+
* guard.
|
|
17
|
+
*/
|
|
18
|
+
describe("CREATIVE_EDGE.md registry cross-references", () => {
|
|
19
|
+
test("every id on a Codified: line exists in the registry", () => {
|
|
20
|
+
const known = new Set(RULES.map((r) => r.id));
|
|
21
|
+
const codifiedLines = doc.split("\n").filter((l) => l.includes("**Codified:**"));
|
|
22
|
+
expect(codifiedLines.length).toBeGreaterThan(0);
|
|
23
|
+
|
|
24
|
+
// Every backticked token on a Codified line must be a well-formed rule id
|
|
25
|
+
// AND exist in the registry - a laxer id-shaped regex would silently skip
|
|
26
|
+
// malformed or single-word ids, defeating the guard.
|
|
27
|
+
const cited = codifiedLines.flatMap((l) => [...l.matchAll(/`([^`]+)`/g)].map((m) => m[1]));
|
|
28
|
+
expect(cited.length).toBeGreaterThan(0);
|
|
29
|
+
|
|
30
|
+
const ID_GRAMMAR = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
31
|
+
const malformed = [...new Set(cited)].filter((id) => !ID_GRAMMAR.test(id));
|
|
32
|
+
expect(malformed, `non-id tokens backticked on Codified: lines`).toEqual([]);
|
|
33
|
+
|
|
34
|
+
const unknown = [...new Set(cited)].filter((id) => !known.has(id));
|
|
35
|
+
expect(unknown, `Codified: ids missing from the registry`).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the edge markers extract non-empty bar and checklist sections", () => {
|
|
39
|
+
// The dispatch shell inlines these for scene/amplify/review agents; a
|
|
40
|
+
// moved heading that empties a section would ship silent blank prompts.
|
|
41
|
+
expect(extractEdgeSection(doc, "bar")).toContain("The bar");
|
|
42
|
+
expect(extractEdgeSection(doc, "checklist")).toContain("amplify-pass checklist");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("the inlined sections stay prompt-sized", () => {
|
|
46
|
+
// Both sections ride inside EVERY scene/amplify/review dispatch prompt;
|
|
47
|
+
// unbounded growth silently inflates every dispatch's token bill.
|
|
48
|
+
expect(extractEdgeSection(doc, "bar").length).toBeLessThan(4000);
|
|
49
|
+
expect(extractEdgeSection(doc, "checklist").length).toBeLessThan(4000);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("extractEdgeSection fails loud on missing or duplicated markers", () => {
|
|
53
|
+
expect(() => extractEdgeSection("no markers here", "bar")).toThrow(/edge:begin bar/);
|
|
54
|
+
expect(() => extractEdgeSection("<!-- edge:begin bar -->text", "bar")).toThrow(/edge:end bar/);
|
|
55
|
+
const dup = "<!-- edge:begin bar -->a<!-- edge:end bar --><!-- edge:begin bar -->b<!-- edge:end bar -->";
|
|
56
|
+
expect(() => extractEdgeSection(dup, "bar")).toThrow(/more than once/);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { globSync } from "tinyglobby";
|
|
8
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import { withLockAt } from "../../video-cli/src/graph/worktreeShell";
|
|
11
|
+
import { PACK_LOCK } from "../../../scripts/test-support/packLock";
|
|
12
|
+
|
|
13
|
+
const exec = promisify(execFile);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The installable dist/ (scripts/build-dist.mjs): the packed @revola-ai/video-cli
|
|
17
|
+
* bin loads craft's corpus scan and doc splice out of node_modules under plain
|
|
18
|
+
* node, so craft must ship transpiled JS the way the kit ships dist/pure. The
|
|
19
|
+
* spawns here run PLAIN node against dist/ - no tsx - because that is exactly
|
|
20
|
+
* the resolution mode the installed world runs in, and the one tsx's
|
|
21
|
+
* extensionless tolerance and TS loading would mask.
|
|
22
|
+
*/
|
|
23
|
+
describe("video-craft dist build", () => {
|
|
24
|
+
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const distDir = join(pkgDir, "dist");
|
|
26
|
+
|
|
27
|
+
// PACK_LOCK held across the WHOLE describe, not only the build
|
|
28
|
+
// (scripts/test-support/packLock.ts names the race): create-video's
|
|
29
|
+
// pack.test.ts packs craft from another worker, and craft's prepack
|
|
30
|
+
// rm -rf's this very dist/ (scripts/dist-lib.mjs) - so a lock released
|
|
31
|
+
// after beforeAll would still let that rm land while an `it` below is
|
|
32
|
+
// executing dist/corpus.js under plain node. `withLockAt` is
|
|
33
|
+
// callback-shaped; the callback parks on a promise that afterAll resolves.
|
|
34
|
+
// The deadline covers waiting behind both pack tests (~20s each).
|
|
35
|
+
let release: () => void = () => {};
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
await new Promise<void>((acquired, refused) => {
|
|
38
|
+
withLockAt(PACK_LOCK, () => new Promise<void>((done) => { release = done; acquired(); }), { deadlineMs: 170_000 })
|
|
39
|
+
.catch(refused);
|
|
40
|
+
});
|
|
41
|
+
await exec(process.execPath, [join(pkgDir, "scripts", "build-dist.mjs")]);
|
|
42
|
+
}, 180_000);
|
|
43
|
+
afterAll(() => { release(); });
|
|
44
|
+
|
|
45
|
+
it("transpiles the non-test source tree, data files included, tests and fixtures excluded", () => {
|
|
46
|
+
for (const rel of ["corpus.js", "projectDocSync.js", "registry.js", "types.js", "radar-check-ids.json"]) {
|
|
47
|
+
expect(existsSync(join(distDir, rel)), `dist/${rel} missing`).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
expect(existsSync(join(distDir, "rules", "tokensNotRawHexes.js"))).toBe(true);
|
|
50
|
+
expect(globSync(["**/*.test.js", "**/*.ts", "fixtures/**"], { cwd: distDir })).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("dist/corpus.js scans a project under plain node - ts-morph, globs and the JSON registry import all resolve", async () => {
|
|
54
|
+
const root = mkdtempSync(join(tmpdir(), "craft-dist-"));
|
|
55
|
+
const scene = join(root, "revola-video", "src", "scenes", "craft", "bad.tsx");
|
|
56
|
+
mkdirSync(dirname(scene), { recursive: true });
|
|
57
|
+
writeFileSync(scene, 'export const Bad = () => "#ff0000";\n');
|
|
58
|
+
|
|
59
|
+
const { stdout } = await exec(process.execPath, [
|
|
60
|
+
"--input-type=module", "-e",
|
|
61
|
+
`const { scanProject } = await import(${JSON.stringify(join(distDir, "corpus.js"))});
|
|
62
|
+
console.log(JSON.stringify(scanProject(${JSON.stringify(root)}, "revola-video").findings));`,
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const findings = JSON.parse(stdout);
|
|
66
|
+
expect(findings).toEqual(expect.arrayContaining([
|
|
67
|
+
expect.objectContaining({ rule: "tokens-not-raw-hexes", match: "#ff0000" }),
|
|
68
|
+
]));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("dist/projectDocSync.js splices the CLAUDE.md block under plain node", async () => {
|
|
72
|
+
const root = mkdtempSync(join(tmpdir(), "craft-dist-sync-"));
|
|
73
|
+
writeFileSync(join(root, "CLAUDE.md"),
|
|
74
|
+
"# t\n<!-- craft:begin claude-md -->\nOLD\n<!-- craft:end claude-md -->\n");
|
|
75
|
+
|
|
76
|
+
await exec(process.execPath, [
|
|
77
|
+
"--input-type=module", "-e",
|
|
78
|
+
`const { syncProjectedDocs } = await import(${JSON.stringify(join(distDir, "projectDocSync.js"))});
|
|
79
|
+
syncProjectedDocs(${JSON.stringify(root)});`,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const claude = readFileSync(join(root, "CLAUDE.md"), "utf8");
|
|
83
|
+
expect(claude).toContain("audio-is-the-clock");
|
|
84
|
+
expect(claude).not.toContain("OLD\n");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Every spec and plan under docs/superpowers/ says whether it is finished, and
|
|
2
|
+
// docs/superpowers/README.md indexes them with the same answer. Before this
|
|
3
|
+
// check the only way to learn a plan's state was to read it end to end or
|
|
4
|
+
// find its bd epic; the build-console work alone has four such documents in
|
|
5
|
+
// three states. A document without a status line, a status outside the
|
|
6
|
+
// vocabulary, an index row that disagrees with its document, a document with
|
|
7
|
+
// no row, or a row naming a document that does not exist, all fail here
|
|
8
|
+
// (bd video-toolchain-cs8i, the fourth review's closing accounting asked for
|
|
9
|
+
// this tracking).
|
|
10
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { describe, expect, test } from "vitest";
|
|
13
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
14
|
+
|
|
15
|
+
const DOCS = join(repoRoot, "docs", "superpowers");
|
|
16
|
+
export const STATUS_VOCABULARY = ["draft", "approved", "in progress", "done", "frozen", "superseded", "record"] as const;
|
|
17
|
+
type Status = (typeof STATUS_VOCABULARY)[number];
|
|
18
|
+
|
|
19
|
+
/** The token at the head of a `Status:` line, or null when the line is absent
|
|
20
|
+
* or its token is outside the vocabulary. The status line must sit within
|
|
21
|
+
* the twelve lines after the title so it is read before the document's body. */
|
|
22
|
+
export const statusOf = (markdown: string): Status | null => {
|
|
23
|
+
const lines = markdown.split("\n");
|
|
24
|
+
const h1 = lines.findIndex((l) => l.startsWith("# "));
|
|
25
|
+
if (h1 < 0) return null;
|
|
26
|
+
const line = lines.slice(h1 + 1, h1 + 13).find((l) => l.startsWith("Status:"));
|
|
27
|
+
if (line === undefined) return null;
|
|
28
|
+
const rest = line.slice("Status:".length).trim();
|
|
29
|
+
const token = STATUS_VOCABULARY.find((t) => rest === t || rest.startsWith(`${t} - `));
|
|
30
|
+
return token ?? null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** `| \`plans/x.md\` | done | ... |` rows of the index, keyed by the document path. */
|
|
34
|
+
export const indexRows = (readme: string): Map<string, string> => {
|
|
35
|
+
const rows = new Map<string, string>();
|
|
36
|
+
for (const line of readme.split("\n")) {
|
|
37
|
+
const m = /^\| `((?:plans|specs)\/[^`]+)` \| ([^|]+) \|/.exec(line);
|
|
38
|
+
if (m) rows.set(m[1], m[2].trim());
|
|
39
|
+
}
|
|
40
|
+
return rows;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const documents = (): string[] =>
|
|
44
|
+
(["plans", "specs"] as const).flatMap((kind) =>
|
|
45
|
+
readdirSync(join(DOCS, kind)).filter((f) => f.endsWith(".md")).sort().map((f) => `${kind}/${f}`),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
describe("every spec and plan carries a status, and the index agrees with it", () => {
|
|
49
|
+
const readme = readFileSync(join(DOCS, "README.md"), "utf8");
|
|
50
|
+
const rows = indexRows(readme);
|
|
51
|
+
|
|
52
|
+
test("the index names the vocabulary this test enforces", () => {
|
|
53
|
+
for (const s of STATUS_VOCABULARY) expect(readme).toContain(`| \`${s}\` |`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("every document has a Status line under its title whose token is in the vocabulary", () => {
|
|
57
|
+
const missing = documents().filter((d) => statusOf(readFileSync(join(DOCS, d), "utf8")) === null);
|
|
58
|
+
expect(missing).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("every document has exactly one index row, and the row's status is the document's", () => {
|
|
62
|
+
const docs = documents();
|
|
63
|
+
const disagreements = docs
|
|
64
|
+
.map((d) => ({ d, doc: statusOf(readFileSync(join(DOCS, d), "utf8")), row: rows.get(d) ?? null }))
|
|
65
|
+
.filter(({ doc, row }) => row === null || row !== doc);
|
|
66
|
+
expect(disagreements).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("every index row names a document that exists", () => {
|
|
70
|
+
const stale = [...rows.keys()].filter((d) => !existsSync(join(DOCS, d)));
|
|
71
|
+
expect(stale).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("statusOf reads the token and refuses anything else", () => {
|
|
75
|
+
expect(statusOf("# T\n\nStatus: done - landed\n")).toBe("done");
|
|
76
|
+
expect(statusOf("<!-- x -->\n# T\nStatus: in progress\n")).toBe("in progress");
|
|
77
|
+
expect(statusOf("# T\n\nStatus: finished\n")).toBeNull();
|
|
78
|
+
expect(statusOf("# T\n\nStatus: done-ish\n")).toBeNull();
|
|
79
|
+
expect(statusOf("# T\n\nno status here\n")).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { globSync } from "tinyglobby";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import { verifyAnchors } from "./anchors";
|
|
6
|
+
import { renderClaudeMdBlock, renderPipelineBlock, spliceBlock } from "./projectDoc";
|
|
7
|
+
import { componentSlugs, renderComponentMeta } from "./projectMeta";
|
|
8
|
+
import { RULES } from "./registry";
|
|
9
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
10
|
+
import { ALL_STAGES, STAGES } from "./stages";
|
|
11
|
+
import { renderRuleSheetsJson, renderStageSheet } from "./stageProjection";
|
|
12
|
+
import { renderWaiversJson } from "./waiverProjection";
|
|
13
|
+
|
|
14
|
+
const read = (rel: string) => readFileSync(join(repoRoot, rel), "utf8");
|
|
15
|
+
|
|
16
|
+
const REGENERATE = "Run: npx tsx packages/video-craft/scripts/generate.ts";
|
|
17
|
+
|
|
18
|
+
describe("drift guards", () => {
|
|
19
|
+
test("every generated kit sidecar matches the registry", () => {
|
|
20
|
+
for (const slug of componentSlugs(RULES)) {
|
|
21
|
+
const rel = `packages/video-kit/src/motion/${slug}.meta.ts`;
|
|
22
|
+
expect(read(rel), `${rel} is stale. ${REGENERATE}`).toBe(renderComponentMeta(RULES, slug));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("no orphaned sidecar outlives its rules", () => {
|
|
27
|
+
// Regenerating only writes CURRENT slugs; deleting or renaming a
|
|
28
|
+
// component rule would otherwise leave the old .meta.ts behind, green.
|
|
29
|
+
const committed = globSync("packages/video-kit/src/motion/*.meta.ts", { cwd: repoRoot })
|
|
30
|
+
.map((p) => p.replace(/^.*\/(.*)\.meta\.ts$/, "$1"))
|
|
31
|
+
.sort();
|
|
32
|
+
expect(committed, `stale sidecar on disk. Delete it, then ${REGENERATE}`)
|
|
33
|
+
.toEqual(componentSlugs(RULES));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("HOW_I_BUILD_VIDEOS.md's generated block matches the registry", () => {
|
|
37
|
+
const rel = "HOW_I_BUILD_VIDEOS.md";
|
|
38
|
+
const current = read(rel);
|
|
39
|
+
expect(current, `${rel} is stale. ${REGENERATE}`)
|
|
40
|
+
.toBe(spliceBlock(current, "pipeline", renderPipelineBlock(RULES)));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("CLAUDE.md's generated block matches the registry", () => {
|
|
44
|
+
const rel = "CLAUDE.md";
|
|
45
|
+
const current = read(rel);
|
|
46
|
+
expect(current, `${rel} is stale. ${REGENERATE}`)
|
|
47
|
+
.toBe(spliceBlock(current, "claude-md", renderClaudeMdBlock(RULES)));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("every prose anchor still resolves", () => {
|
|
51
|
+
expect(verifyAnchors(RULES, repoRoot)).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("radar's generated waivers.generated.json matches the registry", () => {
|
|
55
|
+
const rel = "packages/video-cli/src/radar/waivers.generated.json";
|
|
56
|
+
expect(read(rel), `${rel} is stale. ${REGENERATE}`).toBe(renderWaiversJson(RULES));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("every stage sheet matches the registry and stage table", () => {
|
|
60
|
+
for (const stage of ALL_STAGES) {
|
|
61
|
+
const rel = `docs/rules/stages/${stage}.md`;
|
|
62
|
+
expect(read(rel), `${rel} is stale. ${REGENERATE}`)
|
|
63
|
+
.toBe(`${renderStageSheet(RULES, STAGES, stage)}\n`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("no orphaned stage sheet outlives its stage", () => {
|
|
68
|
+
const committed = globSync("docs/rules/stages/*.md", { cwd: repoRoot })
|
|
69
|
+
.map((p) => p.replace(/^.*\/(.*)\.md$/, "$1"))
|
|
70
|
+
.sort();
|
|
71
|
+
expect(committed, `stale stage sheet on disk. Delete it, then ${REGENERATE}`)
|
|
72
|
+
.toEqual([...ALL_STAGES].sort());
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("dispatch's generated ruleSheets.generated.json matches the registry", () => {
|
|
76
|
+
const rel = "packages/video-cli/src/graph/ruleSheets.generated.json";
|
|
77
|
+
const creativeEdgeDoc = read("docs/style/CREATIVE_EDGE.md");
|
|
78
|
+
expect(read(rel), `${rel} is stale. ${REGENERATE}`)
|
|
79
|
+
.toBe(renderRuleSheetsJson(RULES, STAGES, creativeEdgeDoc));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("hand-written back-references", () => {
|
|
84
|
+
// Notes files cite rules as (Rule: `<id>`.). Nothing else validates those
|
|
85
|
+
// citations, so a rule rename would orphan them silently. The upstream
|
|
86
|
+
// BUILD_NOTES files (revola-video/BUILD_NOTES_CRAFT.md,
|
|
87
|
+
// act854-video/BUILD_NOTES.md) stayed in remotion-videos at extraction and
|
|
88
|
+
// are guarded there, not here (see UPSTREAM_PROVENANCE_ROOTS); this scan
|
|
89
|
+
// covers only the notes files that live in this repo.
|
|
90
|
+
const CITED_FILES = [
|
|
91
|
+
"HOW_I_BUILD_VIDEOS.md",
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Both the singular form (Rule: `id`.) and the plural (Rules: `a`, `b`.).
|
|
95
|
+
const CITATION = /\(Rules?: ([^)]+)\.\)/g;
|
|
96
|
+
const CITED_ID = /`([a-z0-9-]+)`/g;
|
|
97
|
+
|
|
98
|
+
/** The generated pipeline block cites ids by design and is already guarded. */
|
|
99
|
+
const stripGeneratedBlock = (doc: string): string =>
|
|
100
|
+
doc.replace(/<!-- craft:begin pipeline -->[\s\S]*?<!-- craft:end pipeline -->/, "");
|
|
101
|
+
|
|
102
|
+
const citations = CITED_FILES.flatMap((rel) => {
|
|
103
|
+
const doc = rel === "HOW_I_BUILD_VIDEOS.md" ? stripGeneratedBlock(read(rel)) : read(rel);
|
|
104
|
+
return [...doc.matchAll(CITATION)].flatMap((m) =>
|
|
105
|
+
[...m[1].matchAll(CITED_ID)].map((id) => ({ file: rel, id: id[1] })),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("every citation names a registered rule", () => {
|
|
110
|
+
const ids = new Set(RULES.map((r) => r.id));
|
|
111
|
+
const orphaned = citations.filter((c) => !ids.has(c.id));
|
|
112
|
+
expect(
|
|
113
|
+
orphaned.map((c) => `${c.file}: (Rule: \`${c.id}\`.)`),
|
|
114
|
+
"Orphaned back-reference: the cited rule id no longer exists in RULES. Update the citation or restore the rule id.",
|
|
115
|
+
).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("the citation regex actually finds the hand-written citations", () => {
|
|
119
|
+
// Guards against the regex silently matching nothing after a format
|
|
120
|
+
// change. HOW_I_BUILD_VIDEOS.md alone carries 26 citations today; the
|
|
121
|
+
// floor dropped from 40 when the upstream BUILD_NOTES files left the scan.
|
|
122
|
+
expect(citations.length).toBeGreaterThanOrEqual(20);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill } from "remotion";
|
|
3
|
+
|
|
4
|
+
// The template-literal tag family. `qse-qguard-video/src/artifacts/Theft.tsx`
|
|
5
|
+
// tags exactly this way (`data-radar={`branch-${kind}`}`), and the first
|
|
6
|
+
// version of this rule read only bare StringLiteral initializers - so every
|
|
7
|
+
// id in a family like this read as untagged. Three declared ids, one tag site.
|
|
8
|
+
type BranchKind = "relay" | "export" | "forged";
|
|
9
|
+
|
|
10
|
+
export const Branches: React.FC<{ kind: BranchKind }> = ({ kind }) => (
|
|
11
|
+
<AbsoluteFill>
|
|
12
|
+
<div data-radar={`branch-${kind}`} style={{ width: 100, height: 100 }} />
|
|
13
|
+
</AbsoluteFill>
|
|
14
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
doctrine: 1
|
|
2
|
+
sections:
|
|
3
|
+
- id: stolen
|
|
4
|
+
kind: motion
|
|
5
|
+
component: Branches
|
|
6
|
+
vo: "A relay. An export. A forgery."
|
|
7
|
+
names:
|
|
8
|
+
- cue: 0
|
|
9
|
+
element: branch-relay
|
|
10
|
+
says: "a relay"
|
|
11
|
+
- cue: 1
|
|
12
|
+
element: branch-export
|
|
13
|
+
says: "an export"
|
|
14
|
+
- cue: 2
|
|
15
|
+
element: branch-forged
|
|
16
|
+
says: "a forgery"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { AbsoluteFill } from "remotion";
|
|
3
|
+
|
|
4
|
+
// The prop-passthrough convention, copied from
|
|
5
|
+
// `qse-qguard-video/src/artifacts/RequestCard.tsx` + `src/scenes/S1Once.tsx`:
|
|
6
|
+
// a reusable component forwards its tag (`data-radar={dataRadar}`) and the
|
|
7
|
+
// real ids are the literals its CALL SITES pass. Neither half is a bare
|
|
8
|
+
// string literal at the tag site, so a rule reading only the attribute would
|
|
9
|
+
// call both declared ids untagged.
|
|
10
|
+
const Chip: React.FC<{ dataRadar?: string }> = ({ dataRadar }) => (
|
|
11
|
+
<div data-radar={dataRadar} style={{ width: 40, height: 20 }} />
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const Scene: React.FC = () => (
|
|
15
|
+
<AbsoluteFill>
|
|
16
|
+
<Chip dataRadar="cookie-chip" />
|
|
17
|
+
<Chip dataRadar="cookie-chip-copy" />
|
|
18
|
+
</AbsoluteFill>
|
|
19
|
+
);
|