# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Quick orientation (read this first, then `MERGE_PLAN.md`)

- **Entrypoint:** `src/cli/vclaw.ts` — a thin ~530-line dispatch shell; all command logic lives in `src/cli/handlers/*.ts` (25 modules).
- **Domain core:** `src/video/*` — small single-purpose modules (projects, execution, assemble, audio-platform, studio, preview-portal, motion-overlay, provider-platform, …).
- **Contracts & state:** `schemas/video/*` JSON Schemas are the source of truth for artifact *shapes*; the on-disk `projects/<slug>/` tree is the source of truth for project *state*.
- **Tests:** `node:test` files in `src/tests/*.test.ts`, run from compiled `dist/tests/` (`npm test`).
- **Also load:** the sibling `.claude/CLAUDE.md` for the `graphify`, `improvement-run`, and `concierge`/`clawbot` skill triggers.

> **Repository status (2026-07-02):** This is `videoclaw-v3`, the current repo —
> unified from `videoclaw-v2`, itself the merged successor of the older `videoclaw`
> package and the clean-room `vclaw-video-core` rebuild. (The
> npm package is `videoclaw`; "videoclaw-v3" is just the repo name.) Foundation
> copied from `vclaw-video-core`; presenter skills synced from
> `video-creation-projects/video-replicator-veo-cli/.claude/skills/`; Runway
> transport ported from `videoclaw/src/video/providers/runway-useapi.ts`.
> The full merge plan, decisions, and remaining phases are in `MERGE_PLAN.md` —
> **read it before starting any non-trivial work.** It is the source of truth for
> the architecture and what's coming.

## Repository purpose

`videoclaw` is a TypeScript/Node.js 20 multi-provider video CLI (`vclaw`). It targets Veo (Google Flow + UseAPI, including Omni Flash), Seedance, and Runway. Every pipeline stage is explicit, every artifact is machine-readable JSON, and provider routes never silently fall back across materially different paths. The on-disk per-project layout (`projects/<slug>/{project.json, artifacts/, checkpoints/, characters/, events/, ...}`) is the source of truth; the CLI is a thin operator over it. A browser-based Review UI (`vclaw video review-ui`) handles human-in-the-loop storyboard approval.

## Concierge front door (user-facing requests)

`skills/concierge/SKILL.md` is the user-facing front door, and it speaks as
**Clawbot** — VideoClaw's mascot (`clawbot` is a persona alias of the skill).
When the user asks to make a video, gives an ambiguous creative request, types
`/concierge` or `/clawbot`, asks for Clawbot, or seems new to the system, read
that skill and follow it before doing anything else: greet them as Clawbot,
present the menu, route their choice through its lane table, and keep the
plan → preview → spend order (never spend without explicit go-ahead). If their
first message is just a greeting with no task, offer the concierge menu.
Engineering and maintenance requests on this codebase itself are NOT concierge
territory — handle those normally.

## Working discipline — ground in what already exists before you build (READ THIS FIRST)

This project keeps getting bitten by the same failure: **improvising a fresh
(worse) approach instead of reusing the proven artifact that already exists**, and
silently dropping load-bearing pieces between turns.

**The honest root cause is a default to fast *visible* output** — a clip, an HTML,
a keyframe, a render — **over a provably-correct setup.** A fast wrong render is
slower than a slow right one: it burns spend, trust, and rework. The rules below
are forcing functions against that default; the friction IS the point. The
highest-leverage rules are **0, 1, and 4** — do them even when it feels slower.

Before authoring or changing **any** skill, procedure, function, prompt, render
setup, or review surface — and before creating a new command — work through this:

0. **Read the spec in full, once, up front — and write the contract down.** When
   the user points to an authoritative method file (a doc, `WORKFLOW.md`, a
   compliance sheet, a working harness script, a `--validate`d prompt), read the
   WHOLE thing first and extract its requirements into a written checklist/contract
   (a file or notepad) BEFORE building. Do NOT skim it on demand and discover the
   pieces one correction at a time — that is exactly what makes the operator repeat
   himself. For any multi-step build/production, keep that contract on disk and
   **re-read it each turn**; requirements held only in working memory erode over a
   long session. The on-disk contract, not your memory, is what you build against.
1. **Inventory before you build (the #1 rule).** Search first. Grep/read the
   relevant `src/video/*`, `src/cli/handlers/*`, `skills/*`, `docs/*`, the
   project's on-disk `artifacts/*` (e.g. `show-bible.json`, `voice-clones.json`,
   `story-bible`, `flow-characters.json`, `seedance-assets.json`), and any
   `references/` assets, proven harness scripts, or already-`--validate`d prompts.
   If a working artifact exists, **use it** — never regenerate an ad-hoc
   substitute. Most "why did this break again" pain is re-deriving something that
   was already solved and written down.
2. **Artifacts/registries are the source of truth, not memory.** Drive work from
   the on-disk artifacts (the `show-bible` registry, voice clones, reference
   sheets, validated prompt files) — not from paths or prompts re-typed from
   memory. If you catch yourself re-typing a file path or re-authoring a prompt
   that already exists, stop and load it from the artifact.
3. **Prompting is per-model — never reuse one prompt style across routes.**
   Seedance/Runway/Dreamina lock identity from **references + a rich per-subject
   visual descriptor** (never a generic "the man") + the multi-shot framework, and
   take character-sheet + location + per-speaker voice refs. Flow (`veo-useapi`)
   locks identity on a **registered Flow Character** and tolerates generic
   prompts. Image models (GB, `openai-gpt-image-2`) differ again. Check the
   route's ruleset (and any model-specific prompting skill/doc) before authoring a
   prompt or choosing references.
4. **Encode the method so the TOOL enforces it — don't rely on remembering.**
   When a procedure has required inputs/steps, add a fail-fast preflight/readiness
   gate that ERRORS (non-zero, clear blocker) when a required piece is missing, and
   prefer **registry-driven auto-attach** over manual setup. The target shape: an
   operator just prompts; the tool attaches the right inputs and refuses to render
   wrong (see `show-preflight` + show-bible auto-attach).
