Files
hyperframes/skills/remotion-to-hyperframes/assets/test-corpus/tier-4-escape-hatch/validate.sh
T
James 7509916227 feat(skills): remotion-to-hyperframes corpus T4 (5/7)
Adds the escape-hatch tier — lint-only fixtures that test the skill's
ability to refuse translation cleanly when it sees patterns that don't map
to HF's seek-driven model.

Cases (8 total):
  01-use-state.tsx          blocker: r2hf/use-state
  02-use-effect-deps.tsx    blocker: r2hf/use-effect-deps (multi-line body
                            with internal commas — regression target for
                            the regex bug fix in PR 2)
  03-async-metadata.tsx     blocker: r2hf/async-metadata
  04-third-party-react.tsx  blocker: r2hf/third-party-react-ui (@mui/material)
  05-lambda-config.tsx      blocker: r2hf/lambda-import
  06-warnings-only.tsx      warnings: delayRender / useCallback / useMemo
                            (no blockers — translates after dropping wrappers)
  07-custom-hook.tsx        warning: r2hf/custom-hook (pure useFadeIn)
  08-mixed.tsx              multiple blockers + warnings (aggregate test)

Each case documents:
  - The Remotion pattern it demonstrates
  - Why it's a blocker / warning / info
  - What the skill should do (refuse / drop-and-translate / translate-as-is)

Validation harness (validate.sh):
  Runs lint_source.py against each case, asserts:
    - Each expected blocker rule fires with severity="blocker"
    - Each expected warning rule fires with severity="warning"
    - lint_source.py exit code is 1 when blockers expected, 0 otherwise

T4 has no renders to diff. The skill is graded on lint correctness — that's
the gate that decides whether to translate or recommend the runtime interop
pattern from PR #214.

Result: 8/8 cases pass.
2026-04-27 23:55:24 +00:00

111 lines
3.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# validate.sh — assert lint_source.py output matches expected.json for every T4 case.
#
# T4 has no renders to diff. The skill is graded on whether it correctly
# refuses to translate each case (or drops only the lambda config in case 5,
# or warns appropriately in cases 6 and 7).
#
# Usage:
# ./validate.sh
# Exit 0 on pass.
set -euo pipefail
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPTS_DIR="$(cd "$THIS_DIR/../../../scripts" && pwd)"
EXPECTED="$THIS_DIR/expected.json"
if [[ ! -f "$SCRIPTS_DIR/lint_source.py" ]]; then
echo "error: lint_source.py not found at $SCRIPTS_DIR/lint_source.py" >&2
exit 2
fi
if [[ ! -f "$EXPECTED" ]]; then
echo "error: expected.json not found at $EXPECTED" >&2
exit 2
fi
# Drive lint_file() in-process so the per-case overhead is one Python startup,
# not N (8 cases × ~80 ms forking python3 was the dominant cost).
SCRIPTS_DIR="$SCRIPTS_DIR" \
THIS_DIR="$THIS_DIR" \
EXPECTED="$EXPECTED" \
python3 <<'PY'
import json
import os
import sys
from collections import Counter
from pathlib import Path
scripts_dir = Path(os.environ["SCRIPTS_DIR"])
this_dir = Path(os.environ["THIS_DIR"])
expected_path = Path(os.environ["EXPECTED"])
cases_dir = this_dir / "cases"
sys.path.insert(0, str(scripts_dir))
from lint_source import BLOCKER, WARNING, lint_file # noqa: E402
expected = json.loads(expected_path.read_text())
fails: list[str] = []
passes: list[str] = []
for case in expected["cases"]:
file_name = case["file"]
fixture = cases_dir / file_name
if not fixture.exists():
fails.append(f"{file_name}: fixture missing at {fixture}")
continue
findings = lint_file(fixture)
rule_counts: Counter[str] = Counter()
severity_by_rule: dict[str, str] = {}
for f in findings:
rule_counts[f.rule] += 1
severity_by_rule[f.rule] = f.severity
case_failed = False
def assert_rule(expected_entry, expected_severity_floor, kind):
global case_failed
rule = expected_entry["rule"]
min_count = expected_entry["min_count"]
actual = rule_counts[rule]
actual_severity = severity_by_rule.get(rule)
if actual < min_count:
fails.append(f"{file_name}: expected >={min_count} {kind} findings of rule {rule}, got {actual}")
case_failed = True
elif actual_severity not in expected_severity_floor:
fails.append(
f"{file_name}: rule {rule} found but severity={actual_severity!r} (expected {kind})"
)
case_failed = True
for entry in case["expected"]["blockers"]:
assert_rule(entry, {BLOCKER}, "blocker")
for entry in case["expected"]["warnings"]:
assert_rule(entry, {WARNING, BLOCKER}, "warning")
# Implied lint exit code: 1 when blockers are expected, 0 otherwise.
has_blockers = any(f.severity == BLOCKER for f in findings)
expected_has_blockers = bool(case["expected"]["blockers"])
if has_blockers != expected_has_blockers:
fails.append(
f"{file_name}: implied lint exit {1 if has_blockers else 0}, "
f"expected {1 if expected_has_blockers else 0} (blockers expected: {expected_has_blockers})"
)
case_failed = True
if not case_failed:
passes.append(file_name)
print(f"Passed: {len(passes)}")
for name in passes:
print(f" ✓ {name}")
if fails:
print(f"Failed: {len(fails)}")
for msg in fails:
print(f" ✗ {msg}")
sys.exit(1)
sys.exit(0)
PY