tjdrhs90/flutter-flame-harness/flame-harness-evaluator

Claude Code and agent skill by tjdrhs90/flutter-flame-harness

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Phase 6 — skeptical QA. Run the game, watch it, then judge against the contract. Default = functional check; --strict adds quality and edge-case passes.

Install

npx vskill@latest install tjdrhs90/flutter-flame-harness/flame-harness-evaluator

SKILL.md

flame-harness-evaluator

Phase 6 of the flutter-flame-harness pipeline. Skeptical QA gate that decides PASS or FAIL against the negotiated contract. Default mode runs the functional check (6.1) only; --strict adds quality scoring (6.2) and an agent-team edge-case sweep (6.3).

All file schemas (config.md, state.md, handoff/round-N-gen.md, feedback/round-N-qa.md, build-log.md) and the phase transition table are defined in docs/harness-protocol.md — that document is the single source of truth (§1 for config.md; §2 for state.md; §3 for contract.md; §4 for handoff layout; §5 for feedback layout; §6 for log schemas; §7 for the evaluator → admob / evaluator → build / evaluator → generator transitions). Do not redefine schemas here.


Critical Rule

"Run the code, see the app, then judge." Never PASS on code review alone. Execute commands, launch the game on a simulator, play the core loop, capture and study screenshots. Stub detected = automatic FAIL, no exceptions.


Setup — Read Inputs

Before any check, load:

  1. docs/harness/state.md — extract current_round (integer ≥ 1) and confirm

next_role: evaluator.

  1. docs/harness/config.md — extract app_slug, strict_mode (bool), max_rounds (int).
  2. docs/harness/contract.md — parse ## Mandatory Hard Gates and ## Functional Criteria.

Confirm ## Status: AGREED is present; if missing, abort with: flame-harness-evaluator: contract not AGREED — run flame-harness-contract first.

  1. docs/harness/handoff/round-<N>-gen.md (where N = current_round) per protocol §4.

If the file is missing, abort with: flame-harness-evaluator: handoff for round <N> not found — generator must run first.


6.1 Functional Check (default — always runs)

Run every step in order. A failure on any Mandatory Hard Gate from contract.md is an immediate FAIL — do not continue checking remaining criteria.

Step 1 — Static analysis
bash
cd <projects-dir>/<app_slug>
flutter analyze

Required result: 0 issues. If any issues are reported, record each with file, line, and message. This is a Mandatory Hard Gate.

Step 2 — Tests
bash
cd <projects-dir>/<app_slug>
flutter test

Required result: 0 failures. Capture the full output. This is a Mandatory Hard Gate.

Step 3 — Stub / TODO grep
bash
grep -rn "TODO\|stub\|placeholder\|스텁\|미구현" \
<projects-dir>/<app_slug>/lib/ --include="*.dart"

Any match is an automatic FAIL. No exceptions (per docs/harness-protocol.md §3 Hard Gate 3). Record each match with file path, line number, and the matched text.

Step 4 — game_config.dart centralization
bash
grep -rn "[0-9]\{3,\}\.\?[0-9]*" \
<projects-dir>/<app_slug>/lib/game/ --include="*.dart" \
| grep -v "game_config.dart"

Magic numbers (3+ digits) in game logic files other than game_config.dart are a Hard Gate failure (per protocol §3 Hard Gate 4). Exclude intentional non-tuning constants (e.g., HTTP status codes) if clearly justified in a comment; document any exclusion in the feedback file.

Step 5 — l10n completeness
bash
cd <projects-dir>/<app_slug>
flutter gen-l10n

