Claude Skill

visual-asset-critic

Critique a generated image against its source storyboard shot and prompt, producing revision notes. Use when the user has generated an image and wants feedback before committing. Triggers on "does this match the brief", "review this render", "is this on-brand", "what should I cha

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download whystrohm-shotkit-skills_visual-asset-critic-1b56831.zip · 25 KB
Part of whystrohm/shotkit — 5 skills

Install

skills CLI npx skills add https://github.com/whystrohm/shotkit/tree/main/skills/visual-asset-critic
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install whystrohm-shotkit@llmmart
Git git clone https://github.com/whystrohm/shotkit.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole whystrohm/shotkit collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Visual Asset Critic

You are the editorial second-eye on AI-generated images. Most teams don't have one, they generate, glance, accept, and ship. This skill is the structured review pass that catches what a tired creator misses.

The output is a critique with concrete, actionable revision notes. Not vibes. Not "looks good." Specific, prompt-level or post-level fixes.

When to use

Trigger when the user:

  • Uploads or links a generated image with a question about quality
  • Asks "does this match the storyboard"
  • Says "review this render", "is this on-brand", "what should I change"
  • Has a generated image and a shots.json shot reference and wants QA
  • Has a generated image and just wants editorial feedback (no storyboard reference)

What you produce

Two artifacts from every review, always both: a human-readable markdown critique (the primary surface) and a machine-readable critique JSON (so a pipeline can gate on the verdict instead of parsing prose). The JSON is detailed in Step 6; it never replaces the markdown.

The JSON goes to output/critiques/round-{N}/{shot_id}.critique.json. One file per shot per round, never a shared filename. A 12-shot project reviewed over three rounds writes 36 critiques; when they all went to output/critique.json it kept one, and which one depended on review order.

The markdown critique uses these sections:

## Verdict
ACCEPT / REVISE / REJECT, one line

## What's working
2–4 specific positives. Concrete observations, not flattery.

## What's not working
2–5 specific issues. Each one cites a layer. Brand Lock, Series Lock, Shot Spec, Composition, Technical, or Continuity.

## Revision plan
For each issue, the fix:
- Prompt-level (re-roll with this change to the prompt)
- Post-level (acceptable to address in editing/compositing)
- Re-roll required (no prompt fix; budget 2–3 attempts)

## Confidence
HIGH / MEDIUM / LOW, how sure you are about the verdict

Inputs

You need:

Input Required? Default if absent
The generated image Yes Cannot critique without it
Shot ID + shots.json Recommended If absent, ask for shot intent in a sentence
brand-lock.snapshot.md Recommended If absent, critique only on technical merits
The original prompt used Helpful If absent, infer from intent

If only the image is provided with no context, ask for one piece of information: what was this shot supposed to be? A single sentence is enough to anchor the critique.

Workflow

Step 1. Establish intent

What was this shot supposed to do? Pull from:

  • Shot's rationale field (if shots.json provided)
  • Shot's subject, framing, angle, motion fields
  • User's stated intent (if no shots.json)
  • The beat this shot serves

If you can't establish intent in one sentence, ask. Don't critique blind.

Step 2. Critique by layer

Read references/critique-rubric.md for the full rubric. Quick version, check the image against:

  1. Brand Lock, does it respect palette, mood, "never" list?
  2. Series Lock, does it match character/environment/lighting anchors?
  3. Shot Spec, does framing/angle/composition match the spec?
  4. Composition, does it reserve space for on-screen text if applicable?
  5. Technical, skin texture, hands, eyes, anatomy, AI artifacts?
  6. Continuity, if previous shots in the series are available, does it match?

For each layer, note: pass / soft fail / hard fail. The verdict aggregates these.

Step 3. Map issues to fixes

For every "not working" point, the critique must say what to do about it. Three buckets:

Prompt-level fix, change the prompt and re-roll. Specify the exact change:

"The character has brown hair instead of salt-and-pepper. Add 'salt-and-pepper hair' verbatim from series_lock to the prompt, it's missing in the current prompt."

Post-level fix, acceptable to address in compositing. Specify what:

"Color grade is slightly cool, push warmth +5 in post, no need to re-generate."

Re-roll required, no prompt fix will help; the generator just produced a bad sample. Budget 2–3 attempts:

"Hands are mangled. This is a known Flux failure mode; re-roll 2–3 times with same prompt and pick the best."

Step 4. Verdict

Verdict When
ACCEPT All layers pass or soft-fail in ways post can fix
REVISE One or two layers hard-fail; clear fix path
REJECT Three+ layers fail or one critical layer (Brand Lock, Series Lock) hard-fails with no clear fix

Step 5. Confidence

Be honest about uncertainty:

Confidence When
HIGH Storyboard reference + brand-lock + prompt all available, clear assessment
MEDIUM Some references missing but core intent is clear
LOW Only the image, intent is inferred; verdict is your best guess

HIGH is a factual claim about what you had, not a mood. tools/validate_critique.py rejects a 1.1 critique that claims HIGH while shot_id, brand_lock_ref, or prompt_ref is null, because that combination says the three inputs HIGH depends on were not there.

Step 6. Emit structured output (critique JSON)

After writing the markdown critique, also write output/critiques/round-{N}/{shot_id}.critique.json conforming to templates/critique.schema.json at version 1.1. Same review, two surfaces. The markdown is for the human; the JSON is so a scripted QA loop (e.g. visual-prompt-forge revision mode) can act on the verdict without parsing prose.

Map the markdown to the schema, section for section:

Markdown JSON field
## Verdict verdict (ACCEPT / REVISE / REJECT)
## What's working bullets working[] (one string each)
## What's not working + ## Revision plan issues[], merge them: each issue carries its layer/note from "what's not working" and its fix_type/fix from the matching revision-plan line
## Confidence confidence (HIGH / MEDIUM / LOW)

Provenance, all of it required at 1.1. A verdict is a claim about specific bytes. Name them, and hash them:

Field Value
run_id, round from run.json and the round directory you are writing into
created_at UTC instant, YYYY-MM-DDThh:mm:ssZ
shot_id the shot, or null for a standalone image with no storyboard
image_ref, image_sha256 the frame you reviewed, and its SHA-256
prompt_ref, prompt_sha256 the prompt file it came from, and its SHA-256
brand_lock_ref, brand_lock_sha256 the snapshot you judged against, and its SHA-256
generator, model_version the generator id from _capabilities.json, and its model version
seed if the generator exposes one, else null
shasum -a 256 output/frames/round-1/shot_03.png \
              output/prompts/round-1/flux.txt \
              output/brand-lock.snapshot.md

Every one of those fields is required, and every one is nullable. That combination is deliberate: null records that an input genuinely was not available, while a missing field records nothing at all. If you did not have the prompt, write prompt_ref: null and drop your confidence to MEDIUM. Do not omit the key.

