Project Layout
This doc describes the canonical on-disk structure of a videoclaw project — the unit vclaw video init <slug> creates and every downstream stage operates on.
Slug rules
Enforced at vclaw video init time:
| Rule | Constraint |
|---|---|
| Allowed chars | [a-z0-9-] |
| Must start with | [a-z0-9] (not -) |
| Must end with | [a-z0-9] (not -) |
| Length | 3–64 chars |
| Disallowed substrings | --, leading ., reserved names (history, artifacts, checkpoints, events, state, outputs, assets, obsidian, characters, notes, tmp) |
| Argv guard | Reject anything that looks like a flag (^-). Prevents the historical bug where vclaw video init --project foo parsed --project as the slug. |
Recommended slug template when the user doesn't provide one: <yyyy-mm-dd>-<noun>-<noun> (e.g., 2026-05-25-disco-monster).
Canonical layout
projects/<slug>/rootthe unit every stage operates onproject.jsoncommitmanifest — slug, mode, stateartifacts/canonical JSON (schema-enforced)history/append-only snapshots on overwrite
checkpoints/one file per stage — approval statecharacters/canonical identity storeevents/events.jsonlappend-only timelinenotes/free-form human / model markdownoutputs/gitignoredfinal + per-scene mediaassets/gitignoredintermediate visual assetsobsidian/gitignoredObsidian mirror of artifacts*.htmlderived preview / review / client portalsstate/derived state cache
projects/<slug>/
│
├── project.json # MANIFEST (see "project.json shape" below)
├── storyboard.md # Director-mode human-readable approval doc
│ (absent in storyboard-mode until storyboard
│ stage)
│
├── artifacts/ # CANONICAL machine-readable outputs.
│ │ JSON only. Every file MUST have a schema
│ │ in schemas/video/artifacts/.
│ ├── brief.json
│ ├── storyboard.json
│ ├── story-bible.json # Deterministic continuity bible (cast,
│ │ settings, props, scene timeline, continuity
│ │ notes) auto-emitted at storyboard-write time.
│ ├── asset-manifest.json
│ ├── scene-candidates.json
│ ├── scene-selection.json
│ ├── reference-sheets.json
│ ├── execution-plan.json
│ ├── execution-report.json
│ ├── review-report.json
│ ├── publish-report.json
│ ├── analyze-output.json
│ ├── clone-plan.json
│ ├── multi-shot-prompt.json # Written only when `vclaw video multi-shot
│ │ --project <slug>` is used; absent for
│ │ standalone (no --project) invocations.
│ ├── filmmaking-prompts.json # Character sheet, 9-panel storyboard grid,
│ │ reference map, and Seedance prompt packets.
│ └── history/ # Snapshots of artifacts/ files on overwrite.
│ Append-only. One subdir per artifact:
│ history/brief/<ts>.json.
│
├── checkpoints/ # ONE FILE per stage. Tracks approval state,
│ ├── brief.json who approved when, retry count, verdict.
│ ├── storyboard.json
│ ├── assets.json
│ ├── review.json
│ └── publish.json
│
├── characters/ # CANONICAL identity store for this project.
│ └── characters.json
│
├── events/ # Append-only timeline. JSONL.
│ └── events.jsonl Payloads use project-relative paths only.
│
├── notes/ # Human-authored or model-authored MD.
│ │ Free-form. Anything NOT a canonical
│ │ JSON artifact lives here.
│ └── (markdown files)
│
├── outputs/ # DERIVED. Final encoded media only.
│ ├── final/<slug>-<mode>.mp4 Created by publish stage.
│ ├── scene-0.mp4 Per-scene renders.
│ ├── scene-1.mp4
│ └── ... (Gitignored at project level.)
│
├── preview.html # DERIVED. Final portal showcase.
├── edit.html # DERIVED. Editor edit/check portal.
├── review.html # DERIVED. Editor review portal.
├── client-review.html # DERIVED. Client review portal.
├── compare.html # DERIVED. Version/run comparison portal.
├── project-audit.jsonl # Append-only preview portal generation/publish audit.
│
├── assets/ # DERIVED. Intermediate visual assets.
│ ├── storyboard-grid.png Deterministic 3x3 production board from
│ │ `vclaw video storyboard-grid`.
│ ├── storyboard/ Per-scene stills.
│ ├── upscaled/ Upscaled variants.
│ └── ... (Gitignored.)
│
├── obsidian/ # DERIVED. Mirror of canonical artifacts
│ in Obsidian-friendly MD. Created by
│ `vclaw video obsidian-export`.
│ (Gitignored.)
│
└── state/ # Derived state cache. Created on init by
ensureProjectWorkspace.Directory disposition
| Directory | Always-present on init | Schema-enforced | Gitignored at project level |
|---|---|---|---|
project.json | yes | yes | no (commit) |
storyboard.md | director-mode only | n/a (free-form) | no |
artifacts/ | yes (empty) | yes — every file | no |
artifacts/history/ | yes (mkdir at init) | yes | no |
checkpoints/ | yes (empty) | yes (per-stage shape) | no |
characters/ | yes (empty) | yes (characters.json shape) | no |
events/ | yes (empty) | line shape enforced | no |
state/ | yes (empty) | internal cache | no |
notes/ | on demand | no (MD) | no |
outputs/ | on demand | n/a (media) | yes |
preview.html / edit.html / review.html / client-review.html / compare.html | on portal generation | n/a (static HTML) | no |
project-audit.jsonl | on portal generation/publish | JSONL event shape | no |
assets/ | on demand | n/a (media) | yes |
obsidian/ | opt-in command | n/a (MD mirror) | yes |
Git tracking
vclaw video init does not write a per-project .gitignore — handleVideoInit only writes project.json, the pending brief checkpoint, and the project.initialized event. Any gitignore handling is at the repo root, not per-project.
The derived directories — outputs/, assets/, obsidian/ — are re-generable from canonical artifacts and are the candidates to ignore if you wire up repo-root gitignore rules. artifacts/, checkpoints/, characters/, events/, notes/, project.json, and storyboard.md are canonical by design so a project is reproducible from its on-disk state.
project.json shape
The manifest is VideoProjectManifest (src/video/workspace.ts). The pipeline manifest is embedded inline — there is no versioned reference and no schemaVersion. On init, handleVideoInit writes:
{
"slug": "fresh-proof",
"productionMode": "director",
"createdAt": "2026-05-06T02:20:19.421Z",
"updatedAt": "2026-05-06T02:20:19.421Z",
"pipeline": { "name": "director", "stages": ["..."] },
"currentStage": "brief",
"lastCompletedStage": null,
"lastCheckpointStatus": "pending"
}Required fields:
slug— the project slug.productionMode—"storyboard"or"director".createdAt/updatedAt— ISO timestamps.pipeline— the full embeddedVideoPipelineManifestobject (not apipelineRefstring). In practice onlypipeline.nameis read back by downstream code.currentStage/lastCompletedStage/lastCheckpointStatus— stage-machine cursor. On init these are"brief",null, and"pending".
Optional fields, written only by updateProjectManifestMetadata (owner, priority, dueDate, tags, blockedBy, blockedReason) and updateProjectManifestCinemaProfile (cinemaProfile). There is no schemaVersion, no pipelineRef, no createdBy, and no nested metadata object.
Event log shape
events.jsonl lines follow the VideoProjectEvent envelope (src/video/events.ts): { type, recordedAt, payload? }. There is no id/ULID and no source field.
{"type":"artifact.review-report.written","recordedAt":"2026-05-06T21:54:52.404Z","payload":{"artifactPath":"artifacts/review-report.json","verdict":"pass"}}Readers split on newlines and JSON.parse each non-empty line; appendProjectEvent fills recordedAt if absent. Event-payload paths are project-relative by convention.
Artifact schema coverage guardrail
The build pipeline includes check:artifact-schema-coverage (scripts/check-artifact-schema-coverage.mjs), which asserts:
- Every artifact name passed to
writeArtifact(workspace, '<name>', ...)insrc/video/**/*.tshas a matchingschemas/video/artifacts/<name>.schema.json. - Every schema in
schemas/video/artifacts/has either a matchingwriteArtifact()call OR is in the script'sKNOWN_ALTERNATE_WRITERSallowlist (audit-pending; some artifacts are written via specialized helpers rather than the typedwriteArtifact()shim).
Modes:
- Default (advisory) — prints any drift but always exits 0. This is what
check:release-readiness-liteinvokes, so the guardrail reports status inline without blocking the release-readiness pre-flight while the allowlist is being burned down. --strict— exits 1 on drift. Wire into CI gates / pre-commit hooks when you want hard enforcement. Currently zero unexpected drift with the allowlist applied.
Current allowlist (9 schemas needing per-artifact audit): analyze-output, clone-plan, execution-plan, multi-shot-prompt, publish-report, reference-sheets, review-report, scene-candidates, scene-selection. The audit work is tracked in MERGE_PLAN.md §A2.
Slug validation implementation
The slug rules above are enforced by validateInitSlug() in src/cli/vclaw.ts, called from the init command before any filesystem operation. Test coverage in src/tests/cli-init-slug-validation.test.ts (6 cases):
- rejects
--projectas the slug (the historical argv-as-slug bug) - rejects uppercase / whitespace / dots / underscores / leading dots / leading dashes
- rejects reserved per-project directory names
- rejects consecutive
-- - rejects too-short / too-long
- accepts a well-formed slug (
2026-05-25-disco-monster)
Each failure mode has a distinct error message; the argv-as-slug case explicitly names the bug to make the fix discoverable.