5. **Review-first, one-at-a-time for any spend.** Before any paid or slow
   generation, emit the exact **contract** — the prompt + the references + the JSON
   that will actually be submitted — for review (the review portal IS the
   contract; show the EXACT submit payload, not a paraphrase). Then render ONE
   unit, verify it (extract a frame + whisper the audio / QC), get approval, and
   only then the next. Never batch-spend on an unreviewed setup. **Immediately
   before submitting, dry-run `buildExecutionPayload` and confirm the resolved
   refs/prompt/route match the approved contract** — the actual submission has
   silently diverged before (an `@tag` hijacked the references and dropped the
   approved sheets/location). If it doesn't match the contract, do not submit.
6. **New command/skill = conventions below + steps 1 & 4.** Still do the full
   registration (handler + `vclaw.ts` dispatch + `cli-schema.ts` `COMMANDS` entry +
   bump the `cli-schema.test.ts` count + a `cli-*.test.ts` + README/CLI_REFERENCE +
   a schema for any new artifact), but **inventory first** (step 1) and add a
   **preflight gate** (step 4) if it has required inputs.
7. **Verify OUTCOMES, not that a command ran — and never report unverified
   progress.** "Launched a driver" / "submitted" is NOT "it's working." After any
   submit or render, confirm the real provider-side state before claiming it: a
   live job exists (`execute-status` → `live-submitted` + a candidate + a
   `.vclaw-jobs/*.json` file), and then the actual output downloaded + QC'd (frame
   + whisper). If you tell the user something is happening, prove it (a job id /
   status / a frame) — do not assert progress you have not checked. Corollary —
   **clear stale outputs before any re-render:** a changed scene prompt plus an old
   `outputs/scene-N.mp4` makes the resumable driver SKIP that scene and you verify
   the wrong/old clip. Delete stale outputs (and re-select winners that the
   restructure orphaned) first; a `scene-selection-missing` block silently refuses
   every produce.

When in doubt, the on-disk project and its artifacts win over anything you
remember.

## Build, test, and smoke commands

```bash
npm install                              # Node 20+
npm run build                            # clean dist/, tsc, chmod +x the CLI bins
npm run dev                              # tsc --watch
npm test                                 # rebuild, then node --test dist/tests/*.test.js
npm run test:node                        # rerun compiled tests without rebuilding (serial: --test-concurrency=1)
```

Run a single test file after `npm run build` (or `npm run dev`):

```bash
node --test dist/tests/cli-full-flow.test.js
```

End-to-end smokes (each runs `npm run build` first) and local guardrails:

```bash
npm run smoke:runtime                    # init → brief → storyboard → assets → plan → produce --dry-run → status → report → Obsidian
npm run smoke:native-veo                 # native Veo (Flow/Bun) transport path
npm run smoke:character-hydration        # create-time cast hydration + approval-gate cost
npm run smoke:execution-cancel           # adapter + project-level cancel
npm run smoke:portfolio                  # index → report → export-csv visibility
npm run smoke:reference-sheets           # character reference-sheet generation path
npm run smoke:scene-candidates           # scene-candidate generation path
npm run smoke:story-bible-image          # story-bible image-only path
npm run smoke:assemble                   # FFmpeg assemble/stitch layer (dry)
npm run smoke:assemble-render            # real FFmpeg render validation
npm run smoke:multi-shot                 # multi-shot prompt plan→validate round-trip
npm run e2e:image-storyboard             # image-storyboard workflow (runs --verify-server)
npm run e2e:image-storyboard:examples    # image-storyboard e2e against the bundled examples
npm run check:movie-director-wrappers    # bundled Director helper scripts
npm run check:cleanroom-docs             # clean-room docs + skills
npm run check:skill-frontdoor            # repo-local skill front door
npm run check:artifact-schema-coverage   # writers vs schemas drift (advisory)
npm run check:artifact-schema-coverage:strict  # same, but fails the build on drift (--strict)
npm run check:release-readiness-lite     # one-shot: build + tests + main smokes + guardrails
npm run graph:full                       # rebuild the graphify knowledge graph (graphify-out/)
```

`npm run check:release-readiness-lite` is the preferred local pre-flight before non-trivial changes land.

## Big-picture architecture

### Layers (read top-down)

