Skip to content

Commit 2681085

Browse files
Merge pull request #1672 from heygen-com/fix/skill-authoring-fixes
fix(skills): harden music-to-video Step 3 gate, align product-launch-video with pr-to-video
1 parent 7133c39 commit 2681085

5 files changed

Lines changed: 26 additions & 13 deletions

File tree

skills/music-to-video/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ python3 <SKILL_DIR>/scripts/analyze-beatgrid.py "$PROJECT_DIR/assets/bgm.mp3" \
6363

6464
---
6565

66-
## Step 2: Frame skeleton
66+
## Step 2: Frame skeleton (structure only)
6767

6868
Goal: Read the music and lay out the frames — the skeleton of `STORYBOARD.md`.
6969

@@ -73,7 +73,7 @@ Read [`references/frame-skeleton.md`](references/frame-skeleton.md). Turn `audio
7373

7474
---
7575

76-
## Step 3: Plan (user-gated)
76+
## Step 3: Fill the plan (user-gated)
7777

7878
Goal: Turn the skeleton into an approved, complete `STORYBOARD.md`.
7979

@@ -93,7 +93,7 @@ Fix every `✗` (hard errors: duration mismatch, frames not tiling the track, a
9393

9494
---
9595

96-
## Step 4: Build frames
96+
## Step 4: Build frames from the plan
9797

9898
Goal: Build every frame as a self-contained composition file.
9999

skills/music-to-video/scripts/validate-plan.mjs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
// not exist yet), so it checks fields, not on-disk html.
55
//
66
// HARD (exit 1): frontmatter duration_s == audiomap duration; >=1 frame; each frame
7-
// has src + positive duration; frames tile the track gap-free (sum == duration_s).
7+
// has src + positive duration; frames tile the track gap-free (sum == duration_s);
8+
// every frame has >=1 filled group (a frame whose `### Groups` is still
9+
// `TBD (Step 3)`/empty is a Step-2 skeleton, not an approved plan — fail it so a
10+
// skipped Step 3 can never reach the build step).
811
// WARN (exit 0): best-effort group checks — each group exactly one of
912
// template/free_design/asset; a template id exists under the --templates dir;
1013
// phrase_flow frame has no beat_cut asset treatment.
@@ -114,12 +117,18 @@ for (const ln of raw.split(/\r?\n/)) {
114117
if (cur) cur.lines.push(ln);
115118
}
116119

