Skip to content

Prompt-quality preflight

Seven mechanical anti-pattern checks driven by the Seedance 2.0 Handbook. They run inside director-preflight against every storyboard scene's text. They are warnings by default and promote to blocking errors when DIRECTOR_STRICT_PROMPT_QUALITY=1 is set in the environment.

The module (src/video/prompt-quality.ts) is pure: no disk I/O, no global state beyond reading the severity env var at runtime. The checks surface through the existing director-preflight JSON output — there is no new CLI command and no new smoke.

Issue codes

Checkthreshold / ruleseverity
prompt-quality-adjective-soup> 4 comma-separated modifiers in one clausewarn (error if strict)
prompt-quality-multiple-actions> 1 serial third-person-present action per clausewarn (error if strict)
prompt-quality-multiple-camera-moves> 1 camera-move family per promptwarn (error if strict)
prompt-quality-style-word-overload> 3 stacked style wordswarn (error if strict)
prompt-quality-literary-emotionany inner-state language matchwarn (error if strict)
prompt-quality-overlong> 120 words (single-shot budget)warn (error if strict)
negative-directionany negated motion/tempo clause outside the allowlistwarn (error if strict)
The seven prompt-quality anti-pattern checks. Warnings by default, promoted to blocking errors under DIRECTOR_STRICT_PROMPT_QUALITY=1. Thresholds shown are the defaults.
CodeWhat it catchesThreshold
prompt-quality-adjective-soupA single clause piling up comma-separated modifiers> ADJECTIVE_SOUP_THRESHOLD (default 4)
prompt-quality-multiple-actionsSerial third-person-present actions in one clause> 1
prompt-quality-multiple-camera-movesMore than one camera-move family per prompt> 1 movement family
prompt-quality-style-word-overloadStacked "cinematic/epic/atmospheric" style words> STYLE_WORDS_THRESHOLD (default 3)
prompt-quality-literary-emotionInner-state language (feels, profound sadness) instead of visible behaviorany match
prompt-quality-overlongPrompt word count above the single-shot budget> OVERLONG_WORDS_THRESHOLD (default 120)
negative-directionA negated motion/tempo clause (no/don't/avoid/without/never + slow-motion/blur/motion/movement/zoom/camera shake) — these models ignore negated directionany match outside the sanctioned-negation allowlist (on-screen text, specular kill, anti-plastic)

Vocabularies live in CAMERA_MOVE_VOCABULARY, SHOT_TYPE_VOCABULARY, and STYLE_VOCABULARY in src/video/prompt-quality.ts and are deliberately short and conservative.

Camera movement is checked separately from shot size. Movement families include push-in, pull-out, tracking, orbit, static / locked-off, crane reveal, pan, tilt, zoom, handheld, and steadicam. Shot-size terms such as wide shot, medium shot, close-up, and establishing shot do not count as movement conflicts, so wide shot, slow push-in is valid while push-in and orbit still raises prompt-quality-multiple-camera-moves.

For Seedance image-to-video work, keep imported still-image guidance separate from motion prompting. The still image should carry composition, identity, wardrobe, props, lighting, and framing. The Seedance prompt should preserve the source image and add one visible action plus one camera move for a short clip.

Dialogue duration fit

director-preflight also runs a dialogue timing check from dialogue-fit.ts. It is separate from the seven prompt-quality anti-pattern checks because it uses a duration budget rather than prompt wording alone.

Default behavior:

  1. scenes without durationSeconds use a 15-second target
  2. spoken dialogue is estimated at about 2.5 words per second
  3. DIALOGUE_DURATION_OVERFLOW is emitted when the estimated spoken duration exceeds the scene target
  4. the issue is a warning by default
  5. DIRECTOR_STRICT_DIALOGUE_FIT=1 promotes it to a blocking error

This check catches scripts that look fine as text but cannot be delivered naturally inside the clip duration.

Sample output

A scene with adjective soup and a second camera move produces director-preflight JSON like:

json
{
  "pass": true,
  "warnings": [
    {
      "severity": "warn",
      "code": "prompt-quality-adjective-soup",
      "scope": "scene:0",
      "message": "Scene 1: clause has 6 adjectives (threshold: 4): ...",
      "suggestion": "Tighten scene wording before approval; see docs/PROMPT_QUALITY.md."
    }
  ],
  "errors": []
}

Under DIRECTOR_STRICT_PROMPT_QUALITY=1 the same issues appear on errors, pass flips to false, and execute/produce is blocked (director-mode preflight failures gate provider submission).

Promoting to blocking

bash
DIRECTOR_STRICT_PROMPT_QUALITY=1 vclaw video director-preflight --project <slug>

Team workflows that want prompt-quality to be a release gate can export the variable in CI so every run treats handbook violations the same as content hazards. Teams still iterating can leave it unset and treat the warnings as review cues.

Integration points

  • Emitted by checkPromptQuality in src/video/director-preflight.ts.
  • Surfaces in the existing vclaw video director-preflight JSON payload.
  • Flows into director-preflight's result.warnings / result.errors buckets, so all downstream consumers (tests, CI checks, storyboard.md approval review) see them automatically.

Multi-shot cinematic prompt validation

runMultiShotChecks(prompt, preset) enforces the structural rules for multi-shot cinematic prompts. It lives in the same module as runPromptQualityChecks (src/video/prompt-quality.ts) and is invoked via:

bash
vclaw video multi-shot --validate

Unlike the seven anti-pattern checks above, all issues from runMultiShotChecks are always error severity — they are structural requirements of the multi-shot format, not stylistic guidelines, so they cannot be downgraded to warnings by omitting DIRECTOR_STRICT_PROMPT_QUALITY.

Presets

Four presets ship today, each declaring its own duration, shot-count window, per-shot duration bounds, and character budget. The default remains cinematic-15s; the others target a specific provider's clip duration:

Presettotalshot rangeshot countmaxChars
cinematic-15s (default)15 s2–5 s3–71500
seedance-10s10 s2–5 s2–51500
veo-8s8 s2–4 s2–41500
runway-10s10 s2–5 s2–51000

Issue codes

CodeWhat it catches
multi-shot-timecode-parseA timecode in the prompt cannot be parsed
multi-shot-timecode-startFirst timecode is not 00:00
multi-shot-timecode-gapConsecutive timecodes overlap or leave a gap
multi-shot-timecode-totalTimecodes do not sum to the preset's totalSeconds
multi-shot-shot-durationA shot's duration falls outside the preset's [minShotSeconds, maxShotSeconds]
multi-shot-shot-count-out-of-rangeParsed shot count is outside the preset's [minShots, maxShots] window. Message branches on under vs over
multi-shot-overlongTotal prompt character count exceeds the preset's maxChars
multi-shot-repeated-parameterA consecutive pair of shots shares the same shot size, lens, angle, or camera movement
multi-shot-missing-metadataThe required Location / Style / Audio metadata block is absent

Matching for consecutive-shot-repeat checks is hyphen/space-insensitive, so push in and push-in are treated as the same value.

Canonical vocabularies

The shot-parameter vocabularies used for consecutive-repeat detection are defined in src/video/prompt-quality.ts and are now canonical for the entire module:

  • SHOT_SIZE_VOCABULARY — e.g. wide shot, medium shot, close-up
  • LENS_VOCABULARY — e.g. wide angle, telephoto, macro
  • ANGLE_VOCABULARY — e.g. eye level, low angle, bird's eye
  • CAMERA_MOVE_VOCABULARY — movement families shared with runPromptQualityChecks

Smoke

bash
npm run smoke:multi-shot

Builds the project, runs vclaw video multi-shot --plan, then validates a fixture at references/video/.fixtures/multi-shot-valid.txt — a no-network plan → validate round-trip.

filmmaking-prompts artifact lint (prompt-lint)

Distinct from the storyboard-scene anti-pattern checks above, lintFilmmakingPrompts (src/video/prompt-lint.ts) is a separate, pure validator that runs over a filmmaking-prompts.json artifact (not storyboard scenes). It is exposed as its own command:

bash
vclaw video prompt-lint (--project <slug> | --file <path>) [--register prose|numeric] [--cast <Name:descriptor> ...] [--brand <token> ...]

Per Seedance packet it checks the 10-block order (text-driven packets only), word count (warn outside the 280–600 words/packet window), the required video blocks SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE (text-driven only), the single-full-frame grid guard whenever a storyboard-grid reference is attached (else error — the grid leaks as a moving split-screen), no Kelvin/degree numeric tokens in a prose-register packet, and optional proper-name / brand scrubs when --cast / --brand are passed. Output is { packets: [{ sceneIndex, issues[] }], ok }, where ok is true iff no error-severity issue is raised on any packet.

Issue codes

CodeWhat it catches
seedance-block-order10-block order violated (text-driven packets only)
word-countPacket word count outside the 280–600 window (warning)
missing-required-blockA required video block (SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE) is absent
grid-guard-missingA storyboard-grid reference is attached but the single-full-frame guard is missing (error)
numeric-in-proseKelvin/degree numeric tokens in a prose-register packet
proper-name-leakA proper name survives the cast scrub (only when --cast is passed)
brand-leakA brand token survives the scrub (only when --brand is passed)

Follow-on roadmap

  • Per-project threshold overrides (today: hardcoded constants).
  • LLM-backed checks for subtler anti-patterns ("show don't tell", beat structure).
  • Auto-fix suggestions, analogous to DIRECTOR_AUTO_FIX_CONTENT=1 for content hazards.
  • Tie into the Tier-2 prompt-structure schema (item 9 in MASTER_PLAN_ALIGNMENT.md) so each check points at the matching schema section.

Built to be driven by agent hosts like Claude Code, Claude Desktop, or Codex · Source-available, commercial use requires a paid license.