1. `src/cli/vclaw.ts` — the single user-facing entrypoint, now a thin dispatch shell (~530 lines: imports, `printHelp`, the `NOUN_VERB_ALIASES` map, `resolveSubcommand`, the `VIDEO_DISPATCH` table, and `main`). The decomposition is COMPLETE (PRs #89–#120): pure arg/slug/spend helpers live in `src/cli/args.ts`, and ALL command handlers live in `src/cli/handlers/` (25 modules — `analysis`, `audio`, `batch`, `candidates`, `character`, `clone`, `create`, `execution`, `library`, `media-ops`, `media-production`, `migrate-home`, `monitor`, `motion-overlay`, `multi-shot`, `project-ops`, `prompt-craft`, `provider-registration`, `reference-sheets`, `reporting`, `review-portal`, `show`, `stages`, `studio`, `templates`), each a verbatim extraction with module-private helpers and only dispatch-facing handlers exported. ⚠️ When moving handler code, watch for position-sensitive path math (`import.meta.url` etc.) — the compiled location changes (see the documented adaptations in `handlers/studio.ts` and `handlers/motion-overlay.ts`). `src/cli/provider-adapter.ts` is the built-in adapter binary for the four `..._ADAPTER` routes (see Provider routes below). Besides `video`/`studio`, `main` also dispatches two smaller families: `vclaw mcp serve` (`src/mcp/` — a read-only stdio MCP server exposing `list_projects`, `get_project_status`, `get_artifacts`, `get_event_log`, `list_provider_routes`; writes stay CLI-only by design) and `vclaw veo <verb>` (`src/video/veo-subprocess.ts` — spawn-forwarding to the Bun-based `vclaw-cli/flow.ts`, required for Puppeteer/Google Flow verbs like `status`, `resume`, `cancel`, `useapi:*`).
2. `src/video/` — the core domain. Each file is small and single-purpose, e.g. `artifacts.ts`, `artifact-store.ts`, `checkpoints.ts`, `workspace.ts`, `projects.ts`, `status.ts`, `doctor.ts`, `doctor-portfolio.ts`, `readiness.ts`, `execution-plan.ts`, `execute.ts`, `execution-runtime.ts`, `execution-status.ts`, `execution-cancel.ts`, `director-preflight.ts`, `report.ts`, `csv-export.ts`, `obsidian-export.ts`, `project-index.ts`, `metrics.ts`, `next-actions.ts`, `template-store.ts`, `provider-status.ts`, `native-seedance.ts`, `native-veo.ts`, `multi-shot-prompt.ts`, `storyboard-grid.ts`, `cinematography.ts` (detail-leveled quantified camera/lighting/grade/audio emitters — `--detail terse|standard|rich`), `prompt-rules.ts` (standing prompt rules: visual-descriptor-not-names, brand-neutral, no-face-morph, diegetic audio), `seedance-asset-library.ts` (Asset Library character/product consistency), `story-bible.ts` (deterministic continuity bible — cast/settings/props/scene-timeline from brief + storyboard + characters), `brand-definition.ts` (locked brand system — palette/voice/typography/theme map; `filmmaking-prompts` appends a prose BRAND line when present), `assemble/media-qc.ts` (post-stitch ffprobe QC of clips + master), `assemble/narration-fit.ts` (TTS-vs-video timing planner: atempo / loop-video fit). `src/video/studio/` is the planning front door and `src/video/preview-portal/` is the review/delivery portal (both described below).
3. `src/video/provider-platform/` — route descriptors (Veo / Seedance / Runway direct and useapi flavors).
4. `src/video/pipeline-manifests/` — built-in stage definitions for the two production modes.
5. `schemas/video/` — canonical JSON Schema contracts for artifacts and pipeline manifests. Treat these as the source of truth for artifact shapes.
6. `src/tests/` — `node:test` files named `*.test.ts`. `dist/tests/**.test.js` runs via `node --test`.
7. `src/index.ts` — the public library surface; re-exports the subset of `src/video/*` that should be callable from outside the CLI.

### Project lifecycle (per project, on disk)

After `vclaw video init <slug>`, a project lives at `projects/<slug>/` under the workspace root and is the unit of everything else. **The workspace root is the ONE canonical home `~/videoclaw`** (`CANONICAL_WORKSPACE_ROOT` in `src/video/workspace-root.ts`), resolved as `--root` flag → `VCLAW_WORKSPACE` → `VIDEOCLAW_WORKSPACE` → `~/videoclaw` — this replaced the old per-invocation `process.cwd()` default that scattered projects wherever `vclaw` happened to run. `vclaw video migrate-home` consolidates scattered projects into the home (dry-run plan by default; `--confirm` moves each project and leaves a symlink at the old path so old absolute paths still resolve).

```
projects/<slug>/
  project.json                 # manifest: slug, mode, state, metadata, execution profile
  artifacts/                   # canonical JSON: brief, storyboard, story-bible, asset-manifest, review-report, publish-report, analyze-output, clone-plan, execution-plan, execution-report, readiness, character-consistency, ...
    history/                   # artifact snapshots (append-only)
  checkpoints/                 # one file per stage: brief, storyboard, assets, review, publish; tracks approval states
  events/events.jsonl          # append-only timeline
  state/                       # derived state cache
  characters/characters.json   # optional character profiles with GB identity anchors
  storyboard.md                # director-mode approval review file (human-readable)
```

Canonical stage order: **init → brief → storyboard → assets → review → publish**. `readiness`, `plan`/`execution-plan`, `produce`/`execute`, `execute-status`, `execute-cancel` are the runtime-execution layer that sits between assets and review.

`produce`/`execute --auto-chain` (`src/video/execute-autochain.ts`, `runAutoChain`) is the whole-storyboard driver over the existing dormant chain-from-prev engine: it renders scenes sequentially, auto-selects each produced candidate (from `report.candidatesByScene`), sets `chainFromPrev` for every scene after the first, and bundles `continuityFeedback` — so each scene seeds from the previous scene's output video with zero manual `reroll-scene`. The engine itself (seed resolution, `referenceRole='keyframe'`, reference budget) is unchanged; the one supporting change is that a seedance scene now keeps BOTH its character `Asset://` identity refs AND the keyframe video, via the pure `resolveSceneReferencePaths` in `execution-runtime.ts` (formerly an either/or that dropped the chain seed when characters were locked). It is resumable (skips already-selected scenes) and fail-fast (`stoppedAt` on the first barren scene). Default-off → byte-identical to today. Because `executeProject` on async routes (e.g. `seedance-direct`) is SUBMIT-ONLY, the default per-scene runner calls `waitForSceneVideo` (loops `refreshExecutionStatus` until the scene's candidate gains a video output) BEFORE selecting + chaining — otherwise the next scene's chain seed resolves to a still-rendering, video-less candidate and `chain-from-prev-source-missing` fires. Injected test runners simulate completed renders and skip the poll. Auto-chain also emits **continuation advisories** (`continuation-handoff.ts`, `analyzeChainContinuity`) — chain-depth drift warnings, present only when non-empty.

Auto-chain is one of **three resumable scene drivers**, all the same shape (a PURE scheduler + an injectable per-scene runner, offline-testable): `runAutoChain` (SEQUENTIAL + chained, above), `runScenePool` (`execute-pool.ts`, `vclaw video pool`) which keeps up to N INDEPENDENT scene renders in-flight and auto-refills as each completes (no chaining — productizes the hand-written pool-driver loops), and `runRenderScenes` (`execute-render-scenes.ts`, `vclaw video render-scenes`) which renders sequentially but walks a per-scene **fallback route ladder** — try route A, escalate to B then C on provider rejection, first success wins. Real runners + CLI handlers live in `src/cli/handlers/execution.ts`.