The hashes are the point. Without image_sha256, a frame regenerated after this review still satisfies image_ref, and a stale ACCEPT sails through the gate attached to a file nobody looked at.

Severity, assign one per issue. This is the field the gate runs on, so map it from the layer rubric deterministically:

Severity Means Maps from
minor soft-fail, fixable in post a soft-fail on any layer; fix_type: post-level
major hard-fail with a clear fix path a hard-fail that a prompt change or re-roll fixes
blocking hard-fail on a critical layer (Brand Lock / Series Lock) with no clear fix, or a defect that makes the asset unusable an unrecoverable hard-fail

Gating rule, the verdict is derived from severities, not chosen freely. This guarantees the markdown verdict and the JSON verdict always agree:

  • Any blocking issue ⇒ verdict is REJECT.
  • Three or more major issues ⇒ verdict is REJECT.
  • One or two major issues (and no blocking) ⇒ verdict is REVISE.
  • Only minor issues, or none ⇒ verdict is ACCEPT (with post notes).

The three-major rule used to read "escalate to REJECT at your discretion." Discretion in a gate is not a gate, and it disagreed with references/critique-rubric.md, which called three hard fails a REJECT outright. It is now a threshold, and the validator enforces it.

Pick the markdown ## Verdict by this same rule.

Step 7. Run the gate

Writing a schema-valid critique is not the same as passing the gate. Run it:

python tools/validate_critique.py output/critiques/round-1/shot_03.critique.json

Or check the whole tree at once, which also recomputes every hash against the files on disk:

python tools/validate_provenance.py output/

This step is not optional and it is not someone else's job. A critique that says ACCEPT while carrying a major issue is a bug, and the only reason to write a validator for that bug is to actually run it. Before this step existed, the gate ran in CI against two fixtures that ship in the repo and never once against a real client's critique.

Worked examples: examples/critique.accept.json and examples/critique.revise.json show the shape at version 1.0, which is still valid and carries no provenance. examples/worked-run/critiques/ shows version 1.1 with real hashes, two shots across two rounds.

Hard rules

Rule 1. No vibes-based critique

"Looks great" / "feels off" without specifics is not a critique. Every observation must reference something in the image (composition, color, anatomy, lighting direction, etc.).

Rule 2. Prompt-level fixes must be specific

"Change the prompt" is not a fix. "Add 'salt-and-pepper hair' to the character anchor, it's currently missing" is a fix.

Rule 3. Don't critique what wasn't asked

If the brief was "founder at laptop, calm mood" and the generation delivered exactly that, don't note that "the room could be more visually interesting." That's scope creep, not critique.

Rule 4. Be honest about generator limits

Some failures (mangled hands, weird eye reflections, jewelry shimmer) are known generator weaknesses. Surface them as such, don't pretend a different prompt will fix them. Recommend re-roll or post.

Rule 5. When in doubt, ACCEPT and recommend post

Generation is one stage in a pipeline. If the image is 80% right and the gap is fixable in post, that's an ACCEPT with post notes. Don't send the user back to re-generate when an editor would handle it in 90 seconds.

Reference

  • references/critique-rubric.md, the full layer-by-layer rubric

Example output

Verdict

REVISE

What's working

  • Composition correctly reserves the right two-thirds for text overlay (matches shot_03 rationale)
  • Lighting direction matches series_lock (window-left, warm afternoon)
  • Mood reads as calm/considered, on-brand

What's not working

  • Series Lock, character mismatch. The character has brown hair, but series_lock specifies "salt-and-pepper." This is a verbatim-anchor failure.
  • Technical, left hand. Hand on the laptop has six fingers (Flux known failure mode).
  • Shot Spec, framing. Generated as MS but spec called for MCU. Subject is too small in frame.

Revision plan

  • Hair: Prompt-level. Add "salt-and-pepper hair" to the character anchor in the prompt, currently missing. Re-roll.
  • Hand: Re-roll required. Generate 2–3 more times with same prompt and pick a clean one.
  • Framing: Prompt-level. Change "medium shot" to "medium close-up" in the prompt. Re-roll.

Confidence

HIGH

Handoff

After delivering the critique, if the verdict is REVISE, offer:

"Want me to draft the revised prompt? Point visual-prompt-forge at this output tree in revision mode and it will re-emit prompts for just the shots that need them."

If the verdict is REJECT, do not offer that. REJECT means a blocking issue or three or more major ones, which is a failure with no clear fix path, and revision mode is built to stop there and ask. Say what blocked it and what decision it needs: a changed shot spec, a changed brand-lock, or a different generator.

Don't auto-revise. The user picks. The critique you just wrote is exactly what closes that loop.

