Skills · Maintainer
dev-publish-template
/dev-publish-templateTurn the active finished project into a published, library-discoverable template in one shot — infer its media `format` (video/image/carousel/fb-creative/motion-design/poster/st…
SKILL.mddev-publish-template — one-shot extract + publish to the library
This is a maintainer skill. It commits + pushes a template to the public origin repo, exactly like dev-release commits + pushes a binary. The job: from the active project context, turn .ralphy/workspaces/<ws>/projects/<id>/ into a published template under templates/<category>/<slug>/, discoverable at /library, with one user confirmation and one push.
The mechanical extraction is owned by the ralphy template extract verb (issue 033) + the templater skill (the human how-to). This skill is the dev-release-style automation layer on top: it infers the media format, confirms slug/category/format with the user, runs extract, enriches the manifest, seeds a showcase entry, migrates heavy assets, and ships it.
Relationship to neighboring skills
templater(namespace: user) — the manual walkthrough of extracting a project into a template. Read it for the per-source-file extraction details.dev-publish-templatereuses that knowledge but adds the format-inference + confirm + commit-and-push automation.dev-release(namespace: maintainer) — the model for this skill's rigor: a one-shot publish across a public surface with explicit pre-push confirmation and HARD INVARIANTS. Match its structure.- This skill never ships the CLI binary, touches
npm// brew, or cuts a version tag. That isdev-release. This skill ships content (a template) only.
Trigger
Hard triggers (do it):
- User types
/dev-publish-template. - "publish this as a template" / "ship this project to the library" / "make this a public template".
- "extract and publish a template from
<project-id>".
Proactive triggers (offer it, don't auto-execute):
- A render just shipped, the user rated it 8+/10, and a
postmortem/exists — the experience is most reusable while fresh. - The user finished
/postmortemand says the workflow is worth keeping for others.
ALSO FIRE
- After
templaterwould have fired but the user explicitly wants it published (committed + pushed), not just written to the working tree. - When the user names a finished project id and says "library" / "publish" / "share so others can remix".
DO NOT FIRE
- Project not finished. No
scenario.json/ noasset-manifest.json/ no final render → refuse with the same concrete ask thetemplaterskill uses: "Run/producerto finish the pipeline (or/postmortemfirst), then point me at the project id." Templates crystallize finished experience; a half-done project inherits the gaps. - Scaffolding a new project — that is
ralphy template use <existing-slug>. - Write-only, no publish — if the user wants the template in the working tree for review but NOT committed/pushed, hand back to the
templaterskill. - Binary / CLI release — that is
dev-release. - The user said "don't push" / "let's review first" earlier in the session — produce the template + showcase locally and stop before the commit step.
HARD INVARIANTS
- Confirm before extract AND before push. Never run
ralphy template extractuntil the user has confirmed the inferredformat+slug+category. Nevergit pushuntil the user has confirmed the file list. Two gates, both explicit ("yes" / "go"). - Append-only (AGENTS.md #14). Re-running on the same project versions the showcase, never clobbers the template. If
templates/<category>/<slug>/already exists, do NOT pass--forceunless the user explicitly asked for a destructive overwrite — instead append a new showcase entry (a newoutput-NNrow) and report that the recipe is unchanged. The source project is NEVER modified by extraction (the verb guarantees this). - Idempotent. A second run with the same project + slug must not create a duplicate template, must not double-commit an unchanged tree, and must only add a new showcase row if the render actually changed. If
git statusshows no diff after the enrichment step, report "already published, nothing to ship" and stop. - English-only on disk. Every file you write or edit —
template.yaml,README.md, showcase manifest, commit message — lands in English. Before committing, run the Cyrillic gate and treat empty output as the pass:Non-empty → fix before committing. (Repo-wide rule perrg '\p{Cyrillic}' --pcre2 --hidden -g '!.git' templates/<category>/<slug>/docs/developing-ralphy.md.) ralphyis the only extraction entrypoint. Never hand-copy files intotemplates/, nevercurla provider, never write the template dir by hand. The verb writes the manifest the loader expects; manual writes drift from the schema and breakralphy template list / show / suggest / use.- Push to
originonly. Per repo memory:originis GitHubalecs5am/ralphy. NEVER push togitlab. This repo commits directly tomain(no feature branch) per the user's standing override.
Source-of-truth files (read before extracting)
These live in .ralphy/workspaces/<ws>/projects/<project-id>/. The extract verb consumes them; you read them to infer the format and enrich the manifest afterward.
| File | What it tells you |
|---|---|
scenario.json (required) | Scene skeleton, durations, speaker map. Extract refuses without it (E_FILE_UNREADABLE). |
asset-manifest.json (required for a real template) | Locked refs, masters, music beds → which heavy assets to lift to the pool. |
index.html | Present → HyperFrames composition → likely video or motion-design format. Absent → still-image formats. |
prompts/ | Per-scene prompt files; copied verbatim by extract. |
logs/generations.jsonl | The real model stack + per-call cost → fills model-stack notes + cost rollup. |
render/ | The final mp4 / png → the showcase entry's source artifact. |
postmortem/02-lessons.md, postmortem/04-models-and-cost.md | Lessons + spend rollup → README.md "Lessons learned" + cost ballpark. |
BRIEF.md | The original brief → drafts the description + tags. |
If postmortem/ is missing, proceed but warn: the README will lack the lessons/cost sections (extract stubs them as TODO). Offer to pause for /postmortem first.
Step 1 — Validate the active project
Confirm the project id from the active context. Verify the required artifacts exist:
PROJ=<project-id>
ls .ralphy/workspaces/<ws>/projects/$PROJ/scenario.json \
.ralphy/workspaces/<ws>/projects/$PROJ/asset-manifest.json \
.ralphy/workspaces/<ws>/projects/$PROJ/render/ 2>&1
Missing any of these → refuse with the DO-NOT-FIRE ask. Do not improvise content for a half-finished project.
Step 2 — Infer the media format + general-vs-style placement
The --format flag (issue 052) is the primary axis. Infer it from the project, then confirm with the user — never guess silently.
| Signal in the project | Inferred format |
|---|---|
index.html + render/*.mp4 from generated i2v/t2v footage | video |
index.html that is code/animation-driven (kinetic type, geometric splits, no camera footage) | motion-design |
Single still render/*.png as the deliverable, no video | image |
| Single high-impact key-art still (drop poster / flyer / album-cover) | poster |
| Multiple stills as one swipeable post | carousel |
Numbered stills tuned for the ads manager + ads-copy.md | fb-creative |
| A set of die-cut sticker images sharing one mascot | sticker-pack |
For general vs style placement (issue 052): a project that froze one concrete aesthetic / one reproducible video is a style template — after extraction, set style_of: <parent-general-slug> in template.yaml if a general baseline for that format already exists (check ralphy template list --format <f>). A project that is the format's baseline how-to is general (leave style_of unset). Extraction always produces a kind of vibe-style by default; pass --kind vibe-reference when the project ships a full HyperFrames composition worth reproducing wholesale.
Confirm with the user in one line before extracting:
"Inferring format=
video, category=creator-lifestyle, slug=tokyo-y2k-night-run, kind=vibe-reference, style_of=(none/general). Extract with these?"
Wait for explicit confirmation. The user's --category / --slug / --format override always wins.
Step 3 — Run the extraction
Use the real ralphy template extract flags (verified from the verb):
ralphy template extract $PROJ \
--format <video|image|carousel|fb-creative|motion-design|poster|sticker-pack> \
--category <b2b-saas|dtc-commerce|creator-lifestyle|entertainment-viral|cinematic-narrative> \
--slug <kebab-slug> \
--kind <vibe-style|vibe-reference> \
--name "<Human Readable Name>" \
--description "<one-line description>" \
--tags <comma,separated,tags>
Heavy-asset migration (step 6) is a separate flag on the same verb — do not add it yet; default behavior COPIES refs in place (per AGENTS.md #14). The full verified flag set:
--category <c>(required) —b2b-saas|dtc-commerce|creator-lifestyle|entertainment-viral|cinematic-narrative.--slug <s>(required) — kebab-case; rejected if it embeds a banned creator/brand token (DENIED_SLUG_TOKENS).--kind <k>—vibe-reference|vibe-style(defaultvibe-style).--format <f>—video|image|carousel|fb-creative|motion-design|poster|sticker-pack(defaultvideo).--name <n>— human-readable name (defaults from slug).--description <d>— one-line description (defaults to an extracted-template stub).--tags <list>— comma-separated tags (default empty).--lift-heavy— MOVE refs >1MB intoralphy-assets/pool/<slug>/(default COPIES everything in place). Used in step 6.--assets-repo <path>— path to the ralphy-assets checkout; required when--lift-heavyis set.--force— overwrite the target template dir if it already exists. Only with explicit user consent (invariant #2).
The verb writes .ralphy/workspaces/<ws>/templates/<slug>/: template.json (loader manifest), scenario-template.json (slot-substituted scenario), TEMPLATE.md, README.md (drafted from POSTMORTEM lessons), prompts/, refs/, sample-remix.md, and composition-variables.json when the project is a HyperFrames composition.
Note: the verb writes a
template.jsonmanifest. The typedtemplate.yaml(issue 052) carriesformat+style_offor the format-aware surfaces (list --format,suggest --format). If extraction wroteformat/style_ofonly intotemplate.json, mirror them into atemplate.yamlconforming tocli/lib/schemas/template.tssoralphy template list --format <f>finds the new slug — verify with the gate in step 7.
Step 4 — Enrich the manifest
Open the generated template.yaml (or template.json) and fill what extraction could only stub:
tags— synthesize 10–20 from the scenario + brief + cookbook soralphy template suggestranks the slug for its own brief.- Model stack — read
logs/generations.jsonlfor the real per-stage model picks and params; record them in the template'smodel-stacknotes (which model for which stage, what to avoid). ReadMODELS.mdto confirm ids are current. - Cost rollup — sum per-stage spend from
generations.jsonl(or liftpostmortem/04-models-and-cost.md); setestimated_cost_usd+ a "Cost ballpark" line inREADME.md. - Lessons — if
postmortem/02-lessons.mdexists, the README already carries them; distil the top 5–7 into a "Key rules" section. If not, leave the TODO stub and warn the user.
Step 5 — Seed a showcase entry (issue 055)
Issue 055 (the per-template results gallery) has not finalized its schema — its spec says outputs are "stored alongside the template (e.g. showcase/ manifest) and/or pulled from ralphy-assets", and to "seed galleries from the templates extracted in 058". Do NOT invent a conflicting schema. Write a minimal, forward-compatible manifest and let 055 migrate it:
Create templates/<category>/<slug>/showcase/showcase.json with an append-only outputs array. One entry per published render:
{
"version": 1,
"slug": "<slug>",
"outputs": [
{
"id": "output-01",
"source_project": "<project-id>",
"media": "render/<final>.mp4",
"format": "<video|image|...>",
"caption": "<one-line, optional>",
"created": "<YYYY-MM-DD>"
}
]
}
Rules:
- Keep fields minimal (
id,source_project,media,format,caption,created). These are the lowest common denominator of what 055 will consume; richer fields (thumbnail, dimensions) are 055's to add. - Append-only. Re-running on the same project adds
output-02,output-03, … — it never rewrites an existing entry (invariant #2). - The render file itself is heavy → it migrates to
ralphy-assetsin step 6 (showcase media coordinates with the 059 split, per 055's notes). For now, reference the in-repo render path or the pool path if lifted; 055 will repoint. - Flag the dependency in your report: state plainly that the showcase schema is provisional pending issue 055, and that 055 may repoint
mediato aralphy-assetslocation.
Step 6 — Migrate heavy locked refs to the pool
Mirror the templater skill's guidance. For locked refs / masters / music beds the template should re-use across remixes, lift them out of the repo into the companion pool (keeps the repo light, makes the asset reusable):
ralphy template extract $PROJ \
--format <f> --category <c> --slug <s> \
--lift-heavy \
--assets-repo /Users/maximovchinnikov/github/ralphy-assets \
--force # only if re-extracting an existing slug with explicit consent
--lift-heavy MOVES refs ≥1MB to ralphy-assets/pool/<slug>/; the template references them remotely. If you already extracted in step 3 without --lift-heavy, lifting requires re-running with --force (gated on user consent per invariant #2) OR manually moving only the heavy refs and updating template.json:assets to { remote: true, manifestKey, required }. After any pool change, regenerate the catalog: ralphy assets catalog --write. If total heavy assets exceed ~50MB, confirm with the user first (GitHub raw limit is 100MB).
Step 7 — Verify before committing
Run the discovery + lint gates and assert their output:
ralphy template list --format <f> # new slug must appear, source=repo
ralphy template show <slug> # TEMPLATE.md parses, no broken frontmatter
ralphy template suggest "<original brief>" # new slug ranks top-3, score ≥ 0.5
bun run lint:templates # template.yaml schema + slug validity → green
bun run lint:skills # this skill (+ all) → green
rg '\p{Cyrillic}' --pcre2 --hidden -g '!.git' templates/<category>/<slug>/ # empty = pass
If suggest ranks the slug below 0.5, the tags/description are too generic — fix step 4 before shipping. If lint or the Cyrillic gate fails, fix before committing. (Note: do not rely on husky hooks — they have a known flaky test; run these manually.)
Step 8 — Commit + push (the dev-release discipline)
Two repos may change: ugc-cli (the template dir) and, if you lifted heavy assets, ralphy-assets (the pool + manifest).
Show the user the exact file list and wait for explicit confirmation before any git mutation (invariant #1). Then, in ugc-cli:
git add templates/<category>/<slug>/
git commit -m "feat(templates): publish <slug> (<format>) from <project-id>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
git push origin main
If heavy assets were lifted, commit + push the companion repo too (its own git add pool/<slug>/ manifest.json → commit → git push origin main), and regenerate docs/assets-catalog.md in ugc-cli in the same template commit.
Commit directly to main (the user's standing override for this repo); push to origin only — NEVER gitlab (repo memory: gitlab is an unrelated project).
Outputs
On success, report a single summary with the live /library URL:
Published <slug> (<format>, <category>):
• template → templates/<category>/<slug>/ (committed + pushed to origin/main)
• library → https://ralphy.dev/library/<slug>
• showcase → 1 output seeded (provisional schema, pending issue 055)
• suggest → ranks #1 for the original brief (score 0.NN)
If nothing changed (idempotent re-run), report "already published, nothing to ship" and stop.
Cookbook
Publish a finished video project:
"Publish this as a template" → infer
format=video, confirm slug/category,ralphy template extract <id> --format video --category creator-lifestyle --slug <slug>, enrich, seed showcase, push, report/libraryURL.
Publish a poster project:
A single key-art still in
render/→ inferformat=poster,--format poster, no composition-variables sidecar, showcase media is the png.
Re-run on an already-published project (idempotent):
Template dir exists, no recipe change → append
output-02to the showcase, commit only the showcase delta (or nothing if the render is unchanged), never--force.
Refusal — half-finished project:
No
asset-manifest.json→ "This project isn't finished — run/producerto complete the pipeline (and/postmortemfor lessons), then I can publish it."