### Two production modes

Every command accepts `--mode storyboard|director`. Pipeline manifests under `src/video/pipeline-manifests/` define the stage contract per mode. `director` mode adds a storyboard-approval gate: `produce`/`execute` export `storyboard.md` and block before provider submission unless `VIDEOCLAW_APPROVE_STORYBOARD=1` is set. `storyboard-review` (no-execution) can perform preflight + transition the project into `awaiting-approval` without starting a run.

### Studio front door (planning layer)

`vclaw studio` (`src/video/studio/`, handler `handleStudio` in `src/cli/handlers/studio.ts`) is a human-friendly planning front door that sits *above* the low-level CLI — it does not replace it. **Plan-only by default:** it builds a `StudioPlan` from a goal and prints the exact `vclaw video ...` commands and artifacts that would run, without calling providers/FFmpeg. **`--execute` (Phase 2) RUNS the emitted plan** via `src/video/studio/execute.ts` (`runStudioPlan`) by shelling out to the same `vclaw video` commands — it is NOT a second orchestrator (no readiness/route/approval logic is re-derived; the plan stays the source of truth). Three run-time modes: **default `--execute` is provably dry** (`classifyStep` refuses any `SPEND_SUBCOMMANDS` step lacking `--dry-run`; dry spend steps keep `--dry-run`), running free+dry steps and pausing at the real director gate (a `blocked` child) with fail-fast; **`--confirm-spend`** promotes dry spend steps to real renders (`stripDryRunArgv` removes `--dry-run`) but `studioChildEnv` still strips `VIDEOCLAW_APPROVE_STORYBOARD` so the storyboard gate keeps blocking unless approved out-of-band; **`--confirm-spend --auto-approve-storyboard`** sets the approval var for an unattended render (auto-approve without confirm-spend throws). `--from-step <id>` resumes. The runner is pure with an injectable `StudioStepRunner` (offline-tested in `studio-execute.test.ts`/`studio-classify.test.ts`). The planner stays pure/deterministic apart from `session.ts`:
- `recipes.ts` — `STUDIO_RECIPES`, one `StudioRecipe` per goal (command templates, required/optional inputs, `riskLevel`, `executionPolicy`).
- `planner.ts` — `buildStudioPlan()` resolves the goal, fills `<placeholder>` command templates, computes `missingInputs`/`warnings`, and emits a `StudioPlan` (`schemaVersion: 1`).
- `project-context.ts` — `loadStudioProjectContext()` reads readiness + next-actions to enrich the plan; `session.ts` `writeStudioSession()` persists `projects/<slug>/artifacts/studio-session.json` when `--write-session` is passed.
- `types.ts` — shared `StudioGoal` (10 goals), plan, and recipe types.

Goals (each has a short alias, e.g. `presenter`→`presenter-video`): `create-video`, `copy-reference`, `presenter-video`, `music-video`, `ugc-campaign`, `existing-project`, `review-regenerate`, `publish-deliver`, `brand-campaign`, `character-video`. Studio output is JSON on stdout. When extending it, add the recipe to `recipes.ts`, the goal+alias to `handleStudio`, a `studio-*.test.ts`, and update `docs/STUDIO.md`; the command is also registered in `src/video/cli-schema.ts` `COMMANDS` (whose length is asserted by `cli-schema.test.ts`).

### Review-state ladder

The ops layer tracks a normalized `storyboardReviewState` of `missing | current | stale`. This flows through status, index, report, CSV export, Obsidian export, dashboards, next-actions, snapshot diffs, and the doctor layer. A stale director review blocks `execute`/`execute-status` at runtime even if approval is set. When touching review/approval logic, keep this ladder consistent across all surfaces.

### Review & delivery portal (`src/video/preview-portal/`)

The portal generates the standardized HTML surfaces that used to be hand-written per project: `edit.html`/`review.html` (editor/operator human-in-the-loop, with approve/regenerate controls and `VIDEOCLAW_REVIEW_DECISIONS` copy output), `client-review.html` (lightweight client approve/decline/comment, `VIDEOCLAW_CLIENT_FEEDBACK` copy output), and `preview.html` (polished final showcase: every production image is lightbox-enabled for click-to-fullscreen, a soundtrack `<audio controls preload="none">` player renders when a soundtrack is discovered, plus downloads). The module is split into `discovery.ts` (find project assets), `generate.ts` + `templates.ts` + `shared-assets.ts` (render the surfaces), `render.ts`/`publish.ts` (emit/ship), and `audit.ts` (drift checks); `src/video/review-ui.ts` (`vclaw video review-ui`) serves the editor surface interactively. The decisions/feedback flow back through env-var copy blocks rather than a server round-trip, keeping the on-disk project the source of truth. See `docs/preview-portal-audit.md`.

### Mission Control (`vclaw video monitor`)

`src/video/monitor/` serves a read-only localhost cockpit (default port 8765): `monitor/discovery.ts` discovers every project across workspace roots (`--root` adds extra roots to scan) and the server renders a live overview from the on-disk artifacts. Never calls a provider, never spends.

### Storyboard grid & multi-shot prompt handoff

