storyboard-architect — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited storyboard-architect (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are turning a creative brief into a deterministic storyboard. The output is a set of files an editor, agency, or developer can act on without asking follow-up questions.
This is not a creative-writing exercise. The output is a spec.
Trigger this skill when the user:
If the user only wants prompts for an image generator (no narrative structure), use visual-prompt-forge directly instead.
For every storyboard run, create this exact set of files in the working output directory:
output/
├── run.json # Run identity + every input pinned by content hash
├── storyboard.md # Human-readable, structured per shot
├── shots.json # Machine-readable, schema in templates/shots.schema.json
├── text-overlays.json # On-screen text + timing
└── brand-lock.snapshot.md # Frozen copy of the brand-lock used (audit trail)run.json is what makes the rest of the tree auditable later. A filename says nothing about the bytes behind it, so the snapshot sitting next to a set of frames is not proof that it is the snapshot they were built from. The hashes in run.json are that proof. Write it once, as the last step, after the other four files are final. Everything in run.json except rounds is written once and never edited. rounds is append-only: visual-prompt-forge adds one entry per prompt round and never changes an earlier one.
If the user asks for image prompts or HTML preview, hand off to visual-prompt-forge or storyboard-html-preview, those skills consume shots.json directly. Don't try to do their job here.
After ./install.sh, the validators and helpers are in ~/.claude/shotkit-tools/ and the brand packs are in ~/.claude/shotkit-brand-packs/. A --project install puts both under ./.claude/ instead. From a repo clone they are tools/ and brand-packs/. The commands below give the installed path first and the clone path second. The tools need pip install pyyaml jsonschema.
You need these. If any are missing, ask before drafting.
| Input | Required? | Default if absent |
|---|---|---|
| Brief (problem, audience, goal) | Yes | Ask |
| Total duration | Yes | Ask |
| Aspect ratio | Yes | Ask (16:9, 9:16, 1:1) |
| Beat framework | No | Suggest based on brief |
| Brand-lock file path | No | Use ~/.claude/shotkit-brand-packs/_template.md (clone: brand-packs/_template.md) and flag the gap |
| Voiceover style (VO present, on-screen only, captions) | No | Ask if unclear |
| Target generator(s) for downstream prompts | No | Note as "to be specified" |
Audit findings, brand/audit-findings.json from whystrohm-audit | No | Skip Step 1b |
Follow this sequence. Don't skip steps even if the brief seems simple.
If a brand-lock file path is provided, read it first. Extract:
If no brand-lock is provided, copy ~/.claude/shotkit-brand-packs/_template.md (from a clone, brand-packs/_template.md) into the output as brand-lock.snapshot.md with a note: # UNCONFIGURED, using template defaults. Recommend providing a real brand-lock for production work.
If the user points at brand/audit-findings.json, or it sits in the project's brand/ folder and the brief is for the same company, read it. Its format is contracts/audit-findings.v1.schema.json in this repo. Check contract is audit-findings and version is "1" before using it.
top_gaps is a candidate for the pain or world beat: the problem the filmanswers.
rewrite.after is a candidate overlay line. Use it only as written. A bracketedplaceholder in it means the proof is missing, so leave that line out rather than filling it.
audited_at in the Brief section of storyboard.md.The brief still decides. Findings inform the beats; they never replace what the user asked for.
Read references/beat-frameworks.md. Pick the one that matches the brief. Common cases:
If none fit cleanly, build a custom beat structure but document why in storyboard.md rationale section.
Read references/timing-rules.md for the math. Default cadence:
Don't fight the framework. If the brief and the duration disagree, surface the disagreement before drafting.
Read references/shot-grammar.md for controlled vocabulary. The field names below are the schema's field names. templates/shots.schema.json sets additionalProperties: false, so a near-miss like environment instead of environment_ref is a validation failure, not a synonym.
id, sequential, zero-padded (shot_01, shot_02...)beat, which beat this shot servesstart / end, timestamps in seconds, decimal allowed. end must be after startframing, ECU / CU / MCU / MS / MLS / WS / EWSangle, eye-level / high / low / overhead / dutchmotion, static / push / pull / pan-left / pan-right / tilt-up / tilt-down /handheld / orbit / whip / rack. All eleven are legal; the schema enum is the authority and references/shot-grammar.md explains when each earns its keep
depth_of_field, optional, shallow / deep / racksubject, what's in frame, structuredenvironment_ref, references series-lock language, default series_lock.environmentlighting_ref, references series-lock language, default series_lock.lightingon_screen_text, null, one text-overlay id, OR an array of ids when a shot carriesmore than one overlay
vo, voiceover line, or nullrationale, one sentence explaining why this shot at this momentNote on rack: as a motion value it means the rack focus is the shot's movement; as a depth_of_field value it means focus shifts mid-shot. Same word, two fields, two meanings.
Every piece of on-screen text becomes an entry in text-overlays.json. Never bake text into the visual description. Each overlay has:
id, text_01, text_02...shot_id, which shot this overlays oncontent, the actual textfont, references brand-lock typographyposition, center, lower-third, upper-third, left-third, right-third, or {x, y} percentagessize, display, headline, body, captionweight, regular (400), medium (500), semibold (600), bold (700), black (900)color, hex (must come from brand-lock palette)enter, { at: seconds, animation: fade-in | slide-up | slide-down | type-on | hard-cut }exit, { at: seconds, animation: fade-out | slide-up | slide-down | hard-cut }Enter and exit have different animation vocabularies, and templates/text-overlays.schema.json is the authority on both. A shot may carry more than one overlay; list every id in that shot's on_screen_text array, or the extra overlays render nowhere.
Define environment, lighting, and character anchors that apply across every shot. These go at the top of shots.json under series_lock. Without these, image generation will produce incoherent frames.
Every shot has a one-sentence rationale. Why this beat. Why this framing. Why this on-screen text. This is the audit trail. Do not skip it.
Copy the brand-lock file (or template) into the output as brand-lock.snapshot.md. Add these two comments at the very top, in this order:
<!-- snapshot taken: 2026-05-07T14:23:00Z -->
<!-- source: brand-packs/whystrohm.md -->The timestamp is a full UTC instant, YYYY-MM-DDThh:mm:ssZ. A bare date cannot distinguish two runs made on the same day, which is the case that matters. The source is the path it was copied from, or the literal string template default for an unconfigured run. After an install, a pack path is ~/.claude/shotkit-brand-packs/whystrohm.md; from a repo clone it is brand-packs/whystrohm.md. Either passes the validator. Extra comments after these two are fine.
validate_brand_lock.py --snapshot <path> checks both lines. Run it:
python ~/.claude/shotkit-tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md
# from a repo clone:
# python tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.mdLast step, after the other four files are final. Fill in templates/run.schema.json. These fields are required:
| Field | Value |
|---|---|
version | "1.0", the run.json format |
run_id | compact UTC timestamp, a dash, 8 hex characters (below) |
created_at | the same instant as YYYY-MM-DDThh:mm:ssZ |
shotkit_version | line 1 of ~/.claude/shotkit-tools/VERSION; in a repo clone, VERSION at the root |
project | title, duration_s and aspect from shots.json, plus framework and seed if set |
inputs.shots_ref, inputs.shots_sha256 | shots.json and its SHA-256 |
inputs.text_overlays_ref, inputs.text_overlays_sha256 | text-overlays.json and its SHA-256, or both null with no on-screen text |
inputs.brand_lock_ref, inputs.brand_lock_sha256 | brand-lock.snapshot.md and its SHA-256 |
inputs.brand_lock_source | the pack it was copied from, or template default |
Optional: operator, inputs.brand_lock_configured, generators, rounds, meta.
head -n 1 ~/.claude/shotkit-tools/VERSION # from a repo clone: head -n 1 VERSION
shasum -a 256 output/shots.json output/text-overlays.json output/brand-lock.snapshot.mdrun_id is the compact UTC timestamp, a dash, then 8 hex characters, e.g. 20260730T142300Z-9f2c1ab4. The hex suffix is what keeps two operators starting a run in the same second from colliding. Set brand_lock_configured: false when the snapshot is an unfilled template.
Leave rounds empty. Everything in run.json except rounds is written once and never edited. rounds is append-only: visual-prompt-forge adds one entry per prompt round and never changes an earlier one.
storyboard.mdUse the template at templates/storyboard.md.tpl. Read it before writing.
shots.jsonMust validate against templates/shots.schema.json. Read it before writing. The structure is:
{
"version": "1.2",
"project": { "title": "...", "duration_s": 30, "aspect": "9:16", "framework": "..." },
"brand_lock_ref": "brand-lock.snapshot.md",
"series_lock": {
"character": "...",
"environment": "...",
"lighting": "...",
"color_grade": "..."
},
"shots": [
{
"id": "shot_01",
"beat": "hook",
"start": 0.0,
"end": 2.0,
"framing": "MCU",
"angle": "eye-level",
"motion": "static",
"depth_of_field": "shallow",
"subject": "...",
"environment_ref": "series_lock.environment",
"lighting_ref": "series_lock.lighting",
"on_screen_text": "text_01",
"vo": null,
"rationale": "..."
}
]
}Write 1.2 for new storyboards. 1.0 and 1.1 files stay valid; the array form of on_screen_text and the hashed assets block need 1.2.
text-overlays.jsonMust validate against templates/text-overlays.schema.json. Read it before writing.
Run the validator. Do not eyeball this list.
python ~/.claude/shotkit-tools/validate_shots.py output/
python ~/.claude/shotkit-tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md
python ~/.claude/shotkit-tools/validate_provenance.py output/
# from a repo clone:
# python tools/validate_shots.py output/
# python tools/validate_brand_lock.py --snapshot output/brand-lock.snapshot.md
# python tools/validate_provenance.py output/validate_shots.py checks every mechanical rule, because a checkbox is a rule enforced by remembering to look:
end is after start, no duplicate ids, no gaps, no overlaps, and the covered spanmatches project.duration_s within 0.1s
on_screen_text resolves to an overlay, every overlay.shot_id resolves to ashot, and every overlay is reachable from at least one shot
brand_lock_ref resolves on diskIt warns, rather than fails, on judgement calls worth a second look: overlay copy repeated inside a shot subject, a raw hex in a subject, shot ids out of chronological order, an overlay font the brand-lock does not declare.
What the validator cannot check, and you still have to:
series_lock anchors are specific enough to reproduce (not "a person in a room")run.json is written and its hashes are the files as shippedIf the validator fails, fix it before declaring done. A green validator plus an unread rationale is not a finished storyboard.
Load these as needed:
references/beat-frameworks.md, the beat structuresreferences/shot-grammar.md, controlled vocabulary for framing/angle/motionreferences/timing-rules.md, pacing mathreferences/on-screen-text.md, when on-screen text earns its keepexamples/30s-pain-proof-promise/, full output set for a 30-second conversion adexamples/60s-founder-explainer/, full output set for a founder explainerRead these to understand the expected output quality, especially the rationale fields. Both validate clean under validate_shots.py --examples, so they are also the reference for what a passing file looks like.
For what the output tree looks like after generation and review, see ../visual-asset-critic/examples/worked-run/: two shots through two rounds, with real hashes, per-round prompts and frames, and one critique per shot per round.
After producing the five files, tell the user what's in output/ and offer the obvious next steps:
visual-prompt-forge on shots.json."storyboard-html-preview."visual-asset-critic."Don't run those on your own. The user picks.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.