From c7ab21d1bd91251c30a678029a1dcb43cbd995e2 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Fri, 18 Sep 2026 17:20:11 -0400 Subject: [PATCH 01/11] docs(skills): one owner doc for everyday edits and a hyperframes-studio skill Agents got contradictory advice on volume fades and were blocked from splits and copies by the Ask-agent guardrails. - creator-editing-recipes.md is the single owner: adds align-to-event, copy-a-group and swap-media recipes and the one volume rule (use the lane) - variables-and-media, data-attributes (skill and CLI copy), the gsap adapter and tracks-and-clips now defer to it - Ask-agent guardrails allow the neighbour edits a split, retime, copy or swap requires and point at the owner doc - new hyperframes-studio skill: sub-compositions for every scene, one caption track, one element kind per track, safe zones - drift test lints every html example in the owner doc with the real linter and fails on any doc teaching a volume tween --- .claude-plugin/marketplace.json | 1 + AGENTS.md | 2 +- CLAUDE.md | 2 +- README.md | 2 +- package.json | 2 +- packages/cli/src/docs/data-attributes.md | 2 +- packages/cli/src/utils/skillsManifest.ts | 1 + .../editor/domEditingAgentPrompt.test.ts | 6 +- .../editor/domEditingAgentPrompt.ts | 3 +- scripts/creator-editing-recipes.test.mjs | 57 +++++++++++++++ skills-manifest.json | 8 +- skills/hyperframes-animation/adapters/gsap.md | 2 +- .../references/creator-editing-recipes.md | 73 +++++++++++++++++++ .../references/data-attributes.md | 18 ++--- .../references/tracks-and-clips.md | 6 +- .../references/variables-and-media.md | 2 +- skills/hyperframes-studio/SKILL.md | 72 ++++++++++++++++++ skills/hyperframes/SKILL.md | 17 +++-- 18 files changed, 242 insertions(+), 34 deletions(-) create mode 100644 scripts/creator-editing-recipes.test.mjs create mode 100644 skills/hyperframes-studio/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c472c1282f..89f596646f 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -21,6 +21,7 @@ "./skills/hyperframes-creative", "./skills/hyperframes-keyframes", "./skills/hyperframes-registry", + "./skills/hyperframes-studio", "./skills/media-use" ] }, diff --git a/AGENTS.md b/AGENTS.md index 2e77575bef..2d421d404b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ This repo ships AI agent skills via [vercel-labs/skills](https://github.com/verc ```bash npx hyperframes skills update # default: installs/refreshes the core set — workflows install on demand -npx hyperframes skills # all 20 published skills at once +npx hyperframes skills # all 21 published skills at once npx skills add heygen-com/hyperframes # interactive picker (terminal only; repo-internal skills are excluded by default) ``` diff --git a/CLAUDE.md b/CLAUDE.md index c2934f5eff..09fac319e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This repo ships 20 AI agent skills via [vercel-labs/skills](https://github.com/v ```bash npx hyperframes skills update # default: installs/refreshes the core set — workflows install on demand -npx hyperframes skills # all 20 published skills at once — only on explicit request +npx hyperframes skills # all 21 published skills at once — only on explicit request npx skills add heygen-com/hyperframes # interactive picker (terminal only; --all also pulls the 6 repo-internal skills under .claude/skills) npx skills add heygen-com/hyperframes --skill # just one (bare name, no leading slash) ``` diff --git a/README.md b/README.md index fa9805507d..a2d6ad6aa0 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The skills teach agents the HyperFrames production loop: plan the video, write v ## Skills -HyperFrames ships 20 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. +HyperFrames ships 21 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. Default to the **core set** — the router installs each creation workflow on demand. `npx hyperframes skills update` installs exactly that from anywhere; the interactive picker (`npx skills add heygen-com/hyperframes`) lists it as the "Core Skills" group, nothing pre-selected. The picker is interactive-only — a non-interactive or agent run without `--skill` installs all 20. Use `npx skills add heygen-com/hyperframes --all` to install all 20 deliberately (skips the picker), or `npx skills add heygen-com/hyperframes --skill ` for just one (bare name, no leading `/`). diff --git a/package.json b/package.json index 9553f6a213..a1a6f8a6b4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "player:perf": "bun run --filter @hyperframes/player perf", "format:check": "oxfmt --check .", "knip": "knip", - "test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts && vitest run scripts/catalog/", + "test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-large-files.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs scripts/creator-editing-recipes.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts && vitest run scripts/catalog/", "typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json", "test:skills": "node --test 'skills/**/*.test.mjs'", "generate:previews": "tsx scripts/generate-template-previews.ts", diff --git a/packages/cli/src/docs/data-attributes.md b/packages/cli/src/docs/data-attributes.md index 9ee928bc3f..848ed89776 100644 --- a/packages/cli/src/docs/data-attributes.md +++ b/packages/cli/src/docs/data-attributes.md @@ -11,7 +11,7 @@ Core attributes for controlling element timing and behavior. ## Media - `data-media-start="2"` — Media playback offset / trim point (seconds) -- `data-volume="0.8"` — Audio/video gain. `1` is 0 dB, `0` is silence, and values above `1` boost up to `3.98` (+12 dB) +- `data-volume="0.8"` — Audio/video gain. `1` is 0 dB, `0` is silence, and values above `1` boost up to `3.98` (+12 dB). For fades and ducking use the `data-automation` volume lane (`hyperframes-core` skill, `creator-editing-recipes.md`) - `data-has-audio="true"` — Indicates video has an audio track ## Composition diff --git a/packages/cli/src/utils/skillsManifest.ts b/packages/cli/src/utils/skillsManifest.ts index 8678e3f2f3..aaa19efceb 100644 --- a/packages/cli/src/utils/skillsManifest.ts +++ b/packages/cli/src/utils/skillsManifest.ts @@ -156,6 +156,7 @@ export const FALLBACK_CORE_SKILLS: readonly string[] = [ "hyperframes-creative", "hyperframes-keyframes", "hyperframes-registry", + "hyperframes-studio", "media-use", ]; diff --git a/packages/studio/src/components/editor/domEditingAgentPrompt.test.ts b/packages/studio/src/components/editor/domEditingAgentPrompt.test.ts index 235fe6c90e..11f8c96c38 100644 --- a/packages/studio/src/components/editor/domEditingAgentPrompt.test.ts +++ b/packages/studio/src/components/editor/domEditingAgentPrompt.test.ts @@ -80,7 +80,8 @@ describe("buildElementAgentPrompt", () => { "Guardrails:", "- Make a targeted change to this element only.", "- Preserve the rest of the composition and its timing.", - "- Do not modify other elements' data-* attributes or positioning.", + "- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).", + "- For timeline edits (trim, split, speed, volume, copy, swap), follow skills/hyperframes-core/references/creator-editing-recipes.md and use its exact attribute forms.", "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.", ].join("\n"), ); @@ -162,7 +163,8 @@ describe("buildPickerAgentPrompt", () => { "Guardrails:", "- Make a targeted change to this element only.", "- Preserve the rest of the composition and its timing.", - "- Do not modify other elements' data-* attributes or positioning.", + "- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).", + "- For timeline edits (trim, split, speed, volume, copy, swap), follow skills/hyperframes-core/references/creator-editing-recipes.md and use its exact attribute forms.", "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.", ].join("\n"); expect(domPrompt.endsWith(guardrails)).toBe(true); diff --git a/packages/studio/src/components/editor/domEditingAgentPrompt.ts b/packages/studio/src/components/editor/domEditingAgentPrompt.ts index 83a46e0c23..ed56f3a68a 100644 --- a/packages/studio/src/components/editor/domEditingAgentPrompt.ts +++ b/packages/studio/src/components/editor/domEditingAgentPrompt.ts @@ -30,7 +30,8 @@ const GUARDRAIL_LINES = [ "Guardrails:", "- Make a targeted change to this element only.", "- Preserve the rest of the composition and its timing.", - "- Do not modify other elements' data-* attributes or positioning.", + "- Do not modify other elements' data-* attributes or positioning, except where the requested timeline edit requires it (split, retime, reorder, copy a group, swap media).", + "- For timeline edits (trim, split, speed, volume, copy, swap), follow skills/hyperframes-core/references/creator-editing-recipes.md and use its exact attribute forms.", "- Prefer existing inline styles or existing CSS rules for this element over adding unrelated selectors.", ]; diff --git a/scripts/creator-editing-recipes.test.mjs b/scripts/creator-editing-recipes.test.mjs new file mode 100644 index 0000000000..90d136d4ec --- /dev/null +++ b/scripts/creator-editing-recipes.test.mjs @@ -0,0 +1,57 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import { lintHyperframeHtml } from "../packages/lint/src/index.ts"; + +const OWNER = "skills/hyperframes-core/references/creator-editing-recipes.md"; +const STUDIO_SKILL = "skills/hyperframes-studio/SKILL.md"; +const VOLUME_TWEEN = /\.(?:to|from|fromTo|set)\(\s*["'`]#[\w-]+["'`]\s*,\s*\{[^}]*\bvolume\s*:/; +const TWEEN_ALLOWED = new Set([OWNER, "skills/media-use/references/operations.md"]); +const TWEEN_SCANNED = [ + "skills/hyperframes-core/references/variables-and-media.md", + "skills/hyperframes-core/references/data-attributes.md", + "skills/hyperframes-animation/adapters/gsap.md", + "skills/hyperframes-audio/SKILL.md", + "skills/hyperframes-audio/references/attributes.md", + "packages/cli/src/docs/data-attributes.md", +]; + +const read = (path) => readFile(new URL(`../${path}`, import.meta.url), "utf8"); + +const htmlBlocks = (md) => [...md.matchAll(/```html\n([\s\S]*?)```/g)].map((m) => m[1]); + +const wrap = (fragment) => + fragment.includes("
${fragment}
`; + +test("every html example in the owner doc lints with no errors and no volume double-automation", async () => { + const blocks = htmlBlocks(await read(OWNER)); + assert.ok(blocks.length >= 10, "expected the owner doc to keep its worked examples"); + for (const [i, block] of blocks.entries()) { + const { findings } = await lintHyperframeHtml(wrap(block), { filePath: "index.html" }); + const bad = findings.filter( + (f) => f.severity === "error" || f.code.startsWith("audio_volume_"), + ); + assert.deepEqual( + bad.map((f) => `${f.code}: ${f.message}`), + [], + `example ${i + 1} in ${OWNER}`, + ); + } +}); + +test("no other doc teaches a timeline tween as the way to fade volume", async () => { + for (const path of TWEEN_SCANNED) { + if (TWEEN_ALLOWED.has(path)) continue; + const lines = (await read(path)).split("\n"); + const hit = lines.findIndex((l) => VOLUME_TWEEN.test(l)); + assert.equal(hit, -1, `${path}:${hit + 1} teaches a volume tween; point at ${OWNER}`); + } +}); + +test("the Studio skill holds conventions only and points at the owner doc for edits", async () => { + const skill = await read(STUDIO_SKILL); + assert.match(skill, /creator-editing-recipes\.md/); + assert.equal(htmlBlocks(skill).length, 0, "a recipe restated in the Studio skill will drift"); +}); diff --git a/skills-manifest.json b/skills-manifest.json index 9cfb6eee92..db1361965b 100644 --- a/skills-manifest.json +++ b/skills-manifest.json @@ -18,11 +18,11 @@ "files": 4 }, "hyperframes": { - "hash": "d0a114a8f894758e", + "hash": "ffacb7af2a96d788", "files": 26 }, "hyperframes-animation": { - "hash": "4225f299e6ff9a0e", + "hash": "492f6f39edf2a8e5", "files": 121 }, "hyperframes-audio": { @@ -49,6 +49,10 @@ "hash": "51e6dba95a8dfb45", "files": 12 }, + "hyperframes-studio": { + "hash": "de3ad0df0e318e0c", + "files": 1 + }, "media-use": { "hash": "4531919cdbda573e", "files": 158 diff --git a/skills/hyperframes-animation/adapters/gsap.md b/skills/hyperframes-animation/adapters/gsap.md index 6638706693..53dddf92f0 100644 --- a/skills/hyperframes-animation/adapters/gsap.md +++ b/skills/hyperframes-animation/adapters/gsap.md @@ -61,7 +61,7 @@ HyperFrames is stricter than vanilla GSAP. Animate only: - **Compositor-cheap**: `opacity`, `x`, `y`, `scale`, `scaleX`, `scaleY`, `rotation`, `rotationX`, `rotationY`, `skewX`, `skewY`, `transformOrigin` - **Visual fills**: `color`, `backgroundColor`, `borderColor`, `borderRadius` - **CSS variables**: `"--hue": 180` etc. -- **Media `volume`** (on `