Skills · Workflow
templater
/templaterThe full extract → classify → blueprint → de-dup → publish pipeline for a finished `.ralphy/workspaces/<ws>/projects/<id>/`.
SKILL.mdtemplater
You decompose a finished project into the content-entity model (#063) and classify its pieces into reusable blocks. The contract is: a future agent should be able to reproduce the work — same units, same blocks (Template / Style / Recipe / Asset) — without re-deriving any of it from the raw artifacts/ dump.
Where this sits in the Unit lifecycle. templater runs AFTER Unit formation (phase 17) in the canonical Unit lifecycle — it reads each
units/<slug>/unit.json. Only extract/publish a project whose Unit is polished: the render must have passed the native-video final gate (ralphy project status <id> --contract→polished === true, #411). A keyframe-only eval does not qualify.
DO NOT FIRE
- Scaffolding a new project — that is
ralphy template use <existing-slug>, not this skill. - One-off renders — that is the producer playbook.
- Quality evaluation — that is
/evaluator. - Pushing to the live library — that is the #056 publish path (
landing/scripts/publish-entity.ts); templater stops at printing the ordered commands. See HARD INVARIANTS below.
This skill is the full maximal-detail pipeline: one invocation, followed end-to-end, reproduces what a careful maintainer does by hand. The six stages are EXTRACT (read the finished deliverables) → CLASSIFY (factor into the five entities, incl. the recipe-vs-tag split) → BLUEPRINT (capture a per-unit reproduction payload) → DE-DUP (match every candidate against the live library first) → EMIT the entity bundle → PRINT the publish runbook (the exact ordered publish-entity.ts commands).
templater does the extraction, classification, blueprint capture, and de-dup itself. It does not invoke the live push (--push) — that is the user's deliberate step. It prints the full, ordered command set (dry-run first, then --push) and hands control back. Publishing to the live library (Supabase DB + Storage + the committed published.ts) is the #056 primitive — see Publish runbook.
The five entities (read this first)
The library model (landing/lib/library-v2/types.ts) has five entities. Every project decomposes into them:
- Unit — a finished deliverable in a Format (
video,carousel,sticker-pack,podcast-cuts,fb-creative,motion-design,poster,image), holding 1..N ordered media items. A Unit = exactly 1 Template + 1 Style + N Recipes + M Assets. That ingredient list is the Unit's provenance. - Template (block, single-per-unit) — the STRUCTURE / skeleton only, style-agnostic. The beat structure, the slide count + slot layout, the composition skeleton.
- Style (block, single-per-unit) — the visual look / register. The aesthetic + its anchor reference images.
- Recipe (block, multi-per-unit) — a composable effect / treatment. A VFX layer, an encode recipe, an overlay pass, a caption style.
- Asset (block, multi-per-unit, has a
sub:character/location/prop/music) — concrete reusable media: a locked character master, a location plate, a prop, a music bed.
The Unit source of truth is .ralphy/workspaces/<ws>/projects/<id>/units/*/unit.json (formed by ralphy unit create, #069). Each unit.json already carries format, ordered media, and a provenance block (template / style / recipes[] / assets[] slugs) — so much of the decomposition is reading, not inventing. The postmortem/06-units.md record (if the project has one) is the second-highest-signal input: it already marks each provenance block NEW vs. REUSED.
What this skill is NOT
- Not a project scaffolder. That is
ralphy template use <slug> --project <new-id>. - Not a postmortem generator. That is
/postmortem. Run it first if the source has none —postmortem/02-lessons.md(lessons) andpostmortem/06-units.md(units + provenance) are the highest-signal inputs here. - Not the library publisher. It does NOT push to Supabase, does NOT edit
landing/lib/library-v2/published.ts, does NOT runseed-supabase.ts. The publish step is the #056 primitivelanding/scripts/publish-entity.ts(--unit/--blockmodes). templater produces the classified bundle; publish pushes it. - Not a single-template extractor. The old
create-from-project"one template per project" framing is retired. A project produces a SET of entities (often several units + several blocks), not one template.
Source-of-truth files in the source project
Every extraction is keyed to a known file. If a file is missing, the skill degrades gracefully (it derives what it can, never invents). scenario.json is PREFERRED, not required — scenario-less still / HyperFrames projects (sticker packs, FB packs, poster sets) are valid; derive structure from asset-manifest.json + units/ + index.html and skip the scene table (the #062 fix).
Source file in .ralphy/workspaces/<ws>/projects/<id>/ | Required? | What gets pulled | Maps to entity |
|---|---|---|---|
units/*/unit.json | REQUIRED | Finished deliverables + ordered media + provenance block slugs (#069) | Unit (+ seeds all four block axes via provenance) |
asset-manifest.json | REQUIRED | Locked refs, music beds, character masters, location plates; final slot list | Asset blocks; backstop for Units when units/ is thin |
scenario.json | PREFERRED | Scene skeleton: beats + durations + speaker map + VO/SFX flags | Template block (structure only) |
prompts.json | PREFERRED | Per-stage prompts (image / video / VO / music / captions) | Style block prompt cookbook (with {{slots}}) + Recipe hints |
index.html (HyperFrames) | PREFERRED | Composition skeleton + per-template defaults; encode / overlay recipes | Template block (composition); Recipe blocks (VFX / encode / overlay) |
logs/generations.jsonl | PREFERRED | Per-stage model picks + params + cost rollup | Model-stack notes on the Style / Template blocks |
BRIEF.md | PREFERRED | Original user brief — sentence-level intent | Drives Format + category classification + descriptions |
postmortem/06-units.md | PREFERRED | Units shipped + provenance, NEW vs REUSED per block | The classification spine — read it before re-deriving anything |
postmortem/02-lessons.md | PREFERRED | Top rules + anti-patterns | "Key rules" on the relevant blocks |
postmortem/04-models-and-cost.md | PREFERRED | Spend rollup per stage | Cost ballpark on the blocks |
If units/ is genuinely empty AND asset-manifest.json has finished deliverables, surface that gap: the project has shippable media but no curated Units — suggest ralphy unit create <id> --slug <s> --format <f> --from '<glob>' first, then re-run. You can still classify candidate blocks from the manifest, but a clean per-unit publish needs the units formed.
See references/extraction-rules.md for the per-file extraction details and edge cases.
The workflow
-
Resolve the project + read its Units.
ralphy unit list <project-id>and read eachunits/<slug>/unit.json. These are the Units. Readpostmortem/06-units.mdif present — it pre-classifies the provenance blocks (NEW vs REUSED). NEVER fabricate units from the rawartifacts/dump. -
Read
BRIEF.md+ (if present)scenario.jsonheaders for the elevator pitch. This grounds the classification passes. -
Decompose into the five entities. For each Unit, resolve its provenance into concrete block candidates:
- Template — from
scenario.json(beats) or, for scenario-less projects, fromindex.html(composition skeleton) / the slide-slot layout. Structure only, style-agnostic. - Style — from
prompts.json+ the locked anchor refs. The look + its reference images. - Recipe candidates — VFX layers, encode recipes (
-tune grain, CRF), overlay passes, caption styles, baked transitions, prompt techniques. Fromindex.html+prompts.json+ bake scripts + postmortem lessons. Each candidate is then split recipe-vs-tag in step 4. - Assets — locked character masters, location plates, props, music beds from
asset-manifest.json. Each carries asub(character/location/prop/music). Multi-value.
- Template — from
-
Recipe-vs-tag split (the #082/#083 discipline — read
references/recipe-vs-tag.md). For EACH recipe candidate from step 3, decide:- Recipe (stays a block, earns a detail page) ONLY if you can author a real, copyable artifact — an ffmpeg filtergraph, an encode/bake command, a HyperFrames snippet, or a concrete prompt-style technique — sourced from the project's own files, never invented:
cli/lib/ffmpeg-recipes.ts(the canonical builders) for ffmpeg/encode/overlay, the project'sscripts/*.shbake scripts + the capturedblueprint.jsonrecipes[].commandfor bakes,index.htmlfor HyperFrames overlays, the gen-log /prompts/**for prompt recipes. When kept, author the FULL enriched payload:recipeKind(ffmpeg/encode/overlay/bake/hyperframes/prompt) +body(markdown how-to) +artifact(the real code) +params(named knobs) +demo(a self-contained runnabledemo.htmlfor HyperFrames recipes; before/after media for ffmpeg recipes when real frames exist — never fabricated). - Tag (NOT a block, no detail page) if it is a pure textual descriptor with NO extractable artifact (e.g. "rain overlay" / "soft bloom" applied only as a vibe). It becomes a
tags[]entry on the Unit(s) that used it, carried inunit.json. - The failure mode to kill: never publish an EMPTY recipe block (refs:0, no
body, noartifact). That empty chip is the "tag cloud" anti-pattern. If you cannot author a real artifact, it is a tag.
- Recipe (stays a block, earns a detail page) ONLY if you can author a real, copyable artifact — an ffmpeg filtergraph, an encode/bake command, a HyperFrames snippet, or a concrete prompt-style technique — sourced from the project's own files, never invented:
-
Match existing blocks FIRST (de-dup before you author). Before proposing ANY new block — and before authoring a kept recipe's
body/artifact— check whether an existing library block already covers it. ReadPUBLISHED_BLOCKSinlanding/lib/library-v2/published.tsdirectly, and useralphy template list/ralphy template suggestfor template/style candidates andralphy assets list --kind <kind>for asset candidates. Ifpostmortem/06-units.mdalready marked a block REUSED, trust that. Only propose a NEW block for a genuine gap. Worked cautionary example:choose-path-xfade-masterwas published as a NEW recipe when the canonicalffmpeg-xfade-masteralready carried that exact artifact — a duplicate that #081/#083 had to delete and repoint. Reuse + cite the existing slug; never publish a second copy. Over-creating duplicate blocks is the failure mode this step prevents. -
Classify slots + tags + descriptions (LLM, via
callLLM()). Throughcli/lib/providers/llm.ts → callLLM():- Slots — extract
{{slots}}(brand / product / character names / location keys / target language) fromprompts.jsonperreferences/slot-detection.md, so the Style block's prompt cookbook is reusable across subjects. - Format — confirm each Unit's
format(it is inunit.json; validate it against the eight library formats). - Unit tags — the descriptors demoted from step 4 land in each Unit's
unit.jsontags[](filter-only labels for the feed'sTAGSfacet). - Category + description — per
references/category-classifier.md, for any localtemplates/<category>/<slug>/artifact. - English-only on disk. ANY captured prose — storyboards, prompts, VO lines, block bodies, tags, descriptions — must land in English. Translate folklore / foreign terms before they touch a file (the swamp lesson: the Russian word nechist → "the unclean"). The user can chat in any language; the on-disk artifact is English. This is
docs/developing-ralphy.md's hard rule — the publish gaterg '\p{Cyrillic}'must come back empty.
- Slots — extract
-
Capture each Unit's Blueprint (#080 — local + free). For EACH Unit, run
ralphy blueprint create <project-id> --unit <slug>. This is the per-unit reproduction recipe: it reads the (gitignored) project files and writes a self-containedunits/<slug>/blueprint/payload (blueprint.json+ copiedindex.html/ prompt files / hard-asset files), validated againstBlueprintSchema. It is extraction only — reads local files, makes no network call, costs nothing, and is append-only (a re-run on a slug that already has ablueprint/writesblueprint.v2/, never overwrites). Record the result in the bundle asunits[].blueprint(statusNEWif this run created the firstblueprint/,REUSEDif a prior capture already existed, with the payload pathunits/<slug>/blueprint/). Seereferences/extraction-rules.md→ "Blueprint capture (per Unit)". -
Emit the entity bundle. The primary output is a classified bundle: the Units (with resolved provenance, demoted
tags[], AND each Unit's captured Blueprint), plus the NEW blocks worth keeping — each recipe carrying its fullrecipeKind/body/artifact/params/demo. Mark every block NEW vs REUSED. This bundle is what the publish primitive consumes. Print it as JSON (see Output). -
(Optional) Write a local
templates/<category>/<slug>/artifact. When the user wants a downloadable repo template (thevibe-reference/vibe-styleform), write it undertemplates/<category>/<slug>/soralphy template list / show / suggest / usepick it up. This is ONE optional output, not the skill's reason for being — the entity publish is the #056 primitive. Schema is the onecli/commands/template.tsconsumes. Never modify the source project. -
Print the publish runbook (#056), do NOT push. Print the exact ordered
publish-entity.tscommands the user runs — dry-run first, then--push— for the NEW blocks (--block-file, recipes carrying the enriched fields), the Units (--unit, carryingunit.json.tags), and each Unit's Blueprint (--blueprint). See Publish runbook for the full ordered set + the DB-migration prerequisite. Then stop. The user (ordev-publish-template) drives the actual push.
Output
JSON, pipe-friendly:
{
"project": "free-air-vpn-stickerpack",
"units": [
{
"slug": "stickers-outline",
"format": "sticker-pack",
"media_count": 32,
"provenance": {
"template": { "slug": "sticker-set", "status": "REUSED" },
"style": { "slug": "free-air-jelly-pure", "status": "NEW" },
"recipes": [{ "slug": "floodfill-diecut-cutout", "status": "NEW" }],
"assets": [{ "slug": "free-air-mascot", "sub": "character", "status": "NEW" }]
},
"tags": ["white die-cut outline", "jelly mascot"],
"blueprint": {
"status": "NEW",
"path": ".ralphy/workspaces/<ws>/projects/free-air-vpn-stickerpack/units/stickers-outline/blueprint",
"publish_cmd": "cd landing && bun run scripts/publish-entity.ts --blueprint .ralphy/workspaces/<ws>/projects/free-air-vpn-stickerpack/units/stickers-outline/blueprint"
},
"publish_cmd": "cd landing && bun run scripts/publish-entity.ts --unit .ralphy/workspaces/<ws>/projects/free-air-vpn-stickerpack/units/stickers-outline"
}
],
"new_blocks": [
{ "kind": "style", "slug": "free-air-jelly-pure", "blurb": "...", "publish_cmd": "cd landing && bun run scripts/publish-entity.ts --block-file <spec.json>" },
{
"kind": "recipe",
"slug": "floodfill-diecut-cutout",
"blurb": "...",
"recipeKind": "ffmpeg",
"body": "## What it is ...",
"artifact": "colorkey=0xffffff:0.1:0.0,despill=...",
"params": { "key": "0xffffff", "similarity": 0.1 },
"demo": { "kind": "media", "beforeUrl": "...", "afterUrl": "..." }
},
{ "kind": "asset", "slug": "free-air-mascot", "sub": "character", "blurb": "..." }
],
"reused_blocks": [{ "kind": "template", "slug": "sticker-set" }],
"demoted_tags": ["white die-cut outline", "jelly mascot"],
"local_template_artifact": null,
"scenario_present": false,
"warnings": []
}
Publish runbook (#056)
templater extracts + classifies + blueprints + de-dups; the publish to library is the #056 primitive, landing/scripts/publish-entity.ts. It has THREE independent, first-class modes — a Unit, a standalone Block, and a Blueprint each publish on their own. Print the commands below in this order (blocks → units → blueprints), each as a dry-run line then the --push line. templater never runs --push — it prints the runbook and stops; the user (or dev-publish-template) runs it.
0. DB prerequisite (once, on a fresh store)
Before ANY --push, the Supabase schema must carry the blueprints table and the additive columns blocks.recipe_kind, blocks.data, units.tags. They live in supabase/migrations/0001_init_library_v2.sql. On a fresh store, apply that migration first (psql "$SUPABASE_DB_URL" -f supabase/migrations/0001_init_library_v2.sql, or ralphy's seed path). Dry-runs touch nothing remote, so they are always safe to print/run first.
1. NEW blocks first (so unit provenance + tag facets resolve)
Each NEW block is a --block-file <spec.json>. A block spec is { kind, id, name, blurb, sub?, refs?[] }; a recipe spec ALSO carries the enriched payload { recipeKind, body, artifact, params, demo } (#082) — which pack into the blocks.recipe_kind column + the blocks.data jsonb. The recipe's demo (demo.html for HyperFrames, before/after media for ffmpeg) + any refs ride along to Storage so the library page is interactive (live recipe demo, audio player for a music asset). Tags are NOT blocks — they carry no --block-file line; they ride on the Unit.
cd landing && bun run scripts/publish-entity.ts --block-file <block-spec.json> # dry-run (per NEW block)
cd landing && bun run scripts/publish-entity.ts --block-file <block-spec.json> --push # push
2. Units next (carrying unit.json.tags + provenance links)
cd landing && bun run scripts/publish-entity.ts --unit .ralphy/workspaces/<ws>/projects/<id>/units/<slug> # dry-run (per Unit)
cd landing && bun run scripts/publish-entity.ts --unit .ralphy/workspaces/<ws>/projects/<id>/units/<slug> --push # push
The unit's media uploads to Storage; the units row + unit_blocks provenance rows upsert; the tags[] from unit.json land in the units.tags column → the feed's TAGS filter facet. A provenance block id absent from Supabase is WARN-and-skipped (never fabricated) — which is why blocks publish FIRST.
3. Blueprints last (the per-unit reproduction payload, one per Unit)
cd landing && bun run scripts/publish-entity.ts --blueprint .ralphy/workspaces/<ws>/projects/<id>/units/<slug>/blueprint # dry-run (per Unit)
cd landing && bun run scripts/publish-entity.ts --blueprint .ralphy/workspaces/<ws>/projects/<id>/units/<slug>/blueprint --push # push
The --blueprint dir is the units/<slug>/blueprint/ payload step 7 captured (blueprint.json + copied index.html / prompts / hard assets). It uploads the payload to Storage under blueprints/<unitId>/, upserts the 1:1 blueprints row, and appends to PUBLISHED_BLUEPRINTS.
Every mode writes to Supabase (DB + Storage) AND appends to the committed open-source landing/lib/library-v2/published.ts (idempotent by id / unitId, append-only). Default run is DRY-RUN. The maintainer one-shot that runs the whole runbook for you is dev-publish-template (#056).
Edge cases & refusals
- No
units/AND no finished deliverables inasset-manifest.json→ the project isn't done. Refuse, point at what's missing (run /producer or finish the pipeline). - No
units/but finished media exists → DO NOT refuse. Surface the gap, suggestralphy unit create, classify candidate blocks from the manifest anyway. (This is the #062 fix in spirit — never hard-block scenario-less / unit-thin projects.) - No
scenario.json→ DO NOT refuse (the #062 fix). Derive the Template block's structure fromindex.html/ slide layout, skip the scene table. - No
postmortem/→ proceed but warn. Block classification leans harder onprompts.json+asset-manifest.json; offer to/postmortem(now a 7-file set incl.06-units.md) first for a cleaner NEW/REUSED split. - A block looks like an existing one → match it, mark REUSED, do NOT create a duplicate. When genuinely unsure, prefer REUSED and flag the uncertainty in
warnings. (Thechoose-path-xfade-master==ffmpeg-xfade-masterdup is the canonical cautionary case — seereferences/recipe-vs-tag.md.) - A recipe candidate has no extractable artifact → it is a TAG, not a block. Demote it to the Unit's
tags[]; never publish an emptyrefs:0recipe block. Seereferences/recipe-vs-tag.md. - Captured prose contains non-English (folklore, foreign brand copy, RU/other-script VO) → translate before writing it to any file. English-only on disk; the
rg '\p{Cyrillic}'gate must come back empty. - Slug collision in
templates/(only if writing the optional local artifact) → refuse unless--force, show the diff first.
Why this skill exists
A finished project's postmortem captures the expensive lessons; units/*/unit.json captures the finished deliverables and their provenance. Without templater, those sit unindexed — the next agent re-derives the structure, the look, the effects from scratch. templater is the compression + classification layer: it factors the project into the five reusable entities and matches them against the library so the next project starts at the library's wisdom level. The publish primitive (#056) then makes those entities discoverable. This skill is the bridge between "we shipped deliverables" and "we shipped reusable blocks."
References
references/recipe-vs-tag.md— the #082/#083 split: the decision rule, the enriched-recipe payload to author, where to source artifacts, the de-dup cautionary example.references/extraction-rules.md— per-source-file extraction details + edge cases.references/slot-detection.md— LLM prompt + heuristics for{{slots}}in prompts.json.references/kind-decision.md— vibe-reference vs vibe-style decision tree (for the optional local artifact).references/category-classifier.md— the five segment-persona categories + LLM classification prompt.references/pool-migration.md— heavy-asset migration toralphy-assets/pool/(for Asset blocks / the local artifact).cli/lib/schemas/unit.ts— theunit.jsonZod schema (the Unit source of truth, #069).cli/commands/blueprint.ts— theralphy blueprint create|show|list|usesurface (#076/#079); templater runsblueprint createper Unit in step 7.cli/lib/schemas/blueprint.ts— theBlueprintSchemaZod shape (#074): the six reproduction axes a captured Blueprint carries.cli/lib/ffmpeg-recipes.ts— the canonical ffmpeg/encode/overlay builders (buildVhsFilter,buildColorGradeFilter,buildSidechainFilter,buildMixMusicFilter, CRF helpers); the source-of-truth for a kept recipe'sartifact.landing/lib/library-v2/types.ts— the five-entity shapes (Format / Unit / Block kinds), the enriched-Recipe fields (#082:recipeKind/body/artifact/params/demo), andUnit.tags.supabase/migrations/0001_init_library_v2.sql— the schema (theblueprintstable +blocks.recipe_kind/blocks.data/units.tagscolumns) that must exist before any--push.landing/scripts/publish-entity.ts— the publish primitive (#056); templater hands off to it.docs/skills-vs-templates.md— templater = extract/classify; #056 = the Supabase→library writer..agents/skills/dev-publish-template/SKILL.md— the maintainer one-shot that runs the publish for you.