`src/video/multi-shot-prompt.ts` builds project-ready, provider-tuned multi-shot prompt packets (presets via `vclaw video multi-shot --presets`; see `references/video/multi-shot-framework.md`, especially its Anti-patterns section). `multi-shot --plan` can render through alternate composers via `--format default|seedance-paragraph|per-shot` (default = the original `{ preset, shots[] }` JSON, unchanged) and wrap the rendered text bilingually via `--lang en|zh|en+zh` (offline identity translator — the flag surfaces the two-block `en+zh` structure, not live translation); `--category <id>` drives the composed prose. On a non-`default` `--format`, `--hook <patternId>` (named `HOOK_PATTERNS` opening directive from `cinematography.ts`) prepends an `Opening hook — ...` line and `--dialogue "<speaker>: <line> [|| <speaker>: <line>]"` appends spoken dialogue to the opening via `withDialogue` — both are post-render text transforms (composers stay pure), default off / unchanged output. `--dialogue` parses a trailing `[emotion]` per speaker, and `--emotion-cues` rewrites those named emotions into physical-cue descriptors (`rewriteEmotionAsPhysical`/`EMOTION_PHYSICAL_CUE_MAP` in `emotion-cues.ts`) — advisory/additive, extremes left named, default off / byte-identical. `filmmaking-prompts --phase storyboard|video` gates the heavy video `seedancePackets` to `[]` in the `storyboard` phase (lock-the-grid step) while keeping the storyboard/camera-language portion. Joey cinematic-adaptation opt-in flags (all additive — omit = byte-identical legacy output) route through these same commands: `filmmaking-prompts` takes `--sheet 8-shot|6-panel` (`characterSheetSixPanelPrompt`), `--realism`/`--wet`/`--haze thin|light|heavy` (the `captureRealismBlock` keystone + `volumetricHaze`, at `--detail rich`), `--background mid-gray|white|black` (`backgroundPlate`), and `--lighting <id>`/`--grade <id>` (rich cinematography-suffix registers, e.g. `night-fire`/`bleach-bypass`); `multi-shot` takes `--genre <id>` (`resolveStyleLine`, Nolan fallback) and `--vfx <id>` (physical-VFX effects register, `src/video/vfx-register.ts`). Operator trigger-word map: mid-gray → `backgroundPlate`; haze → `volumetricHaze`; anti-plastic → `captureRealismBlock`; wet → moisture clause; bleach-bypass/lifted-blacks → lift/gamma grade; no-on-screen-text → Last Frame suppression. See `docs/CLI_REFERENCE.md` ("Joey cinematic opt-in flags") and the framework Anti-patterns.

`src/video/storyboard-grid.ts` (`vclaw video storyboard-grid`, `renderStoryboardGrid`) renders a **deterministic shot-spec sheet** — a 3×3 SVG→PNG of CAM/MOVE/MOOD annotation panels — **not** a cinematic storyboard with character imagery. It is the *layout/intent contract*, not the finished reference image. The intended two-step is: (1) `storyboard-grid` to lock panel order + camera language, then (2) generate the real cinematic 3×3 grid via an image model (always `openai-gpt-image-2` for multi-panel composites) and re-attach it with `vclaw video filmmaking-prompts --storyboard-grid <path>`, which feeds it into the Seedance/Veo/Runway prompt packets.

Two production-learned gotchas baked into the generated packets (see the framework Anti-patterns): (a) grids passed as provider `reference_images` get **reproduced as a moving 9-panel split-screen** unless the prompt explicitly forces single-full-frame output — the packets now embed that guard; (b) real-person content filters (xskill/ARK Seedance) reject photoreal faces as `reference_images`, so use `filmmaking-prompts --no-faces` to render the grid prompt in a silhouette / no-face register.

`vclaw video prompt-lint` (`src/video/prompt-lint.ts`, pure/deterministic — the handler does the I/O) lints a filmmaking-prompts artifact BEFORE spend: Seedance 10-block order, the 280–600-words-per-packet window, brand/proper-name scrub leaks, the single-full-frame grid guard whenever a storyboard-grid reference is attached, SUBJECT LOCK / CAPTURE REALISM / CAMERA CAPTURE presence on video packets, grid-panel annotation style (advisory), the character-identity word budget (30–60 words target, >100 hard failure), and two newer advisories: the reference-transfer contract and allocation-model over-allocation. `--checklist` prints the route checklist. `vclaw video cinema-profile` persists per-project cinematography defaults onto the project manifest (`updateProjectManifestCinemaProfile` in `workspace.ts`) so the prompt/packet layer picks them up without re-typing flags. `vclaw video diagnose` (`src/video/output-diagnosis.ts`) is the companion for AFTER a render: an output-quality troubleshoot tree — `--symptom <text>` matches an observed defect to causes + fixes, `--retry-pattern` prints the conservative retry pattern.

### Provider routes and adapters

Live execution calls route-specific adapters. A custom adapter is set via one of:

```
VCLAW_VEO_USEAPI_ADAPTER
VCLAW_SEEDANCE_DIRECT_ADAPTER
VCLAW_RUNWAY_USEAPI_ADAPTER
VCLAW_DREAMINA_USEAPI_ADAPTER
```

Adapters receive JSON on stdin and must return JSON on stdout (`externalJobId` for submit, `pending|completed|failed` for poll).

For `seedance-direct`, `veo-useapi`, `runway-useapi`, and `dreamina-useapi`, `vclaw` ships a built-in adapter binary (`dist/cli/provider-adapter.js`) used automatically unless the full `..._ADAPTER` override is set. The built-in adapters read route-specific `..._SUBMIT_CMD` / `..._POLL_CMD` / `..._CANCEL_CMD` command shims. Routes also have native in-process transports: `native-seedance.ts` (uses `SUTUI_API_KEY`), `native-veo.ts` (drives the local `vclaw-cli` Bun package), `native-runway.ts` (pure Node fetch + fs, UseAPI bearer auth), `native-dreamina.ts` (pure Node fetch + fs, UseAPI bearer auth).