Files (shotkit)
  • examples
    • worked-run
      • critiques
        • round-1
          • shot_01.critique.json 1.2 KB
            {
              "version": "1.1",
              "run_id": "20260730T142300Z-9f2c1ab4",
              "round": 1,
              "created_at": "2026-07-30T14:41:00Z",
              "shot_id": "shot_01",
              "brand_lock_ref": "brand-lock.snapshot.md",
              "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50",
              "image_ref": "frames/round-1/shot_01.png",
              "image_sha256": "7c93763a04dacf1258cf8605cc6f3af45f0f7069f6fefe9bff2a14c0a779bd3d",
              "prompt_ref": "prompts/round-1/flux.txt",
              "prompt_sha256": "a0203e0c49cafd75d8fcb86997ba619c5ac82700dc4ab430812a9929aca89328",
              "generator": "flux",
              "model_version": "2 Pro",
              "seed": 481207,
              "verdict": "ACCEPT",
              "confidence": "HIGH",
              "working": [
                "Framing matches the spec: MCU, eye level, subject held frame-left with the right two-thirds open.",
                "Character matches the series_lock anchor verbatim: salt-and-pepper hair, navy crewneck.",
                "Lighting reads window-left, warm afternoon, as specified.",
                "Hands, eyes, and skin texture are clean."
              ],
              "issues": [
                {
                  "layer": "Technical",
                  "severity": "minor",
                  "note": "Grade sits a touch cool against the warm-afternoon spec.",
                  "fix_type": "post-level",
                  "fix": "Push warmth +5 in the grade. No re-generation needed."
                }
              ]
            }
            
          • shot_02.critique.json 1.3 KB
            {
              "version": "1.1",
              "run_id": "20260730T142300Z-9f2c1ab4",
              "round": 1,
              "created_at": "2026-07-30T14:44:00Z",
              "shot_id": "shot_02",
              "brand_lock_ref": "brand-lock.snapshot.md",
              "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50",
              "image_ref": "frames/round-1/shot_02.png",
              "image_sha256": "d025648c33854b27eedb9b99fcebf81a4980e61f4fc69e387b0810490e29d6d4",
              "prompt_ref": "prompts/round-1/flux.txt",
              "prompt_sha256": "a0203e0c49cafd75d8fcb86997ba619c5ac82700dc4ab430812a9929aca89328",
              "generator": "flux",
              "model_version": "2 Pro",
              "seed": 481207,
              "verdict": "REVISE",
              "confidence": "HIGH",
              "working": [
                "Posture reads as release, which is the beat.",
                "Environment and lighting match the series_lock anchors."
              ],
              "issues": [
                {
                  "layer": "Shot Spec",
                  "severity": "major",
                  "note": "Generated as MS but the subject reads too small for a payoff beat at 9:16.",
                  "fix_type": "prompt-level",
                  "fix": "Change 'medium shot' to 'medium close-up' in the prompt. Re-roll."
                },
                {
                  "layer": "Technical",
                  "severity": "major",
                  "note": "Left hand on the desk is malformed, a known Flux failure mode.",
                  "fix_type": "re-roll",
                  "fix": "Re-roll 2-3 times with the same prompt and pick a clean sample."
                }
              ]
            }
            
        • round-2
          • shot_02.critique.json 900 B
            {
              "version": "1.1",
              "run_id": "20260730T142300Z-9f2c1ab4",
              "round": 2,
              "created_at": "2026-07-30T15:19:00Z",
              "shot_id": "shot_02",
              "brand_lock_ref": "brand-lock.snapshot.md",
              "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50",
              "image_ref": "frames/round-2/shot_02.png",
              "image_sha256": "168ccb9e622e99e48a2a13f05a982ef906a32f4eee5a79974913f555789c6296",
              "prompt_ref": "prompts/round-2/revised-flux.txt",
              "prompt_sha256": "f9a807549ff558950dd8a01b8525f89f7e4d480e43d28273ec0182249de83cd7",
              "generator": "flux",
              "model_version": "2 Pro",
              "seed": 481207,
              "verdict": "ACCEPT",
              "confidence": "HIGH",
              "working": [
                "Reframed to MCU; the subject now carries the payoff beat.",
                "Hands are anatomically correct in this sample.",
                "Character, environment, and lighting still match the series_lock anchors."
              ],
              "issues": []
            }
            
      • frames
        • round-1
          • shot_01.png 74 B · in bundle
          • shot_02.png 74 B · in bundle
        • round-2
          • shot_02.png 74 B · in bundle
      • prompts
        • round-1
          • flux.txt 1.4 KB
            # Storyboard: Worked Run, Two Shots
            # Generator: flux
            # Model: 2 Pro
            # Aspect: 9:16
            # Brand-lock: brand-lock.snapshot.md
            # Run: 20260730T142300Z-9f2c1ab4
            # Round: 1
            
            # shot_01, hook, 0.0-3.0s, MCU eye-level static
            Medium close-up framing, camera at eye level, static. A founder, mid-thirties, salt-and-pepper hair, navy crewneck, seated at the desk with his hands still on the laptop keys, looking slightly off-camera. He is held in the left third of the frame and the right two thirds are left clear. Minimalist home office, white walls, oak desk, single houseplant. Soft natural side-light from a large window camera-left, warm afternoon golden hour. Shot at f/2.0, shallow depth of field, 50mm prime. Warm filmic, muted teal shadows, slight grain. Calm, considered mood. Photorealistic, natural skin texture, no AI artifacts.
            
            # shot_02, promise, 3.0-8.0s, MS eye-level push
            Medium shot, camera at eye level, pushing slowly toward the subject. A founder, mid-thirties, salt-and-pepper hair, navy crewneck, leaning back from the laptop with his shoulders dropping and a small almost-smile. The desk surface is visible in the lower frame. Minimalist home office, white walls, oak desk, single houseplant. Soft natural side-light from a large window camera-left, warm afternoon golden hour. Shot at f/2.0, shallow depth of field, 50mm prime. Warm filmic, muted teal shadows, slight grain. Calm, considered mood. Photorealistic, natural skin texture, no AI artifacts.
            
        • round-2
          • revised-flux.txt 1.1 KB
            # Storyboard: Worked Run, Two Shots
            # Generator: flux
            # Model: 2 Pro
            # Aspect: 9:16
            # Brand-lock: brand-lock.snapshot.md
            # Run: 20260730T142300Z-9f2c1ab4
            # Round: 2
            # Revision of round 1. Shots not listed here already passed.
            
            # shot_02, promise, 3.0-8.0s, MCU eye-level push, revision (was REVISE)
            # fix [Shot Spec, major]: framing MS -> MCU, subject read too small in frame
            # fix [Technical, re-roll]: left hand was malformed, same prompt, pick a clean sample
            Medium close-up framing, camera at eye level, pushing slowly toward the subject. A founder, mid-thirties, salt-and-pepper hair, navy crewneck, leaning back from the laptop with his shoulders dropping and a small almost-smile. His hands rest loosely and are fully visible. Minimalist home office, white walls, oak desk, single houseplant. Soft natural side-light from a large window camera-left, warm afternoon golden hour. Shot at f/2.0, shallow depth of field, 50mm prime. Warm filmic, muted teal shadows, slight grain. Calm, considered mood. Photorealistic, natural skin texture, anatomically correct hands, no AI artifacts.
            
      • brand-lock.snapshot.md 2.9 KB
        <!-- snapshot taken: 2026-05-07T14:23:00Z -->
        <!-- source: brand-packs/whystrohm.md -->
        
        # Brand Lock: WhyStrohm
        
        ## Identity
        
        **Brand:** WhyStrohm
        **One-line description:** Managed content infrastructure for founder-led brands. Voice extraction, brand guardrails encoded in code, programmatic video, automated publishing. 30 minutes a week of founder time, 48-hour content cycles.
        **Archetype:** Operator
        **Voice posture:** Calm, considered, confident without shouting
        
        ## Palette
        
        | Role | Hex | Use |
        |---|---|---|
        | Background | `#F5F0E8` | Cream, primary canvas |
        | Ink | `#2A2A32` | Near-black, primary text and dark elements |
        | Accent (warm) | `#D94F3A` | Coral, emphasis, periods on signature beats |
        | Accent (cool) | `#C9BBE0` | Pale lavender, secondary highlights |
        | Muted | `#7A7580` | Secondary text, captions |
        | Rule | `#E8E1D4` | Subtle borders, dividers, card backgrounds |
        
        ## Typography
        
        **Display font:** `Inter Black 900`, headline weight, hooks, on-screen text
        **Body font:** `Inter Medium 500`, body copy, captions, UI
        **Mono font:** `JetBrains Mono Regular`, code, data, technical references
        
        ## Mood adjectives
        
        - operator (not creator)
        - considered (not reactive)
        - deterministic (not vibes-based)
        - confident (without volume)
        - defense-grade (not fragile)
        
        ## Never list
        
        - never use stock photo aesthetic
        - never use AI uncanny faces
        - never over-saturate the cream background
        - never use coral as a flood color (only as accent or signature periods)
        - never use em dashes in copy
        - never use emojis in body copy
        - never use bullet points in narrative copy
        - never use clip-art or generic icon sets
        - never use hype words ("game-changing", "revolutionary", "next-level", "comprehensive")
        - never use exclamation points in headlines
        - never default to dark mode, light mode hybrid is the brand
        - never animate text with bouncing or wobbling, type-on, fade, or hard cut only
        
        ## Aspect ratios
        
        - 9:16, primary for short-form social
        - 16:9, long-form, hero films, web embeds
        - 1:1, feed posts, podcast covers
        - 4:5. Instagram feed alternative
        
        ## Color grade direction
        
        Warm filmic, muted teal shadows. Slight grain. Cream highlights, deep navy/charcoal shadows, never crushed. Reminiscent of Kodak Portra 400 with a slight digital cleanup.
        
        ## Motion language
        
        Camera moves are minimal and deliberate. Default to static. When motion is used, slow push or slow pull only. Cuts on action, not on time. Type-on for emphasis, never bouncing or wobbling. Coral periods animate as a hard pop on signature beats. Transitions are hard cuts or 6-frame dissolves only.
        
        ## Voice rules
        
        - no em dashes
        - no emojis in body copy
        - no exclamation points in headlines
        - no hype words
        - prefer specific numbers over vague claims
        - prefer present-tense over future-tense
        - prefer "operator" over "creator", "infrastructure" over "agency"
        
        ---
        
        **Last updated:** 2026-05-07
        **Owner:** Yuri Strohm
        **Version:** 1.0
        
      • preview.html 19 KB · in bundle
      • run.json 1.8 KB
        {
          "version": "1.0",
          "run_id": "20260730T142300Z-9f2c1ab4",
          "created_at": "2026-07-30T14:23:00Z",
          "shotkit_version": "3.0.0",
          "operator": "worked-example",
          "project": {
            "title": "Worked Run, Two Shots",
            "duration_s": 8,
            "aspect": "9:16",
            "framework": "custom"
          },
          "inputs": {
            "shots_ref": "shots.json",
            "shots_sha256": "24e5babc987ea595076aba90316bad23bdcf64a4f1e80ee0d4bef2fd29b55095",
            "text_overlays_ref": "text-overlays.json",
            "text_overlays_sha256": "5071e111d5a64c3dcfb3548061cabe7e00ec7505ce01314e85a0cd8328f0b89d",
            "brand_lock_ref": "brand-lock.snapshot.md",
            "brand_lock_sha256": "f364bc71f68d16187d0721fbc092450174545e247f2f563a4c45686b4a877f50",
            "brand_lock_source": "brand-packs/whystrohm.md",
            "brand_lock_configured": true
          },
          "generators": [
            {
              "id": "flux",
              "model_version": "2 Pro",
              "capabilities_sha256": "948776594cafead0455c9b3f12e47bdd58f27f1f9c5d49a93d908c0b08377ece"
            }
          ],
          "rounds": [
            {
              "round": 1,
              "started_at": "2026-07-30T14:23:00Z",
              "reason": "initial",
              "prompts": [
                {
                  "generator": "flux",
                  "ref": "prompts/round-1/flux.txt",
                  "sha256": "a0203e0c49cafd75d8fcb86997ba619c5ac82700dc4ab430812a9929aca89328",
                  "shot_ids": [
                    "shot_01",
                    "shot_02"
                  ]
                }
              ]
            },
            {
              "round": 2,
              "started_at": "2026-07-30T15:02:00Z",
              "reason": "shot_02 came back REVISE on Shot Spec framing and a Technical hand defect",
              "prompts": [
                {
                  "generator": "flux",
                  "ref": "prompts/round-2/revised-flux.txt",
                  "sha256": "f9a807549ff558950dd8a01b8525f89f7e4d480e43d28273ec0182249de83cd7",
                  "shot_ids": [
                    "shot_02"
                  ]
                }
              ],
              "post_only_shots": []
            }
          ]
        }
        
      • shots.json 1.8 KB
        {
          "version": "1.2",
          "project": {
            "title": "Worked Run, Two Shots",
            "duration_s": 8,
            "aspect": "9:16",
            "framework": "custom"
          },
          "brand_lock_ref": "brand-lock.snapshot.md",
          "series_lock": {
            "character": "founder, mid-thirties, salt-and-pepper hair, navy crewneck",
            "environment": "minimalist home office, white walls, oak desk, single houseplant",
            "lighting": "soft natural side-light from a large window camera-left, warm afternoon golden hour",
            "color_grade": "warm filmic, muted teal shadows, slight grain"
          },
          "shots": [
            {
              "id": "shot_01",
              "beat": "hook",
              "start": 0.0,
              "end": 3.0,
              "framing": "MCU",
              "angle": "eye-level",
              "motion": "static",
              "depth_of_field": "shallow",
              "subject": "founder seated at the desk, hands still on the laptop, looking slightly off-camera, held frame-left with the right two thirds open",
              "environment_ref": "series_lock.environment",
              "lighting_ref": "series_lock.lighting",
              "on_screen_text": "text_01",
              "vo": null,
              "rationale": "Opens on stillness so the first overlay lands in silence; frame-left placement reserves the space the hook needs."
            },
            {
              "id": "shot_02",
              "beat": "promise",
              "start": 3.0,
              "end": 8.0,
              "framing": "MS",
              "angle": "eye-level",
              "motion": "push",
              "depth_of_field": "shallow",
              "subject": "founder leaning back from the laptop, shoulders dropping, a small almost-smile, desk surface visible",
              "environment_ref": "series_lock.environment",
              "lighting_ref": "series_lock.lighting",
              "on_screen_text": "text_02",
              "vo": null,
              "rationale": "The slow push and the released posture carry the payoff without a line of voiceover."
            }
          ]
        }
        
      • text-overlays.json 761 B
        {
          "version": "1.0",
          "overlays": [
            {
              "id": "text_01",
              "shot_id": "shot_01",
              "content": "You are the bottleneck.",
              "font": "Inter Black 900",
              "size": "display",
              "weight": "black",
              "color": "#2A2A32",
              "position": "right-third",
              "enter": { "at": 0.3, "animation": "fade-in" },
              "exit": { "at": 2.8, "animation": "hard-cut" }
            },
            {
              "id": "text_02",
              "shot_id": "shot_02",
              "content": "Build the system instead.",
              "font": "Inter Black 900",
              "size": "headline",
              "weight": "black",
              "color": "#D94F3A",
              "position": "lower-third",
              "enter": { "at": 4.0, "animation": "slide-up" },
              "exit": { "at": 8.0, "animation": "fade-out" }
            }
          ]
        }
        
    • critique.accept.json 835 B
      {
        "version": "1.0",
        "shot_id": "shot_01",
        "brand_lock_ref": "brand-lock.snapshot.md",
        "image_ref": "frames/round-1/shot_01.png",
        "verdict": "ACCEPT",
        "confidence": "HIGH",
        "working": [
          "Framing matches the shot spec: MCU, eye level, subject anchored frame-left with negative space right.",
          "Character matches the series_lock anchor verbatim: salt-and-pepper hair, navy crewneck.",
          "Palette and color grade sit inside the brand-lock; nothing from the never-list appears.",
          "Hands, eyes, and skin texture are clean; no AI artifacts."
        ],
        "issues": [
          {
            "layer": "Technical",
            "severity": "minor",
            "note": "Color grade reads a touch cool versus the warm-afternoon spec.",
            "fix_type": "post-level",
            "fix": "Push warmth +5 in the grade. No re-generation needed."
          }
        ]
      }
      
    • critique.revise.json 1.4 KB
      {
        "version": "1.0",
        "shot_id": "shot_03",
        "brand_lock_ref": "brand-lock.snapshot.md",
        "image_ref": "frames/round-1/shot_03.png",
        "verdict": "REVISE",
        "confidence": "HIGH",
        "working": [
          "Composition reserves the right two-thirds of the frame for the text overlay, matching the shot_03 rationale.",
          "Lighting direction matches series_lock: window-left, warm afternoon.",
          "Mood reads as calm and considered, on-brand."
        ],
        "issues": [
          {
            "layer": "Series Lock",
            "severity": "major",
            "note": "Character has brown hair; series_lock specifies salt-and-pepper. Verbatim-anchor failure with a clear fix, so this caps the verdict at REVISE rather than forcing REJECT.",
            "fix_type": "prompt-level",
            "fix": "Add 'salt-and-pepper hair' to the character anchor in the prompt; it is currently missing. Re-roll."
          },
          {
            "layer": "Technical",
            "severity": "major",
            "note": "Left hand on the laptop has six fingers (known Flux failure mode).",
            "fix_type": "re-roll",
            "fix": "Re-roll 2-3 times with the same prompt and pick a clean sample."
          },
          {
            "layer": "Shot Spec",
            "severity": "minor",
            "note": "Generated as MS but the spec called for MCU; subject is slightly small in frame.",
            "fix_type": "prompt-level",
            "fix": "Change 'medium shot' to 'medium close-up' in the prompt. Re-roll."
          }
        ]
      }
      
  • references
    • critique-rubric.md 6 KB
      # Critique Rubric
      
      The structured layer-by-layer pass. Use this as the checklist when reviewing a generated image.
      
      ## Layer 1. Brand Lock check
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Palette | Image colors come from brand-lock palette | Colors close but slightly off | Colors not in palette at all |
      | Mood adjectives | Image reads as the brand mood | Mood reads as adjacent (e.g. "calm" vs "neutral") | Image reads as a different mood (e.g. "energetic" when brief was "calm") |
      | "Never" list | None of the items in the never list are present | One item in the never list shows softly | Multiple never-list violations |
      | Aspect ratio | Matches `project.aspect` | Within a crop of the spec | Wrong aspect |
      
      Hard fail on Brand Lock = REJECT or REVISE depending on whether prompt fix exists.
      
      ## Layer 2. Series Lock check
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Character anchor | All described features visible | One minor feature off (e.g. wrong shirt color) | Wrong person (different age/race/build than anchor) |
      | Environment | Matches series_lock | Slight environment drift | Different environment entirely |
      | Lighting direction | Matches series_lock | Lighting is right but slightly different angle | Lighting from wrong direction (continuity break) |
      | Color grade | Matches series_lock | Slight tonal drift | Different color grade |
      
      Hard fail on Series Lock = REJECT (continuity break) or REVISE if specific fix available.
      
      ## Layer 3. Shot Spec check
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Framing | Matches spec (ECU/CU/MS/etc.) | One step off (e.g. MS instead of MCU) | Two+ steps off |
      | Angle | Matches spec | Slight angle variation | Wrong angle (low when spec was eye-level) |
      | Subject action | Matches `subject` description | Subject doing similar but slightly different action | Subject doing wrong action |
      | Depth of field | Matches if specified | Slight DOF variation | Deep when shallow was specified |
      
      Soft fail = post-level fix or accept. Hard fail = revise.
      
      ## Layer 4. Composition check
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Negative space for text | Reserved per spec | Reserved but tight | No reserved space, overlay won't fit |
      | Subject placement | Matches rationale | Slightly off (subject in left-third vs right-third) | Subject blocks where text was meant to go |
      | Eye-line | Looks where intended | Slight gaze direction off | Looking the wrong way |
      | Headroom | Appropriate | Slightly tight or loose | Cropped at hairline / huge headroom |
      
      Composition fails are usually prompt-level fixable. Re-roll with explicit composition language.
      
      ## Layer 5. Technical check
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Hands | Anatomically correct | Slight finger weirdness | Six fingers, fused fingers, wrong joints |
      | Eyes | Symmetrical, focused | Slight asymmetry | Wonky eyes, wrong reflections |
      | Skin texture | Natural | Slightly plastic | Heavy AI plastic |
      | Anatomy | Correct | Minor weirdness | Major anatomy errors |
      | Background artifacts | Clean | Minor weirdness | Distorted text, melted objects, impossible geometry |
      
      Technical hard fails are almost always **re-roll required**. The prompt was probably fine; the generator just produced a bad sample. Budget 2–3 re-rolls.
      
      ## Layer 6. Continuity check (if previous shots available)
      
      | Check | Pass | Soft fail | Hard fail |
      |---|---|---|---|
      | Character match | Same person, same look | Slight drift | Clearly different person |
      | Lighting continuity | Same direction, time of day | Subtle shift | Direction reversed, time of day jumped |
      | Environment continuity | Same space | Slight environment drift | Different space |
      | Color grade match | Identical | Subtle shift | Visibly different |
      
      Continuity hard fails break the storyboard. REVISE with verbatim-anchor checks on the prompt.
      
      ## From pass/fail to severity
      
      The tables above grade each check as pass, soft fail, or hard fail. The JSON critique
      records a severity instead, and the verdict is derived from those severities. Map them
      this way, and only this way:
      
      | Rubric result | Severity | Why |
      |---|---|---|
      | Soft fail on any layer | `minor` | Post can absorb it. `fix_type` is usually `post-level` |
      | Hard fail **with** a clear fix path | `major` | A prompt change or a re-roll resolves it |
      | Hard fail on Brand Lock or Series Lock **with no** fix path | `blocking` | Nothing downstream recovers this |
      | Any defect that makes the asset unusable | `blocking` | Same, regardless of layer |
      
      ## Aggregating verdict
      
      The verdict follows from the severities. It is not a separate judgement:
      
      | Severities present | Verdict |
      |---|---|
      | any `blocking` | REJECT |
      | three or more `major` | REJECT |
      | one or two `major` | REVISE |
      | only `minor`, or none | ACCEPT (with post notes) |
      
      `tools/validate_critique.py` enforces exactly this table, so a critique that disagrees with
      it fails rather than shipping. Earlier versions of this file counted hard fails and called
      3+ a REJECT while the skill said "escalate at your discretion." Those two rules disagreed,
      and the disagreement is the reason the threshold is now a number.
      
      Soft fails are noted but don't change the verdict on their own. If you have three or more of
      them, look again: a cluster of soft fails usually means one of them is really a hard fail
      you talked yourself out of.
      
      ## Speed bumps to remember
      
      - **Don't critique what wasn't asked**, if the spec didn't call for cinematic mood, don't say "could be more cinematic"
      - **Don't pile-on once verdict is set**, if you're rejecting, list the issues that drive the rejection; don't list every cosmetic concern
      - **Be specific, always**, "lighting is off" is not a critique; "key light is camera-right but series_lock says camera-left" is
      - **Cite the layer**, every issue gets tagged with which layer it falls under. This makes the fix path obvious
      - **Distinguish prompt failure from generator failure**, if the prompt was fine and the generator produced garbage hands, that's "re-roll required", not "fix the prompt"
      
  • templates
    • critique.schema.json 6.7 KB
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://whystrohm.com/schemas/shotkit/critique.schema.json",
        "title": "Critique",
        "description": "Machine-readable verdict for one generated frame reviewed against its shot spec and brand-lock. The structured sibling of the markdown critique. Version 1.1 adds run provenance: the frame, prompt, and brand-lock are identified by content hash as well as by path, so a verdict can be proven to describe the bytes that were actually reviewed. Every provenance field is required at 1.1 and explicitly nullable, so an absent input is a recorded decision rather than an omission.",
        "type": "object",
        "required": ["version", "verdict", "confidence", "issues"],
        "additionalProperties": false,
        "properties": {
          "version": {
            "type": "string",
            "enum": ["1.0", "1.1"],
            "description": "1.1 adds run_id, round, created_at, the *_sha256 hashes, prompt_ref, generator, model_version, seed, and the meta passthrough. 1.0 documents remain valid and are treated as legacy: they carry a verdict with no proof of what it reviewed."
          },
          "meta": {
            "type": "object",
            "description": "Forward-compat passthrough for tooling that needs to annotate a critique without a schema bump. Ignored by core skills.",
            "additionalProperties": true
          },
          "run_id": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{8}$" }
            ],
            "description": "The run this critique belongs to. Matches run.json:run_id. Format: UTC compact timestamp, a dash, then 8 hex characters."
          },
          "round": {
            "oneOf": [
              { "type": "null" },
              { "type": "integer", "minimum": 1 }
            ],
            "description": "Revision round, 1-indexed. Round 1 is the first generation pass."
          },
          "created_at": {
            "oneOf": [
              { "type": "null" },
              {
                "type": "string",
                "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$"
              }
            ],
            "description": "UTC ISO-8601 instant the critique was written. Second precision, trailing Z required."
          },
          "shot_id": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^shot_[0-9]{2,3}$" }
            ],
            "description": "Shot this frame was generated for. Null when critiquing a standalone image with no storyboard."
          },
          "brand_lock_ref": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "minLength": 1 }
            ],
            "description": "Path to the brand-lock.snapshot.md the frame was judged against, relative to the output directory root. Null when no brand-lock was available, which caps confidence at MEDIUM."
          },
          "brand_lock_sha256": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^[0-9a-f]{64}$" }
            ],
            "description": "SHA-256 of the brand-lock file as read. Null only when brand_lock_ref is null."
          },
          "image_ref": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "minLength": 1 }
            ],
            "description": "Path to the frame that was critiqued, relative to the output directory root."
          },
          "image_sha256": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^[0-9a-f]{64}$" }
            ],
            "description": "SHA-256 of the frame bytes as reviewed. This is what makes the verdict falsifiable: if the file at image_ref no longer hashes to this value, the verdict does not describe it."
          },
          "prompt_ref": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "minLength": 1 }
            ],
            "description": "Path to the prompt file the frame was generated from, relative to the output directory root. Null when the prompt is unknown, which caps confidence at MEDIUM."
          },
          "prompt_sha256": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^[0-9a-f]{64}$" }
            ],
            "description": "SHA-256 of the prompt file. Null only when prompt_ref is null."
          },
          "generator": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" }
            ],
            "description": "Generator id that produced the frame. Must match an id in visual-prompt-forge/adapters/_capabilities.json. Null when unknown."
          },
          "model_version": {
            "oneOf": [
              { "type": "null" },
              { "type": "string", "minLength": 1 }
            ],
            "description": "Model version string as of generation, copied from the capability matrix entry. The matrix records what is current; this records what ran."
          },
          "seed": {
            "oneOf": [
              { "type": "null" },
              { "type": "integer" },
              { "type": "string", "minLength": 1 }
            ],
            "description": "Generation seed if the generator exposes one. Null when it does not."
          },
          "verdict": {
            "type": "string",
            "enum": ["ACCEPT", "REVISE", "REJECT"]
          },
          "confidence": {
            "type": "string",
            "enum": ["HIGH", "MEDIUM", "LOW"]
          },
          "working": {
            "type": "array",
            "description": "Concrete positives. Observations, not flattery.",
            "items": { "type": "string", "minLength": 1 }
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["layer", "severity", "note", "fix_type", "fix"],
              "additionalProperties": false,
              "properties": {
                "layer": {
                  "type": "string",
                  "enum": ["Brand Lock", "Series Lock", "Shot Spec", "Composition", "Technical", "Continuity"]
                },
                "severity": {
                  "type": "string",
                  "enum": ["minor", "major", "blocking"],
                  "description": "Any blocking issue forces REJECT. One or two major issues cap the verdict at REVISE. Three or more major issues force REJECT. Enforced by tools/validate_critique.py, not by this schema."
                },
                "note": { "type": "string", "minLength": 1 },
                "fix_type": {
                  "type": "string",
                  "enum": ["prompt-level", "post-level", "re-roll"]
                },
                "fix": { "type": "string", "minLength": 1 }
              }
            }
          }
        },
        "allOf": [
          {
            "if": {
              "properties": { "version": { "const": "1.1" } },
              "required": ["version"]
            },
            "then": {
              "required": [
                "version",
                "run_id",
                "round",
                "created_at",
                "shot_id",
                "brand_lock_ref",
                "brand_lock_sha256",
                "image_ref",
                "image_sha256",
                "prompt_ref",
                "prompt_sha256",
                "generator",
                "model_version",
                "seed",
                "verdict",
                "confidence",
                "issues"
              ]
            }
          }
        ]
      }
      
  • SKILL.md 12.8 KB
    ---
    name: visual-asset-critic
    description: Critique a generated image against its source storyboard shot and prompt, producing revision notes. Use when the user has generated an image and wants feedback before committing. Triggers on "does this match the brief", "review this render", "is this on-brand", "what should I change", or uploading an image alongside a shot ID. The QA loop for AI visuals. Works even without a storyboard-architect run. Pairs with storyboard-architect upstream, visual-prompt-forge sibling.
    ---
    
    # Visual Asset Critic
    
    You are the editorial second-eye on AI-generated images. Most teams don't have one, they generate, glance, accept, and ship. This skill is the structured review pass that catches what a tired creator misses.
    
    The output is a critique with concrete, actionable revision notes. Not vibes. Not "looks good." Specific, prompt-level or post-level fixes.
    
    ## When to use
    
    Trigger when the user:
    
    - Uploads or links a generated image with a question about quality
    - Asks "does this match the storyboard"
    - Says "review this render", "is this on-brand", "what should I change"
    - Has a generated image and a `shots.json` shot reference and wants QA
    - Has a generated image and just wants editorial feedback (no storyboard reference)
    
    ## What you produce
    
    **Two artifacts from every review, always both:** a human-readable markdown critique (the primary surface) and a machine-readable critique JSON (so a pipeline can gate on the verdict instead of parsing prose). The JSON is detailed in Step 6; it never replaces the markdown.
    
    The JSON goes to `output/critiques/round-{N}/{shot_id}.critique.json`. One file per shot
    per round, never a shared filename. A 12-shot project reviewed over three rounds writes 36
    critiques; when they all went to `output/critique.json` it kept one, and which one depended
    on review order.
    
    The markdown critique uses these sections:
    
    ```
    ## Verdict
    ACCEPT / REVISE / REJECT, one line
    
    ## What's working
    2–4 specific positives. Concrete observations, not flattery.
    
    ## What's not working
    2–5 specific issues. Each one cites a layer. Brand Lock, Series Lock, Shot Spec, Composition, Technical, or Continuity.
    
    ## Revision plan
    For each issue, the fix:
    - Prompt-level (re-roll with this change to the prompt)
    - Post-level (acceptable to address in editing/compositing)
    - Re-roll required (no prompt fix; budget 2–3 attempts)
    
    ## Confidence
    HIGH / MEDIUM / LOW, how sure you are about the verdict
    ```
    
    ## Inputs
    
    You need:
    
    | Input | Required? | Default if absent |
    |---|---|---|
    | The generated image | Yes | Cannot critique without it |
    | Shot ID + shots.json | Recommended | If absent, ask for shot intent in a sentence |
    | brand-lock.snapshot.md | Recommended | If absent, critique only on technical merits |
    | The original prompt used | Helpful | If absent, infer from intent |
    
    If only the image is provided with no context, ask for one piece of information: **what was this shot supposed to be?** A single sentence is enough to anchor the critique.
    
    ## Workflow
    
    ### Step 1. Establish intent
    
    What was this shot supposed to do? Pull from:
    
    - Shot's `rationale` field (if shots.json provided)
    - Shot's `subject`, `framing`, `angle`, `motion` fields
    - User's stated intent (if no shots.json)
    - The beat this shot serves
    
    If you can't establish intent in one sentence, ask. Don't critique blind.
    
    ### Step 2. Critique by layer
    
    Read `references/critique-rubric.md` for the full rubric. Quick version, check the image against:
    
    1. **Brand Lock**, does it respect palette, mood, "never" list?
    2. **Series Lock**, does it match character/environment/lighting anchors?
    3. **Shot Spec**, does framing/angle/composition match the spec?
    4. **Composition**, does it reserve space for on-screen text if applicable?
    5. **Technical**, skin texture, hands, eyes, anatomy, AI artifacts?
    6. **Continuity**, if previous shots in the series are available, does it match?
    
    For each layer, note: pass / soft fail / hard fail. The verdict aggregates these.
    
    ### Step 3. Map issues to fixes
    
    For every "not working" point, the critique must say what to do about it. Three buckets:
    
    **Prompt-level fix**, change the prompt and re-roll. Specify the exact change:
    > "The character has brown hair instead of salt-and-pepper. Add 'salt-and-pepper hair' verbatim from series_lock to the prompt, it's missing in the current prompt."
    
    **Post-level fix**, acceptable to address in compositing. Specify what:
    > "Color grade is slightly cool, push warmth +5 in post, no need to re-generate."
    
    **Re-roll required**, no prompt fix will help; the generator just produced a bad sample. Budget 2–3 attempts:
    > "Hands are mangled. This is a known Flux failure mode; re-roll 2–3 times with same prompt and pick the best."
    
    ### Step 4. Verdict
    
    | Verdict | When |
    |---|---|
    | ACCEPT | All layers pass or soft-fail in ways post can fix |
    | REVISE | One or two layers hard-fail; clear fix path |
    | REJECT | Three+ layers fail or one critical layer (Brand Lock, Series Lock) hard-fails with no clear fix |
    
    ### Step 5. Confidence
    
    Be honest about uncertainty:
    
    | Confidence | When |
    |---|---|
    | HIGH | Storyboard reference + brand-lock + prompt all available, clear assessment |
    | MEDIUM | Some references missing but core intent is clear |
    | LOW | Only the image, intent is inferred; verdict is your best guess |
    
    HIGH is a factual claim about what you had, not a mood. `tools/validate_critique.py`
    rejects a `1.1` critique that claims HIGH while `shot_id`, `brand_lock_ref`, or
    `prompt_ref` is null, because that combination says the three inputs HIGH depends on were
    not there.
    
    ### Step 6. Emit structured output (critique JSON)
    
    After writing the markdown critique, **also** write
    `output/critiques/round-{N}/{shot_id}.critique.json` conforming to
    `templates/critique.schema.json` at version `1.1`. Same review, two surfaces. The markdown
    is for the human; the JSON is so a scripted QA loop (e.g. `visual-prompt-forge` revision
    mode) can act on the verdict without parsing prose.
    
    **Map the markdown to the schema, section for section:**
    
    | Markdown | JSON field |
    |---|---|
    | `## Verdict` | `verdict` (`ACCEPT` / `REVISE` / `REJECT`) |
    | `## What's working` bullets | `working[]` (one string each) |
    | `## What's not working` + `## Revision plan` | `issues[]`, merge them: each issue carries its layer/note from "what's not working" and its `fix_type`/`fix` from the matching revision-plan line |
    | `## Confidence` | `confidence` (`HIGH` / `MEDIUM` / `LOW`) |
    
    **Provenance, all of it required at 1.1.** A verdict is a claim about specific bytes. Name
    them, and hash them:
    
    | Field | Value |
    |---|---|
    | `run_id`, `round` | from `run.json` and the round directory you are writing into |
    | `created_at` | UTC instant, `YYYY-MM-DDThh:mm:ssZ` |
    | `shot_id` | the shot, or `null` for a standalone image with no storyboard |
    | `image_ref`, `image_sha256` | the frame you reviewed, and its SHA-256 |
    | `prompt_ref`, `prompt_sha256` | the prompt file it came from, and its SHA-256 |
    | `brand_lock_ref`, `brand_lock_sha256` | the snapshot you judged against, and its SHA-256 |
    | `generator`, `model_version` | the generator id from `_capabilities.json`, and its model version |
    | `seed` | if the generator exposes one, else `null` |
    
    ```bash
    shasum -a 256 output/frames/round-1/shot_03.png \
                  output/prompts/round-1/flux.txt \
                  output/brand-lock.snapshot.md
    ```
    
    Every one of those fields is required, and every one is nullable. That combination is
    deliberate: `null` records that an input genuinely was not available, while a missing field
    records nothing at all. If you did not have the prompt, write `prompt_ref: null` and drop
    your confidence to MEDIUM. Do not omit the key.
    
    The hashes are the point. Without `image_sha256`, a frame regenerated after this review
    still satisfies `image_ref`, and a stale ACCEPT sails through the gate attached to a file
    nobody looked at.
    
    **Severity, assign one per issue.** This is the field the gate runs on, so map it from the layer rubric deterministically:
    
    | Severity | Means | Maps from |
    |---|---|---|
    | `minor` | soft-fail, fixable in post | a soft-fail on any layer; `fix_type: post-level` |
    | `major` | hard-fail **with** a clear fix path | a hard-fail that a prompt change or re-roll fixes |
    | `blocking` | hard-fail on a critical layer (Brand Lock / Series Lock) with **no** clear fix, or a defect that makes the asset unusable | an unrecoverable hard-fail |
    
    **Gating rule, the verdict is derived from severities, not chosen freely.** This guarantees the markdown verdict and the JSON verdict always agree:
    
    - Any `blocking` issue ⇒ verdict is `REJECT`.
    - Three or more `major` issues ⇒ verdict is `REJECT`.
    - One or two `major` issues (and no blocking) ⇒ verdict is `REVISE`.
    - Only `minor` issues, or none ⇒ verdict is `ACCEPT` (with post notes).
    
    The three-major rule used to read "escalate to REJECT at your discretion." Discretion in a
    gate is not a gate, and it disagreed with `references/critique-rubric.md`, which called
    three hard fails a REJECT outright. It is now a threshold, and the validator enforces it.
    
    Pick the markdown `## Verdict` by this same rule.
    
    ### Step 7. Run the gate
    
    Writing a schema-valid critique is not the same as passing the gate. Run it:
    
    ```bash
    python tools/validate_critique.py output/critiques/round-1/shot_03.critique.json
    ```
    
    Or check the whole tree at once, which also recomputes every hash against the files on
    disk:
    
    ```bash
    python tools/validate_provenance.py output/
    ```
    
    This step is not optional and it is not someone else's job. A critique that says `ACCEPT`
    while carrying a `major` issue is a bug, and the only reason to write a validator for that
    bug is to actually run it. Before this step existed, the gate ran in CI against two
    fixtures that ship in the repo and never once against a real client's critique.
    
    Worked examples: `examples/critique.accept.json` and `examples/critique.revise.json` show
    the shape at version `1.0`, which is still valid and carries no provenance.
    `examples/worked-run/critiques/` shows version `1.1` with real hashes, two shots across two
    rounds.
    
    ## Hard rules
    
    ### Rule 1. No vibes-based critique
    
    "Looks great" / "feels off" without specifics is not a critique. Every observation must reference something in the image (composition, color, anatomy, lighting direction, etc.).
    
    ### Rule 2. Prompt-level fixes must be specific
    
    "Change the prompt" is not a fix. "Add 'salt-and-pepper hair' to the character anchor, it's currently missing" is a fix.
    
    ### Rule 3. Don't critique what wasn't asked
    
    If the brief was "founder at laptop, calm mood" and the generation delivered exactly that, don't note that "the room could be more visually interesting." That's scope creep, not critique.
    
    ### Rule 4. Be honest about generator limits
    
    Some failures (mangled hands, weird eye reflections, jewelry shimmer) are known generator weaknesses. Surface them as such, don't pretend a different prompt will fix them. Recommend re-roll or post.
    
    ### Rule 5. When in doubt, ACCEPT and recommend post
    
    Generation is one stage in a pipeline. If the image is 80% right and the gap is fixable in post, that's an ACCEPT with post notes. Don't send the user back to re-generate when an editor would handle it in 90 seconds.
    
    ## Reference
    
    - `references/critique-rubric.md`, the full layer-by-layer rubric
    
    ## Example output
    
    > ## Verdict
    > REVISE
    >
    > ## What's working
    > - Composition correctly reserves the right two-thirds for text overlay (matches shot_03 rationale)
    > - Lighting direction matches series_lock (window-left, warm afternoon)
    > - Mood reads as calm/considered, on-brand
    >
    > ## What's not working
    > - **Series Lock, character mismatch.** The character has brown hair, but series_lock specifies "salt-and-pepper." This is a verbatim-anchor failure.
    > - **Technical, left hand.** Hand on the laptop has six fingers (Flux known failure mode).
    > - **Shot Spec, framing.** Generated as MS but spec called for MCU. Subject is too small in frame.
    >
    > ## Revision plan
    > - **Hair:** Prompt-level. Add "salt-and-pepper hair" to the character anchor in the prompt, currently missing. Re-roll.
    > - **Hand:** Re-roll required. Generate 2–3 more times with same prompt and pick a clean one.
    > - **Framing:** Prompt-level. Change "medium shot" to "medium close-up" in the prompt. Re-roll.
    >
    > ## Confidence
    > HIGH
    
    ## Handoff
    
    After delivering the critique, if the verdict is REVISE, offer:
    
    > "Want me to draft the revised prompt? Point `visual-prompt-forge` at this output tree in
    > revision mode and it will re-emit prompts for just the shots that need them."
    
    If the verdict is REJECT, do not offer that. REJECT means a blocking issue or three or more
    major ones, which is a failure with no clear fix path, and revision mode is built to stop
    there and ask. Say what blocked it and what decision it needs: a changed shot spec, a
    changed brand-lock, or a different generator.
    
    Don't auto-revise. The user picks. The critique you just wrote is exactly what closes that
    loop.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related