Then confirm that every configured lib/l10n/app_<locale>.arb (the project's default_language, plus app_en.arb when default_language ≠ en) contains identical key sets:

bash
python3 -c "
import glob, json
arbs = glob.glob('lib/l10n/app_*.arb')
keysets = {f: set(json.load(open(f))) for f in arbs}
allkeys = set().union(*keysets.values()) if keysets else set()
bad = {f: sorted(allkeys - ks) for f, ks in keysets.items() if allkeys - ks}
if len(arbs) < 1: print('NO ARB FILES'); exit(1)
if bad: print('MISSING KEYS:', bad); exit(1)
print('l10n OK', list(keysets))
"

Missing keys in either locale are a Hard Gate failure (protocol §3 Hard Gate 6).

Step 5a — Platform-robustness gates

Verify the ## Platform-Robustness Gates (R1–R11) from contract.md / docs/game-gotchas.md:

bash
# R1 audio: frequent SFX pooled + audio calls guarded; BGM stop on teardown
grep -rn "AudioPool" lib/ || echo "WARN: no AudioPool — frequent SFX may stutter"
grep -rn "FlameAudio\|AudioPool\|\.bgm" lib/ | grep -i "try\|catch" >/dev/null || echo "CHECK: audio not in try/catch"
grep -rn "bgm.stop\|\.stop()" lib/ || echo "CHECK: BGM stop on teardown/background"
# R2 haptics (if used)
ls lib/systems/haptics.dart 2>/dev/null && grep -nE "kIsWeb|isIOS|isAndroid|enabled|Stopwatch|elapsed" lib/systems/haptics.dart
# R3 lifecycle
grep -rn "WidgetsBindingObserver\|didChangeAppLifecycleState\|pauseEngine" lib/ || echo "FAIL: no lifecycle pause"
# R4 performance: no per-frame whereType in update hot paths
grep -rn "whereType" lib/ | grep -i "update" && echo "CHECK: whereType inside update — verify it's cached, not per-frame"
# R5 branding: custom icon + splash + localized display name (not defaults)
ls assets/icons/icon.png 2>/dev/null || echo "FAIL: no custom app icon"
grep -q "flutter_launcher_icons" pubspec.yaml && grep -q "flutter_native_splash" pubspec.yaml || echo "FAIL: launcher-icons/native-splash not configured"
sips -g hasAlpha assets/icons/icon.png 2>/dev/null | grep -qi "hasAlpha: no" || echo "CHECK: icon may have alpha (App Store rejects)"
# R5 Android adaptive icon: padded foreground + monochrome configured & emitted (not the full-bleed iOS icon → avoids the cropped/"zoomed" launcher look)
grep -q "adaptive_icon_foreground" pubspec.yaml || echo "CHECK: no adaptive_icon_foreground — Android reuses full-bleed icon and looks cropped/zoomed"
grep -q "adaptive_icon_monochrome" pubspec.yaml || echo "CHECK: no adaptive_icon_monochrome — Android 13+ themed icon will look off"
ls android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 2>/dev/null | grep -q . || echo "CHECK: adaptive-icon XML not emitted (run flutter_launcher_icons)"
grep -rn "CFBundleDisplayName" ios/Runner/Info.plist 2>/dev/null || echo "CHECK: iOS display name not set"
grep -rn 'android:label' android/app/src/main/AndroidManifest.xml 2>/dev/null | grep -qiv "runner" || echo "CHECK: Android label still default/runner"
# R6 native config: orientation locked (matches config.orientation, no ~ipad), iPhone-only, export compliance, root back
grep -q "UISupportedInterfaceOrientations" ios/Runner/Info.plist 2>/dev/null && ! grep -q "UISupportedInterfaceOrientations~ipad" ios/Runner/Info.plist || echo "CHECK: orientation not locked / ~ipad still present"
grep -rq "TARGETED_DEVICE_FAMILY = 1" ios/Runner.xcodeproj/project.pbxproj 2>/dev/null || echo "FAIL: not iPhone-only (TARGETED_DEVICE_FAMILY != 1)"
grep -q "ITSAppUsesNonExemptEncryption" ios/Runner/Info.plist 2>/dev/null || echo "CHECK: export-compliance key missing"
grep -rq "PopScope" lib/ || echo "CHECK: no root back-button handler"
# R6 bundle id identical on both platforms (== config.bundle_id, lowercase no _/-)
IOSID=$(grep -oE 'PRODUCT_BUNDLE_IDENTIFIER = [A-Za-z0-9._-]+' ios/Runner.xcodeproj/project.pbxproj 2>/dev/null | head -1 | sed 's/.*= //')
ANDID=$(grep -oE 'applicationId *= *"[^"]+"' android/app/build.gradle.kts 2>/dev/null | head -1 | sed -E 's/.*"([^"]+)"/\1/')
echo "iOS=$IOSID Android=$ANDID (must be byte-identical == config.bundle_id)"
{ [ -n "$IOSID" ] && [ "$IOSID" = "$ANDID" ]; } || echo "FAIL: iOS and Android bundle id differ (or unset)"
echo "$IOSID" | grep -qE '^[a-z0-9.]+$' || echo "FAIL: bundle id has uppercase/_/- (must be lowercase [a-z0-9.])"
# R6 consent order: UMP form must run BEFORE ATT (AdMob 안내가 "다음 화면에서
# '허용'을 탭하세요" 라고 말한다. 뒤집히면 안내가 무의미하고 허용률이 떨어진다)
grep -n "requestATT\|loadConsentForm" lib/main.dart 2>/dev/null | awk -F: '
/loadConsentForm/ { c=$1 } /requestATT/ { a=$1 }
END { if (a && c && a < c) print "FAIL: requestATT() runs before loadConsentForm() — 두 알림이 겹친다"
else if (!a || !c) print "CHECK: main.dart 에서 ATT/동의 호출을 찾지 못했다" }'
# R7 assets & CI: audio present, no missing-asset refs, CI workflow exists
ls assets/audio/*.wav assets/audio/*.mp3 assets/audio/*.ogg 2>/dev/null | grep -q . || echo "FAIL: no audio assets — game ships silent"
ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | grep -q . || echo "FAIL: no CI workflow"
# R8 Play listing graphics: hi-res icon (512) + feature graphic (1024x500) per locale
ls android/fastlane/metadata/android/*/images/icon.png 2>/dev/null | grep -q . || echo "FAIL: no Play hi-res icon (512x512)"
ls android/fastlane/metadata/android/*/images/featureGraphic.png 2>/dev/null | grep -q . || echo "FAIL: no Play feature graphic (1024x500)"
# R9 durable save: SaveRepository mirrors to Keychain + Block Store + prefs; PreferencesService routes through it
grep -q "flutter_secure_storage" pubspec.yaml && grep -q "play_services_block_store" pubspec.yaml || echo "FAIL: durable-save deps missing (saves won't survive reinstall/device on iOS)"
ls lib/data/save_repository.dart 2>/dev/null || echo "FAIL: no SaveRepository (durable save layer)"
grep -q "FlutterSecureStorage" lib/data/save_repository.dart 2>/dev/null && grep -q "PlayServicesBlockStore" lib/data/save_repository.dart 2>/dev/null || echo "FAIL: SaveRepository missing a durable tier (Keychain/Block Store)"
# R10 accessibility & safety: reduce-motion read, menu Semantics present, no >3Hz flashing
grep -rq "disableAnimations" lib/ || echo "CHECK: OS Reduce Motion (MediaQuery.disableAnimations) not respected"
grep -rq "Semantics(" lib/screens/ lib/ui/ 2>/dev/null || echo "CHECK: no Semantics labels on menu/overlay buttons (screen-reader nav)"
grep -rniE "flash|strobe|invert" lib/ | grep -i "update\|timer\|0\.0[0-9]" && echo "CHECK: verify no full-screen flash faster than 3/sec (photosensitive safety)"
# R11 test depth: >=3 system unit tests + >=1 widget test + >=1 integration test
UNIT=$(ls test/*_test.dart 2>/dev/null | grep -viE 'widget' | wc -l | tr -d ' ')
grep -rlq "testWidgets" test/ 2>/dev/null || echo "FAIL: no widget test (R11 needs >=1)"
ls integration_test/*.dart >/dev/null 2>&1 || echo "FAIL: no integration test (R11 needs >=1 core-loop test)"
[ "${UNIT:-0}" -ge 1 ] || echo "CHECK: few unit test files — R11 wants >=3 system unit tests"
# every asset path declared in pubspec must exist on disk
python3 - <<'PY'
import re,glob,os,sys
try: txt=open('pubspec.yaml').read()
except FileNotFoundError: sys.exit(0)
m=re.search(r'\n\s*assets:\s*\n((?:\s*-\s.*\n)+)', txt)
missing=[]
if m:
for line in m.group(1).splitlines():
p=line.strip().lstrip('- ').strip().strip('"\'')
if not p: continue
if p.endswith('/'):
if not (os.path.isdir(p) and os.listdir(p)): missing.append(p)
elif not os.path.exists(p): missing.append(p)
print('MISSING ASSETS:',missing) if missing else print('assets OK')
PY

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
60
Repository forks
5
7-day trend score
21

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/tjdrhs90/flutter-flame-harness/flame-harness-evaluator/badge)](https://verified-skill.com/skills/tjdrhs90/flutter-flame-harness/flame-harness-evaluator)

Something wrong with this skill or its page? Tell us.