120+
// HARD: every frame needs >=1 filled group. A frame with no parseable group head
121+
// is still a Step-2 skeleton (`### Groups` = `TBD (Step 3)`/empty) — erroring here
122+
// is what stops a skipped Step 3 from reaching the build step.
117123
const framesWithGroups = new Set(blocks.map((b) => b.frameLabel));
118124
for (const f of frames) {
125+
const title = String(f.title ?? "").trim();
119126
const lbl = `Frame ${f.number ?? ""}${f.title ?? f.index}`.replace(/\s+\s+$/, "");
120-
if (![...framesWithGroups].some((s) => s.includes(String(f.title ?? "")))) {
121-
warns.push(
122-
`${lbl}: no parseable groups (expected \`- **gN** — template|free_design|asset …\`)`,
127+
const hasGroup = title !== "" && [...framesWithGroups].some((s) => s.includes(title));
128+
if (!hasGroup) {
129+
errors.push(
130+
`${lbl}: no filled groups — still a Step-2 skeleton (\`### Groups\` is TBD/empty). ` +
131+
`Run Step 3: fill its groups (\`- **gN** — template|free_design|asset …\`) before building.`,
123132
);
124133
}
125134
}

skills/music-to-video/sub-agents/frame-worker.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ If your dispatch carries lint / validate feedback from a prior pass, address eac
3434

3535
## What comes fixed — realize it as given
3636

37+
- **No plan = stop.** If your `### Groups` is `TBD`/empty (Step 3 was skipped), report back and write nothing — never invent groups, templates, or copy.
3738
- **The plan** is set in your `## Frame` block: the groups, templates / primitives, copy, brand,
3839
and anchors. Build it as written. If a plan is genuinely wrong (wrong template or copy), stop
3940
and report — the orchestrator re-plans at Step 3.

skills/product-launch-video/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,14 @@ Inject transitions, run checks, pause for review, then render.
155155

156156
`npx hyperframes validate`
157157

158-
`npx hyperframes inspect --strict-layout`
158+
`npx hyperframes inspect`
159159

160160
`npx hyperframes snapshot --at <frame-midpoints>`
161161

162162
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.
163163

164+
**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.
165+
164166
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.
165167

166168
Preview: `npx hyperframes preview`

skills/product-launch-video/sub-agents/frame-worker.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
**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.
2121

22-
**OUTPUT**`compositions/frames/<frame_id>.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`.
22+
**OUTPUT**`compositions/frames/<frame_id>.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`.
2323

2424
## You do NOT decide
2525

@@ -48,15 +48,16 @@ Generic seek-safety + structure live in `hyperframes-core` (read it; not restate
4848

4949
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/<id>.md` per effect and `ANIM_DIR/blueprints/<id>.md` for the blueprint (plus its linked `examples/<id>.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 `<style>` + `<script>` (including the gsap load) must live INSIDE `<template>`, because the runtime only clones template contents and `lint` / `validate` / `inspect` can miss the resulting blank sub-composition.
5050
2. **Design** — translate `scene` + the (sometimes shot-by-shot) narrative + the recipes you just read into a visual plan using `frame.md`'s components and type ramp. Honor the note's phases shot-by-shot per the whole-shot rule above, and find a visual idea that reinforces the beat, not a literal restyle of the words. Place the named assets.
51-
3. **Author** — write the full sub-composition to `compositions/frames/<frame_id>.html` (rewrite to iterate; last write wins). `<template>`-wrapped root carrying `data-composition-id="<frame_id>"`, exactly one `gsap.timeline({ paused: true })` registered at `window.__timelines["<frame_id>"]`, built synchronously — per the core contract.
52-
4. **Self-check, then finish**run the checklist below and fix in place. Writing the passing file is your terminal action.
51+
3. **Author** — write the full sub-composition to `compositions/frames/<frame_id>.html` (rewrite to iterate; last write wins). `<template>`-wrapped root carrying `data-composition-id="<frame_id>"` and styled via `#root` (not a class on that element — see the self-check below), exactly one `gsap.timeline({ paused: true })` registered at `window.__timelines["<frame_id>"]`, built synchronously — per the core contract.
52+
4. **Self-check, then finish**re-read your file against the checklist below and fix in place. Writing the file is your terminal action; you do **not** run the CLI.
5353

54-
## Self-check (fix before finishing)
54+
## Self-check before finishing (you do NOT run the CLI)
5555

56-
The orchestrator runs `lint` / `validate` / `inspect`; catch these yourself first (codes are `hyperframes lint`'s; the rules behind them are in `hyperframes-core`):
56+
You **can't** meaningfully run `hyperframes lint` / `validate` / `inspect` here: they operate on the **assembled project** (the `index.html` graph / bundle), and your frame isn't wired in yet — so they report on _other_ files, not yours (a false green). The **orchestrator** runs them at **Step 6, after assembly** (the correct unit), and **re-dispatches you with the finding** if your frame fails (see **Retry** above). So get it right on write: re-read your file against this checklist before finishing — the codes in parens are `hyperframes lint`'s and what the orchestrator may cite back (the rules behind them live in `hyperframes-core`):
5757

5858
- `missing_template_wrapper` / `missing_composition_id` — root is `<template>`-wrapped and carries `data-composition-id="<frame_id>"`.
5959
- **Template transport** — every `<style>` and `<script>` block, including the GSAP load, lives inside `<template>`.
60+
- `subcomposition_root_styled_by_class`**style the frame root via `#root`, never a class on the `data-composition-id` element**: at render a class on the root gets scoped to a descendant selector that can't match it, so the **whole scene renders unstyled** (Studio preview still looks right — trust this rule, not the preview). Descendants use plain selectors.
6061
- `clip_missing_data_attrs` — every `class="clip"` element has `data-start` / `data-duration` / `data-track-index`.
6162
- `timeline_not_paused` / `timeline_not_registered` — one paused timeline, registered at `window.__timelines["<frame_id>"]`.
6263
- `css_transition_used` + repeat / yoyo / non-deterministic logic — none present (the renderer seeks frame-by-frame).

0 commit comments

Comments
 (0)