{"slug":"method-figure","title":"method-figure","summary":"Generate a publication-grade method / architecture / pipeline / workflow figure (a paper or README 'Figure 1') as an AUDITABLE object, not a one-shot prompt. A deterministic JSON blueprint LOCKS the content; an image model (gpt-image-2, baked by the agent via mcp__codex__codex — ","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-06T17:21:27.083375Z","repo":{"url":"https://github.com/wanshuiyin/ARIS-Movie-Director","stars":62,"forks":4,"license":"MIT","updatedAt":"2026-09-09T05:40:53Z"},"bodyHtml":"<hr>\n<h2>name: method-figure\ndescription: \"Generate a publication-grade method / architecture / pipeline / workflow figure (a paper or README 'Figure 1') as an AUDITABLE object, not a one-shot prompt. A deterministic JSON blueprint LOCKS the content; an image model (gpt-image-2, baked by the agent via mcp__codex__codex — Codex GPT-5.5 xhigh, sandbox workspace-write) bakes the aesthetic from a labeled-condition render + the project's real identity refs; a cross-model panel (Gemini + Codex) blind-transcribes the result and a script hard-diffs it against the blueprint; the loop regenerates until Gemini AND Codex approve and the diff is empty — then the calling agent (Claude) gives the structural sign-off. NOT for statistical plots (use a plotting tool) or photo scenes.\"\nargument-hint: [method_figure_brief.json | blueprint.json]\nallowed-tools: Bash(*), Read, Write, Edit, Grep, Glob, mcp__codex__codex, mcp__codex__codex-reply, mcp__gemini-cli__ask-gemini, mcp__gemini__chat</h2>\n<h1>method-figure</h1>\n<p>Turn \"draw our method figure\" from a one-shot gamble into the <strong>same audited spiral the framework uses for\ncomics</strong>: a blueprint is the source of truth, the image model bakes the look, a cross-model panel + a\ndeterministic diff keep it honest, and the loop converges to a publication-grade figure that is\n<strong>reproducible</strong> (re-run the blueprint) and <strong>auditable</strong> (a trace of every round).</p>\n<p>Two things are simultaneously true: (a) gpt-image-2 CAN render a clean Figure-1 with legible labels when\n<em>conditioned on a labeled blueprint</em> — do not assume it garbles text; (b) on a free prompt it DRIFTS\n(renames phases, invents nodes, garbles a token, leaves pasted-looking floating labels). The blueprint +\nblind-transcribe-then-hard-diff loop turns (a) into a reliable result and catches (b) every round.</p>\n<pre><code>  system description ─▶ ① BLUEPRINT (JSON content-lock)  ── validate_blueprint.py\n                              ▼\n                        ② CONDITION (white-bg labeled SVG → PNG) + identity sheet (real chibi, optional)  ── render_condition.py --png\n                              ▼\n                        ③ BAKE  — agent: mcp__codex__codex(prompt+abs ref paths+out_path, workspace-write, gpt-5.5, config{xhigh}) → gpt-image-2 native PNG  ── pickup_image.py --out-existing (sig+size+dims, mtime-bound, HARD-VETO struct/zlib/PIL/SVG, fail-closed)\n                              ▼\n                        ④ PANEL — Gemini ‖ Codex BLIND-transcribe → content_diff.py (observed ⊖ blueprint) → Claude structural sign-off\n                              ▼\n                        ⑤ agent reads the diff + the panel blockers → re-bake re-asserting the locked labels\n                              ▼\n                   converged? ─ no ─▶ ③   (bounded: max_rounds → escalate to human)\n                              │ yes\n                              ▼\n                        ⑥ APPROVE → figure.png + blueprint.json + trace.jsonl\n</code></pre>\n<h2>Constants</h2>\n<ul>\n<li><strong>GENERATOR</strong> = Codex <code>gpt-5.5</code>, <code>config: {model_reasoning_effort: xhigh, include_image_gen_tool: true}</code> → the native <code>image_generation</code>\ntool (gpt-image-2). The <code>gpt-5.5</code> pin is a single hardcoded COMPAT DEFAULT in the bake sidecar payload\n(<code>run_spiral.py</code> mirrors <code>run_comic.py</code>'s canonical bake plan; a config-driven model override is PLANNED, not\nyet implemented). It pins the BAKE only — the panel's Codex reviewer is un-pinned (see PANEL below).\n<strong>CRITICAL</strong>: image_gen is produced ONLY via <strong><code>mcp__codex__codex</code></strong> (the agent tool), NOT\n<code>codex exec</code>. <code>codex exec</code> / over-specified / forbid-list prompts make Codex hand-draw a code fallback\n(struct+zlib PNG or SVG/matplotlib) — visually indistinguishable for trivial shapes, useless for a real\nmethod-figure. The working invocation is <strong><code>mcp__codex__codex</code></strong> with a <strong>dead-simple</strong> prompt +\n<code>sandbox: \"workspace-write\"</code> (it must WRITE the out_path) + <code>model: \"gpt-5.5\"</code> +\n<code>config: {model_reasoning_effort: \"xhigh\", include_image_gen_tool: true}</code> (the schema has NO top-level effort\nparam; <code>config{xhigh}</code> shorthand below ALWAYS expands to <strong>both</strong> these keys — without <code>include_image_gen_tool</code>\ncodex won't fire its native image tool, it falls back to descriptive text / an SVG renderer) + <code>cwd: &lt;project&gt;</code>.\nReference images are passed by <strong>absolute file path inside the prompt</strong> (the schema has NO <code>-i</code>); the output\npath is a <strong>deterministic abs path</strong> in the prompt. Pick it up with <strong><code>pickup_image.py --out-existing</code></strong>\n(verifies the EXPLICIT out_path: PNG sig + size + dims, <code>mtime &gt;= request.created_at</code>) which <strong>HARD-VETOES</strong>\nstruct/zlib/PIL/<code>&lt;svg&gt;</code>/matplotlib markers in the agent transcript (fail-closed; there is <strong>no</strong> 'native sig\nwins' override). <strong>Honesty caveat:</strong> as of Jun 2026 native headless persistence is unreliable, so this\nfail-closed verifier — not any <code>sandbox</code> setting — is the first guard against a non-native bake. But the\nHARD-VETO is a <strong>BEST-EFFORT denylist</strong> against the <em>known</em> codex-exec hand-draw fallback (struct/zlib/PIL/\nSVG/matplotlib markers), <strong>NOT a complete security boundary</strong> — a novel fallback that emits a sig-valid PNG\nwithout those markers can slip past it. The <strong>load-bearing faithfulness gate is the cross-model blind-transcribe\npanel + the deterministic <code>content_diff</code></strong> (the pixels are what reviewers transcribe), with this denylist as a\ncheap upstream filter.</li>\n<li><strong>PANEL</strong> (automated blind-transcribe) = the orchestrator SHELLS the <code>gemini</code> + <code>codex</code> CLIs as subprocesses\n(both must be on PATH; MCP is ONLY the bake seam): Gemini = <code>gemini --model auto-gemini-3</code>; Codex =\n<code>codex exec -i &lt;png&gt;</code> with NO model pin (it follows the local codex config — currently <code>gpt-5.6-sol</code>) at\neffort <code>xhigh</code> — so the reviewer model ≠ the bake's pinned <code>gpt-5.5</code>. Plus the deterministic <code>content_diff</code>.\n<strong>Claude (this agent) is the post-pass STRUCTURAL sign-off, not a blind transcriber</strong> — the loop converges on\nGemini-approve + Codex-approve + empty-diff, then Claude signs off.</li>\n<li><strong>CROSS-MODEL ACQUITTAL</strong> — Codex is the generation family, so a Codex <code>approve</code> can only <em>diagnose/veto</em>,\nnever be the sole acquitter. ACCEPT requires <strong>Gemini approve + Claude structural approve + the hard-diff empty</strong>.</li>\n<li><strong>MAX_ROUNDS</strong> = 4, then escalate to human with best-so-far + open blockers.</li>\n<li><strong>LABEL_POLICY</strong> = <strong><code>baked</code> only in v0</strong> — the image model renders ALL text; nothing is hand-pasted.\n(<code>hybrid</code>/<code>overlay</code> — lock structure + vector-overlay the labels for paper zero-tolerance text — are on the\nv1 roadmap; do NOT use a vector overlay as an ad-hoc patch on a finished bake, it reads as pasted.)</li>\n<li><strong>OUTPUT_DIR</strong> = <code>figures/method_figure/&lt;figure_id&gt;/</code> (figure.png, blueprint.json, condition.svg, trace.jsonl).</li>\n<li><strong>NATIVE-IMAGE FAIL-CLOSED</strong> — accept a bake ONLY if a real native PNG exists at the <strong>explicit out_path</strong>,\nsha/size/dims check out and <code>mtime &gt;= request.created_at</code>, and the agent transcript shows <strong>no</strong> struct/zlib/\nPIL/<code>&lt;svg&gt;</code>/matplotlib fallback (<code>pickup_image.py --out-existing</code>, HARD-VETO — a clean sig never overrides a\nfallback marker). This veto is a <strong>BEST-EFFORT denylist</strong> against the <em>known</em> codex-exec hand-draw fallback,\n<strong>NOT a complete security boundary</strong> (a novel marker-free fallback could evade it). The load-bearing\nfaithfulness gate remains the <strong>cross-model blind-transcribe panel + the deterministic <code>content_diff</code></strong>; the\ndenylist is a cheap upstream filter that matters because native headless persistence is currently unreliable.</li>\n<li><strong>SERIALIZE BAKES</strong> — never run two image generations at once. The default <code>--bake-mode=agent</code> writes each\nnative PNG to its <strong>explicit per-round <code>out_path</code></strong> (no shared dir), so concurrent agent bakes still risk a\nrequest/status sidecar race — keep one runner per figure. (The global <code>~/.codex/generated_images</code> dir +\nnewest-after-marker pickup that could cross-pollinate concurrent bakes is a hazard of the <strong>LEGACY\n<code>--bake-mode=exec</code> path ONLY</strong>, which is retired for real bakes.)</li>\n</ul>\n<h2>Input contract / ARIS hand-off (who decides WHAT, who only renders)</h2>\n<p>This skill is <strong>pure render + verify</strong>. Ownership:</p>\n<ul>\n<li><strong>Upstream owns the semantics</strong> — what to depict, the labels, the graph, the grouping, the headline\nclaim/number, the identity refs. method-figure does NOT choose content and <strong>must not invent</strong> a node,\nclaim, number, or method structure (if one is missing it ESCALATES, it does not make it up).</li>\n<li><strong>Step-0 is now DETERMINISTIC</strong> — <code>run_spiral.py</code> calls <a href=\"scripts/compile_brief.py\"><code>scripts/compile_brief.py</code></a>\nto map a <code>method_figure_brief.json</code> → a schema-valid <code>blueprint.json</code> + <code>traceability.json</code>, fail-closed (an\nobject that can't trace to a brief field, or a missing claim/number/trait, is refused — not invented). It is\nno longer a manual LLM hop. Full field map + the guards: <code>references/blueprint_authoring.md</code>.</li>\n<li><strong>method-figure owns</strong> validation → condition render → image bake → cross-model panel → diff → retry, and\nhas VETO power: it returns <code>FAILED / Logic Drift</code> rather than ship a figure whose pixels contradict the blueprint.</li>\n</ul>\n<p><strong>The default single input</strong> = a <strong><code>method_figure_brief.json</code></strong> (<code>schemas/method_figure_brief.schema.json</code>) —\nONE ARIS-format file; the blueprint, the coordinates, and the identity wiring are all derived. The identity\nsheet is resolved from the brief's <code>identity_refs[].path</code> (no separate <code>--identity</code> to manage). <strong>Where the\ninput comes from</strong>, in authority order:</p>\n<ol>\n<li><strong>a <code>method_figure_brief.json</code></strong> — the canonical ARIS hand-off (what <code>paper-plan</code> emits); auto-detected (by\nits <code>schema_version: \"method-figure/brief/v1\"</code>) + compiled. ·</li>\n<li>an existing hand-tuned <code>blueprint.json</code> — power-user override (<code>--from-blueprint</code>, used as-is). ·</li>\n<li>an <code>experiment-plan</code> / <code>paper-write</code> method section / free-text — no brief yet: the agent first DRAFTS a\n<code>method_figure_brief.json</code> from it (claims/numbers verbatim; anything missing → Refuse-and-Escalate), then compiles.</li>\n</ol>\n<p><strong>ARIS integration:</strong> the canonical producer is <strong><code>paper-plan</code></strong> — after its <code>claims_matrix</code> it emits the\n<code>method_figure_brief</code> (components, flows, phases, the headline claim/number, identity refs, <code>forbidden_tokens</code>).\nYou feed that <strong>one file</strong> to <code>run_spiral.py</code>; Step-0 compiles it and the traceability is enforced by the\ncompiler (an un-traceable object is a Refuse-and-Escalate, not a render). The identity sheet is created once\nupstream and locked; method-figure only reads it.</p>\n<h2>Fast path — one command (single input: a brief)</h2>\n<p>Feed ONE <code>method_figure_brief.json</code>; the whole loop is one command (all commands below run from the <strong>repo\nroot</strong>; the panel shells the <code>gemini</code> + <code>codex</code> CLIs, so both must be on PATH):</p>\n<pre><code>python3 skills/method-figure/scripts/run_spiral.py your_method_figure_brief.json --out-dir figures/method_figure/&lt;id&gt;\n#  auto-detects a brief → Step-0 compile_brief.py → blueprint.json + traceability.json (deterministic, fail-closed)\n#  → validates → renders condition(+png) → [bake (agent: mcp__codex__codex --bake-mode=agent, workspace-write,\n#  gpt-5.5 config{xhigh} → gpt-image-2 native PNG via the .bakereq.json sidecar) → pickup_image.py --out-existing\n#  verify (fail-closed, HARD-VETO over the status file's mcp_output) → Gemini + Codex blind-transcribe → content_diff → blockers] × rounds\n#  → on PANEL-CLEAN writes figure.png + blueprint.json + traceability.json + trace.jsonl.\n#  input auto-detect: a brief is detected ONLY by schema_version \"method-figure/brief/v1\" vs a blueprint (version);\n#  a bare components+flows JSON with NO schema_version is REFUSED — it REQUIRES --from-brief (fail-closed, no guessing)\n#  --identity is OPTIONAL (resolved from the brief's identity_refs[0].path);  --dry-run prints the round-1 bake\n#  prompt;  --p0-only runs the zero-credit gate (validate+compile+render+prompt-lint) then stops;  --max-rounds N.\n#  There is NO --effort knob (the flag is removed) — bake + review effort are hardcoded xhigh by design.\n#  --gemini-cmd overrides how the google-family reviewer is shelled (default: the legacy `gemini` CLI). Legacy\n#  CLI dead (IneligibleTierError, 2026-07)? pass --gemini-cmd \"python3 cli/gemini_agy_shim.py\" — the shipped\n#  Antigravity shim pins a Gemini model (the second-reviewer slot must stay google-family for quorum honesty).\n</code></pre>\n<blockquote>\n<p><strong>Power-user / override:</strong> already have a hand-tuned blueprint? <code>run_spiral.py blueprint.json --identity sheet.png --out-dir … --from-blueprint</code> runs the legacy path unchanged. A worked example brief lives at\n<a href=\"examples/method_figure/method_figure_brief.json\"><code>examples/method_figure/method_figure_brief.json</code></a>.\nLong-running (each bake ~3-8 min) — run it in the background; watch <code>trace.jsonl</code>. It converges to\n<strong>PANEL-CLEAN</strong> — BOTH reviewers returned parseable JSON, <strong>Gemini approve AND Codex approve</strong>, the\ndeterministic <code>content_diff</code> empty, core scores (incl. <code>character_identity</code> when an identity sheet is given)\n≥ threshold, and no anomalies/blockers — then STOPS and hands to the calling agent (Claude) for the final\n<strong>structural</strong> sign-off (the generator family never self-acquits). The manual steps below are exactly what\n<code>run_spiral.py</code> automates (run them to debug one stage).</p>\n</blockquote>\n<h2>Who runs <code>--bake-mode=agent</code> (the agent-wrapper SOP) — REQUIRED for the default mode to function</h2>\n<p>The bake is a <strong>synchronous sidecar handshake</strong> and the <strong>skill agent</strong> is its fulfiller (without it, every bake\npolls to <code>--bake-timeout</code> and escalates with <code>failure_kind=\"other\"</code> — fail-closed, not a hang, never a false throttle):</p>\n<ol>\n<li>Launch the orchestrator in the <strong>BACKGROUND</strong> (from the repo root):\n<code>python3 skills/method-figure/scripts/run_spiral.py your_brief.json --out-dir figures/method_figure/&lt;id&gt; --bake-mode agent</code>.</li>\n<li><strong>Loop</strong> until it prints PANEL-CLEAN / escalates / exits:\n<ul>\n<li>watch <code>&lt;out-dir&gt;/</code> for a new <code>*.bakereq.json</code> (the orchestrator writes <code>round&lt;N&gt;.png.bakereq.json</code>);</li>\n<li>read it; call <code>mcp__codex__codex</code> with <strong>exactly</strong> its\n<code>{prompt: &lt;prompt_text&gt;, model:\"gpt-5.5\", config:{include_image_gen_tool:true, model_reasoning_effort:\"xhigh\"}, sandbox:\"workspace-write\", cwd:&lt;cwd&gt;}</code>\n(codex writes the native PNG to the sidecar's <code>out_path</code>). The <code>config</code> MUST carry <strong>both</strong>\n<code>include_image_gen_tool:true</code> AND <code>model_reasoning_effort:\"xhigh\"</code>: without <code>include_image_gen_tool</code> Codex will\n<strong>not</strong> fire its native <code>gpt-image-2</code> tool (it falls back to a struct/zlib/SVG hand-draw), and <code>xhigh</code> is the\nrequired reasoning tier;</li>\n<li>then read <code>request_id</code> from the <code>*.bakereq.json</code> and write <code>&lt;out&gt;.bakestatus.json</code> carrying <strong>the status, a\nbounded raw <code>mcp_output</code>, AND that <code>request_id</code> VERBATIM</strong> — <code>mcp_output</code> so the HARD-VETO can scan it (the core\nfeeds this file to <code>pickup --transcript</code>; an <code>ok</code> status with no raw output makes the veto INERT), and\n<code>request_id</code> because <code>pickup_image.py --out-existing --request-id</code> <strong>fail-closes the bake if the status\n<code>request_id</code> is missing or mismatched</strong> (write it on BOTH ok and fail):\n<code>{\"status\":\"ok\",\"mcp_output\":\"&lt;raw&gt;\",\"request_id\":\"&lt;verbatim from bakereq&gt;\"}</code>, or\n<code>{\"status\":\"fail\",\"failure_kind\":\"throttle\",\"mcp_output\":\"&lt;raw&gt;\",\"request_id\":\"&lt;verbatim from bakereq&gt;\"}</code> on a\n429 / <code>MODEL_CAPACITY_EXHAUSTED</code> / overloaded error (else\n<code>{\"status\":\"fail\",\"failure_kind\":\"other\",\"mcp_output\":\"&lt;raw&gt;\",\"mcp_error\":\"&lt;raw&gt;\",\"request_id\":\"&lt;verbatim from bakereq&gt;\"}</code>).</li>\n</ul>\n</li>\n<li>The core proceeds to verify ONLY on <code>status:\"ok\"</code>, via <code>pickup_image.py --out-existing</code> (sig + dims + size &gt;\n500000 + <code>mtime &gt;= created_at</code>, HARD-VETO over <code>mcp_output</code>, and <code>--request-id</code> fail-close if the status\n<code>request_id</code> is absent/mismatched). <code>--bake-mode=exec</code> is the legacy/CI non-image path and RAISES if it reaches a real bake.</li>\n</ol>\n<h2>Workflow (what run_spiral.py automates — or run by hand)</h2>\n<h3>① Author the BLUEPRINT (content lock)</h3>\n<p>Write <code>blueprint.json</code> per <code>schemas/blueprint.schema.json</code>. The <code>*_exact</code> fields (<code>label_exact</code>, <code>desc_exact</code>,\ngroup/edge/callout <code>*_exact</code>, <code>rail.label_exact</code>) are the <strong>LOCKED text re-asserted verbatim every round</strong>;\n<code>expected_tokens[]</code> are what the panel must blind-transcribe and the diff checks. Then:</p>\n<pre><code>python3 skills/method-figure/scripts/validate_blueprint.py blueprint.json   # jsonschema (if installed) + unique ids · edges resolve · box/group/callout bounds · no dup labels\n</code></pre>\n<h3>② Render the CONDITION</h3>\n<pre><code>python3 skills/method-figure/scripts/render_condition.py blueprint.json --out condition.svg --png condition.png   # white-bg labeled layout → rasterized\n</code></pre>\n<p>Prepare <code>identity_sheet.png</code> from the project's REAL characters if the figure has any (never invent robots).\nThe condition PNG + the identity sheet are the two image references.</p>\n<h3>③ BAKE (round N) — agent seam</h3>\n<p>Call <code>mcp__codex__codex</code> (sandbox <strong>workspace-write</strong> — it must WRITE the out_path; <code>model: gpt-5.5</code>,\n<code>config: {model_reasoning_effort: xhigh, include_image_gen_tool: true}</code>, <code>cwd: &lt;project&gt;</code>) with the prompt from\n<code>references/prompt_templates.md §A</code> — it RE-ASSERTS every <code>*_exact</code> label + the round-N blockers + the carried\n<code>positive_invariants</code>, with <code>condition.png</code> + <code>identity_sheet.png</code> referenced by <strong>absolute path inside the\nprompt</strong> (the schema has no <code>-i</code>) and the <strong>exact out_path</strong> to save the native PNG. Write the bake status to\n<code>round&lt;N&gt;.png.bakestatus.json</code> carrying the raw <code>mcp_output</code> (so the HARD-VETO can scan it) <strong>AND the\n<code>request_id</code> copied VERBATIM from <code>round&lt;N&gt;.png.bakereq.json</code></strong> (pickup <code>--request-id</code> fail-closes if it's\nmissing/mismatched), then verify the explicit out_path (no marker/glob):</p>\n<pre><code>python3 skills/method-figure/scripts/pickup_image.py --out-existing --out figures/method_figure/&lt;id&gt;/round&lt;N&gt;.png --min-bytes 500000 --aspect &lt;W/H&gt; --created-at &lt;epoch&gt; --request-id &lt;uuid4 hex from round&lt;N&gt;.png.bakereq.json&gt; --transcript figures/method_figure/&lt;id&gt;/round&lt;N&gt;.png.bakestatus.json\n</code></pre>\n<h3>④ PANEL — blind transcribe, then hard diff</h3>\n<p>Ask each of the TWO blind transcribers — Gemini + Codex (<code>references/prompt_templates.md §B</code>) — for the STRICT\nJSON of <code>references/reviewer_protocol.md</code>: they transcribe <code>observed_tokens</code> / <code>observed_edges</code> /\n<code>identity_audit</code> and an <code>anomalies</code> list (the <strong>Negative-Space Audit</strong>), NOT shown the expected labels.\n<strong>Claude is NOT a transcriber</strong> — it never produces a blind <code>round&lt;N&gt;.cc.json</code>; its structural sign-off comes\npost-pass in ⑤/⑥. Save as <code>round&lt;N&gt;.{gemini,codex}.json</code>, then:</p>\n<pre><code>python3 skills/method-figure/scripts/content_diff.py blueprint.json round&lt;N&gt;.gemini.json round&lt;N&gt;.codex.json\n# → missing_tokens / unaccounted_tokens / anomalies ; empty == content-accurate\n</code></pre>\n<h3>⑤ Decide (stop rule) — the agent consolidates</h3>\n<p>Read the diff report + the two transcribers' <code>blockers</code>. The executing agent itself merges <strong>blockers only</strong>\n(ignore <code>nice_to_have</code> — chasing polish makes it oscillate), carries the union of <code>positive_invariants</code>\nforward, and writes the round-N+1 bake prompt.</p>\n<ul>\n<li><strong>ACCEPT</strong> iff: diff has no <code>missing_tokens</code>/<code>anomalies</code> · Gemini <code>approve</code> · Codex <code>approve</code> (required, but\nnever the sole acquitter) · Claude structural <code>approve</code> · every core score ≥ <code>acceptance.min_core_score</code> (default 4).</li>\n<li><strong>RETRY</strong> iff: blockers are prompt/condition-fixable and <code>round &lt; MAX_ROUNDS</code> → back to ③.</li>\n<li><strong>ESCALATE</strong> to human iff: same root failure 2 rounds · irreconcilable reviewers · MAX_ROUNDS hit · or a\nnon-prompt-fixable failure (throttle / identity drift / no native image).</li>\n</ul>\n<h3>⑥ Finalize + trace</h3>\n<p>On ACCEPT: copy the approved PNG to <code>figures/method_figure/&lt;id&gt;/figure.png</code>, keep <code>blueprint.json</code>, and append\nto <code>trace.jsonl</code> per round: <code>{round, blueprint_sha, condition_sha, generated_sha, reviewers:{...verdicts}, hard_diff:{missing_tokens,anomalies}, fixes:[...], decision}</code> + a final <code>{final_approve, image, blueprint, accepted_round, verdicts}</code>. Failures are kept — the fixes that were needed are the memory (the figure-wiki).</p>\n<h2>Hard do / don't (earned lessons)</h2>\n<ul>\n<li><strong>DO</strong> lock content in the blueprint and RE-ASSERT every <code>*_exact</code> label in every regeneration — image\nmodels drift content every round; the blueprint is the anchor.</li>\n<li><strong>DO</strong> bake via the agent (<code>mcp__codex__codex</code>, workspace-write) and fail-closed if no real native PNG at the\nexplicit out_path (<code>pickup_image.py --out-existing</code>, HARD-VETO struct/zlib/PIL/<code>&lt;svg&gt;</code>/matplotlib in the status file's <code>mcp_output</code>).</li>\n<li><strong>DO</strong> use the project's real identity refs; anchor each character to the identity sheet. For a character\nfigure, every reviewer ENUMERATES each chibi's visible hands — a wrong count / 3rd / floating / merged limb\nis a single-reviewer veto (the literal-diff is blind to anatomy).</li>\n<li><strong>DO</strong> run the zero-credit P0 gate before the first metered bake: <code>run_spiral.py brief.json --out-dir … --p0-only</code>\n(validate brief → compile blueprint → render condition → confirm the bake prompt carries ALL locked labels,\nthe identity path resolves, the background is white). A blocker caught here costs zero image credits.</li>\n<li><strong>DON'T</strong> regenerate when the score-signature is IDENTICAL across rounds — that means the judge is broken\n(gone design-blind), not the figure. Stop and audit the rubric (<code>feedback_gate_identical_scores_judge_broken</code>).</li>\n<li><strong>DON'T</strong> hand-paste text onto a finished bake (reads as pasted/fake) — that is what burned us; the whole\nfigure, text included, is generated. (Engineered vector overlay is a future <em>policy</em>, not a patch.)</li>\n<li><strong>DON'T</strong> use a dark theme for a paper/README figure — light/pastel on white.</li>\n<li><strong>DON'T</strong> let one model (especially the generator's family) self-acquit; the panel is cross-model.</li>\n</ul>\n<h2>Scope</h2>\n<table>\n<thead>\n<tr>\n<th>Figure type</th>\n<th>Fit</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>method overview / pipeline / architecture / workflow</td>\n<td><strong>excellent</strong></td>\n</tr>\n<tr>\n<td>conceptual / taxonomy / comparison diagrams</td>\n<td>good</td>\n</tr>\n<tr>\n<td>statistical plots</td>\n<td>no → plotting tool</td>\n</tr>\n<tr>\n<td>exact-topology deterministic vector figures</td>\n<td>prefer a pure-vector renderer</td>\n</tr>\n<tr>\n<td>photo-realistic scenes / long narrative comics</td>\n<td>no (comics use the framework's spiral engine)</td>\n</tr>\n</tbody>\n</table>\n<p>A converged worked example ships in <code>examples/method_figure/</code>: the ARIS-Movie-Director Figure 1 — blueprint +\nfigure.png + condition.svg + the real 4-round <code>trace.jsonl</code> (Gemini approve + Codex approve + empty diff, then\nClaude's structural sign-off). <code>PROMPTS.md</code> there\npublishes the <strong>exact, unedited prompt sequence</strong> that baked it (all 4 <code>gpt-image-2</code> bakes + the cross-model\ncritiques, paths redacted) — the canonical exhibit of <em>how detailed a condition must be</em>; copy its shape.</p>\n<h2>Implemented / roadmap</h2>\n<ul>\n<li>✅ <code>scripts/compile_brief.py</code> — <strong>Step-0 automation</strong>: deterministic <code>method_figure_brief.json</code> →\n<code>blueprint.json</code> + <code>traceability.json</code> (the ADJ-4 field map, <code>auto_layout</code>, fail-closed\n<code>validate_traceability</code>). This is what makes the skill single-input — <code>run_spiral.py brief.json</code> auto-detects\n<ul>\n<li>compiles, so you never hand-write a blueprint or hand-place coordinates.</li>\n</ul>\n</li>\n<li>✅ <code>scripts/run_spiral.py</code> — the one-command orchestrator (sniff input → [Step-0 if brief] → bake→pickup→\npanel→diff→consolidate→decide loop to PANEL-CLEAN). <code>--p0-only</code> runs the zero-credit gate;\n<code>--from-brief</code>/<code>--from-blueprint</code> disambiguate. Folds blocker-consolidation + invariant-carry inline.</li>\n<li>\uD83D\uDD2D <code>scripts/overlay_labels.py</code> + <code>label_policy: hybrid/overlay</code> — vector-overlay the structured labels on\nthe bake for paper zero-tolerance text. Default stays <code>baked</code> (fully generated).</li>\n<li>\uD83D\uDD2D a Claude-vision reviewer inside the orchestrator (currently the automated panel is Gemini + Codex +\nthe deterministic diff; Claude — the calling agent — gives the structural sign-off on the converged figure).</li>\n</ul>\n<h2>Protocols (governance contracts this skill honors)</h2>\n<ul>\n<li><a href=\"../../protocols/reviewer-independence.md\"><code>reviewer-independence</code></a> — reviewers blind-transcribe from the image only; the generator (Codex image_gen) ≠ the visual judges.</li>\n<li><a href=\"../../protocols/acceptance-gate.md\"><code>acceptance-gate</code></a> — the loop drives, can't acquit: ACCEPT needs the deterministic content-diff clean + Gemini approve + Codex no-veto + Claude structural sign-off.</li>\n<li><a href=\"../../protocols/artifact-integrity.md\"><code>artifact-integrity</code></a> — the baker doesn't judge its own figure's numbers; the blueprint is ground truth, verified by the blind diff.</li>\n<li><a href=\"../../protocols/reviewer-routing.md\"><code>reviewer-routing</code></a> — bake sidecar pins Codex <code>gpt-5.5</code> + <code>xhigh</code> (a\nhardcoded compat default; config-driven override is planned); the CLI reviewers pin NO model (they follow the\nlocal codex config — currently <code>gpt-5.6-sol</code>) at <code>xhigh</code>; Gemini <code>auto-gemini-3</code>; never downgrade effort.</li>\n<li><a href=\"../../protocols/review-tracing.md\"><code>review-tracing</code></a> — every round's reviewer verdicts are logged to <code>trace.jsonl</code>.</li>\n</ul>\n","files":[{"path":"examples/method_figure/blueprint.json","sizeBytes":14681,"isText":true},{"path":"examples/method_figure/condition.svg","sizeBytes":11388,"isText":false},{"path":"examples/method_figure/figure.png","sizeBytes":132,"isText":false},{"path":"examples/method_figure/method_figure_brief.json","sizeBytes":3839,"isText":true},{"path":"examples/method_figure/PROMPTS.md","sizeBytes":22048,"isText":true},{"path":"examples/method_figure/trace.jsonl","sizeBytes":2054,"isText":false},{"path":"references/blueprint_authoring.md","sizeBytes":10492,"isText":true},{"path":"references/paper_to_brief.md","sizeBytes":4548,"isText":true},{"path":"references/prompt_templates.md","sizeBytes":4030,"isText":true},{"path":"references/reviewer_protocol.md","sizeBytes":5822,"isText":true},{"path":"schemas/blueprint.schema.json","sizeBytes":7364,"isText":true},{"path":"schemas/method_figure_brief.schema.json","sizeBytes":4156,"isText":true},{"path":"scripts/compile_brief.py","sizeBytes":29692,"isText":true},{"path":"scripts/content_diff.py","sizeBytes":7073,"isText":true},{"path":"scripts/pickup_image.py","sizeBytes":17210,"isText":true},{"path":"scripts/render_condition.py","sizeBytes":8258,"isText":true},{"path":"scripts/run_spiral.py","sizeBytes":31143,"isText":true},{"path":"scripts/validate_blueprint.py","sizeBytes":6418,"isText":true},{"path":"SKILL.md","sizeBytes":24892,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-06T17:22:00.194292Z","sha256":"64EFC9B2E4F7C1DD0B9D78830F1087E3826E8B7A25168A05D911BA04FE4243D3","sizeBytes":78881},"review":null,"source":{"repositoryUrl":"https://github.com/wanshuiyin/ARIS-Movie-Director","path":"skills/method-figure","license":"MIT","commit":"a1e7319f8a0af18511c300462e24b06091b4c927","subtreeSha":"F76D1C78DC03FC68ADBE564329B92076E205CFDE05F81EBC6CD0D182F3F6691B","lastSyncedAt":"2026-09-23T13:50:40.850809Z"},"reviewedAt":"2026-09-06T17:22:43.33376Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/wanshuiyin/ARIS-Movie-Director/tree/main/skills/method-figure"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wanshuiyin-aris-movie-director@llmmart"},{"target":"git","command":"git clone https://github.com/wanshuiyin/ARIS-Movie-Director.git"}]}