**`dreamina-useapi` = Seedance 2.0 / Dreamina via useapi.net.** Dreamina (CapCut/ByteDance Seed) exposes the Seedance family (`seedance-2.0` default, `-2.0-fast`, `-2.0-mini`, `-1.5-pro`, `-1.0-pro`, `-1.0-mini`, `-1.0-fast`) and `sora2` through useapi.net; CA-region accounts unlock 1080p Seedance 2.0 (and 4k on `seedance-2.0` as of 2026-06-26 — opted into route-locally via `VCLAW_DREAMINA_RESOLUTION=4k`, since the shared execution profile only expresses 720p/1080p; the transport clamps 4k→1080p on non-seedance-2.0 models and →720p on the 720p-only ones). `-2.0-fast`/`-2.0-mini`/`sora2` are 720p-only. `native-dreamina.ts` reuses the **same `USEAPI_API_TOKEN`** as runway-useapi (no new token) and reads the account from `VCLAW_DREAMINA_ACCOUNT` (e.g. `CA:ai@example.com`), region from `VCLAW_DREAMINA_REGION` (default `CA`), and model from `VCLAW_DREAMINA_MODEL` (default `seedance-2.0`); the route adapter override is `VCLAW_DREAMINA_USEAPI_ADAPTER`. The account is registered server-side out-of-band (`POST /accounts` with `{email,password,region,maxJobs}`), so submit only needs the account id + token. For image-to-video, the first image reference is uploaded via `POST /dreamina/assets/<account>` to obtain an `assetRef`, which is passed as `firstFrameRef` on `POST /dreamina/videos` (first_frame mode auto-detects aspect ratio from the image); poll uses `GET /dreamina/videos/<jobid>` (`status: created → completed|failed`) and downloads `response.videoUrl`. Like runway-useapi, real human faces are rejected by Seedance moderation — describe stylized characters by visual descriptor.

