Skip to content

Commit a5bc116

Browse files
ukimsanovclaude
andcommitted
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>
1 parent f4a7961 commit a5bc116

4 files changed

Lines changed: 37 additions & 12 deletions

File tree

skills/website-to-hyperframes/references/step-3-storyboard.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ When planning beats, decide which ones deserve an HTML-in-Canvas treatment vs. a
192192
**Before writing beats,** read the SFX manifest. Locate it from your current directory:
193193

194194
```bash
195-
find / -path '*/website-to-hyperframes/assets/sfx/manifest.json' -maxdepth 12 2>/dev/null | head -1
195+
find "$HOME" -path '*/website-to-hyperframes/assets/sfx/manifest.json' -maxdepth 10 2>/dev/null | head -1
196196
```
197197

198198
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
362362

363363
### Text Animations
364364

365-
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.
365+
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.
366366

367367
Format (FORMAT EXAMPLES of structure, not prescriptions — pick based on brand/mood/context):
368368

skills/website-to-hyperframes/references/step-5-build.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ Build the composition for Beat N. Save to compositions/beat-N-name.html.
301301
FIRST: Locate and read the beat-builder guide. Your CWD is the project directory, so
302302
the skill lives outside it — run this to find it:
303303
304-
find / -path '*/website-to-hyperframes/references/beat-builder-guide.md' -maxdepth 12 2>/dev/null | head -1
304+
find "$HOME" -path '*/website-to-hyperframes/references/beat-builder-guide.md' -maxdepth 10 2>/dev/null | head -1
305305
306306
Read that file end to end. It has your full workflow, all rules, easing vocabulary,
307307
and file references. Follow its workflow exactly:

skills/website-to-hyperframes/references/step-6-validate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Run it as the LAST gate in your DoD pass, after fixing everything else:
4242
node <repo-root>/skills/website-to-hyperframes/scripts/w2h-verify.mjs <project-dir>
4343
```
4444

45-
(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`.)
45+
(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`.)
4646

4747
**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.
4848

skills/website-to-hyperframes/scripts/w2h-verify.mjs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -399,13 +399,23 @@ async function checkSfxTimestampConsistency() {
399399
};
400400
}
401401

402+
// Collect ALL audio tags per file (multi-timestamp SFX like click.mp3 have
403+
// 3 tags). Use a two-step extraction so we don't depend on src= and
404+
// data-start= attribute ordering — the documented canonical pattern in
405+
// capabilities.md puts src= LAST in the tag, which an order-dependent
406+
// regex would miss → false MISSING reports.
402407
const indexSfx = new Map();
403-
const audioRegex =
404-
/<audio[^>]*src=["'](?:[^"']*\/)?sfx\/([\w-]+\.mp3)["'][^>]*?data-start=["']([0-9.]+)["']/g;
405-
let m;
406-
while ((m = audioRegex.exec(index)) !== null) {
407-
if (!indexSfx.has(m[1])) indexSfx.set(m[1], []);
408-
indexSfx.get(m[1]).push(parseFloat(m[2]));
408+
const audioTagRegex = /<audio[^>]*?>/g;
409+
let tagMatch;
410+
while ((tagMatch = audioTagRegex.exec(index)) !== null) {
411+
const tag = tagMatch[0];
412+
const srcMatch = tag.match(/src=["'](?:[^"']*\/)?sfx\/([\w-]+\.mp3)["']/);
413+
const dsMatch = tag.match(/data-start=["']([0-9.]+)["']/);
414+
if (!srcMatch || !dsMatch) continue;
415+
const file = srcMatch[1];
416+
const t = parseFloat(dsMatch[1]);
417+
if (!indexSfx.has(file)) indexSfx.set(file, []);
418+
indexSfx.get(file).push(t);
409419
}
410420

411421
const drifts = [];
@@ -593,16 +603,24 @@ async function checkMp4Exists() {
593603

594604
// ─── Helpers ─────────────────────────────────────────────────────────────────
595605

606+
// Cached so multiple checks don't re-read the same files. The script is a
607+
// one-shot CLI so a process-scoped cache is fine; no invalidation needed.
608+
let _compositionsCache = null;
596609
async function readBeatCompositions() {
610+
if (_compositionsCache) return _compositionsCache;
597611
const dir = join(PROJECT_DIR, "compositions");
598-
if (!existsSync(dir)) return [];
612+
if (!existsSync(dir)) {
613+
_compositionsCache = [];
614+
return _compositionsCache;
615+
}
599616
const files = await readdir(dir);
600617
const beats = files.filter((f) => /^beat-/i.test(f) && f.endsWith(".html"));
601618
const out = [];
602619
for (const f of beats) {
603620
const content = await readFile(join(dir, f), "utf-8");
604621
out.push({ name: f, content });
605622
}
623+
_compositionsCache = out;
606624
return out;
607625
}
608626

@@ -653,9 +671,15 @@ function extractTopLevelPositionArgs(content) {
653671
}
654672

655673
// Returns a map of { "beat-1-name": durationInSeconds, ... } from index.html.
674+
// Cached per-process (one-shot CLI, no invalidation needed).
675+
let _beatDurationsCache = null;
656676
async function readBeatDurationsFromIndex() {
677+
if (_beatDurationsCache) return _beatDurationsCache;
657678
const indexPath = join(PROJECT_DIR, "index.html");
658-
if (!existsSync(indexPath)) return {};
679+
if (!existsSync(indexPath)) {
680+
_beatDurationsCache = {};
681+
return _beatDurationsCache;
682+
}
659683
const content = await readFile(indexPath, "utf-8");
660684
const map = {};
661685

@@ -673,6 +697,7 @@ async function readBeatDurationsFromIndex() {
673697
if (idMatch) map[idMatch[1]] = dur;
674698
if (srcMatch) map[srcMatch[1]] = dur;
675699
}
700+
_beatDurationsCache = map;
676701
return map;
677702
}
678703

0 commit comments

Comments
 (0)