From bc9c8dab438a50bbd6d46dcbc94d8862a3d7011b Mon Sep 17 00:00:00 2001 From: Miao Yang Date: Wed, 24 Jun 2026 00:46:03 +0800 Subject: [PATCH 1/2] fix(music-to-video): make Step 3 plan a hard gate so it can't be skipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare Step-2 skeleton (frames with `### Groups: TBD (Step 3)`) satisfied every hard check in validate-plan.mjs and exited 0, so the Step-3 gate ("validate-plan exits 0") was passable without ever filling the plan — an orchestrator could go straight from skeleton to building frames. - validate-plan.mjs: a frame with no filled group is now a HARD error (exit 1), not a warning. A skeleton can no longer clear the Step-3 gate. - frame-worker.md: add a one-line hard stop — a TBD/empty `### Groups` means Step 3 was skipped; report back and write nothing, never improvise. - SKILL.md: lengthen Step 2/3/4 titles so the plan reads as a required gate (structure-only skeleton -> fill the plan -> build from the plan). Co-Authored-By: Claude Opus 4.8 (1M context) --- skills/music-to-video/SKILL.md | 6 +++--- skills/music-to-video/scripts/validate-plan.mjs | 17 +++++++++++++---- .../music-to-video/sub-agents/frame-worker.md | 1 + 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/skills/music-to-video/SKILL.md b/skills/music-to-video/SKILL.md index 20d8e460e..7aa224478 100644 --- a/skills/music-to-video/SKILL.md +++ b/skills/music-to-video/SKILL.md @@ -63,7 +63,7 @@ python3 /scripts/analyze-beatgrid.py "$PROJECT_DIR/assets/bgm.mp3" \ --- -## Step 2: Frame skeleton +## Step 2: Frame skeleton (structure only) Goal: Read the music and lay out the frames — the skeleton of `STORYBOARD.md`. @@ -73,7 +73,7 @@ Read [`references/frame-skeleton.md`](references/frame-skeleton.md). Turn `audio --- -## Step 3: Plan (user-gated) +## Step 3: Fill the plan (user-gated) Goal: Turn the skeleton into an approved, complete `STORYBOARD.md`. @@ -93,7 +93,7 @@ Fix every `✗` (hard errors: duration mismatch, frames not tiling the track, a --- -## Step 4: Build frames +## Step 4: Build frames from the plan Goal: Build every frame as a self-contained composition file. diff --git a/skills/music-to-video/scripts/validate-plan.mjs b/skills/music-to-video/scripts/validate-plan.mjs index 81ffe9067..972365fc0 100644 --- a/skills/music-to-video/scripts/validate-plan.mjs +++ b/skills/music-to-video/scripts/validate-plan.mjs @@ -4,7 +4,10 @@ // not exist yet), so it checks fields, not on-disk html. // // HARD (exit 1): frontmatter duration_s == audiomap duration; >=1 frame; each frame -// has src + positive duration; frames tile the track gap-free (sum == duration_s). +// has src + positive duration; frames tile the track gap-free (sum == duration_s); +// every frame has >=1 filled group (a frame whose `### Groups` is still +// `TBD (Step 3)`/empty is a Step-2 skeleton, not an approved plan — fail it so a +// skipped Step 3 can never reach the build step). // WARN (exit 0): best-effort group checks — each group exactly one of // template/free_design/asset; a template id exists under the --templates dir; // phrase_flow frame has no beat_cut asset treatment. @@ -114,12 +117,18 @@ for (const ln of raw.split(/\r?\n/)) { if (cur) cur.lines.push(ln); } +// HARD: every frame needs >=1 filled group. A frame with no parseable group head +// is still a Step-2 skeleton (`### Groups` = `TBD (Step 3)`/empty) — erroring here +// is what stops a skipped Step 3 from reaching the build step. const framesWithGroups = new Set(blocks.map((b) => b.frameLabel)); for (const f of frames) { + const title = String(f.title ?? "").trim(); const lbl = `Frame ${f.number ?? ""} — ${f.title ?? f.index}`.replace(/\s+—\s+$/, ""); - if (![...framesWithGroups].some((s) => s.includes(String(f.title ?? "")))) { - warns.push( - `${lbl}: no parseable groups (expected \`- **gN** — template|free_design|asset …\`)`, + const hasGroup = title !== "" && [...framesWithGroups].some((s) => s.includes(title)); + if (!hasGroup) { + errors.push( + `${lbl}: no filled groups — still a Step-2 skeleton (\`### Groups\` is TBD/empty). ` + + `Run Step 3: fill its groups (\`- **gN** — template|free_design|asset …\`) before building.`, ); } } diff --git a/skills/music-to-video/sub-agents/frame-worker.md b/skills/music-to-video/sub-agents/frame-worker.md index 2c8d1ac4e..b725c169f 100644 --- a/skills/music-to-video/sub-agents/frame-worker.md +++ b/skills/music-to-video/sub-agents/frame-worker.md @@ -34,6 +34,7 @@ If your dispatch carries lint / validate feedback from a prior pass, address eac ## What comes fixed — realize it as given +- **No plan = stop.** If your `### Groups` is `TBD`/empty (Step 3 was skipped), report back and write nothing — never invent groups, templates, or copy. - **The plan** is set in your `## Frame` block: the groups, templates / primitives, copy, brand, and anchors. Build it as written. If a plan is genuinely wrong (wrong template or copy), stop and report — the orchestrator re-plans at Step 3. From f4aeff6fafd2f79e0dc2322e45f09fb828269689 Mon Sep 17 00:00:00 2001 From: Miao Yang Date: Wed, 24 Jun 2026 01:00:44 +0800 Subject: [PATCH 2/2] fix(product-launch-video): stop frame-worker CLI runs, relax layout checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the fixes already landed in pr-to-video: - frame-worker no longer told to run the hyperframes CLI — the frame isn't assembled yet, so lint/validate/inspect report on other files (a false green); the self-check is now "re-read against the contract" and the orchestrator runs the CLI at Step 6 after assembly. - add the `#root` root-styling rule (`subcomposition_root_styled_by_class`): a class on the composition-id element scopes to a non-matching descendant selector, so the whole scene renders unstyled. - drop `--strict-layout` from the orchestrator's `inspect` step. - document the caption `text_box_overflow` false-positive (~1-4px on `#caption-word-*`) so it isn't chased. Co-Authored-By: Claude Opus 4.8 (1M context) --- skills/product-launch-video/SKILL.md | 4 +++- .../product-launch-video/sub-agents/frame-worker.md | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/skills/product-launch-video/SKILL.md b/skills/product-launch-video/SKILL.md index c09782289..78ce4873c 100644 --- a/skills/product-launch-video/SKILL.md +++ b/skills/product-launch-video/SKILL.md @@ -155,12 +155,14 @@ Inject transitions, run checks, pause for review, then render. `npx hyperframes validate` -`npx hyperframes inspect --strict-layout` +`npx hyperframes inspect` `npx hyperframes snapshot --at ` If a command fails, surface stderr and stop. Do not pile on recovery commands. If a gate names a frame, fix `compositions/frames/NN-*.html` with the cheapest safe fix: edit the frame HTML for a local issue; re-dispatch the frame worker only when the whole shot must be rebuilt. +**Known false-positive — do not chase it.** `inspect` may report a handful of `text_box_overflow` errors of ~1–4px on the **caption** highlight words (selector `#caption-word-*` / `.caption-line`). The caption pill uses a deliberately snug `line-height` (set once in `scripts/captions.mjs`) and has **no `overflow:hidden`**, so a heavy display glyph's ink spills a few px into the pill's own padding — nothing is actually clipped. Treat these as expected and proceed. Do **not** inflate the caption `line-height` (it balloons the pill, which is worse) and do **not** re-dispatch a frame for them. Only act on a `text_box_overflow` when it names a **frame** element (`#el-NN-*`), not a caption word. + After checks pass, pause for user review. The video is assembled, viewable, and editable in Studio. Manage preview only once across Step 3 and Step 6: open it if the user asked earlier, offer it if they declined earlier, and do not ask again if they are already reviewing in Studio. Preview: `npx hyperframes preview` diff --git a/skills/product-launch-video/sub-agents/frame-worker.md b/skills/product-launch-video/sub-agents/frame-worker.md index 23cc898b9..7930f3ac1 100644 --- a/skills/product-launch-video/sub-agents/frame-worker.md +++ b/skills/product-launch-video/sub-agents/frame-worker.md @@ -19,7 +19,7 @@ **Retry** — if your context carries lint / validate feedback from a prior pass, read it first and re-author so none of those findings recur; treat each as a hard constraint. -**OUTPUT** — `compositions/frames/.html`, one self-contained sub-composition. Writing it (past the self-check) is your **terminal action** — you do not edit `STORYBOARD.md`, mint audio, assemble the index, or report back. The orchestrator picks up the file and marks the frame's `status`. +**OUTPUT** — `compositions/frames/.html`, one self-contained sub-composition. Writing it (past the self-check below) is your **terminal action** — you do not edit `STORYBOARD.md`, mint audio, assemble the index, run the CLI, or report back. The orchestrator picks up the file and marks the frame's `status`. ## You do NOT decide @@ -48,15 +48,16 @@ Generic seek-safety + structure live in `hyperframes-core` (read it; not restate 1. **Read** — `hyperframes-core`'s composition contract (the structural law), then `frame.md` (the look) and your `## Frame N` block (content + effects / blueprint / assets). **Then open the recipe body of every id the block cites** — `ANIM_DIR/rules/.md` per effect and `ANIM_DIR/blueprints/.md` for the blueprint (plus its linked `examples/.html` when the recipe is unclear): you reproduce these, not improvise them. Internalize the self-check codes below before you write — most lethal is **template transport**: every `