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
| Check | threshold / rule | severity |
|---|---|---|
| prompt-quality-adjective-soup | > 4 comma-separated modifiers in one clause | warn (error if strict) |
| prompt-quality-multiple-actions | > 1 serial third-person-present action per clause | warn (error if strict) |
| prompt-quality-multiple-camera-moves | > 1 camera-move family per prompt | warn (error if strict) |
| prompt-quality-style-word-overload | > 3 stacked style words | warn (error if strict) |
| prompt-quality-literary-emotion | any inner-state language match | warn (error if strict) |
| prompt-quality-overlong | > 120 words (single-shot budget) | warn (error if strict) |
| negative-direction | any negated motion/tempo clause outside the allowlist | warn (error if strict) |
| Code | What it catches | Threshold |
|---|---|---|
prompt-quality-adjective-soup | A single clause piling up comma-separated modifiers | > ADJECTIVE_SOUP_THRESHOLD (default 4) |
prompt-quality-multiple-actions | Serial third-person-present actions in one clause | > 1 |
prompt-quality-multiple-camera-moves | More than one camera-move family per prompt | > 1 movement family |
prompt-quality-style-word-overload | Stacked "cinematic/epic/atmospheric" style words | > STYLE_WORDS_THRESHOLD (default 3) |
prompt-quality-literary-emotion | Inner-state language (feels, profound sadness) instead of visible behavior | any match |
prompt-quality-overlong | Prompt word count above the single-shot budget | > OVERLONG_WORDS_THRESHOLD (default 120) |
negative-direction | A negated motion/tempo clause (no/don't/avoid/without/never + slow-motion/blur/motion/movement/zoom/camera shake) — these models ignore negated direction | any 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:
- scenes without
durationSecondsuse a 15-second target - spoken dialogue is estimated at about 2.5 words per second
DIALOGUE_DURATION_OVERFLOWis emitted when the estimated spoken duration exceeds the scene target- the issue is a warning by default
DIRECTOR_STRICT_DIALOGUE_FIT=1promotes 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:
{
"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
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
checkPromptQualityinsrc/video/director-preflight.ts. - Surfaces in the existing
vclaw video director-preflightJSON payload. - Flows into
director-preflight'sresult.warnings/result.errorsbuckets, 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:
vclaw video multi-shot --validateUnlike 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:
| Preset | total | shot range | shot count | maxChars |
|---|---|---|---|---|
cinematic-15s (default) | 15 s | 2–5 s | 3–7 | 1500 |
seedance-10s | 10 s | 2–5 s | 2–5 | 1500 |
veo-8s | 8 s | 2–4 s | 2–4 | 1500 |
runway-10s | 10 s | 2–5 s | 2–5 | 1000 |
Issue codes
| Code | What it catches |
|---|---|
multi-shot-timecode-parse | A timecode in the prompt cannot be parsed |
multi-shot-timecode-start | First timecode is not 00:00 |
multi-shot-timecode-gap | Consecutive timecodes overlap or leave a gap |
multi-shot-timecode-total | Timecodes do not sum to the preset's totalSeconds |
multi-shot-shot-duration | A shot's duration falls outside the preset's [minShotSeconds, maxShotSeconds] |
multi-shot-shot-count-out-of-range | Parsed shot count is outside the preset's [minShots, maxShots] window. Message branches on under vs over |
multi-shot-overlong | Total prompt character count exceeds the preset's maxChars |
multi-shot-repeated-parameter | A consecutive pair of shots shares the same shot size, lens, angle, or camera movement |
multi-shot-missing-metadata | The 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-upLENS_VOCABULARY— e.g.wide angle,telephoto,macroANGLE_VOCABULARY— e.g.eye level,low angle,bird's eyeCAMERA_MOVE_VOCABULARY— movement families shared withrunPromptQualityChecks
Smoke
npm run smoke:multi-shotBuilds 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:
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
| Code | What it catches |
|---|---|
seedance-block-order | 10-block order violated (text-driven packets only) |
word-count | Packet word count outside the 280–600 window (warning) |
missing-required-block | A required video block (SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE) is absent |
grid-guard-missing | A storyboard-grid reference is attached but the single-full-frame guard is missing (error) |
numeric-in-prose | Kelvin/degree numeric tokens in a prose-register packet |
proper-name-leak | A proper name survives the cast scrub (only when --cast is passed) |
brand-leak | A 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=1for 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.
