templater
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.
Two exits: library entities vs workspace bundle
Reusable know-how leaves the repo through TWO distinct doors. Pick the door before running anything:
- Project → library entities (THIS skill). The user wants the work browsable and reproducible per unit — "publish this to the library", "others should reproduce this style", "extract the blocks". Scope: ONE finished project, factored into Unit + Template/Style/Recipe/Asset blocks. The publishing implementation lives in the sibling
ralphy-web repository.
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 in
../ralphy-web/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 is owned by the sibling ralphy-web repository — see Publish runbook.
The five entities (read this first)
The library model (../ralphy-web/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) and postmortem/06-units.md (units + provenance) are the highest-signal inputs here.
- Not the library publisher. The publish step is the #056 primitive in
../ralphy-web/scripts/publish-entity.ts. templater produces the classified bundle; the web repository publishes 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 each units/<slug>/unit.json. These are the Units. Read postmortem/06-units.md if present — it pre-classifies the provenance blocks (NEW vs REUSED). NEVER fabricate units from the raw artifacts/ dump.
Read BRIEF.md + (if present) scenario.json headers 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, from index.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. From index.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 a sub (character/location/prop/music). Multi-value.
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's scripts/*.sh bake scripts + the captured blueprint.json recipes[].command for bakes, index.html for 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 runnable demo.html for 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 in unit.json.
- The failure mode to kill: never publish an EMPTY recipe block (refs:0, no
body, no artifact). That empty chip is the "tag cloud" anti-pattern. If you cannot author a real artifact, it is a tag.
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. Read ../ralphy-web/lib/library-v2/library.json, and use ralphy template list / ralphy template suggest for template/style candidates and ralphy assets list --kind <kind> for asset candidates. If postmortem/06-units.md already marked a block REUSED, trust that. Only propose a NEW block for a genuine gap. Worked cautionary example: choose-path-xfade-master was published as a NEW recipe when the canonical ffmpeg-xfade-master already 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()). Through cli/lib/providers/llm.ts → callLLM():
- Slots — extract
{{slots}} (brand / product / character names / location keys / target language) from prompts.json per references/slot-detection.md, so the Style block's prompt cookbook is reusable across subjects.
- Format — confirm each Unit's
format (it is in unit.json; validate it against the eight library formats).
- Unit tags — the descriptors demoted from step 4 land in each Unit's
unit.json tags[] (filter-only labels for the feed's TAGS facet).
- Category + description — per
references/category-classifier.md, for any local templates/<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 gate rg '\p{Cyrillic}' must come back empty.
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-contained units/<slug>/blueprint/ payload (blueprint.json + copied index.html / prompt files / hard-asset files), validated against BlueprintSchema. 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 a blueprint/ writes blueprint.v2/, never overwrites). Record the result in the bundle as units[].blueprint (status NEW if this run created the first blueprint/, REUSED if a prior capture already existed, with the payload path units/<slug>/blueprint/). See references/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 full recipeKind/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 (the vibe-reference / vibe-style form), write it under templates/<category>/<slug>/ so ralphy template list / show / suggest / use pick it up. This is ONE optional output, not the skill's reason for being — the entity publish is the #056 primitive. Schema is the one cli/commands/template.ts consumes. Never modify the source project.
Print the publish runbook (#056), do NOT push. Print the exact ordered publish-entity.ts commands the user runs — dry-run first, then --push — for the NEW blocks (--block-file, recipes carrying the enriched fields), the Units (--unit, carrying unit.json.tags), and each Unit's Blueprint (--blueprint). See Publish runbook for the full ordered set + the DB-migration prerequisite. Then stop. The user (or dev-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 ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir>"
},
"publish_cmd": "cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir>"
}
],
"new_blocks": [
{ "kind": "style", "slug": "free-air-jelly-pure", "blurb": "...", "publish_cmd": "cd ../ralphy-web && 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 in ../ralphy-web/scripts/publish-entity.ts. Print commands in the required 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 from the web repository.
0. Web repository prerequisite
Before ANY --push, verify that ../ralphy-web is present, clean, on its expected branch, and has its publishing credentials configured. 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 ../ralphy-web && bun run scripts/publish-entity.ts --block-file <block-spec.json> # dry-run (per NEW block)
cd ../ralphy-web && bun run scripts/publish-entity.ts --block-file <block-spec.json> --push # push
2. Units next (carrying unit.json.tags + provenance links)
cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir> # dry-run (per Unit)
cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir> --push # push
The unit's media uploads to the configured CDN and the committed library entry receives its provenance and tags. A missing provenance block is warned and skipped, never fabricated — which is why blocks publish FIRST.
3. Blueprints last (the per-unit reproduction payload, one per Unit)
cd ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir> # dry-run (per Unit)
cd ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir> --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 updates the committed library data and uploads media through the web repository's configured storage path (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 in asset-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, suggest ralphy 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 from index.html / slide layout, skip the scene table.
- No
postmortem/ → proceed but warn. Block classification leans harder on prompts.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. (The choose-path-xfade-master == ffmpeg-xfade-master dup is the canonical cautionary case — see references/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 empty refs:0 recipe block. See references/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 to ralphy-assets/pool/ (for Asset blocks / the local artifact).
cli/lib/schemas/unit.ts — the unit.json Zod schema (the Unit source of truth, #069).
cli/commands/blueprint.ts — the ralphy blueprint create|show|list|use surface (#076/#079); templater runs blueprint create per Unit in step 7.
cli/lib/schemas/blueprint.ts — the BlueprintSchema Zod 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's artifact.
../ralphy-web/lib/library-v2/types.ts — the five-entity shapes (Format / Unit / Block kinds), the enriched-Recipe fields (#082: recipeKind/body/artifact/params/demo), and Unit.tags.
supabase/migrations/0001_init_library_v2.sql — the schema (the blueprints table + blocks.recipe_kind / blocks.data / units.tags columns) that must exist before any --push.
../ralphy-web/scripts/publish-entity.ts — the publish primitive (#056); templater hands off to it.
docs/skills-vs-templates.md — templater = extract/classify; #056 = the web library writer.
.agents/skills/dev-publish-template/SKILL.md — the maintainer one-shot that runs the publish for you.
1---2name: templater3description: The full extract → classify → blueprint → de-dup → publish pipeline for a finished `.ralphy/workspaces/<ws>/projects/<id>/`. Reads its `units/*/unit.json` (#069) as the Unit source of truth, then factors the project into ALL FIVE entities — Unit + the four typed blocks Template (structure), Style (look), Recipe (effect/treatment), Asset (reusable media). Applies the recipe-vs-tag split (#082/#083): a candidate is a Recipe only if it carries an extractable artifact (ffmpeg filtergraph / HyperFrames snippet / encode recipe / prompt technique); otherwise it is a Tag on the Unit. Captures a per-unit Blueprint (#080), de-dups every block against the live library FIRST, then prints the exact ordered `publish-entity.ts` commands (dry-run → --push). Publishing itself is the #056 path. USE WHEN the user says any of: "save this as a template", "turn the project into a template", "templatify <project-id>", "extract a template from <project>", "decompose this project into blocks", "what units/blocks did this project prod4---56# templater78You 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.**910> **Where this sits in the Unit lifecycle.** templater runs AFTER Unit formation (phase 17) in the canonical [Unit lifecycle](../../../docs/playbooks/unit-lifecycle.md) — 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.1112## Two exits: library entities vs workspace bundle1314Reusable know-how leaves the repo through TWO distinct doors. Pick the door before running anything:1516- **Project → library entities (THIS skill).** The user wants the work **browsable and reproducible per unit** — "publish this to the library", "others should reproduce this style", "extract the blocks". Scope: ONE finished project, factored into Unit + Template/Style/Recipe/Asset blocks. The publishing implementation lives in the sibling `ralphy-web` repository.1718## DO NOT FIRE1920- **Scaffolding a new project** — that is `ralphy template use <existing-slug>`, not this skill.21- **One-off renders** — that is the producer playbook.22- **Quality evaluation** — that is `/evaluator`.23- **Pushing to the live library** — that is the #056 publish path in `../ralphy-web/scripts/publish-entity.ts`; templater stops at printing the ordered commands. See HARD INVARIANTS below.2425This 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).2627templater 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 is owned by the sibling `ralphy-web` repository — see [Publish runbook](#publish-runbook-056).2829## The five entities (read this first)3031The library model (`../ralphy-web/lib/library-v2/types.ts`) has five entities. Every project decomposes into them:3233- **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**.34- **Template** (block, single-per-unit) — the STRUCTURE / skeleton only, style-agnostic. The beat structure, the slide count + slot layout, the composition skeleton.35- **Style** (block, single-per-unit) — the visual look / register. The aesthetic + its anchor reference images.36- **Recipe** (block, multi-per-unit) — a composable effect / treatment. A VFX layer, an encode recipe, an overlay pass, a caption style.37- **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.3839The **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.4041## What this skill is NOT4243- **Not a project scaffolder.** That is `ralphy template use <slug> --project <new-id>`.44- **Not a postmortem generator.** That is `/postmortem`. Run it first if the source has none — `postmortem/02-lessons.md` (lessons) and `postmortem/06-units.md` (units + provenance) are the highest-signal inputs here.45- **Not the library publisher.** The publish step is the #056 primitive in `../ralphy-web/scripts/publish-entity.ts`. templater produces the classified bundle; the web repository publishes it.46- **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.4748## Source-of-truth files in the source project4950Every 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).5152| Source file in `.ralphy/workspaces/<ws>/projects/<id>/` | Required? | What gets pulled | Maps to entity |53|---|---|---|---|54| `units/*/unit.json` | **REQUIRED** | Finished deliverables + ordered media + provenance block slugs (#069) | **Unit** (+ seeds all four block axes via `provenance`) |55| `asset-manifest.json` | **REQUIRED** | Locked refs, music beds, character masters, location plates; final slot list | **Asset** blocks; backstop for Units when `units/` is thin |56| `scenario.json` | PREFERRED | Scene skeleton: beats + durations + speaker map + VO/SFX flags | **Template** block (structure only) |57| `prompts.json` | PREFERRED | Per-stage prompts (image / video / VO / music / captions) | **Style** block prompt cookbook (with `{{slots}}`) + Recipe hints |58| `index.html` (HyperFrames) | PREFERRED | Composition skeleton + per-template defaults; encode / overlay recipes | **Template** block (composition); **Recipe** blocks (VFX / encode / overlay) |59| `logs/generations.jsonl` | PREFERRED | Per-stage model picks + params + cost rollup | Model-stack notes on the Style / Template blocks |60| `BRIEF.md` | PREFERRED | Original user brief — sentence-level intent | Drives Format + category classification + descriptions |61| `postmortem/06-units.md` | PREFERRED | Units shipped + provenance, NEW vs REUSED per block | The classification spine — read it before re-deriving anything |62| `postmortem/02-lessons.md` | PREFERRED | Top rules + anti-patterns | "Key rules" on the relevant blocks |63| `postmortem/04-models-and-cost.md` | PREFERRED | Spend rollup per stage | Cost ballpark on the blocks |6465If `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.6667See `references/extraction-rules.md` for the per-file extraction details and edge cases.6869## The workflow70711. **Resolve the project + read its Units.** `ralphy unit list <project-id>` and read each `units/<slug>/unit.json`. These are the Units. Read `postmortem/06-units.md` if present — it pre-classifies the provenance blocks (NEW vs REUSED). NEVER fabricate units from the raw `artifacts/` dump.72732. **Read `BRIEF.md` + (if present) `scenario.json` headers** for the elevator pitch. This grounds the classification passes.74753. **Decompose into the five entities.** For each Unit, resolve its provenance into concrete block candidates:76 - **Template** — from `scenario.json` (beats) or, for scenario-less projects, from `index.html` (composition skeleton) / the slide-slot layout. Structure only, style-agnostic.77 - **Style** — from `prompts.json` + the locked anchor refs. The look + its reference images.78 - **Recipe candidates** — VFX layers, encode recipes (`-tune grain`, CRF), overlay passes, caption styles, baked transitions, prompt techniques. From `index.html` + `prompts.json` + bake scripts + postmortem lessons. Each candidate is then split recipe-vs-tag in step 4.79 - **Assets** — locked character masters, location plates, props, music beds from `asset-manifest.json`. Each carries a `sub` (`character`/`location`/`prop`/`music`). Multi-value.80814. **Recipe-vs-tag split (the #082/#083 discipline — read `references/recipe-vs-tag.md`).** For EACH recipe candidate from step 3, decide:82 - **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's `scripts/*.sh` bake scripts + the captured `blueprint.json` `recipes[].command` for bakes, `index.html` for 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 runnable `demo.html` for HyperFrames recipes; before/after media for ffmpeg recipes **when real frames exist** — never fabricated).83 - **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 in `unit.json`.84 - **The failure mode to kill: never publish an EMPTY recipe block** (refs:0, no `body`, no `artifact`). That empty chip is the "tag cloud" anti-pattern. If you cannot author a real artifact, it is a tag.85865. **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. Read `../ralphy-web/lib/library-v2/library.json`, and use `ralphy template list` / `ralphy template suggest` for template/style candidates and `ralphy assets list --kind <kind>` for asset candidates. If `postmortem/06-units.md` already marked a block REUSED, trust that. Only propose a **NEW** block for a genuine gap. **Worked cautionary example:** `choose-path-xfade-master` was published as a NEW recipe when the canonical `ffmpeg-xfade-master` already 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.87886. **Classify slots + tags + descriptions (LLM, via `callLLM()`).** Through `cli/lib/providers/llm.ts → callLLM()`:89 - **Slots** — extract `{{slots}}` (brand / product / character names / location keys / target language) from `prompts.json` per `references/slot-detection.md`, so the Style block's prompt cookbook is reusable across subjects.90 - **Format** — confirm each Unit's `format` (it is in `unit.json`; validate it against the eight library formats).91 - **Unit tags** — the descriptors demoted from step 4 land in each Unit's `unit.json` `tags[]` (filter-only labels for the feed's `TAGS` facet).92 - **Category + description** — per `references/category-classifier.md`, for any local `templates/<category>/<slug>/` artifact.93 - **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 gate `rg '\p{Cyrillic}'` must come back empty.94957. **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-contained `units/<slug>/blueprint/` payload (`blueprint.json` + copied `index.html` / prompt files / hard-asset files), validated against `BlueprintSchema`. 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 a `blueprint/` writes `blueprint.v2/`, never overwrites). Record the result in the bundle as `units[].blueprint` (status `NEW` if this run created the first `blueprint/`, `REUSED` if a prior capture already existed, with the payload path `units/<slug>/blueprint/`). See `references/extraction-rules.md` → "Blueprint capture (per Unit)".96978. **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 full `recipeKind`/`body`/`artifact`/`params`/`demo`. Mark every block NEW vs REUSED. This bundle is what the publish primitive consumes. Print it as JSON (see [Output](#output)).98999. **(Optional) Write a local `templates/<category>/<slug>/` artifact.** When the user wants a downloadable repo template (the `vibe-reference` / `vibe-style` form), write it under `templates/<category>/<slug>/` so `ralphy template list / show / suggest / use` pick it up. This is ONE optional output, not the skill's reason for being — the entity publish is the #056 primitive. Schema is the one `cli/commands/template.ts` consumes. Never modify the source project.10010110. **Print the publish runbook (#056), do NOT push.** Print the exact ordered `publish-entity.ts` commands the user runs — dry-run first, then `--push` — for the NEW blocks (`--block-file`, recipes carrying the enriched fields), the Units (`--unit`, carrying `unit.json.tags`), and each Unit's Blueprint (`--blueprint`). See [Publish runbook](#publish-runbook-056) for the full ordered set + the DB-migration prerequisite. Then stop. The user (or `dev-publish-template`) drives the actual push.102103## Output104105JSON, pipe-friendly:106107```json108{109 "project": "free-air-vpn-stickerpack",110 "units": [111 {112 "slug": "stickers-outline",113 "format": "sticker-pack",114 "media_count": 32,115 "provenance": {116 "template": { "slug": "sticker-set", "status": "REUSED" },117 "style": { "slug": "free-air-jelly-pure", "status": "NEW" },118 "recipes": [{ "slug": "floodfill-diecut-cutout", "status": "NEW" }],119 "assets": [{ "slug": "free-air-mascot", "sub": "character", "status": "NEW" }]120 },121 "tags": ["white die-cut outline", "jelly mascot"],122 "blueprint": {123 "status": "NEW",124 "path": ".ralphy/workspaces/<ws>/projects/free-air-vpn-stickerpack/units/stickers-outline/blueprint",125 "publish_cmd": "cd ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir>"126 },127 "publish_cmd": "cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir>"128 }129 ],130 "new_blocks": [131 { "kind": "style", "slug": "free-air-jelly-pure", "blurb": "...", "publish_cmd": "cd ../ralphy-web && bun run scripts/publish-entity.ts --block-file <spec.json>" },132 {133 "kind": "recipe",134 "slug": "floodfill-diecut-cutout",135 "blurb": "...",136 "recipeKind": "ffmpeg",137 "body": "## What it is ...",138 "artifact": "colorkey=0xffffff:0.1:0.0,despill=...",139 "params": { "key": "0xffffff", "similarity": 0.1 },140 "demo": { "kind": "media", "beforeUrl": "...", "afterUrl": "..." }141 },142 { "kind": "asset", "slug": "free-air-mascot", "sub": "character", "blurb": "..." }143 ],144 "reused_blocks": [{ "kind": "template", "slug": "sticker-set" }],145 "demoted_tags": ["white die-cut outline", "jelly mascot"],146 "local_template_artifact": null,147 "scenario_present": false,148 "warnings": []149}150```151152## Publish runbook (#056)153154templater extracts + classifies + blueprints + de-dups; the **publish to library is the #056 primitive** in `../ralphy-web/scripts/publish-entity.ts`. Print commands in the required 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 from the web repository.155156### 0. Web repository prerequisite157158Before ANY `--push`, verify that `../ralphy-web` is present, clean, on its expected branch, and has its publishing credentials configured. Dry-runs touch nothing remote, so they are always safe to print/run first.159160### 1. NEW blocks first (so unit provenance + tag facets resolve)161162Each 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.163164```bash165cd ../ralphy-web && bun run scripts/publish-entity.ts --block-file <block-spec.json> # dry-run (per NEW block)166cd ../ralphy-web && bun run scripts/publish-entity.ts --block-file <block-spec.json> --push # push167```168169### 2. Units next (carrying unit.json.tags + provenance links)170171```bash172cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir> # dry-run (per Unit)173cd ../ralphy-web && bun run scripts/publish-entity.ts --unit <absolute-unit-dir> --push # push174```175176The unit's media uploads to the configured CDN and the committed library entry receives its provenance and tags. A missing provenance block is warned and skipped, never fabricated — which is why blocks publish FIRST.177178### 3. Blueprints last (the per-unit reproduction payload, one per Unit)179180```bash181cd ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir> # dry-run (per Unit)182cd ../ralphy-web && bun run scripts/publish-entity.ts --blueprint <absolute-blueprint-dir> --push # push183```184185The `--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`.186187Every mode updates the committed library data and uploads media through the web repository's configured storage path (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).188189## Edge cases & refusals190191- **No `units/` AND no finished deliverables in `asset-manifest.json`** → the project isn't done. Refuse, point at what's missing (run /producer or finish the pipeline).192- **No `units/` but finished media exists** → DO NOT refuse. Surface the gap, suggest `ralphy unit create`, classify candidate blocks from the manifest anyway. (This is the #062 fix in spirit — never hard-block scenario-less / unit-thin projects.)193- **No `scenario.json`** → DO NOT refuse (the #062 fix). Derive the Template block's structure from `index.html` / slide layout, skip the scene table.194- **No `postmortem/`** → proceed but warn. Block classification leans harder on `prompts.json` + `asset-manifest.json`; offer to `/postmortem` (now a 7-file set incl. `06-units.md`) first for a cleaner NEW/REUSED split.195- **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`. (The `choose-path-xfade-master` == `ffmpeg-xfade-master` dup is the canonical cautionary case — see `references/recipe-vs-tag.md`.)196- **A recipe candidate has no extractable artifact** → it is a TAG, not a block. Demote it to the Unit's `tags[]`; never publish an empty `refs:0` recipe block. See `references/recipe-vs-tag.md`.197- **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.198- **Slug collision in `templates/`** (only if writing the optional local artifact) → refuse unless `--force`, show the diff first.199200## Why this skill exists201202A 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."203204## References205206- `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.207- `references/extraction-rules.md` — per-source-file extraction details + edge cases.208- `references/slot-detection.md` — LLM prompt + heuristics for `{{slots}}` in prompts.json.209- `references/kind-decision.md` — vibe-reference vs vibe-style decision tree (for the optional local artifact).210- `references/category-classifier.md` — the five segment-persona categories + LLM classification prompt.211- `references/pool-migration.md` — heavy-asset migration to `ralphy-assets/pool/` (for Asset blocks / the local artifact).212- `cli/lib/schemas/unit.ts` — the `unit.json` Zod schema (the Unit source of truth, #069).213- `cli/commands/blueprint.ts` — the `ralphy blueprint create|show|list|use` surface (#076/#079); templater runs `blueprint create` per Unit in step 7.214- `cli/lib/schemas/blueprint.ts` — the `BlueprintSchema` Zod shape (#074): the six reproduction axes a captured Blueprint carries.215- `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's `artifact`.216- `../ralphy-web/lib/library-v2/types.ts` — the five-entity shapes (Format / Unit / Block kinds), the enriched-Recipe fields (#082: `recipeKind`/`body`/`artifact`/`params`/`demo`), and `Unit.tags`.217- `supabase/migrations/0001_init_library_v2.sql` — the schema (the `blueprints` table + `blocks.recipe_kind` / `blocks.data` / `units.tags` columns) that must exist before any `--push`.218- `../ralphy-web/scripts/publish-entity.ts` — the publish primitive (#056); templater hands off to it.219- `docs/skills-vs-templates.md` — templater = extract/classify; #056 = the web library writer.220- `.agents/skills/dev-publish-template/SKILL.md` — the maintainer one-shot that runs the publish for you.