AgentStack
SKILL verified MIT Self-run

Comic Json Compiler

skill-wanshuiyin-aris-movie-director-comic-json-compiler · by wanshuiyin

Phase-1 (S9, the FINAL comic-author step) — assemble the LOCKED storyboard + locked per-panel blueprints into ONE schema-valid `comic.json` (the comic-ir/1.0 contract boundary handed to comic-director / run_comic.py). Project the page_order + each panel's condition{} + render fields into `pages[]` + keyed `panels{}` per schemas/comic.schema.json; author ONLY authored fields and leave image_path/a…

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-wanshuiyin-aris-movie-director-comic-json-compiler

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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 Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

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.

Are you the author of Comic Json Compiler? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

comic-json-compiler — locked storyboard + blueprints → one schema-valid comic.json (S9)

The last node of the left third of Figure 1: take the locked storyboard_spec (the authoritative page order + the MOTIF STATE TABLE) and the locked per-panel blueprint nodes and compile them into the single comic.json (comic-ir/1.0) that is the contract boundary to [comic-director](../comic-director/SKILL.md) (Phase 2/3). You don't hand-write this file panel-by-panel — that already happened upstream; this step is the deterministic projection + reconciliation that turns the authoring nodes into the IR and catches the integrity bugs that only show up once everything is assembled — chiefly the page-count / orphan-panel mismatch (the orphan-panel class). The two deterministic scripts the compile gate runs are per-page (run_comic.py needs --page/--panels) and the JSON schema leaves condition/content_svg OPTIONAL — so neither of them sees the whole pages[]/panels{} set and neither catches an orphan. That whole-comic reconciliation against the storyboard authority is THIS skill's own inline job (the ~7-line set diff below), run before the gate; the gate then certifies the per-page + wiki-conformance floor.

  locked storyboard_spec (page_order = AUTHORITY)            ┐
  locked motif_ledger    (per-panel ground-truth)           │
  locked panel_spec × N  (the 9-field specs)                 ├─▶ ① ASSEMBLE  pages[] + panels{}  per comic.schema.json
  locked blueprint × N   (content_svg + expected_literals)   │        (author ONLY authored fields; engine fields EMPTY)
  locked prompt_bundle × N (optional, identity refs)         ┘        ▼
                                                                ② RECONCILE page-count integrity  ── INLINE python (this skill; no phantom script)
                                                                   (orphans · dangling refs · page_count vs page_order · grid2x2==4 · recap legitimacy)
                                                                      ▼
                                                                ③ VALIDATE  jsonschema(comic.schema.json) — SHAPE only (it does NOT enforce content_svg/literals)
                                                                      ▼
                                                                ④ FAIL-CLOSED contracts  (every panel a content_svg · baked figure ⇒ expected_literals)  ← THIS skill asserts; schema won't
                                                                      ▼
                                                                ⑤ comic-cross-layer-gate decision:compile_ --gate compile  (DETERMINISTIC: run_comic.py --dry-run per page AND cli/validate_wiki.py both exit 0)
                                                                      ▼
                                                            approve? ─ revise ─▶ fix the OFFENDING node upstream, recompile  (you don't invent content)
                                                                      │ approve
                                                                      ▼
                                                                ⑥ EMIT comic.json (output-versioning) + movie.project.json pointer-hub + asset-copy manifest

Constants

  • CONTRACT BOUNDARY = comic.json (comic-ir/1.0) + the assets it references. Everything downstream

(comic-director, run_comic.py, the Phase-3 viewer build packages/viewer/build_comic.py) reads ONLY this file + its assets — so a bug that survives this step survives to production.

  • AUTHORITY = the storyboard's page_order. When the assembled comic.json page set disagrees with the

locked storyboard_spec.page_order, the storyboard wins — the compiler conforms, it does NOT silently ship its own page count. (This is the converse of the storyboard gate's page-order veto.)

  • GATE = comic-cross-layer-gate decision:compile_ --gate compile (the target is the **real wiki

decision node this skill writes — there is NO comic node type and comic.json is not a wiki node, so it can never be a gate target or an edge endpoint). Per [comic-cross-layer-gate](../comic-cross-layer-gate/SKILL.md) the compile gate is purely DETERMINISTIC: NO review:* fan-out, NO Codex adjudication (so unlike every other gate, the "create review nodes first" pre-step does NOT apply here). It PASSES (approve) iff BOTH on-disk scripts exit 0**: python3 skills/comic-director/scripts/run_comic.py --project --page --panels --dry-run (per page) AND python3 cli/validate_wiki.py ; else revise with their stderr as the blocker list. (The whole-comic orphan/page-count catch is NOT in either script — it is this skill's inline step ② below, run before the gate.)

  • VERDICT enum (the compile gate's legal set, per the gate skill): approve / revise. On approve flip

the compile decision to final and emit; on revise fix the offending upstream node and recompile (the compiler never patches content into comic.json by hand). A verdict is never a node status: the status the FLIP writes is locked on advance, under_review on needs-work, rejected on a terminal fail (schema-enum statuses only — revise/regenerate are verdicts, not statuses).

  • NO INVENTION — the compiler is a projection + reconciliation step. It must NOT invent a panel, a

number, an expected_literal, a page, or a content_svg. A missing piece is a HALT with the offending node id, not a fabrication ([artifact-integrity](../../protocols/artifact-integrity.md)).

  • ENGINE FIELDS STAY EMPTYimage_path / active_attempt_id / wiki_node_id are written by the spiral

engine on KEEP. Authoring them is a veto (you'd be claiming a bake that never ran).

  • ASCII node-id discipline — every wiki touch goes through the one node_id_to_filename helper (:_),

per [wiki-helper-resolution](../../protocols/wiki-helper-resolution.md).

Input contract — what must be LOCKED before you compile

This is a compile step: it consumes locked upstream nodes, it does not author them.

  • One storyboard_spec (status: "locked") — its page_order (the authority), page_ids, panel_ids,

global_policies (page-order authority, text-mode rules, mirror-lock policy), motif_ledger_id, consolidated_asset_requests.

  • The motif_ledger it points at — the per-panel ground-truth the gate uses to confirm condition prose

agrees with each panel's continuity row.

  • One panel_spec per panel (status: "locked") — page_type, world, text_mode, expected_literals,

content_blueprint, bubbles, side_narration, motifs, (cover/finale: safe_zones + endcard fields).

  • One blueprint per panel (status: "locked") — content_svg, expected_literals (verbatim),

safe_zones, html_bubbles, crop, negative_space_policy, generator_script, file_sha256.

  • (optional) the prompt_bundle per panel — for identity_ref_paths (else fall back to the project

canonical from identity_refs).

  • HALT if any of these is not locked (or its content_svg/output_ref file is missing) — name the

offending node id. Convergence is the upstream gate's job, not yours.

Workflow (the inline reconcile + the two deterministic gate scripts — all runnable, no phantom tool)

① ASSEMBLE pages[] + panels{}

Project the locked nodes into the IR. Copy examples/comic_m3_audit/comic.json as the shape.

  1. Top levelschema_version: "comic-ir/1.0", comic_id, defaults (text_mode, default_locale,

panels_per_page_cap, nav_default, pixel_rendering), identity_refs (the locked cast hex table from the style bible — pure pointer/hex hub, never duplicated art), ui_tokens (viewer theming, palette pinned).

  1. pages[] — reading order, ONE entry per storyboard_spec.page_order page, in that exact order. Each:

id (the stable storyboard page id, e.g. P00_cover/P02_b08/P03_end), type (cover|single|grid|grid2x2|feature|finale), panel_ids (the panel ids that page shows), plus optional beat/beat_title/narration/skill/title/links (cover/endcard buttons — https:// only).

  1. panels{} — ONE keyed entry per panel id that appears in any page. Per panel project:
  • condition{} (what to GENERATE): content_svg (the locked blueprint's deterministic SVG = the

content authority), expected_literals (verbatim from the blueprint), world, identity_ref (from the prompt_bundle or null → project canonical), identity_desc, characters, scene.

  • render fields (for the viewer): text_mode, crop (shape∈hero|wide|square, position [x,y] in

0..1, zoom ≥ 1), safe_zones (each with an id), bubbles (speaker∈researcher|executor|reviewer, style∈say|shout|terse|thought|whisper, text{zh,en}, optional anchor = one of this panel's safe-zone ids), overlays, caption{zh,en}.

  • leave EMPTY: image_path, active_attempt_id, wiki_node_id.
  1. Emit movie.project.json — the pure pointer-hub manifest (project_id, title{zh,en}, story,

schema_version, comic_json, art_bible, identity_ref, dirs, text_mode_default, palette). It names the other artifacts; it NEVER duplicates their content.

② RECONCILE page-count integrity (the heart of this skill — INLINE python, NO external script)

There is no reconcile_pages.py — it never existed. This catch is run inline (the schema and the per-page run_comic.py are both blind to it), so paste-and-run this ~15-line whole-comic diff against the storyboard authority. This is where the orphan-panel class dies. Exit non-zero on ANY blocker; do NOT proceed to the gate until it is clean.

# inline reconcile — run against the assembled comic.json + the locked storyboard_spec node (NO phantom tool)
import json, sys, re
c  = json.load(open("comic.json", encoding="utf-8"))
sb = json.load(open("wiki/nodes/storyboard_.json", encoding="utf-8"))["payload"]   # the LOCKED authority
page_refs = [pid for p in c["pages"] for pid in p["panel_ids"]]      # every page→panel reference (with dups)
closing_refs = [(p.get("closing") or {}).get("image") for p in c["pages"] if (p.get("closing") or {}).get("image")]
ref_set   = set(page_refs) | set(closing_refs);  defined = set(c["panels"])  # a finale endcard (closing.image) IS a use
orphans   = sorted(defined  - ref_set)            # defined, not on a page AND not a closing endcard → BLOCKER
dangling  = sorted(ref_set  - defined)            # a page/closing points at a missing panel → BLOCKER
# PAGE-ORDER AUTHORITY — ELEMENT-WISE vs the storyboard (NOT a length check): catches reorder / id-swap / dup /
# over-count that a count comparison silently passes.
comic_order = [p["id"] for p in c["pages"]]
sb_order    = list(sb["page_order"])
order_mismatch = comic_order != sb_order               # STRICT: any reorder / missing / extra / dup → BLOCKER
order_missing  = [pid for pid in sb_order if pid not in comic_order]    # storyboard page absent from comic.json
order_extra    = [pid for pid in comic_order if pid not in sb_order]    # comic.json page not in storyboard
page_id_dups   = sorted({pid for pid in comic_order if comic_order.count(pid) > 1})  # duplicate page id (over-count)
bad_grid  = [p["id"] for p in c["pages"] if p["type"] == "grid2x2" and len(p["panel_ids"]) != 4]  # arity → BLOCKER
empty_pg  = [p["id"] for p in c["pages"] if not p["panel_ids"]]    # empty page → BLOCKER
# FAIL-CLOSED engine contracts — jsonschema leaves these OPTIONAL, so this inline step is the REAL enforcer:
def cond(pn): return pn.get("condition") or {}
no_csvg = sorted(pid for pid, pn in c["panels"].items() if not cond(pn).get("content_svg"))   # null/missing content_svg
# cfg_usable EXACTLY (run_comic.py): the EFFECTIVE mode resolves from defaults (a panel inheriting a baked
# default is still baked); a baked panel WITH a content_svg MUST carry expected_literals as a non-empty LIST
# whose every entry is an ascii-tokenizable string. ONE predicate = never weaker than the engine floor.
def emode(pn): return pn.get("text_mode") or (c.get("defaults") or {}).get("text_mode") or "html"
def baked_lits_bad(pn):
    if emode(pn) != "baked" or not cond(pn).get("content_svg"): return False
    exp = cond(pn).get("expected_literals")
    return not (isinstance(exp, list) and len(exp) > 0
                and all(isinstance(e, str) and re.findall(r"[a-z0-9+._-]+", e.lower()) for e in exp))
baked_lits_fail = sorted(pid for pid, pn in c["panels"].items() if baked_lits_bad(pn))
engine_authored = sorted(pid for pid, pn in c["panels"].items()                 # authored ENGINE fields MUST be empty
                         if pn.get("image_path") or pn.get("active_attempt_id") or pn.get("wiki_node_id"))
# bubble.anchor (if set) MUST resolve to a panel safe_zone id. safe_zones is a panel TOP-LEVEL field
# (comic.schema.json) — NOT under condition; jsonschema only TYPES anchor as a string, so assert it inline.
def sz_ids(pn): return {z.get("id") for z in (pn.get("safe_zones") or [])}
bad_anchor = sorted(f"{pid}:{b.get('anchor')}" for pid, pn in c["panels"].items()
                    for b in (pn.get("html_bubbles") or pn.get("bubbles") or [])
                    if isinstance(b, dict) and b.get("anchor") and b["anchor"] not in sz_ids(pn))
# DECLARED recap reuse is the ONLY legitimate multi-reference — read it from the storyboard, never guess.
gp = sb.get("global_policies", {})
sanctioned = set(gp.get("recap_panel_ids") or gp.get("sanctioned_recap") or [])   # storyboard-declared recap/grid set
dups = sorted({pid for pid in page_refs if page_refs.count(pid) > 1} - sanctioned)  # UNDECLARED panel dup → BLOCKER
blockers = {"orphans": orphans, "dangling": dangling, "order_mismatch": order_mismatch, "order_missing": order_missing,
            "order_extra": order_extra, "page_id_dups": page_id_dups, "bad_grid2x2": bad_grid, "empty_pages": empty_pg,
            "undeclared_dups": dups, "content_svg_null": no_csvg, "baked_literals_invalid": baked_lits_fail,
            "engine_fields_authored": engine_authored,
            "bubble_anchor_unresolved": bad_anchor}
if any(blockers.values()):
    sys.exit(f"RECONCILE BLOCKERS: {blockers}  (pages {len(comic_order)} vs storyboard {len(sb_order)})")
print("reconcile OK")
  • The orphan check counts closing.image. In the reference comic.json, S22 (the wordless constellation

finale) is the endcard of the finale page P_B12 (P_B12.closing.image = "S22") — a legitimate use, NOT an orphan. (An earlier draft of this check read only panel_ids and FALSE-flagged it.) A TRUE orphan is a panel in panels{} referenced by neither a page's panel_ids NOR any closing.image; that is a real BLOCKER the compiler must catch, not ship — page it, or (if genuinely cut) make an upstream storyboard edit + re-lock, never a quiet drop. When a locked storyboard_spec node is present, its page_order is the element-wise page authority (reorder / missing / extra / duplicate page id all block).

  • Recap reuse is the ONE legitimate multi-reference and must be declared, not inferred. In the

reference comic, S12,S13,S14,S15 are each referenced twice — once as individual single pages (P_B08_1..4) and once in the grid2x2 recap/hero page (P02_b08). That is by-design (the storyboard's mirror_locks / recap policy), so a panel referenced >1 time is a veto ONLY if it is not on the storyboard's declared recap/grid set. The diff reads the storyboard's global_policies (the sanctioned recap ids) to know which reuses are sanctioned — an undeclared duplicate reference is still a BLOCKER.

③ VALIDATE against the schema (SHAPE only — it does NOT enforce the fail-closed contracts)

python3

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [wanshuiyin](https://github.com/wanshuiyin)
- **Source:** [wanshuiyin/ARIS-Movie-Director](https://github.com/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.

Versions

  • v0.1.0 Imported from the upstream source.