**Seedance character consistency = the Asset Library, not raw URLs.** `ark/seedance-2.0` (the official Volcengine Ark Seedance 2.0, Standard = 1080p) locks character identity via managed **Asset Library avatars** (`Asset://` URIs), NOT raw photoreal image URLs (those trip the "real person" content filter and don't lock identity). `src/video/seedance-asset-library.ts` (`vclaw video seedance-register-assets`) registers character images as Assets, waits for international-profile sync, and writes `artifacts/seedance-assets.json` (canonical schema `schemas/video/artifacts/seedance-assets.schema.json`; shape `{ schemaVersion, projectSlug, groupName, generatedAt, assets:[{name, assetId, assetUri, intlAssetUri}] }`); `readSeedanceAssets(workspaceRoot, slug)` reads it back into a name→`Asset://`-URI map (graceful when absent). On the `seedance-direct` route only, `buildExecutionPayload` (`execution-runtime.ts`) auto-resolves each scene's `referencePaths` from that artifact by matching `scene.characters` names → their `Asset://` URIs (a project without the artifact behaves as before); `native-seedance.ts`'s `seedanceReferenceParams` then routes `Asset://` references into `reference_images`. References are capped at ≤9 image / ≤3 video / ≤3 audio per submission via `assertReferenceBudget`, preflighted across the whole payload in `submitSeedanceDirectNative` before any submit (fail-fast, no partial submission). Describe characters by visual descriptor (not proper names) in prompts — names don't survive across generations. (Validated 2026-05-29 against the same Ark endpoint the user's production project uses.) `@Name` tags in scene prompts (`resolveAssetTags` in `prompt-rules.ts`, lookup via `asset-tag-lookup.ts` `buildAssetTagLookup`) resolve at `buildExecutionPayload` time to the character's visual descriptor (text) + its saved reference (`Asset://`/image), merged through `resolveSceneReferencePaths`; unresolved tags strip the `@` and warn (never fatal), `@imageN` positional bindings are reserved, and it runs before `stripProperNames`. `@location` tags resolve via locked environment plates: `vclaw video environment-auto-create` (`src/video/environment-auto-create.ts`, mirrors `character-auto-create`) generates seamless no-people location plates and writes `artifacts/environment-assets.json` (own schema, like seedance-assets); `readEnvironmentAssets` (`environment-assets.ts`) feeds `buildAssetTagLookup`'s `environmentsByName` so `@tokyo-alley` resolves to the plate descriptor + ref (absent → graceful no-op).

**Flow identity is registered, not referenced.** The Flow-side counterpart of `seedance-register-assets`: `vclaw video flow-register-characters` / `flow-register-voices` (`src/video/flow-character-library.ts`, handlers in `provider-registration.ts`) register reusable Google Flow Characters/Voices via useapi and write `artifacts/flow-characters.json` / `flow-voices.json` (needs `USEAPI_API_TOKEN` + `USEAPI_ACCOUNT_EMAIL`). `vclaw video flow-r2v` (`src/video/native-flow-r2v.ts`) then submits reference-to-video against those registrations, running `applyR2vPromptHygiene` over the prompt first. This is what working-discipline rule 3 means by "locks identity on a registered Flow Character".

### Cartoon-show layer (show-bible → show-preflight → voice clones)

The repeatable cartoon-SHOW production system (from the Jack-Vs-AI workflow). `vclaw video show-bible` (`src/cli/handlers/show.ts`) is the show's asset-library index: it ties the project's characters + locations (environment plates) + voice clones into one reusable world and tracks the episode list — plan/derive by default, deterministic, no provider calls. `vclaw video show-preflight` (`src/video/show-preflight.ts`, `buildShowPreflight`) is the fail-fast, **route-aware** readiness gate that working-discipline rule 4 points at: given the show-bible + storyboard, it verifies every cast/speaking subject in every scene has what the CHOSEN route actually needs — Seedance-family routes (`seedance-direct`/`runway-useapi`/`dreamina-useapi`) require a resolvable character sheet per cast member (on `seedance-direct` it must additionally be a registered Asset Library avatar — a raw portrait trips the real-person filter), a location plate per scene, and a bound, resolvable voice clip per speaking character, with subjects described by full visual descriptor (never a bare generic noun); Flow (`veo-useapi`) requires a registered Flow Character per cast member (registration is out-of-band via `flow-characters.json`, so the gate enforces — it never fabricates). `vclaw video voice-clone` (`src/video/voice-clone.ts`) implements the production-learned voice lock: a raw MP3/WAV voice reference DRIFTS to a generic accent, but the same audio as the track of a **black-frame video** locks it — the command builds that black-frame+audio MP4 (shared `runFfmpeg`) and persists reusable voice-clone assets in `artifacts/voice-clones.json` (mirroring the seedance-assets/environment-assets pattern), bindable to characters.

### Overnight batch queue

`src/video/batch-queue.ts` + the `vclaw video batch-submit` / `batch-monitor` / `batch-status` commands queue many independent video jobs to run unattended overnight. The default route is the **free** `runway-useapi` explore mode (low-res, slow — backfill drafts); target `dreamina-useapi` (or `seedance-direct`) for paid hi-res. An operator-authored manifest (`schemas/video/artifacts/batch-queue-manifest.schema.json` — an input-only artifact, allowlisted in `check-artifact-schema-coverage.mjs`) compiles via the pure `buildBatchPayload()` into a single `VideoExecutionPayload` with N tasks, so it reuses the existing native route transports (`native-runway`/`native-dreamina`/`native-seedance`) and their job-state — no duplicate submit/poll logic. `batch-submit` persists `<dir>/batch-queue.json`; `batch-monitor` polls once (the transport downloads to `<dir>/scene-<i>.mp4`), copies each completed scene to `<dir>/clips/<jobId>.mp4`, and writes `<dir>/batch-status.json`. It is **resumable/idempotent**: re-running only advances pending→done/failed and never re-downloads completed clips, so `batch-monitor --out <dir> --once` is safe to schedule via launchd (one pass per tick). Opt-in wedge handling: `--stall-minutes <n>` (0=off) flags scenes the provider has left `submitted` past the stall window as **wedged** (`detectWedgedScenes`/`applyWedgeHandling` in `batch-queue.ts`), and `--fail-wedged` marks them `failed` so the queue reaches terminal and the monitor exits instead of polling a stuck job to the deadline. The monitor also backs off automatically when the explore queue is throttled (`isExploreThrottled`/`nextBackoffMs`), and the opt-in `--auto-resubmit` (with `--stall-minutes <n>`, bounded by `--max-resubmits <n>`, default 2) re-submits wedged scenes as fresh single-scene jobs (`planResubmits`/`runResubmitPass`) — **only on the free `runway-useapi` route** (refused on paid routes up front, and the resubmit path self-guards, so it never spends credits). See `docs/CLI_REFERENCE.md` ("Overnight batch video queue").

### Director Blueprint (director layer)

`src/video/project-blueprint.ts` + `blueprint-prompt.ts` + `director-defaults.ts` (`vclaw video director-blueprint --project <slug> (--from-json <path> [--write] | --show)`) persist a project-level **visual bible** (`artifacts/project-blueprint.json`): color system, lighting grammar, per-character camera language, forbidden moves, "the one rule". It is distinct from the story bible (continuity); the blueprint locks **visual direction** above the execution layer. Authoring is creative and lives in the `ai-director` skill (`skills/ai-director/SKILL.md`); the CLI half is deterministic (validate/normalize/persist). `filmmaking-prompts` auto-appends a prose DIRECTOR block and flags forbidden-move violations when a blueprint exists; no blueprint → byte-identical legacy output. See `docs/DIRECTOR_BLUEPRINT.md`.

### Motion overlay (`src/video/motion-overlay/`)

`vclaw video motion-overlay --input <video>` turns an existing talking-head video into a reel with speech-synced motion-graphics overlays. Pipeline: ingest → transcribe (Gemini STT) → slice into ≤10s takes → compose overlay prompts (retention principles + concept→animation metaphor map live as deterministic code, not markdown) → render via one of four layouts (`split|overlay|motion-only|avatar-host`). Render transports: Omni Flash V2V (moderation-prone on person footage), **local** per-frame render (`render-local.ts`/`animate*.ts` — word reveal, count-up, gauge fill), and `avatar-host` (locked go-bananas character via `--gb-character <Name:ID>`, pin+chain identity). Plan/dry by default; spend requires `--execute --confirm-spend`; retry/resume self-heals probabilistic Flow moderation. See `docs/MOTION_OVERLAY.md`.

### Audio platform (`src/video/audio-platform/`)

`narrate`, `dialogue`, `sfx`, and `soundtrack` are backend-pluggable audio commands over a backend registry (`registry.ts`): TTS via `gemini-tts`/`elevenlabs-tts`, SFX via ElevenLabs, music via `lyria` (Vertex), `lyria3` (Gemini API, key-based), `flowmusic` (Lyria 3 Pro vocals via useapi.net), and Suno. All spend-gated: without `--confirm-spend` they exit 3 with `spend_confirmation_required` (`--dry-run` to plan). `assemble` consumes their outputs via the mix-plan (dialogue + SFX mixed at stitch; `assemble/narration-fit.ts` handles TTS-vs-video timing).

### Media production & local post-production

Two handler families cover the finishing lane. `handlers/media-production.ts`: `assemble` (FFmpeg stitch, incl. `--from-clips`), `gen-image` (diegetic prop/screen/overlay stills; Flow image models via `gen-image-flow.ts`), `overlay` (graphic/alert/lower-third motion graphics with a drawtext preflight), `music-video` (vocal-synced beat-exact assembler, plan/dry by default), `title-card` (Pillow+RAQM title overlays), `stitch-ad`, `animation-styles` (shared `animation-styles.json` registry), `finish` (`src/video/finish.ts` — upscale a rendered cut to an HD/QHD master via hosted Topaz (spend-gated) or local Real-ESRGAN/Topaz CLI; the anti-plastic recipe is baked in: photoreal model, denoise/sharpen DISABLED, film grain kept, and Topaz `grain` clamps to 0.1 because the published schema overstates the range), and `lipsync` (OmniHuman, spend-gated). `assemble`'s stitch layer also supports opt-in **reading-holds** (`assemble/stitch.ts`, `readingHold`) — readable pre-roll holds on dense motion-comic segments. `handlers/media-ops.ts` is local-FFmpeg-only post-production on a finished cut — `make-vertical`/`make-square`/`make-loop`, `thumbnail`, `burn-subtitles`, `verify-final`, and `qc` (recursively scans `projects/<slug>/final/` + `outputs/` for clips and runs the assemble media-QC probe: missing-audio, nonstandard codec, duration drift; graceful `status: 'skipped'` when no clips) — real file I/O but NO provider calls and NO spend. Two adjacent quality/finishing tools: `vclaw video image-ops` (`src/video/image-ops.ts` + `native-magnific.ts`) is still-image post-processing, currently the Magnific precision-v2 image upscaler (pure planning core + injectable runner; `--dry-run` plans without spending; the IMAGE endpoint is live-verified, distinct from the flaky video upscaler), and `vclaw video consistency-audit` (`src/video/consistency-audit.ts`) is an automated character-consistency VISION audit across rendered scenes/keyframes — the engine-side forcing function that catches wardrobe/face drift on recurring characters BEFORE a render is presented as done.

### Gemini key pool

`src/video/gemini-key-pool.ts` provides round-robin selection with per-key cooldown across `GEMINI_API_KEYS`, `GOOGLE_API_KEYS`, `GOOGLE_API_KEY`. `analyze-template --auto` and `analyze --auto` use it via `src/video/gemini-analyze.ts`. `VCLAW_GEMINI_API_ENDPOINT` overrides the endpoint.

### Compatibility aliases (preserve on changes)

- `execution-plan` ↔ `plan`
- `execute` ↔ `produce`

(The old `omx` wrapper binary has been removed entirely — don't reintroduce it.)

## Conventions that are not obvious

- TypeScript is `strict` with **NodeNext** ESM. Relative imports in `src/` must include the emitted `.js` extension (e.g. `'../video/projects.js'`) — required by NodeNext ESM resolution. Don't "fix" these to drop the extension.
- `dist/` is generated — never edit it, never commit it. Edit `src/` and rebuild.
- Filenames: `kebab-case.ts`. Identifiers: `camelCase` for functions/variables, `PascalCase` for types.
- 2-space indent; modules stay small and single-purpose.
- CLI output is machine-readable JSON by default; do not add silent fallbacks across provider routes.
- Tests use `node:test` with `assert/strict`. Prefer `mkdtemp`/`tmpdir` for temp-directory isolation. Put CLI end-to-end tests under `src/tests/cli-*.test.ts` and module-contract tests under `src/tests/*.test.ts`.
- When adding a new CLI subcommand: add the handler in the appropriate `src/cli/handlers/*.ts` module (or a new one following the verbatim-move pattern) and register the dispatch-table entry (plus any alias) in `src/cli/vclaw.ts`; update the relevant `src/video/*` module(s), a schema under `schemas/video/` if it introduces or changes an artifact, register the command in the `COMMANDS` array of `src/video/cli-schema.ts` (bump the hardcoded command-count assertion in `cli-schema.test.ts` to match), add a `cli-*.test.ts`, and update `README.md` + `docs/CLI_REFERENCE.md`. The `check:cleanroom-docs` guardrail watches docs drift.
- Project slugs are validated by `isProjectSlug` (`src/video/projects.ts`); both `parseProjectSlug` and `handleVideoInit` (`validateInitSlug`) enforce it so flag-looking values (e.g. `--project`) cannot be silently accepted as slugs. Preserve this guard when adding new slug-accepting commands.
- Architecture diagrams under `docs/assets/*.jpg` are generated from Mermaid sources in `docs/DIAGRAMS_SOURCE.md`. Edit the Mermaid blocks there and regenerate the images via the Go Bananas Pro model — never hand-edit the JPGs.
- `check:skill-frontdoor` deliberately ignores `skills/seedance-prompts/SKILL.md` and the three presenter skills (`bunty`, `davendra-presenter`, `nex-presenter`) because their docs legitimately reference the legacy Python pipeline scripts. Don't "fix" the ignore list — it's load-bearing.
- Do not commit secrets, `.env.local`, provider cookies, or `.omx/` state (already gitignored).

## Autonomy directive (from AGENTS.md)

Proceed by default on obvious next steps. Keep work scoped to this repository and its generated `projects/<slug>/` folders. If a blocker is local and solvable, solve it; if it's external, note it and continue with the next meaningful lane rather than pausing for confirmation.

## Recommended reading order

`docs/ARCHITECTURE.md` → `docs/CLI_REFERENCE.md` → `docs/STUDIO.md` → `docs/ASSEMBLE.md` → `docs/PRODUCTION_WORKFLOW.md` → `docs/REVIEW_UI_STORYBOARD_WORKFLOW.md` → `docs/preview-portal-audit.md` → `docs/STORY_BIBLE.md` → `docs/DIRECTOR_BLUEPRINT.md` → `docs/MOTION_OVERLAY.md` → `docs/REFERENCE_SHEETS.md` → `docs/SCENE_CANDIDATES.md` → `docs/OPERATIONS.md` → `docs/GENERATION_TELEMETRY.md` → `docs/OBSIDIAN.md` → `docs/TEMPLATES.md` → `docs/MIGRATION.md` → `docs/DEPRECATION.md` → `docs/RELEASE_READINESS.md` → `docs/MASTER_PLAN_ALIGNMENT.md` → `docs/DIAGRAMS_SOURCE.md`.

Architecture decision records live in `docs/adr/` (no-silent-fallback across routes, on-disk project as source of truth, Seedance identity via Asset Library, director-mode approval gate) — consult them before relitigating those decisions.

## Agent skills

Per-repo configuration for the Matt Pocock engineering skills (`to-issues`, `to-prd`, `triage`, `diagnose`, `tdd`, `improve-codebase-architecture`, `zoom-out`, `qa`). Re-run `/setup-matt-pocock-skills` to change any of these.

### Issue tracker

Issues and PRDs live as GitHub issues on `davendra/videoclaw-v3` via the `gh` CLI. See `docs/agents/issue-tracker.md`.

### Triage labels

Canonical five-role vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`); labels created on first `triage` use. See `docs/agents/triage-labels.md`.

### Domain docs

Single-context: one `CONTEXT.md` glossary + `docs/adr/` at the repo root. See `docs/agents/domain.md`.

## graphify

This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.

Rules:
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
