From fd086a38c6f8ca8dd365695a19f25fa10b7e1056 Mon Sep 17 00:00:00 2001 From: calesthio Date: Fri, 3 Jul 2026 18:52:33 -0700 Subject: [PATCH] Add taste direction governance --- AGENT_GUIDE.md | 5 +- schemas/artifacts/proposal_packet.schema.json | 43 ++++++ schemas/styles/playbook.schema.json | 41 ++++++ skills/INDEX.md | 2 + skills/meta/bespoke-composition.md | 4 +- skills/meta/reviewer.md | 11 ++ skills/meta/taste-direction.md | 126 ++++++++++++++++++ .../pipelines/animation/proposal-director.md | 3 + .../pipelines/explainer/proposal-director.md | 4 + styles/premium-minimalist.yaml | 117 ++++++++++++++++ .../test_taste_governance_contracts.py | 104 +++++++++++++++ 11 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 skills/meta/taste-direction.md create mode 100644 styles/premium-minimalist.yaml create mode 100644 tests/contracts/test_taste_governance_contracts.py diff --git a/AGENT_GUIDE.md b/AGENT_GUIDE.md index 0ffcda2d..1abfd8e5 100644 --- a/AGENT_GUIDE.md +++ b/AGENT_GUIDE.md @@ -143,7 +143,7 @@ Orthogonal to *runtime* is *authoring mode*: **how** the composition is built. P - **Templated** — assemble the stock `cut.type` scene-types (`text_card`, `stat_card`, `bar_chart`, …) into the `Explainer`/`CinematicRenderer` compositions. Fast, cheap, reliable — and the reason most videos look alike. Right for batch output, localization variants, quick drafts, and low-stakes internal clips. - **Atelier** — **hand-author the composition from scratch**: bespoke scenes, a one-off theme, and motion written for this piece, rendered via `composition_mode: "atelier"` (see `video_compose` → `_render_via_atelier`). No reusable creative components; a fresh visual language every time. -**Default to atelier for hero work** — marketing, launches, brand pieces, any single-deliverable explainer that must impress. The deciding rule: *reuse engine knowledge, never creative components.* In atelier mode the stock scene-type catalog, `hyperframes-registry` blocks, fixtures, and finished components are **off-limits** — they are frozen looks that reintroduce sameness. Before building, route through **`skills/meta/bespoke-composition.md`**, which sequences: art direction (`visual-style`) → motion principles (Disney 12 via `framer-motion`/`lottie-bodymovin`) → engine mechanics (`remotion-best-practices` + the stock components read *only as a mechanics codex*) → render via the atelier path. Close with a **distinctness review**: *could this be any other product's video? does it reuse a look I've made before?* — the inverse of "does it match the reference." Atelier costs more tokens and iteration than templated; say so at proposal so the user opts in knowingly. +**Default to atelier for hero work** — marketing, launches, brand pieces, any single-deliverable explainer that must impress. The deciding rule: *reuse engine knowledge, never creative components.* In atelier mode the stock scene-type catalog, `hyperframes-registry` blocks, fixtures, and finished components are **off-limits** — they are frozen looks that reintroduce sameness. Before building, route through **`skills/meta/taste-direction.md`** to set the design read and taste dials, then **`skills/meta/bespoke-composition.md`**, which sequences: art direction (`visual-style`) → motion principles (Disney 12 via `framer-motion`/`lottie-bodymovin`) → engine mechanics (`remotion-best-practices` + the stock components read *only as a mechanics codex*) → render via the atelier path. Close with a **distinctness review**: *could this be any other product's video? does it reuse a look I've made before?* — the inverse of "does it match the reference." Atelier costs more tokens and iteration than templated; say so at proposal so the user opts in knowingly. ### Escalate Blockers Explicitly @@ -630,10 +630,13 @@ Tool rules: | Playbook | Best For | |----------|----------| | `clean-professional` | Corporate, educational, SaaS | +| `premium-minimalist` | Investor updates, expert explainers, product narratives | | `flat-motion-graphics` | Social media, TikTok, startups | | `minimalist-diagram` | Technical deep-dives, architecture | | `ink-sketch` (Ink Theater) | Hand-drawn ink-on-white doodle animation; a character that draws itself, walks, dances; contraption explainers | +For custom, atelier, brand, launch, or hero work, read `skills/meta/taste-direction.md` before choosing a playbook. Carry its `taste_profile` into the proposal so later stages can preserve the design read, visual variance, motion intensity, information density, reference strategy, and anti-patterns. + ### Hand-drawn "doodle" animation → Ink Theater / Ink Puppet For any brief that wants a **hand-drawn ink doodle** look — "a sketch that comes to life", "a pencil / stick figure that walks or dances", "a little character that acts out the idea", whiteboard-doodle explainers — use the **Ink Theater** engine + **Ink Puppet** mocap system (`skills/creative/ink-theater.md`, `ink-theater/README.md`). It is a **style + reusable engine, not a new pipeline**: illustration / contraption pieces run on the `animation` pipeline; a mocap character (draws itself → walks / dances / waves via `InkPuppet.choreograph([...])`) runs on `character-animation`. Cross-tool entry points: **`/ink-art`** (create a vector doodle from scratch) and **`/animated-drawing`** (animate a *supplied* drawing with mocap — raster; `skills/creative/animated-drawing.md`). Never hand-tune character motion — the agent only chooses named mocap clips. diff --git a/schemas/artifacts/proposal_packet.schema.json b/schemas/artifacts/proposal_packet.schema.json index c6af6308..1f47f24e 100644 --- a/schemas/artifacts/proposal_packet.schema.json +++ b/schemas/artifacts/proposal_packet.schema.json @@ -164,6 +164,7 @@ "type": "string", "description": "Required when composition_mode='atelier'. Short note (or path to art-direction.md) committing to a fresh visual language for THIS piece — palette, type, motion, signature device. Per skills/meta/bespoke-composition.md step 1, written down BEFORE authoring scenes." }, + "taste_profile": { "$ref": "#/$defs/taste_profile" }, "music_source": { "type": "object", "description": "Resolved music plan from the proposal stage", @@ -331,5 +332,47 @@ }, "metadata": { "type": "object" } }, + "$defs": { + "taste_profile": { + "type": "object", + "required": ["design_read", "visual_variance", "motion_intensity", "information_density"], + "properties": { + "design_read": { + "type": "string", + "description": "Brief-specific read of what the video should feel like and why." + }, + "visual_variance": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How much scenes may vary visually while still feeling coherent." + }, + "motion_intensity": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How energetic the motion language should be." + }, + "information_density": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How much information can be on screen at once." + }, + "palette_discipline": { "type": "string" }, + "layout_variation": { "type": "string" }, + "reference_strategy": { "type": "string" }, + "anti_patterns": { + "type": "array", + "items": { "type": "string" } + }, + "quality_gates": { + "type": "array", + "items": { "type": "string" } + } + }, + "additionalProperties": false + } + }, "additionalProperties": false } diff --git a/schemas/styles/playbook.schema.json b/schemas/styles/playbook.schema.json index a8c22c21..7f6aa27c 100644 --- a/schemas/styles/playbook.schema.json +++ b/schemas/styles/playbook.schema.json @@ -159,6 +159,7 @@ "items": { "type": "string" }, "minItems": 1 }, + "taste_profile": { "$ref": "#/$defs/taste_profile" }, "chart_palette": { "description": "Ordered array of hex colors for chart data series.", "type": "array", @@ -238,6 +239,46 @@ "highlight": { "type": "string" } }, "additionalProperties": false + }, + "taste_profile": { + "type": "object", + "required": ["design_read", "visual_variance", "motion_intensity", "information_density"], + "properties": { + "design_read": { + "type": "string", + "description": "Brief-specific read of what the video should feel like and why." + }, + "visual_variance": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How much scenes may vary visually while still feeling coherent." + }, + "motion_intensity": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How energetic the motion language should be." + }, + "information_density": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "description": "How much information can be on screen at once." + }, + "palette_discipline": { "type": "string" }, + "layout_variation": { "type": "string" }, + "reference_strategy": { "type": "string" }, + "anti_patterns": { + "type": "array", + "items": { "type": "string" } + }, + "quality_gates": { + "type": "array", + "items": { "type": "string" } + } + }, + "additionalProperties": false } } } diff --git a/skills/INDEX.md b/skills/INDEX.md index a33fbab1..c8a05090 100644 --- a/skills/INDEX.md +++ b/skills/INDEX.md @@ -281,6 +281,7 @@ Cross-cutting skills that apply to all pipelines: | Checkpoint Protocol | `meta/checkpoint-protocol.md` | When/how to checkpoint and request human approval | | Skill Creator | `meta/skill-creator.md` | Dynamically create new skills during pipeline runs | | Animation Runtime Selector | `meta/animation-runtime-selector.md` | Choose render runtime + animation library per scene | +| Taste Direction | `meta/taste-direction.md` | Convert a brief into taste dials, anti-patterns, and reference strategy for proposal/playbook/atelier work | | Bespoke Composition (Atelier) | `meta/bespoke-composition.md` | Hand-author a composition from scratch (hero work) — no stock scene-types; routes art-direction → motion principles → engine mechanics → atelier render | ## Style Playbooks @@ -290,6 +291,7 @@ Style playbooks (`styles/*.yaml`) define visual language, typography, motion, au | Playbook | Category | Mood | Best For | |----------|----------|------|----------| | `clean-professional` | motion-graphics | polished, trustworthy | Corporate, educational, SaaS | +| `premium-minimalist` | minimalist | calm, editorial | Investor updates, expert explainers, product narratives | | `flat-motion-graphics` | motion-graphics | energetic, bold | Social media, TikTok, startups | | `minimalist-diagram` | whiteboard | focused, technical | Technical deep-dives, architecture | diff --git a/skills/meta/bespoke-composition.md b/skills/meta/bespoke-composition.md index dc2e13b9..84c8a9cf 100644 --- a/skills/meta/bespoke-composition.md +++ b/skills/meta/bespoke-composition.md @@ -43,7 +43,9 @@ Author in this order. Each step routes you to existing knowledge — do not skip ### 1. Commit to an art direction *for this subject* — the divergence engine Before writing any component, decide a visual language that fits **this** topic and no other. -Use the **`visual-style`** Layer 3 skill (CREATE mode) to lock: palette, type personality, +Read **`skills/meta/taste-direction.md`** first and write the `taste_profile`: the design read, +`visual_variance`, `motion_intensity`, `information_density`, reference strategy, and +anti-patterns. Then use the **`visual-style`** Layer 3 skill (CREATE mode) to lock: palette, type personality, motion character, layout system, and **one signature device** unique to this piece. Difference between videos is guaranteed here — not by withholding components, but by forcing a fresh direction each time. Write it down (a short `art-direction.md` in the project) and build to it. diff --git a/skills/meta/reviewer.md b/skills/meta/reviewer.md index f47f1310..576b4941 100644 --- a/skills/meta/reviewer.md +++ b/skills/meta/reviewer.md @@ -59,6 +59,17 @@ If a style playbook is active, verify: Each violation is a **suggestion** severity finding. +### Step 4b: Taste Direction Review + +If `proposal_packet.production_plan.taste_profile` or the active playbook's `taste_profile` exists, verify: +- [ ] `design_read` explains the brief, audience, and delivery promise; it is not just "modern/clean/professional" +- [ ] `visual_variance`, `motion_intensity`, and `information_density` are reflected in scene layout, pacing, callout density, and asset prompts +- [ ] `reference_strategy` is present when atelier work, AI image/video, product/brand visuals, or mood boards depend on visual nuance +- [ ] Listed `anti_patterns` are actually avoided +- [ ] Quality gates are concrete enough for the next stage to enforce + +At proposal stage, a missing `taste_profile` is a **suggestion** for preset/low-stakes work and a **critical** finding for atelier, product/brand, launch, hero, or custom-playbook work. At scene_plan/edit/compose, treat dial violations as **suggestion** unless they break the approved delivery promise. + ### Step 5: Evaluate Success Criteria For each `success_criteria` item from the manifest: diff --git a/skills/meta/taste-direction.md b/skills/meta/taste-direction.md new file mode 100644 index 00000000..a870a3f4 --- /dev/null +++ b/skills/meta/taste-direction.md @@ -0,0 +1,126 @@ +# Taste Direction - Meta Skill + +## When to Use + +Use this before committing to visual identity, mood boards, proposal packets, custom playbooks, atelier composition, image reference batches, or brand-heavy videos. + +This skill defines OpenMontage's video taste profile contract. Do not treat it as a frontend style recipe. The output is a compact video taste profile that travels through proposal, scene planning, asset prompts, edit, compose, and review. + +## Output Contract + +Write a `taste_profile` when the proposal or playbook needs a stronger creative contract: + +```json +{ + "design_read": "Premium expert explainer: calm authority, high trust, low ornament.", + "visual_variance": 4, + "motion_intensity": 3, + "information_density": 5, + "palette_discipline": "Neutral base, one accent, no decorative gradients.", + "layout_variation": "Alternate editorial split frames with data-forward full-frame scenes.", + "reference_strategy": "One reference still per scene family before asset generation.", + "anti_patterns": ["generic AI-purple gradient backgrounds"], + "quality_gates": ["Every scene should carry the design read without explanatory labels."] +} +``` + +`visual_variance`, `motion_intensity`, and `information_density` are 1-10 integer dials: + +| Dial | Low | Mid | High | +|------|-----|-----|------| +| `visual_variance` | Tight system, repeated grammar | Pattern with purposeful scene families | Each beat may use a distinct visual mode | +| `motion_intensity` | Calm holds, small transitions | Clear motion accents and reveals | Fast kinetic language, frequent directional changes | +| `information_density` | One idea per frame | Main idea plus support detail | Dense dashboards, diagrams, or layered callouts | + +## Process + +### 1. Make a Design Read + +Before choosing a playbook or palette, state what the video needs to feel like and why. Tie it to the audience, promise, platform, and subject matter. + +Good reads are specific: + +- "Investor-facing AI launch: precise, restrained, and credible; avoid hype visuals." +- "Youth science short: bright, curious, and kinetic; make invisible physics feel tactile." +- "Security incident explainer: tense and surgical; high contrast, low ornament, readable evidence." + +Weak reads are only adjectives: + +- "modern and clean" +- "cinematic" +- "professional" + +### 2. Set the Three Dials + +Pick `visual_variance`, `motion_intensity`, and `information_density` before writing concepts. These numbers should explain later choices: + +- High motion plus low information means short kinetic beats, not dense diagrams. +- Low motion plus high information means stable frames, chart builds, and long readable holds. +- High variance means scene families need stronger anchors: recurring type, palette, framing, or sound motif. + +### 3. Choose a Style Path + +Use the dials to pick one of three paths: + +| Path | Use When | Artifact | +|------|----------|----------| +| Existing playbook | A preset honestly matches the read | `production_plan.playbook` | +| Custom playbook | The subject has its own visual world | generated `styles/.yaml` with `taste_profile` | +| Atelier art direction | Hero work needs a one-off language | `production_plan.art_direction` plus `taste_profile` | + +Do not let preset availability override the design read. If the content calls for a custom visual world, write the custom playbook or art direction. + +### 4. Plan References + +If the work uses AI image/video, mood boards, brand assets, or atelier composition, create a reference strategy: + +- Use one reference still per scene family or major beat. +- Do not compress the whole direction into one mood board image. +- For brand/product work, create or inspect a brand kit before asset generation. +- For screen demos, inspect the real UI and write a redesign/audit note before styling overlays. + +### 5. Carry the Profile Downstream + +At proposal stage: + +- Add `production_plan.taste_profile`. +- Log style/playbook selection in `decision_log`. +- Explain how the dials affect runtime, composition mode, and asset generation. + +At scene planning: + +- Vary layouts according to `visual_variance`. +- Keep on-screen text and callouts within `information_density`. +- Set transition families and camera movement from `motion_intensity`. + +At assets: + +- Include palette, texture, framing, and reference strategy in image/video prompts. +- Generate reference stills before full batches when the profile depends on visual nuance. + +At edit/compose: + +- Match hold times and cut rhythm to the motion dial. +- Avoid adding decorative overlays that violate the design read. + +## Anti-Default Checklist + +Flag these before moving forward: + +- Generic AI-purple gradients or default corporate-blue visuals with no subject reason. +- Same transition on every cut when `visual_variance` is 4 or higher. +- Kinetic motion that makes narration harder to follow. +- Dense callouts when `information_density` is 4 or lower. +- Text-only slides unless the design read intentionally calls for typographic storytelling. +- Mood boards that look attractive but do not map to concrete scene families. +- Brand/product videos that never show or inspect the real brand/product surface. + +## Review Hooks + +Reviewer should check: + +- Does `taste_profile.design_read` explain a real creative choice? +- Do scene plans and edits respect the three dials? +- Are anti-patterns actually avoided? +- Is the reference strategy present when AI images/video or atelier work depends on visual nuance? +- Could this video belong to any topic after replacing the title? If yes, the taste direction is too generic. diff --git a/skills/pipelines/animation/proposal-director.md b/skills/pipelines/animation/proposal-director.md index e2cd27dd..caffcc8c 100644 --- a/skills/pipelines/animation/proposal-director.md +++ b/skills/pipelines/animation/proposal-director.md @@ -46,6 +46,7 @@ A `render_runtime_selection` decision with only one option considered when both | Tool registry | `support_envelope()` output | What's actually available right now | | Cost tracker | `tools/cost_tracker.py` | Cost estimation data | | Style playbooks | `styles/*.yaml` | Available visual styles | +| Meta skill | `skills/meta/taste-direction.md` | Design read, taste dials, reference strategy | | User input | Topic, any preferences expressed | Creative direction | ## Process @@ -121,6 +122,8 @@ Record all findings. **Do not propose an animation mode that requires tools you This is the key differentiator from the explainer proposal. **Present the user with concrete animation approaches, explain what each looks like, what tools/keys they need, and what's already available.** +Before locking animation mode or visual identity, read `skills/meta/taste-direction.md` and write a `production_plan.taste_profile`. The three dials (`visual_variance`, `motion_intensity`, `information_density`) should explain whether the concept needs calm data builds, kinetic typography, dense diagrams, reference stills, or a custom/atelier visual system. + #### Step 3a: Tool Availability Scan Before designing concepts, scan what's available and present it honestly. **Do NOT hardcode provider names, costs, or key names in this output** — they drift. Read them live from the registry: diff --git a/skills/pipelines/explainer/proposal-director.md b/skills/pipelines/explainer/proposal-director.md index e4384e37..db718b72 100644 --- a/skills/pipelines/explainer/proposal-director.md +++ b/skills/pipelines/explainer/proposal-director.md @@ -40,6 +40,7 @@ A `render_runtime_selection` decision with only one option considered when both | Tool registry | `support_envelope()` output | What's actually available right now | | Cost tracker | `tools/cost_tracker.py` | Cost estimation data | | Style playbooks | `styles/*.yaml` | Available visual styles | +| Meta skill | `skills/meta/taste-direction.md` | Design read, taste dials, reference strategy | | User input | Topic, any preferences expressed | Creative direction | ## Process @@ -175,6 +176,8 @@ Choose the structure that best fits the research findings: The existing playbooks (`clean-professional`, `flat-motion-graphics`, `minimalist-diagram`) are starting points, not destinations. Most videos should get a **custom visual identity** derived from the subject matter, audience, and tone. A video about coffee should feel warm and tactile. A video about cybersecurity should feel technical and urgent. A video about marine biology should feel deep and fluid. +Before choosing or generating a playbook, read `skills/meta/taste-direction.md` and write a compact `production_plan.taste_profile`. The taste profile records the design read, `visual_variance`, `motion_intensity`, `information_density`, reference strategy, and anti-patterns. Use it to explain why the selected playbook, `composition_mode`, and asset strategy fit the brief. + **How to design visual identity:** 1. **Start from the content.** What colors does the subject naturally evoke? What textures, materials, lighting? A video about volcanoes should feel different from a video about meditation — in colors, motion speed, typography weight, and transition style. @@ -194,6 +197,7 @@ The existing playbooks (`clean-professional`, `flat-motion-graphics`, `minimalis **Record your visual identity choices in the proposal_packet:** - `production_plan.playbook`: name of preset OR "custom" +- `production_plan.taste_profile`: design read, taste dials, reference strategy, and anti-patterns - If custom, include color choices and font choices in the concept's `visual_approach` - Include the reasoning: "Warm amber palette because the subject is coffee craftsmanship" - Log as decision: `category: "playbook_selection"` diff --git a/styles/premium-minimalist.yaml b/styles/premium-minimalist.yaml new file mode 100644 index 00000000..89b1013c --- /dev/null +++ b/styles/premium-minimalist.yaml @@ -0,0 +1,117 @@ +identity: + name: "Premium Minimalist" + category: minimalist + mood: calm, editorial, precise + pace: deliberate + best_for: "Investor updates, expert explainers, product narratives, high-trust launch videos" + +taste_profile: + design_read: "Premium expert explainer: calm authority, high trust, low ornament." + visual_variance: 4 + motion_intensity: 3 + information_density: 5 + palette_discipline: "Off-white field, charcoal text, restrained cobalt accent, no decorative gradients." + layout_variation: "Alternate editorial split frames, centered evidence frames, and full-bleed product/detail moments." + reference_strategy: "Create one reference still for each scene family before batch asset generation." + anti_patterns: + - "generic corporate-blue template cards" + - "decorative gradient backgrounds" + - "fast kinetic transitions that reduce comprehension" + quality_gates: + - "Every frame should feel intentionally sparse, not unfinished." + - "Motion should clarify hierarchy instead of decorating it." + +visual_language: + color_palette: + primary: ["#111827", "#374151"] + accent: ["#2563EB", "#0F766E"] + background: "#F9FAFB" + text: "#111827" + muted: "#6B7280" + composition: asymmetrical editorial grid with large margins, hard alignment, and one focal object per frame + texture: flat matte fields, fine divider lines, subtle photographic grain only when source imagery needs cohesion + +typography: + headings: + font: "Inter" + weight: 700 + tracking: "-0.01em" + body: + font: "Inter" + weight: 400 + line_height: 1.55 + code: + font: "JetBrains Mono" + weight: 400 + stat_card: + font: "Inter" + weight: 800 + size_multiplier: 3.2 + scale_system: "major_third" + weight_matrix: + title: 800 + heading: 700 + body: 400 + caption: 500 + +motion: + transitions: [fade, dissolve, slide-left] + animation_style: "restrained ease-out, precise reveal, no bounce" + pacing_rules: + min_scene_hold_seconds: 2.75 + max_scene_hold_seconds: 12 + text_card_hold_seconds: 3.75 + stat_card_hold_seconds: 3.25 + transition_duration_seconds: 0.45 + entrance: "fade-up 12px with opacity ramp" + exit: "soft fade with slight y-offset" + +audio: + voice_style: "measured, expert, warm, low hype" + music_mood: "minimal pulse, clean synth bed, quiet confidence" + music_volume: 0.07 + sfx_style: "small tactile ticks and soft paper-like swishes" + ducking_threshold_db: -4 + +asset_generation: + image_prompt_prefix: "premium minimalist editorial frame, restrained palette, precise composition, generous negative space, " + image_negative_prompt: "busy, glossy, decorative gradient, neon, cluttered, low contrast, generic corporate template" + diagram_style: "thin-line editorial diagram, charcoal text, cobalt highlight, large margins" + consistency_anchors: + - "Off-white background with charcoal typography" + - "Cobalt accent used only for hierarchy or proof points" + - "Large margins and hard alignment" + - "No decorative shapes unless they carry information" + +overlays: + stat_card: + bg: "#FFFFFF" + border: "#D1D5DB" + radius: 6 + shadow: "0 1px 6px rgba(17,24,39,0.08)" + key_term: + bg: "#EFF6FF" + text: "#1D4ED8" + radius: 4 + code_block: + bg: "#111827" + text: "#F9FAFB" + highlight: "#93C5FD" + +quality_rules: + - "Minimum contrast ratio 4.5:1 for all text" + - "No more than 2 accent colors on screen at once" + - "Keep one primary focal object or claim per frame" + - "Use motion to reveal hierarchy, not to add energy" + - "Avoid decorative gradients and oversized floating shapes" + +chart_palette: + - "#2563EB" + - "#0F766E" + - "#111827" + - "#64748B" + +color_rules: + harmony_type: "analogous" + contrast_validation: true + colorblind_safe: true diff --git a/tests/contracts/test_taste_governance_contracts.py b/tests/contracts/test_taste_governance_contracts.py new file mode 100644 index 00000000..0ed28532 --- /dev/null +++ b/tests/contracts/test_taste_governance_contracts.py @@ -0,0 +1,104 @@ +"""Contract tests for taste-direction governance. + +The taste-direction meta skill is an agent-facing contract: it must be easy to +discover, and its output must fit the canonical proposal/style artifacts. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema +import yaml + + +ROOT = Path(__file__).resolve().parent.parent.parent + + +def _load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def _taste_profile() -> dict: + return { + "design_read": "Premium expert explainer: calm authority, high trust, low ornament.", + "visual_variance": 4, + "motion_intensity": 3, + "information_density": 5, + "palette_discipline": "Neutral base, one blue accent, no decorative gradients.", + "layout_variation": "Alternate editorial split frames with data-forward full-frame scenes.", + "reference_strategy": "One reference still per scene family before asset generation.", + "anti_patterns": [ + "generic AI-purple gradient backgrounds", + "reusing the same transition on every cut", + ], + "quality_gates": [ + "Each scene carries the design read without needing explanatory labels.", + "Motion stays purposeful and never outruns narration comprehension.", + ], + } + + +def test_style_playbook_schema_accepts_taste_profile(): + schema = _load_json(ROOT / "schemas" / "styles" / "playbook.schema.json") + assert "taste_profile" in schema["properties"] + playbook = yaml.safe_load((ROOT / "styles" / "clean-professional.yaml").read_text(encoding="utf-8")) + playbook["taste_profile"] = _taste_profile() + + jsonschema.validate(instance=playbook, schema=schema) + + +def test_proposal_packet_schema_accepts_taste_profile(): + schema = _load_json(ROOT / "schemas" / "artifacts" / "proposal_packet.schema.json") + proposal = { + "version": "1.0", + "concept_options": [ + { + "id": f"c{i}", + "title": f"Concept {i}", + "hook": "A precise hook under twenty words.", + "narrative_structure": "problem_solution", + "visual_approach": "Premium minimalist scenes with data-led visual proof.", + "target_duration_seconds": 60, + "why_this_works": "It ties the audience problem to a visible payoff.", + } + for i in range(1, 4) + ], + "selected_concept": {"concept_id": "c1", "rationale": "Best fit for the brief."}, + "production_plan": { + "pipeline": "animated-explainer", + "stages": [{"stage": "proposal", "tools": [], "approach": "Plan the production."}], + "render_runtime": "remotion", + "taste_profile": _taste_profile(), + }, + "cost_estimate": { + "total_estimated_usd": 0, + "line_items": [], + "budget_verdict": "no_budget_set", + }, + "approval": {"status": "pending"}, + } + + jsonschema.validate(instance=proposal, schema=schema) + + +def test_taste_direction_is_discoverable_to_new_agents(): + skill_path = ROOT / "skills" / "meta" / "taste-direction.md" + assert skill_path.is_file(), "Missing Layer 2 taste-direction meta skill" + + index = (ROOT / "skills" / "INDEX.md").read_text(encoding="utf-8") + assert "Taste Direction" in index + assert "meta/taste-direction.md" in index + + guide = (ROOT / "AGENT_GUIDE.md").read_text(encoding="utf-8") + assert "taste-direction.md" in guide + + +def test_premium_minimalist_playbook_exists_and_validates(): + from styles.playbook_loader import load_playbook, list_playbooks + + assert "premium-minimalist" in list_playbooks() + playbook = load_playbook("premium-minimalist") + assert playbook["taste_profile"]["motion_intensity"] <= 4 + assert playbook["taste_profile"]["information_density"] >= 4