mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-02 20:18:35 +00:00
fix(skill): address PR #1026 review — find $HOME, order-indep audioRegex, cached reads
Three issues from Miguel's + Rames's reviews:
**[Blocking] find / violates CLAUDE.md guidance (Miguel)**
CLAUDE.md says: "When running find, search from . (or a specific path),
not / — scanning the full filesystem can exhaust system resources on
large trees." I introduced 3 instances of `find /` in skill prose to
help sub-agents locate skill files from unknown CWDs. Replaced all 3
with `find "$HOME" ... -maxdepth 10`. Verified all 4 skill files
resolve correctly under $HOME on the testbed setup.
Files: step-3-storyboard.md (×2), step-5-build.md, step-6-validate.md.
**[Blocking] SFX audio regex assumed attribute ordering (Miguel + Rames)**
The v2 audioRegex required src= to appear lexically BEFORE data-start=
in the same <audio> tag. But capabilities.md:365 — in the same skill —
documents the canonical pattern with src= LAST:
<audio id="..." data-start="..." data-duration="..." data-volume="..."
data-track-index="..." src="...">
Real compositions following the docs would have audio tags that don't
match the regex → SFX reported as MISSING → false FAIL in the script
output → false alarm in the user-facing summary. Exactly what v2 was
supposed to fix.
Replaced with the same two-step shape that readBeatDurationsFromIndex
already uses correctly: match `<audio[^>]*?>` to grab the whole tag,
then extract src= and data-start= from the tag string with independent
regexes. Verified both attribute orderings (src first, src last) now
work via inline node test.
**[Minor] readBeatCompositions / readBeatDurationsFromIndex re-read on
every call (Rames)**
Added process-scoped caches to both helpers. The script is a one-shot
CLI so no invalidation needed — first call hits disk, subsequent calls
return the cached result. readBeatCompositions was called 3×,
readBeatDurationsFromIndex 2× — now 1× each.
**Regression checks**
- huly-v3: 4 PASS · 3 FAIL · 1 INFO (unchanged — same 3 real issues
flagged: 48px wordmark, missing shaders, 3 SFX drifts)
- huly-launch-v4: 6 PASS · 0 FAIL · 2 INFO (unchanged)
- Lint + format: clean
2 files changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f4a7961bc1
commit
a5bc11632f
@@ -192,7 +192,7 @@ When planning beats, decide which ones deserve an HTML-in-Canvas treatment vs. a
|
||||
**Before writing beats,** read the SFX manifest. Locate it from your current directory:
|
||||
|
||||
```bash
|
||||
find / -path '*/website-to-hyperframes/assets/sfx/manifest.json' -maxdepth 12 2>/dev/null | head -1
|
||||
find "$HOME" -path '*/website-to-hyperframes/assets/sfx/manifest.json' -maxdepth 10 2>/dev/null | head -1
|
||||
```
|
||||
|
||||
Or if you already copied SFX into the project (Step 5 does this), read your local `sfx/manifest.json`. Each entry has a filename, duration in seconds, and description. Assign **specific SFX files** to exact moments in the storyboard. Step 5 implements what you specify here — it makes no SFX decisions.
|
||||
@@ -362,7 +362,7 @@ Write this section for THIS project's actual brand and the assets audited above
|
||||
|
||||
### Text Animations
|
||||
|
||||
Every text element in this beat must name a specific effect from the catalog. The reference page is at [`../../hyperframes/references/text-effects.md`](../../hyperframes/references/text-effects.md) (or locate it with `find / -path '*/hyperframes/references/text-effects.md' -maxdepth 12 2>/dev/null | head -1`). It lists 24 effect IDs (from the separate `pixel-point/animate-text` skill); pick what fits the brand and this beat's mood — don't default to the same effect every beat.
|
||||
Every text element in this beat must name a specific effect from the catalog. The reference page is at [`../../hyperframes/references/text-effects.md`](../../hyperframes/references/text-effects.md) (or locate it with `find "$HOME" -path '*/hyperframes/references/text-effects.md' -maxdepth 10 2>/dev/null | head -1`). It lists 24 effect IDs (from the separate `pixel-point/animate-text` skill); pick what fits the brand and this beat's mood — don't default to the same effect every beat.
|
||||
|
||||
Format (FORMAT EXAMPLES of structure, not prescriptions — pick based on brand/mood/context):
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ Build the composition for Beat N. Save to compositions/beat-N-name.html.
|
||||
FIRST: Locate and read the beat-builder guide. Your CWD is the project directory, so
|
||||
the skill lives outside it — run this to find it:
|
||||
|
||||
find / -path '*/website-to-hyperframes/references/beat-builder-guide.md' -maxdepth 12 2>/dev/null | head -1
|
||||
find "$HOME" -path '*/website-to-hyperframes/references/beat-builder-guide.md' -maxdepth 10 2>/dev/null | head -1
|
||||
|
||||
Read that file end to end. It has your full workflow, all rules, easing vocabulary,
|
||||
and file references. Follow its workflow exactly:
|
||||
|
||||
@@ -42,7 +42,7 @@ Run it as the LAST gate in your DoD pass, after fixing everything else:
|
||||
node <repo-root>/skills/website-to-hyperframes/scripts/w2h-verify.mjs <project-dir>
|
||||
```
|
||||
|
||||
(Locate the repo root from a project subdirectory: `find / -path '*/skills/website-to-hyperframes/scripts/w2h-verify.mjs' -maxdepth 12 2>/dev/null | head -1`.)
|
||||
(Locate the repo root from a project subdirectory: `find "$HOME" -path '*/skills/website-to-hyperframes/scripts/w2h-verify.mjs' -maxdepth 10 2>/dev/null | head -1`.)
|
||||
|
||||
**The script's output is the deliverable.** Paste the entire report — the table, the percentages, the FAIL lines — verbatim into your final user-facing summary, in the "What I verified" / "What I did NOT verify" section. The user will read it directly. You don't get to summarize, simplify, or omit rows.
|
||||
|
||||
|
||||
@@ -399,13 +399,23 @@ async function checkSfxTimestampConsistency() {
|
||||
};
|
||||
}
|
||||
|
||||
// Collect ALL audio tags per file (multi-timestamp SFX like click.mp3 have
|
||||
// 3 tags). Use a two-step extraction so we don't depend on src= and
|
||||
// data-start= attribute ordering — the documented canonical pattern in
|
||||
// capabilities.md puts src= LAST in the tag, which an order-dependent
|
||||
// regex would miss → false MISSING reports.
|
||||
const indexSfx = new Map();
|
||||
const audioRegex =
|
||||
/<audio[^>]*src=["'](?:[^"']*\/)?sfx\/([\w-]+\.mp3)["'][^>]*?data-start=["']([0-9.]+)["']/g;
|
||||
let m;
|
||||
while ((m = audioRegex.exec(index)) !== null) {
|
||||
if (!indexSfx.has(m[1])) indexSfx.set(m[1], []);
|
||||
indexSfx.get(m[1]).push(parseFloat(m[2]));
|
||||
const audioTagRegex = /<audio[^>]*?>/g;
|
||||
let tagMatch;
|
||||
while ((tagMatch = audioTagRegex.exec(index)) !== null) {
|
||||
const tag = tagMatch[0];
|
||||
const srcMatch = tag.match(/src=["'](?:[^"']*\/)?sfx\/([\w-]+\.mp3)["']/);
|
||||
const dsMatch = tag.match(/data-start=["']([0-9.]+)["']/);
|
||||
if (!srcMatch || !dsMatch) continue;
|
||||
const file = srcMatch[1];
|
||||
const t = parseFloat(dsMatch[1]);
|
||||
if (!indexSfx.has(file)) indexSfx.set(file, []);
|
||||
indexSfx.get(file).push(t);
|
||||
}
|
||||
|
||||
const drifts = [];
|
||||
@@ -593,9 +603,16 @@ async function checkMp4Exists() {
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// Cached so multiple checks don't re-read the same files. The script is a
|
||||
// one-shot CLI so a process-scoped cache is fine; no invalidation needed.
|
||||
let _compositionsCache = null;
|
||||
async function readBeatCompositions() {
|
||||
if (_compositionsCache) return _compositionsCache;
|
||||
const dir = join(PROJECT_DIR, "compositions");
|
||||
if (!existsSync(dir)) return [];
|
||||
if (!existsSync(dir)) {
|
||||
_compositionsCache = [];
|
||||
return _compositionsCache;
|
||||
}
|
||||
const files = await readdir(dir);
|
||||
const beats = files.filter((f) => /^beat-/i.test(f) && f.endsWith(".html"));
|
||||
const out = [];
|
||||
@@ -603,6 +620,7 @@ async function readBeatCompositions() {
|
||||
const content = await readFile(join(dir, f), "utf-8");
|
||||
out.push({ name: f, content });
|
||||
}
|
||||
_compositionsCache = out;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -653,9 +671,15 @@ function extractTopLevelPositionArgs(content) {
|
||||
}
|
||||
|
||||
// Returns a map of { "beat-1-name": durationInSeconds, ... } from index.html.
|
||||
// Cached per-process (one-shot CLI, no invalidation needed).
|
||||
let _beatDurationsCache = null;
|
||||
async function readBeatDurationsFromIndex() {
|
||||
if (_beatDurationsCache) return _beatDurationsCache;
|
||||
const indexPath = join(PROJECT_DIR, "index.html");
|
||||
if (!existsSync(indexPath)) return {};
|
||||
if (!existsSync(indexPath)) {
|
||||
_beatDurationsCache = {};
|
||||
return _beatDurationsCache;
|
||||
}
|
||||
const content = await readFile(indexPath, "utf-8");
|
||||
const map = {};
|
||||
|
||||
@@ -673,6 +697,7 @@ async function readBeatDurationsFromIndex() {
|
||||
if (idMatch) map[idMatch[1]] = dur;
|
||||
if (srcMatch) map[srcMatch[1]] = dur;
|
||||
}
|
||||
_beatDurationsCache = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user