@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,6 @@
|
|
|
1
|
+
// A project whose plan declares no names: bindings never runs radar:scene's
|
|
2
|
+
// geometry checks against declared elements, so the scene-root contract is
|
|
3
|
+
// not owed here and its absence is not a finding.
|
|
4
|
+
export default function SceneHook() {
|
|
5
|
+
return <div style={{ width: 900, height: 500 }} />;
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The qse-qguard-video shape: the scene module itself carries no scene-root
|
|
2
|
+
// tag because a shared Shell component (a sibling file) carries it for every
|
|
3
|
+
// scene. Project-wide scope keeps this from being a false positive.
|
|
4
|
+
export default function SceneHook() {
|
|
5
|
+
return (
|
|
6
|
+
<div data-radar="hero" style={{ width: 900, height: 500 }} />
|
|
7
|
+
);
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The shared shell owns the scene-space contract for every scene it wraps
|
|
2
|
+
// (this project has no whole-scene camera transform, hence "static").
|
|
3
|
+
export function Shell({ children }: { children: React.ReactNode }) {
|
|
4
|
+
return (
|
|
5
|
+
<div data-radar-scene-root="static" style={{ position: "absolute", inset: 0 }}>
|
|
6
|
+
{children}
|
|
7
|
+
</div>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Fixture: shapes that must NOT fire.
|
|
2
|
+
export const Fine = ({ on, blur }: { on: boolean; blur: number }) => (
|
|
3
|
+
<>
|
|
4
|
+
{/* a ternary yielding undefined is the sanctioned way to omit a value */}
|
|
5
|
+
<div style={{ filter: blur > 0.5 ? `blur(${blur}px)` : undefined }} />
|
|
6
|
+
{/* longhand undefined with NO shorthand sibling cannot clear anything */}
|
|
7
|
+
<div style={{ paddingTop: undefined, color: "red" }} />
|
|
8
|
+
{/* both defined is legal CSS-in-JS cascade */}
|
|
9
|
+
<div style={{ padding: 4, paddingTop: 12 }} />
|
|
10
|
+
{/* borderRadius is not a longhand of border: border does not reset it.
|
|
11
|
+
The CORNER spellings are the ones that actually slip through a prefix
|
|
12
|
+
match, so they are the cases that must be pinned. */}
|
|
13
|
+
<div style={{ border: "1px solid", borderRadius: undefined }} />
|
|
14
|
+
<div style={{ border: "1px solid", borderTopLeftRadius: undefined }} />
|
|
15
|
+
<div style={{ border: "1px solid", borderTopRightRadius: undefined }} />
|
|
16
|
+
<div style={{ border: "1px solid", borderBottomLeftRadius: undefined }} />
|
|
17
|
+
<div style={{ border: "1px solid", borderBottomRightRadius: undefined }} />
|
|
18
|
+
{/* background does not reset background-blend-mode */}
|
|
19
|
+
<div style={{ background: "red", backgroundBlendMode: undefined }} />
|
|
20
|
+
{/* the shorthand itself may be undefined */}
|
|
21
|
+
<div style={{ padding: on ? 8 : undefined }} />
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// Fixture: a longhand explicitly set to `undefined` beside its shorthand.
|
|
4
|
+
// React writes the longhand after the shorthand and clears it, so the
|
|
5
|
+
// shorthand silently stops applying. This is the exact shape that wiped
|
|
6
|
+
// every panel's padding in the product-room build.
|
|
7
|
+
export const Bad = ({ style }: { style?: React.CSSProperties }) => {
|
|
8
|
+
const { padding, paddingTop, ...rest } = style ?? {};
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<div style={{ padding, paddingTop, paddingLeft: undefined }} />
|
|
12
|
+
<div style={{ margin: 8, marginRight: undefined }} />
|
|
13
|
+
<div style={{ background: "red", backgroundColor: undefined }} />
|
|
14
|
+
<div style={{ ...rest }} />
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// A scene that references only token-object members; rgba()/hsl() strings are
|
|
2
|
+
// deliberately out of the rule's scope (hex only).
|
|
3
|
+
const CK = { panel: "tokenized", ink: "tokenized" } as const;
|
|
4
|
+
|
|
5
|
+
export const Panel = () => (
|
|
6
|
+
<div style={{ background: CK.panel, color: CK.ink }}>
|
|
7
|
+
<div style={{ boxShadow: "0 0 12px rgba(0, 0, 0, 0.4)" }} />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// A NEW video project must not silently arrive with zero machine gates.
|
|
2
|
+
//
|
|
3
|
+
// Every source rule enumerates its `include` globs per project, because
|
|
4
|
+
// layouts differ (`qpa-video/03_scenes/**` against `revola-video/src/scenes/**`)
|
|
5
|
+
// and a silent default would make each rule's blast radius a guess - the
|
|
6
|
+
// reasoning in types.ts's `SourceCheck.include` doc. The cost of that
|
|
7
|
+
// decision is that creating a project is invisible to every rule until
|
|
8
|
+
// somebody edits ten include lists by hand, and nothing failed when they
|
|
9
|
+
// did not: `klaviyo-segments-video` and `radar-fixture-video` were both
|
|
10
|
+
// absent from every rule's include list for their entire build, and
|
|
11
|
+
// `qse-qguard-video` was excluded from every source check for its whole
|
|
12
|
+
// build (the incident `deterministic-scene-source`'s own comment records).
|
|
13
|
+
//
|
|
14
|
+
// This test is the missing failure (production-readiness P5). It compares the
|
|
15
|
+
// projects that exist against the projects each rule's globs name, so a new
|
|
16
|
+
// project turns the suite red until it is either enrolled or deliberately
|
|
17
|
+
// excused here with a reason.
|
|
18
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { globSync } from "tinyglobby";
|
|
21
|
+
import { describe, expect, test } from "vitest";
|
|
22
|
+
import { RULES } from "./registry";
|
|
23
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Deliberately narrower than "every project", with the reason. An entry here
|
|
27
|
+
* is a scope decision on the record, not a silent omission - and the test
|
|
28
|
+
* below fails on a STALE entry too, so an excuse cannot outlive its project.
|
|
29
|
+
*/
|
|
30
|
+
const NARROW_BY_DESIGN: Record<string, { projects: string[]; why: string }> = {
|
|
31
|
+
// Entries excusing projects that stayed upstream in remotion-videos at the
|
|
32
|
+
// 2026-08-24 extraction were pruned: the staleness test below is the reason
|
|
33
|
+
// an excuse cannot outlive its project, and those projects are not in this
|
|
34
|
+
// repo. The historical scope decisions live in remotion-videos' copy of
|
|
35
|
+
// this file.
|
|
36
|
+
"tokens-not-raw-hexes": {
|
|
37
|
+
projects: ["qse-qguard-video", "radar-fixture-video"],
|
|
38
|
+
why: "scoped to projects with a committed token module to check against; "
|
|
39
|
+
+ "both fixture projects would report their whole palette as a violation with nothing to point at instead",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Source rules with no subject in THIS repo, with the reason. Unlike
|
|
45
|
+
* NARROW_BY_DESIGN (which excuses a rule from one project), an entry here says
|
|
46
|
+
* the rule's globs currently reach nothing at all. The staleness test below
|
|
47
|
+
* deletes the excuse for you: the moment a matching file lands, the entry
|
|
48
|
+
* fails until it is removed and the rule is enforced for real again.
|
|
49
|
+
*/
|
|
50
|
+
const NO_SUBJECTS_HERE: Record<string, string> = {
|
|
51
|
+
// Empty, and that is the design working. Both former entries expired when
|
|
52
|
+
// `workday-dple-video` landed with a committed token module (src/tokens.ts)
|
|
53
|
+
// and geometry sidecars, exactly as each entry's own text predicted it would
|
|
54
|
+
// - "this entry expires the moment the first sidecar is written". The
|
|
55
|
+
// staleness test below is what deleted them: an excuse here cannot outlive
|
|
56
|
+
// the condition that justified it.
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Top-level project directories that actually carry scene source today. A
|
|
60
|
+
* plan-and-assets-only directory (act854-video, whose scenes live under
|
|
61
|
+
* qpa-video/03_scenes/act854) has nothing for a source rule to read. */
|
|
62
|
+
const projectsWithSceneSource = (): string[] =>
|
|
63
|
+
readdirSync(repoRoot)
|
|
64
|
+
.filter((name) => !name.startsWith(".") && statSync(join(repoRoot, name)).isDirectory())
|
|
65
|
+
.filter((name) => /-video$/.test(name) || name === "spec-videos" || name === "datapeak-usecases" || name === "remocn-sandbox")
|
|
66
|
+
.filter((name) => existsSync(join(repoRoot, name, "package.json")))
|
|
67
|
+
.filter((name) => globSync(["src/**/*.{jsx,tsx}", "03_scenes/**/*.{jsx,tsx}"], {
|
|
68
|
+
cwd: join(repoRoot, name),
|
|
69
|
+
ignore: ["**/node_modules/**", "**/build/**"],
|
|
70
|
+
}).length > 0)
|
|
71
|
+
.sort();
|
|
72
|
+
|
|
73
|
+
/** The top-level directories a rule's include globs reach. `*` as the first
|
|
74
|
+
* segment (geometry-sidecar-exports-typed's `*/src/scenes/...`) covers every
|
|
75
|
+
* project by construction. */
|
|
76
|
+
const projectsCoveredBy = (include: string[]): { names: Set<string>; wildcard: boolean } => {
|
|
77
|
+
const names = new Set<string>();
|
|
78
|
+
let wildcard = false;
|
|
79
|
+
for (const glob of include) {
|
|
80
|
+
const head = glob.split("/")[0];
|
|
81
|
+
if (head === "*" || head === "**") { wildcard = true; continue; }
|
|
82
|
+
names.add(head);
|
|
83
|
+
}
|
|
84
|
+
return { names, wildcard };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
describe("source-rule include coverage", () => {
|
|
88
|
+
const projects = projectsWithSceneSource();
|
|
89
|
+
|
|
90
|
+
test("finds the projects at all (a broken discovery must not read as full coverage)", () => {
|
|
91
|
+
// Post-extraction this repo carries exactly the two test-fixture projects
|
|
92
|
+
// (the vendored qpa-video slice has no package.json on purpose, so it is
|
|
93
|
+
// fixture data, not a project). A discovery that misses either one would
|
|
94
|
+
// silently shrink every rule's blast radius to nothing.
|
|
95
|
+
expect(projects).toContain("qse-qguard-video");
|
|
96
|
+
expect(projects).toContain("radar-fixture-video");
|
|
97
|
+
expect(projects.length).toBeGreaterThanOrEqual(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("every project with scene source is in every source rule's include list", () => {
|
|
101
|
+
const gaps: string[] = [];
|
|
102
|
+
for (const rule of RULES) {
|
|
103
|
+
if (rule.enforcement.kind !== "source") continue;
|
|
104
|
+
const { names, wildcard } = projectsCoveredBy(rule.enforcement.check.include);
|
|
105
|
+
if (wildcard) continue;
|
|
106
|
+
const excused = new Set(NARROW_BY_DESIGN[rule.id]?.projects ?? []);
|
|
107
|
+
for (const project of projects) {
|
|
108
|
+
if (names.has(project) || excused.has(project)) continue;
|
|
109
|
+
gaps.push(`${rule.id} does not cover ${project}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
expect(
|
|
113
|
+
gaps,
|
|
114
|
+
"A new video project starts with ZERO machine gates until it is added to each rule's include list. "
|
|
115
|
+
+ "Add the project's own glob to the rule (layouts differ, so it must be explicit), or - if the rule "
|
|
116
|
+
+ "genuinely does not apply - add it to NARROW_BY_DESIGN in this file with the reason.",
|
|
117
|
+
).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// The coverage test above asks "is every project reached by each rule?".
|
|
121
|
+
// It cannot see the opposite failure: a rule excused for every project that
|
|
122
|
+
// exists, whose remaining globs name directories that do not, matches
|
|
123
|
+
// nothing at all and reports green forever. `tokens-not-raw-hexes` sat in
|
|
124
|
+
// exactly that state after the 2026-08-24 extraction - tier `gate`, with a
|
|
125
|
+
// `verify:` line promising a machine check, scanning zero files - and
|
|
126
|
+
// nothing failed. A gate that cannot reach a subject is a claim, not a gate.
|
|
127
|
+
test("every source rule's include globs actually match a file", () => {
|
|
128
|
+
const dark: string[] = [];
|
|
129
|
+
for (const rule of RULES) {
|
|
130
|
+
if (rule.enforcement.kind !== "source") continue;
|
|
131
|
+
const hits = globSync(rule.enforcement.check.include, {
|
|
132
|
+
cwd: repoRoot,
|
|
133
|
+
ignore: ["**/node_modules/**", "**/build/**"],
|
|
134
|
+
});
|
|
135
|
+
if (hits.length === 0 && !(rule.id in NO_SUBJECTS_HERE)) dark.push(`${rule.id} [tier=${rule.tier}] matches no file`);
|
|
136
|
+
}
|
|
137
|
+
expect(
|
|
138
|
+
dark,
|
|
139
|
+
"A source rule whose globs match nothing enforces nothing, however good its check is. "
|
|
140
|
+
+ "Point the globs at a directory that exists, or - if the rule legitimately has no subject "
|
|
141
|
+
+ "in this repo - add it to NO_SUBJECTS_HERE in this file with the reason.",
|
|
142
|
+
).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("no NO_SUBJECTS_HERE entry is stale", () => {
|
|
146
|
+
const ruleIds = new Set(RULES.map((r) => r.id));
|
|
147
|
+
const stale: string[] = [];
|
|
148
|
+
for (const [ruleId, why] of Object.entries(NO_SUBJECTS_HERE)) {
|
|
149
|
+
if (!ruleIds.has(ruleId)) { stale.push(`${ruleId}: no such rule`); continue; }
|
|
150
|
+
if (!why.trim()) { stale.push(`${ruleId}: no reason given`); continue; }
|
|
151
|
+
const rule = RULES.find((r) => r.id === ruleId);
|
|
152
|
+
if (rule?.enforcement.kind !== "source") { stale.push(`${ruleId}: not a source rule`); continue; }
|
|
153
|
+
const hits = globSync(rule.enforcement.check.include, {
|
|
154
|
+
cwd: repoRoot,
|
|
155
|
+
ignore: ["**/node_modules/**", "**/build/**"],
|
|
156
|
+
});
|
|
157
|
+
// The point of the excuse is that it expires on its own: the moment a
|
|
158
|
+
// subject lands, the rule must go back to being enforced for real.
|
|
159
|
+
if (hits.length > 0) stale.push(`${ruleId}: now matches ${hits.length} file(s); remove its NO_SUBJECTS_HERE entry`);
|
|
160
|
+
}
|
|
161
|
+
expect(stale).toEqual([]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("no NARROW_BY_DESIGN entry is stale", () => {
|
|
165
|
+
const ruleIds = new Set(RULES.map((r) => r.id));
|
|
166
|
+
const stale: string[] = [];
|
|
167
|
+
for (const [ruleId, entry] of Object.entries(NARROW_BY_DESIGN)) {
|
|
168
|
+
if (!ruleIds.has(ruleId)) { stale.push(`${ruleId}: no such rule`); continue; }
|
|
169
|
+
if (!entry.why.trim()) stale.push(`${ruleId}: no reason given`);
|
|
170
|
+
for (const project of entry.projects) {
|
|
171
|
+
if (!projects.includes(project)) stale.push(`${ruleId}: excuses "${project}", which is not a project with scene source`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
expect(stale).toEqual([]);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { isUpstreamProvenance, upstreamCitationError } from "./provenance";
|
|
5
|
+
import { RULES } from "./registry";
|
|
6
|
+
import { toolchainRepoRoot as repoRoot } from "./testSupport";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Every rule's learnedFrom must point at a file that exists, or cite the
|
|
10
|
+
* upstream remotion-videos repo this toolchain was extracted from (see
|
|
11
|
+
* UPSTREAM_PROVENANCE_ROOTS). The digest and notes files are primary
|
|
12
|
+
* sources, not living notes, so they carry no (Rule: `<id>`.)
|
|
13
|
+
* back-references and drift.test.ts does not scan them - this check is the
|
|
14
|
+
* whole provenance guard for such sources: a typo'd, renamed, or deleted
|
|
15
|
+
* local source file fails here instead of rotting silently in an unchecked
|
|
16
|
+
* history string, and an upstream citation still has to be well-formed.
|
|
17
|
+
*/
|
|
18
|
+
describe("learnedFrom provenance", () => {
|
|
19
|
+
test("every local learnedFrom path exists in the repo", () => {
|
|
20
|
+
const missing = RULES.filter(
|
|
21
|
+
(r) => !isUpstreamProvenance(r.learnedFrom) && !existsSync(join(repoRoot, r.learnedFrom)),
|
|
22
|
+
).map((r) => `${r.id} -> ${r.learnedFrom}`);
|
|
23
|
+
expect(missing).toEqual([]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("every upstream learnedFrom citation is well-formed", () => {
|
|
27
|
+
const malformed = RULES.filter(
|
|
28
|
+
(r) => isUpstreamProvenance(r.learnedFrom) && upstreamCitationError(r.learnedFrom),
|
|
29
|
+
).map((r) => `${r.id} -> ${r.learnedFrom}`);
|
|
30
|
+
expect(malformed).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { renderClaudeMdBlock, renderPipelineBlock, spliceBlock } from "./projectDoc";
|
|
3
|
+
import type { Rule } from "./types";
|
|
4
|
+
|
|
5
|
+
const rule = (id: string, claim: string, why: string, over: Partial<Rule> = {}): Rule => ({
|
|
6
|
+
id, claim, why,
|
|
7
|
+
learnedFrom: "revola-video/BUILD_NOTES_CRAFT.md",
|
|
8
|
+
tier: "craft",
|
|
9
|
+
scope: { kind: "pipeline" },
|
|
10
|
+
enforcement: { kind: "prose", unanchorable: "test fixture" },
|
|
11
|
+
...over,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const gate = (id: string, claim: string, verify: string): Rule =>
|
|
15
|
+
rule(id, claim, `${id} why.`, { tier: "gate", verify });
|
|
16
|
+
|
|
17
|
+
describe("spliceBlock", () => {
|
|
18
|
+
const doc = [
|
|
19
|
+
"# Title",
|
|
20
|
+
"",
|
|
21
|
+
"<!-- craft:begin rules -->",
|
|
22
|
+
"stale content",
|
|
23
|
+
"<!-- craft:end rules -->",
|
|
24
|
+
"",
|
|
25
|
+
"Trailing prose.",
|
|
26
|
+
].join("\n");
|
|
27
|
+
|
|
28
|
+
test("replaces only what is between the markers", () => {
|
|
29
|
+
const out = spliceBlock(doc, "rules", "fresh content");
|
|
30
|
+
expect(out).toContain("fresh content");
|
|
31
|
+
expect(out).not.toContain("stale content");
|
|
32
|
+
expect(out).toContain("# Title");
|
|
33
|
+
expect(out).toContain("Trailing prose.");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("keeps the markers so the block can be spliced again", () => {
|
|
37
|
+
const once = spliceBlock(doc, "rules", "first body");
|
|
38
|
+
expect(spliceBlock(once, "rules", "second body")).toContain("second body");
|
|
39
|
+
expect(spliceBlock(once, "rules", "second body")).not.toContain("first body");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("throws when the marker is missing rather than appending silently", () => {
|
|
43
|
+
expect(() => spliceBlock("# Title", "rules", "x")).toThrow(/marker "craft:begin rules" not found/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("throws when the end marker is missing", () => {
|
|
47
|
+
expect(() => spliceBlock("<!-- craft:begin rules -->", "rules", "x"))
|
|
48
|
+
.toThrow(/marker "craft:end rules" not found/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("renderPipelineBlock", () => {
|
|
53
|
+
test("renders each rule as claim plus why, sorted by id", () => {
|
|
54
|
+
const out = renderPipelineBlock([rule("zebra", "Z claim.", "Z why."), rule("alpha", "A claim.", "A why.")]);
|
|
55
|
+
expect(out.indexOf("A claim.")).toBeLessThan(out.indexOf("Z claim."));
|
|
56
|
+
expect(out).toContain("A why.");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("cites where each rule was learned", () => {
|
|
60
|
+
expect(renderPipelineBlock([rule("a", "A.", "B.")])).toContain("revola-video/BUILD_NOTES_CRAFT.md");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("says it is generated", () => {
|
|
64
|
+
expect(renderPipelineBlock([])).toContain("generated");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("renderClaudeMdBlock", () => {
|
|
69
|
+
test("names every pipeline rule, sorted by id, without its why", () => {
|
|
70
|
+
const out = renderClaudeMdBlock([
|
|
71
|
+
rule("b-rule", "Do B.", "Because B."),
|
|
72
|
+
rule("a-rule", "Do A.", "Because A."),
|
|
73
|
+
]);
|
|
74
|
+
expect(out.split("\n")[0]).toMatch(/generated from packages\/video-craft/);
|
|
75
|
+
// Craft-tier and unwaived, so each is named by id rather than by claim
|
|
76
|
+
// (video-toolchain-cg6); ordering by id is unchanged.
|
|
77
|
+
expect(out).toContain("`a-rule`");
|
|
78
|
+
expect(out).toContain("`b-rule`");
|
|
79
|
+
expect(out.indexOf("`a-rule`")).toBeLessThan(out.indexOf("`b-rule`"));
|
|
80
|
+
// Compact by design: the why lives in HOW_I_BUILD_VIDEOS.md, not here.
|
|
81
|
+
expect(out).not.toContain("Because A.");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a gate still renders as a full claim line", () => {
|
|
85
|
+
const out = renderClaudeMdBlock([gate("g-rule", "Do G.", "video check-pin --project x")]);
|
|
86
|
+
expect(out).toContain("- Do G. (`g-rule`)");
|
|
87
|
+
expect(out).toContain("verify: video check-pin --project x");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("excludes component-scoped rules", () => {
|
|
91
|
+
const component: Rule = {
|
|
92
|
+
...rule("c-rule", "Do C.", "Because C."),
|
|
93
|
+
scope: { kind: "component", slug: "rolling-number", field: "gotcha" },
|
|
94
|
+
};
|
|
95
|
+
expect(renderClaudeMdBlock([component])).not.toContain("c-rule");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Trial 3 run 1 produced five rebuild rounds. Every defect that cost a round sat
|
|
100
|
+
// in a rule that was stated and not checked, and the flat list gave an agent no
|
|
101
|
+
// way to tell those rules from a comment-hygiene note. Tiering exists to put the
|
|
102
|
+
// expensive rules first and carry the command that settles each one.
|
|
103
|
+
describe("renderClaudeMdBlock tiering", () => {
|
|
104
|
+
const rules = [
|
|
105
|
+
rule("zulu-craft", "Craft Z.", "why."),
|
|
106
|
+
gate("alpha-gate", "Gate A.", "video render-twice --comp x"),
|
|
107
|
+
rule("mike-process", "Process M.", "why.", { tier: "process" }),
|
|
108
|
+
rule("alpha-craft", "Craft A.", "why."),
|
|
109
|
+
gate("zulu-gate", "Gate Z.", "video check-pin --project x"),
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
// Asserted on ids rather than claims: gates carry their claim, craft and
|
|
113
|
+
// process compact to an id index (see below), so the id is the one token
|
|
114
|
+
// every rule contributes at every tier.
|
|
115
|
+
test("gates come before craft, and craft before process", () => {
|
|
116
|
+
const out = renderClaudeMdBlock(rules);
|
|
117
|
+
expect(out.indexOf("`alpha-gate`")).toBeLessThan(out.indexOf("`alpha-craft`"));
|
|
118
|
+
expect(out.indexOf("`alpha-craft`")).toBeLessThan(out.indexOf("`mike-process`"));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("sorts by id inside a tier", () => {
|
|
122
|
+
const out = renderClaudeMdBlock(rules);
|
|
123
|
+
expect(out.indexOf("`alpha-gate`")).toBeLessThan(out.indexOf("`zulu-gate`"));
|
|
124
|
+
expect(out.indexOf("`alpha-craft`")).toBeLessThan(out.indexOf("`zulu-craft`"));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Guards the above against being read as a whole-tier invariant. A compacted
|
|
128
|
+
// tier renders as TWO id-sorted groups - waived rules in full, then the index
|
|
129
|
+
// - so a waived rule with a late id prints above a plain rule with an early
|
|
130
|
+
// one. Asserting whole-tier id order would fail on correct output the moment
|
|
131
|
+
// any fixture rule gained a waiver; sorting is per-group, by construction.
|
|
132
|
+
test("a waived rule hoists above the index regardless of its id", () => {
|
|
133
|
+
const out = renderClaudeMdBlock([
|
|
134
|
+
rule("alpha-craft", "Alpha claim.", "why."),
|
|
135
|
+
rule("zulu-craft", "Zulu claim.", "why.", {
|
|
136
|
+
exceptions: [{ project: "p", decision: "d", recordedIn: "p/docs/spec.md" }],
|
|
137
|
+
}),
|
|
138
|
+
]);
|
|
139
|
+
expect(out.indexOf("`zulu-craft`")).toBeLessThan(out.indexOf("`alpha-craft`"));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("carries each gate's verify command, so the rule is settled by running something", () => {
|
|
143
|
+
const out = renderClaudeMdBlock(rules);
|
|
144
|
+
expect(out).toContain("video render-twice --comp x");
|
|
145
|
+
expect(out).toContain("video check-pin --project x");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("labels the gate tier so its weight is legible without reading every rule", () => {
|
|
149
|
+
expect(renderClaudeMdBlock(rules)).toMatch(/remember nothing else/i);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("still emits every rule - tiering reorders, it never drops", () => {
|
|
153
|
+
const out = renderClaudeMdBlock(rules);
|
|
154
|
+
for (const r of rules) expect(out).toContain(`\`${r.id}\``);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// CLAUDE.md is injected into EVERY session; HOW_I_BUILD only when a session
|
|
158
|
+
// touches pipeline behaviour. Spending ~6k tokens per session restating 107
|
|
159
|
+
// claims that HOW_I_BUILD carries verbatim buys nothing, but dropping the
|
|
160
|
+
// craft rules outright would leave a session that never opens HOW_I_BUILD
|
|
161
|
+
// unaware they exist. So gates stay in full and everything else is named by
|
|
162
|
+
// id: an agent can never not know a rule exists, and fetches the claim when
|
|
163
|
+
// the id looks relevant. (video-toolchain-cg6)
|
|
164
|
+
describe("craft and process compact to an id index", () => {
|
|
165
|
+
test("a plain craft rule contributes its id but not its claim", () => {
|
|
166
|
+
const out = renderClaudeMdBlock(rules);
|
|
167
|
+
expect(out).toContain("`alpha-craft`");
|
|
168
|
+
expect(out).not.toContain("Craft A.");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("a plain process rule contributes its id but not its claim", () => {
|
|
172
|
+
const out = renderClaudeMdBlock(rules);
|
|
173
|
+
expect(out).toContain("`mike-process`");
|
|
174
|
+
expect(out).not.toContain("Process M.");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("gates keep their full claim and verify line", () => {
|
|
178
|
+
const out = renderClaudeMdBlock(rules);
|
|
179
|
+
expect(out).toContain("Gate A.");
|
|
180
|
+
expect(out).toContain("video render-twice --comp x");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("points at where the compacted claims can be read", () => {
|
|
184
|
+
expect(renderClaudeMdBlock(rules)).toMatch(/HOW_I_BUILD_VIDEOS\.md/);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// The one exemption, and the reason it is not optional: exceptionLines' own
|
|
189
|
+
// doc comment records that a waiver invisible in the projection is how a
|
|
190
|
+
// session inside the waived project "fixes" the client's decision back out.
|
|
191
|
+
// Compacting a waived rule to a bare id would reintroduce exactly that.
|
|
192
|
+
describe("waived rules stay in full at any tier", () => {
|
|
193
|
+
const waived = rule("waived-craft", "Waived craft claim.", "why.", {
|
|
194
|
+
exceptions: [{ project: "some-video", decision: "client chose otherwise", recordedIn: "some-video/docs/spec.md" }],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("keeps the claim, so the waiver is not attached to an invisible rule", () => {
|
|
198
|
+
expect(renderClaudeMdBlock([waived])).toContain("Waived craft claim.");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("keeps the waiver line naming the project and where it was recorded", () => {
|
|
202
|
+
const out = renderClaudeMdBlock([waived]);
|
|
203
|
+
expect(out).toContain("waived in `some-video`");
|
|
204
|
+
expect(out).toContain("some-video/docs/spec.md");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("an unwaived neighbour in the same tier still compacts", () => {
|
|
208
|
+
const out = renderClaudeMdBlock([waived, rule("plain-craft", "Plain craft claim.", "why.")]);
|
|
209
|
+
expect(out).toContain("Waived craft claim.");
|
|
210
|
+
expect(out).not.toContain("Plain craft claim.");
|
|
211
|
+
expect(out).toContain("`plain-craft`");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("omits a tier heading when that tier is empty", () => {
|
|
216
|
+
expect(renderClaudeMdBlock([rule("only-craft", "C.", "why.")])).not.toMatch(/remember nothing else/i);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("renderPipelineBlock tiering", () => {
|
|
221
|
+
test("states each rule's tier, so the reference doc agrees with the injected block", () => {
|
|
222
|
+
expect(renderPipelineBlock([rule("a", "A.", "B.")])).toMatch(/Tier: craft/);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("gives a gate its verify, so the reference doc says how to settle it", () => {
|
|
226
|
+
const out = renderPipelineBlock([gate("g", "G.", "video check-pin --project x")]);
|
|
227
|
+
expect(out).toMatch(/Tier: gate/);
|
|
228
|
+
expect(out).toContain("video check-pin --project x");
|
|
229
|
+
});
|
|
230
|
+
});
|