Install
$ agentstack add skill-wanshuiyin-aris-movie-director-comic-panel-prompt-builder ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
comic-panel-prompt-builder — the Deterministic Bake Compiler (搬运工原則)
The last authoring step before a pixel ever gets baked. Given ONE upstream-approved panel_spec and its status: locked blueprint, this skill COMPILES — it does not brainstorm — the exact message [comic-director](../comic-director/SKILL.md) (Phase 2/3) will hand to codex image_gen, plus the locked identity-ref list. It is the comic twin of the framework's shot-prompt-builder: a pure Layer-3 transform with no Codex call in the happy path — it only emits a prompt_bundle; the spiral engine submits it. Everything the generation backend ever sees passes through here, so this is where the 搬运工原則 is enforced at emit time: the backend (Codex/gpt-image-2) owns optics; ART_BIBLE.md owns style; no camera / lens / lighting / "8K hyperrealistic cinematic" padding leaks into the bake, and no bubble text gets baked unless the panel is explicitly text_mode: "baked". The author (storyboard/blueprint layers) decides what; this skill decides nothing — it transcribes a locked spec into a backend-legal string and FAILS (it never papers over) when the spec is malformed.
panel_spec (status:locked) ─▶ ① RESOLVE — load panel_spec + its blueprint + ART_BIBLE style_prefix
+ blueprint (status:locked) enforce 4 prerequisites (node_type, blueprint status:locked, REAL refs, style_prefix resolves)
▼
② COMPOSE — fixed-section condition_string (order is load-bearing, do NOT refactor)
▼ style_prefix + [BAKED DIALOGUE only if text_mode==baked] + SCENE + FIXED ELEMENTS
▼ + ALLOWED CHANGE vs prior panel + FORBIDDEN + SCREEN-TEXT WHITELIST(expected_literals)
▼
②·5 INJECT (retry only) — active failure_mode positive-invariants to the FRONT
▼
③ VALIDATE — banned-vocab + length + no-baked-bubbles + real-refs on the COMPOSED string
▼ (the hard gate — ANY hit = fail-closed, push the fix UPSTREAM)
▼
④ EMIT — write the prompt_bundle node; route html_bubbles[] to the JSON viewer ONLY
▼
pass? ─ no ─▶ exit 4 (banned/length) | exit 7 (failure_mode trigger still fires) | exit 3 (prereq)
│ yes
▼
⑤ stdout one-line JSON {prompt_bundle_id, panel_id, source_blueprint_id}
Role
You are a fail-closed transcriber, not an author. You take a panel_spec that an upstream layer already gate-approved and its blueprint that the cross-layer gate already flipped to status: locked, and you produce the single exact composed_prompt (style_prefix + a fixed-section condition string) + the locked identity-ref paths that the spiral engine will bake — together with the deterministic proof (a clean banned_vocab_scan) that the message is backend-legal. You never invent a scene element, a number, a cast member, or a style adjective; you never silently fix a malformed spec (you FAIL and point upstream); and you never call Codex on the happy path (a Codex call only ever appears as a reported upstream bug). The cross-model judgement of the baked pixels is not yours — that lives downstream in comic-director's panel_gate; keeping you deterministic is what stops the executor from self-acquitting.
Node — what this skill reads / writes (schemas/node_schema.json)
- Reads
panel_spec(node prefixpanel:) — payload fields used:source_storyboard_id,page_id,
panel_id, sequence_index, page_type, world, asset_ids[], text_mode, expected_literals[], content_blueprint, bubbles[], side_narration, motifs. The panel_spec must be its layer's gate-approved output (status: locked), not a draft.
- Reads the upstream
blueprint(node prefixblueprint:, found via the panel'scontent_blueprint) —
payload fields used (the schema's 9 blueprint required fields, verbatim from validate_wiki.py PAYLOAD_REQUIRED): source_panel_id, content_svg, expected_literals[], safe_zones[], html_bubbles[], crop, negative_space_policy, generator_script, file_sha256. Refuse unless the blueprint is status: locked — that is the schema signal that the cross-layer gate signed off (the gate's ⑥ FLIP writes status → locked on advance; comic-blueprint-author emits blueprints at status: locked). The blueprint node has NO review_status field (only the asset node does, and "approved" is not even a legal status enum value) — never gate on review_status here. html_bubbles[] is the JSON viewer-overlay payload — it is routed to the viewer, NEVER concatenated into the bake string.
- Writes one
prompt_bundle(node prefixprompt:, node_id a lowercase[a-z0-9_-]+slug of the
panel_id) — payload required by the schema (the 6 prompt_bundle PAYLOADREQUIRED fields, verbatim): source_panel_id, source_blueprint_id, style_prefix, composed_prompt, banned_vocab_scan, identity_ref_paths. Status canon: author draft/pending → under_review → locked on a clean validate (or rejected). Write LEGAL edges (every type ∈ validate_wiki.py EDGETYPES): derived_from (promptbundle → panelspec) and uses_blueprint (promptbundle → blueprint). On retry, the failuremode trace uses a legal derived_from edge (promptbundle → failuremode, with evidence.consulted_failure_mode + score); if a trigger still fires after repair, a legal failure_of edge (failuremode → promptbundle — failure_of points failuremode → target, matching the gate + the script, trigger in evidence). There is NO generated_from / consulted_failure_mode / violates_failure_mode edge type in EDGETYPES — those would fail the release gate. Emit node IDs only to stdout — the orchestrator re-reads the node file from disk (no filename guessing).
Two engine contracts to compile to (fail-closed)
These are the same two contracts comic-author authors to — this skill is where they are enforced at emit time, on the resolved spec, before any credit is spent:
- Every panel needs a content_svg. On the wiki node this is
blueprint.payload.content_svg(top-level
on the blueprint payload — NOT nested under any condition. key; the condition.content_svg prefix exists ONLY in the runtime comic.json the engine reads). The blueprint's content_svg is the content authority + the bake's layout reference. If it is null / missing / not on disk (or its file_sha256 mismatches) → refuse (exit 3). A scene-only panel still has a layout blueprint SVG; there is no such thing as a panel with no content_svg.
- A
bakedfigure-panel must declare non-empty, ascii-tokenizableexpected_literals. If
text_mode == "baked" and the blueprint carries a figure but expected_literals is empty → refuse (exit 3). Those literals become the SCREEN-TEXT WHITELIST block (below) and are exactly what panel_gate's blind token-diff later verifies. A scene panel with no audited numbers must be text_mode: "html" (dialogue is an HTML overlay, no baked-literal contract).
Procedure (numbered — an agent can execute this step by step)
The shipped entrypoint (do NOT hand-roll the validator):
python3 skills/comic-panel-prompt-builder/scripts/build_prompt.py \
[--retry --reason ""]
scripts/build_prompt.py IS this procedure made executable — it reads the panel_spec + blueprint nodes from /wiki/nodes/, resolves the identity lock + refs, composes the fixed-section message, and calls the single canonical scripts/_validate.py (which owns MAX_MESSAGE_CHARS = 4000, the ~80-pattern BANNED_VOCAB, no_baked_bubbles, real_refs — imported, never duplicated). It writes the trace files wiki/prompt_build//{_resolved.json,_meta.json,_validation.json} and, on a clean validate, the prompt_bundle node + its legal edges. --retry REQUIRES --reason (exit 2 otherwise) — a retry must name the active failure it is repairing. Run python3 scripts/_validate.py --composed --text-mode --bubbles ... --refs ... standalone for a quick re-check of an already-composed message.
- Phase −1 · parse + scaffold. Parse
$2(thepanel_idorpanel:slugnode_id); make
wiki/prompt_build//. --retry without --reason → exit 2. The emitted node_id is prompt: (the schema node_id pattern is [a-z0-9_-]+; the display panel_id may carry case). Never guess output filenames.
- Phase 0 · resolve + prerequisites (refuse to proceed unless ALL hold). Load into one
_resolved.json: the panel_spec node; its blueprint node (via panel_spec.payload.content_blueprint); and the style_prefix from ART_BIBLE.md (the world-keyed STYLE_PREFIX[]: line — §0 register + §0.5 two-world palette). Enforce:
- (a)
node_type == "panel_spec"andstatus == "locked"— else exit 2 (input missing/malformed). - (b) the upstream
blueprint.status == "locked"— the schema signal that the cross-layer gate signed
off (the gate's ⑥ FLIP writes status → locked). The blueprint node has no review_status field; gating on review_status == "approved" is unreachable (not even a legal status value) → never do it. Optionally also assert the legal decision node / decides edge for this blueprint exists as gate proof. Else exit 3.
- (c) every ref is a REAL locked asset path, not a placeholder:
blueprint.payload.content_svgexists on
disk (and file_sha256 matches), and each identity ref — resolved by following panel_spec.payload.asset_ids[] to its identity-sheet asset node (the asset must be status: locked, and publishes a real .png via its ref_requirements) — is a real .png (NOT pending:*, NOT null where one is required). A null ref is allowed ONLY as the documented "use project canonical" fallback; an unresolved pending:* → exit 3.
- (d) the
style_prefixresolves and is sane: ≤ 200 chars (hard), ≤ 32 words (warn). Else exit 3.
Also resolve asset names (scene/prop/identity asset_ids[] → display names) for the whitelist/reviewer context only — names go in the whitelist, raw IDs never go in the prompt.
- Phase 1 · compose the condition string DETERMINISTICALLY in this EXACT load-bearing layout (order is
load-bearing — it empirically drives baked-text fidelity; do NOT refactor headings or order without an A/B test). Front = strongest conditioning weight.
- STYLE PREFIX — from
ART_BIBLE.md: tone + two-world palette + texture, world-keyed by
panel_spec.world (e.g. warm / seam / dark-cyber / starfield). NO camera/lens/lighting/quality vocab.
- [BAKED DIALOGUE] — only if
text_mode == "baked"lift the panel'sbubbles[]text into the
prompt (so the image model draws legible balloons). If text_mode == "html" (or code): SKIP this block entirely — the bubbles are the viewer's job (see Phase 4). This is the single switch that decides baked-vs-overlay text.
- SCENE COMPOSITION NARRATIVE — the scene staging authority is the
blueprint.payload.content_svg
layout (the content blueprint already lays out the scene) summarized with the panel's panel_spec.payload.side_narration. There is NO condition.scene / condition.characters / chibi_action on the panel_spec node — those keys live ONLY in the runtime comic.json (do not read them off the node). The world's warm/cold contrast is by design, not drift.
- "FIXED ELEMENTS (must NOT change):" — the identity lock, obtained by following
panel_spec.payload.asset_ids[] to each identity-sheet asset node and reading its asset.payload.identity_lock (the canonical-cast hex/beard/silhouette lock — there is NO identity_desc field on the panel_spec node; the node-model identity authority is the asset's identity_lock) + the locked props the motif ledger pins for this panel.
- "ALLOWED CHANGE (vs the prior panel):" — from the panel's
motifsdelta (the comic reinterpretation
of "ALLOWED MOTION": what may legitimately differ from the previous panel).
- "FORBIDDEN (must NOT happen):" — the FIXED baseline `["no new character entering frame", "no off-model
drift", "no scene/world recolor"] + any panel-specific mustnotadd`.
- "SCREEN-TEXT WHITELIST (exact characters to preserve):" — the
expected_literals[]from the
blueprint (e.g. ["REJECT","37","T-16:05"]) + any baked-in signage/label names. Even when HTML owns the bubbles, baked-in figure text (chips, stamps, code) still needs char-exact preservation — this whitelist is the upstream of panel_gate's literal diff. Write _meta.json (character/element counts) for the trace.
- Phase 1·5 · failure_mode positive-invariant injection (retry only — the spiral active-memory hook).
On --retry, query the wiki for ACTIVE failure_mode nodes scoped to this panel via target_layer ∈ {prompt_pattern, visual_transition, global}; rank by recency*severity (recency = 1.0/age_days, severity default 3); take the top 10. Match the composed string against each mode's semantic_signature by weighted Jaccard (0.35*assets + 0.25*cterms + 0.20*kw + 0.20*frags); threshold 0.55 fires a match. For a matched mode, inject its repair_pattern ONLY if encoding_style == "positive_invariant" (else WARN + skip — negatives like "no missing ears" make diffusion fixate on the negated concept). Inject each as a "POSITIVE INVARIANTS (must be present in every panel, highest priority):" block at the FRONT of the message. Record the consult as a legal derived_from edge (promptbundle → failuremode) with evidence.consulted_failure_mode: true + the match score (there is no consulted_failure_mode edge type). This is exactly the --reason a retry must name.
- Phase 1·6 · trigger re-scan. Re-scan the FULL message against the
trigger_patternsof EVERY active
banlist mode (not just injected ones). If a matched mode is a positive_invariant whose repair_pattern isn't present yet → append it in-place into the POSITIVE INVARIANTS block. If any violation STILL remains → write a legal failure_of edge (failuremode → promptbundle — failure_mode → target, trigger in evidence; there is no violates_failure_mode edge type) and exit 7 (a regenerate signal to the orchestrator).
- Phase 2 · VALIDATE = the hard gate (runs on the FULL composed message; this is the rubric). Delegate to
scripts/_validate.py (the single source): passes_all = length_ok AND no_banned_vocab AND no_baked_bubbles AND real_refs_ok. ANY failure → preserve _validation.json, set the panel's review_gates.storyboard_json_gate = "fail", and exit 4 — see the EXACT gate below. Do NOT silently add quotes / fix the spec — a malformed spec FAILS validation so the fix is pushed back to the authoring layer.
- Phase 3 · emit. Write the
prompt_bundlenode (payload = the schema's 6 required fields exactly:
{source_panel_id, source_blueprint_id, style_prefix, composed_prompt, banned_vocab_scan, identity_ref_paths}); set its status locked. Write the LEGAL edges (every type ∈ EDGETYPES): derived_from (promptbundle → panelspec) and uses_blueprint (promptbundle → blueprint) — NOT generated_from (illegal, fails the release gate). **Route html_bubbles[] to the JSON viewer overlay only
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: wanshuiyin
- Source: wanshuiyin/ARIS-Movie-Director
- License: MIT
- Homepage: https://wanshuiyin.github.io/ARIS-Movie-Director/comic/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.