ワンクリックで
visual-loop
프론트엔드 변경을 실제 브라우저 렌더링으로 캡처해 Vision 분석한다. UI 코드를 수정한 직후 시각 회귀를 확인할 때 사용한다.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
프론트엔드 변경을 실제 브라우저 렌더링으로 캡처해 Vision 분석한다. UI 코드를 수정한 직후 시각 회귀를 확인할 때 사용한다.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | visual-loop |
| description | 프론트엔드 변경을 실제 브라우저 렌더링으로 캡처해 Vision 분석한다. UI 코드를 수정한 직후 시각 회귀를 확인할 때 사용한다. |
| argument-hint | [url] [--viewport=desktop,tablet,mobile] |
| allowed-tools | Bash,Read,Write,Edit,Glob,Grep,Skill,Agent |
| context | fork |
| model | sonnet |
역할: 당신은 프론트엔드 변경사항을 정적+시각 closed loop로 검증하는 UX 품질 엔지니어입니다. 컨텍스트: 자체 정적 분석 후 "실제 렌더링이 의도한 대로 나오는지" 추가 검증이 필요한 시점에 호출됩니다. 출력: 3 viewport 스크린샷 + Gemini Vision 분석 + 정적 분석과의 delta 리포트.
출처: Boris Cherny 15 features (Chrome 확장 + Claude Desktop 브라우저 자동 검증 루프) WSL 제약: Chrome 확장/Claude Desktop 대신 Playwright + Gemini Vision 조합 관련 스킬: /screenshot-analyze (비전 분석), /playwright-cli (브라우저 자동화)
정적 분석의 맹점 보완. 정적 분석만으로는 실제 브라우저 렌더링 결과(폰트 로딩 실패, flex 깨짐, 3rd-party CSS 간섭 등)는 못 잡음. 이 스킬이 실제 렌더링을 시각적으로 검증하여 false negative를 줄인다.
/visual-loop <url> OR.tsx/.jsx/.css 변경된 PR에서 시각 검증 필요 시$1 = 검증 URL (예: http://localhost:3000/dashboard)--viewport= (선택) = desktop,tablet,mobile 중 콤마 구분. 기본: 3개 모두# 1.1 playwright 설치 확인
command -v playwright-cli || echo "playwright-cli 없음 — npm install -g @playwright/cli 필요"
# 1.2 dev server 가동 확인
curl -sf --max-time 3 -o /dev/null "$URL" && echo "server OK" || echo "server 미가동"
# 1.3 dev server 자동 기동 옵션 (package.json 감지)
if [ -f "package.json" ] && grep -q '"\"dev"\"' package.json; then
echo "package.json 감지 — 'npm run dev' 병행 실행 필요"
fi
Dev server 없으면: 사용자에게 npm run dev를 별도 터미널에서 실행하라고 안내 후 대기 금지 종료.
정합화: playwright-cli 단독 스크린샷 →
shared/scripts/playwright-devtools-capture.mjs재사용으로 전환 (qa/forge-fix Gate R/G와 동일 엔진 = 로직 단일화). 스크린샷과 동시에-aria.json(a11y snapshot)을 확보해 Step 2.5 기능축 판정 입력으로 쓴다.
mkdir -p /tmp/visual-loop/
# 3 viewport 병렬 캡처 — viewport당 1회 호출
node "${FORGE_ROOT:-$HOME/forge}/shared/scripts/playwright-devtools-capture.mjs" \
--url "$URL" --out-prefix /tmp/visual-loop/{vp} \
--viewports {vp} --phase green
3개 병렬 Agent로 호출 (각 viewport 담당: desktop/tablet/mobile). 결과 저장:
/tmp/visual-loop/{vp}-{vp}-shot.png (fullPage 스크린샷)/tmp/visual-loop/{vp}-aria.json (a11y snapshot — Step 2.5 입력)/tmp/visual-loop/{vp}-console.json, /tmp/visual-loop/{vp}-network.json (참고용)폴백: 캡처 헬퍼가 exit 3(PLAYWRIGHT_UNAVAILABLE) 반환 시 /playwright-cli 스킬로 스크린샷만 폴백 캡처 — 이 경우 aria.json이 없으므로 Step 2.5(기능축)는 skip하고 Step 3(Vision)만으로 진행.
에이전트 브라우저 실행 보안 경계(staging 격리·run-code 감사·시크릿 마스킹·DOM=untrusted): ${FORGE_ROOT:-$HOME/forge}/.claude/rules-on-demand/agent-browser-security.md 준수.
핵심: "요소가 보이나/작동하나"는 Vision이 아니라 **aria snapshot(JSON tree)**으로 판정한다. Gemini Vision은 disabled/hidden/모달가림 요소를 신뢰성 있게 구분하지 못함(실측 확인) — 기능 판정을 Vision에 맡기지 않는다.
Step 2의 각 viewport 캡처 Agent가 자신의 -aria.json을 받은 직후 곧바로 수행(신규 Agent fan-out 없음):
입력: /tmp/visual-loop/{vp}-aria.json
(실제 산출: page.accessibility.snapshot() 노드 = {role, name, disabled?, focused?, children} —
enabled/focusable 필드 없음. interestingOnly 기본 pruning 적용 — 안 보이는 노드는 트리에서 아예 빠질 수 있음)
판정 대상: 검증하려는 요소별 {role, name(=accessible name), enabled(= disabled 필드 부재/false로 판정)}
(focused는 관측 가능하나 판정 기준 아님 — 별도 참고용)
불일치 처리:
- role 불일치 / name 불일치 / disabled:true인데 enabled 기대 = 기능 FAIL 1건 (결정론)
- 요소 자체가 트리에 없음 = pruning 오탐 가능성 있으므로 즉시 FAIL 금지 → WARN 1건(재확인 권고)로 기록
출력(viewport별): /tmp/visual-loop/{vp}-functional-axis.json
{ "viewport": "...", "checks": [{"target": "...", "expected": {...}, "found": {...}, "pass": bool, "severity": "fail|warn"}], "fail_count": N, "warn_count": M }
범위 축소: "요소 존재/활성 여부" 판정은 Step 2.5(aria축)가 전담 — Vision에게 재위임 금지. Vision은 aria-tree로 검증 불가능한 순수 외관 이슈만 담당.
각 스크린샷(/tmp/visual-loop/{vp}-{vp}-shot.png)에 대해 /screenshot-analyze 스킬을 병렬 Agent로 호출:
프롬프트 템플릿:
"다음 스크린샷({viewport} viewport, {width}x{height})을 분석하여 (요소 존재/활성 여부는 판정하지 말 것 — aria축 전담):
1. 시각적 계층 구조 (Visual hierarchy) — 가장 큰 주목 요소
2. 색상 대비 이슈 (텍스트 가독성)
3. Touch target 크기 (모바일만)
4. Layout 깨짐 (overflow, overlap, 잘림 — 표현 문제, 요소 활성여부 아님)
5. 애니메이션/차트/그라디언트 등 tree로 검증 불가한 시각 표현
각 항목을 PASS/WARN/FAIL로 판정. JSON 반환."
출력: /tmp/visual-loop-analysis-{viewport}.json
현실 반영(honest): visual-loop 이 ad-hoc 호출 컨텍스트에는
toHaveScreenshot베이스라인 생산자가 아직 배선되지 않았다 (qa/forge-fix 테스트 컨텍스트에만 존재). 따라서{vp}-pixel-diff.json은 보통 부재하며, 아래 게이트는 그 경우 정상적으로 skip된다 — FAIL이 아니다.
{vp}-pixel-diff.json이 존재하는 경우(qa/forge-fix 테스트 컨텍스트에서 넘어온 경우)에만 **.claude/hooks/pixel-diff-gate.sh**로 수치 판정한다(육안/Vision이 아니라 수치):
bash "${FORGE_ROOT:-$HOME/forge}/.claude/hooks/pixel-diff-gate.sh" /tmp/visual-loop/{vp}-pixel-diff.json 0.01
# exit 2 = diffPixelRatio > 1% → 외관 FAIL (결과 파일이 있을 때만 의미 있음)
# exit 0 = 통과 (결과 파일 없으면도 통과 — graceful skip, ad-hoc 호출의 기본 케이스)
베이스라인이 없는 최초 실행(또는 애초에 생산자 미배선): 게이트가 diff 파일 부재로 자동 통과(graceful skip) — FAIL로 취급하지 않는다. Vision(Step 3)은 이 수치 판정의 보조 신호일 뿐, 결과 파일이 존재하는 경우에 한해 외관 최종 판정은 pixel-diff-gate 결과가 우선한다.
핵심 원칙: 스크린샷을 캡처하고 구현한 Generator(Lead 에이전트)가 직접 시각 결과를 평가하면 자기합리화 편향이 생긴다. 독립 Evaluator subagent가 결과를 종합하여 PASS/FAIL을 판정한다.
evaluator_agent = Agent(
subagent_type="general-purpose",
model="sonnet",
prompt="""
당신은 독립 Visual QA Evaluator 에이전트입니다.
구현 에이전트(Generator)의 컨텍스트(의도, 디자인 결정 이유)를 공유받지 않습니다.
오직 아래 파일만을 근거로 시각 품질을 판정하십시오.
입력 파일 경로:
- (선택) 외부 정적 분석 결과: {static_analysis_result_path}
- (선택) 프로젝트 DESIGN.md: {project-root}/DESIGN.md — 존재 시 외관축 대조 기준(토큰/간격/anti-slop)으로 Read, 없으면 skip
- 기능축(결정론, viewport별): /tmp/visual-loop/{desktop,tablet,mobile}-functional-axis.json
- 외관축(수치, viewport별, **조건부 — toHaveScreenshot 결과 존재 시만**): /tmp/visual-loop/{desktop,tablet,mobile}-pixel-diff.json — ad-hoc visual-loop 호출엔 베이스라인 생산자 미배선이라 보통 부재 → 파일 없으면 해당 viewport는 graceful skip(FAIL 아님, 정상 케이스)
- Vision 보조(tree-불가 외관 이슈만): /tmp/visual-loop-analysis-{desktop,tablet,mobile}.json
- 스크린샷: /tmp/visual-loop/{desktop,tablet,mobile}-{viewport}-shot.png
수행할 작업:
1. viewport별 {vp}-functional-axis.json Read → fail_count 합산 (기능축, 결정론 — Vision으로 재판정하지 않는다)
2. viewport별 pixel-diff.json Read → **파일이 존재할 때만** `pixel-diff-gate.sh` 판정 결과(exit 0/2에 해당하는 diffPixelRatio vs 0.01) 확인. 파일 없으면(ad-hoc 호출의 일반 케이스) graceful skip으로 기록 — FAIL로 카운트하지 않는다.
3. Vision 분석 JSON Read → 스크린샷과 대조해 tree-불가 외관 이슈(P0/P1)만 추출 (요소 존재/활성 판정은 무시 — 기능축이 이미 결정론으로 처리)
4. (선택) DESIGN.md 있으면 외관 이슈가 committed direction/토큰/anti-slop 위반인지 대조
5. (선택) 외부 정적 분석 결과와 시각 결과 비교 — 없으면 시각 결과 단독 보고. Delta 분류:
- 정적 PASS → 시각 WARN/FAIL: "시각 발견" (정적 분석이 놓친 이슈)
- 정적 FAIL → 시각 PASS: "오탐 가능" (재검토 필요)
- 양쪽 FAIL: "이슈 확정"
6. 최종 PASS/FAIL 판정 — **2축 결정론 + 1축 조건부**(하나라도 해당하면 FAIL, 육안 종합 아님):
(a) 기능축: 임의 viewport의 {vp}-functional-axis.json fail_count ≥ 1
(b) Vision 보조: tree-불가 시각 이슈 중 P0 1건 이상
(c) 외관축(수치, **pixel-diff.json 결과 파일이 실제로 존재할 때만 적용** — 없으면 이 조건 자체가 해당 없음, FAIL 사유로 세지 않는다): 임의 viewport의 pixel-diff-gate.sh 판정이 exit 2(diffPixelRatio > 1%)
7. 판정 결과를 {evaluator_result_path} 에 JSON으로 Write (판정 근거로 어느 축이 FAIL을 유발했는지 명시)
절대 관대하게 보지 않는다:
- "전체적으로 괜찮아 보인다" 금지 → 각 항목 개별 검증
- Generator의 의도를 추정하여 실수를 용납하지 않는다
- rubric·수치 기준은 대리지표(proxy)다 — 점수 최적화(reward hacking)·무한 폴리싱 금지. intent(디자인 의도·기능 충족)로 판정. (G15)
"""
)
입력/출력 파일:
/tmp/visual-loop/{vp}-functional-axis.json × 3, /tmp/visual-loop/{vp}-pixel-diff.json × 3(선택), /tmp/visual-loop-analysis-{viewport}.json × 3, (선택) {project-root}/DESIGN.md, (선택) 외부 정적 분석 결과/tmp/visual-loop-evaluator-result.jsonEvaluator 결과가 나오면 Step 4(Delta 분석)는 해당 JSON을 기반으로 요약만 수행한다.
Evaluator 결과 (/tmp/visual-loop-evaluator-result.json) 를 읽어 Delta 요약:
Delta 판정 기준:
| 정적 결과 | 시각 결과 | 판정 | 처리 |
|---|---|---|---|
| PASS | PASS | 일치 | 보고만 |
| PASS | WARN/FAIL | 시각 발견 | 정적 분석이 놓친 이슈 → 리포트 |
| FAIL | PASS | 검토 필요 | 정적 오탐 가능 → 재검토 |
| FAIL | FAIL | 일치 | 이슈 확정 |
"시각 발견" 항목이 이 PoC의 핵심 가치.
저장 경로: forge-outputs/docs/reviews/visual-loop/{YYYY-MM-DD}-{slug}-report.md
리포트 구조:
# Visual Loop Report — {URL}
**날짜:** {date} **Viewport:** {desktop/tablet/mobile}
## 요약
- 시각 분석: {PASS X / WARN Y / FAIL Z}
- **시각 발견(정적 누락):** {count}
- **Evaluator 최종 판정**: PASS / FAIL
## 스크린샷



## Delta 상세
### 정적 PASS → 시각 WARN/FAIL (시각 발견)
| 항목 | Viewport | Gemini 소견 | 제안 수정 |
|---|---|---|---|
### 정적 FAIL → 시각 PASS (오탐 가능)
...
## 권고 조치
- P0 (즉시): ...
- P1 (이번 주): ...
시각 발견이 명확한 경우(예: 모바일에서 버튼 잘림):
Step 6 사용자 승인 후 수정이 실제로 시각 이슈를 해결했는지 1회 자동 재검증한다.
re-verify 절차 (cap=1회, 초과 시 Human에 위임):
1. Step 2 재캡처(스크린샷+aria.json, 동일 viewport 세트) → Step 2.5 기능축 재판정
2. Step 3 Gemini Vision 재분석 → Step 3.4 외관 수치 재판정(pixel-diff-gate.sh)
3. Step 3.5 독립 Evaluator 재스폰 (동일 프롬프트, 3축 결과 갱신 반영)
4. Evaluator 판정:
- PASS → "✅ re-verify PASS. Step 6 수정 확인됨." + 리포트 업데이트
- FAIL → "❌ re-verify FAIL. 수정 미해결. Human 개입 요청." + 상세 delta 첨부
5. cap=1 초과 시 (2회 이상 재시도 불가) → [STOP] Human 위임
토큰 캡 적용: re-verify 시작 전 VISUAL_LOOP_TOKEN_CAP 확인
토큰 캡 가드 (전체 스킬):
VISUAL_LOOP_TOKEN_CAP = 환경변수 VISUAL_LOOP_TOKEN_CAP (기본: 400000)
Step 2 시작 전 / Step 7 시작 전 확인:
if estimated_tokens ≥ VISUAL_LOOP_TOKEN_CAP:
"[STOP] VISUAL_LOOP_TOKEN_CAP={cap} 도달. 현재 단계 시작 취소."
완료된 스텝 결과 + 리포트 경로(있으면) 반환
VISUAL_LOOP_TOKEN_CAP 미설정 시 기본값 400000 적용 (정상 fix→re-verify 경로 ~310000 추정을 캡이 상회 — 자기 트립 방지).| 리소스 | 1회 호출당 |
|---|---|
| Playwright 실행 | 로컬 (무료) |
| Gemini Vision API | |
| 스킬 Agent fan-out | 7개 (playwright 3 + analyze 3 + evaluator 1) |
| 소요 시간 |
비용 통제: 매 PR 자동 호출 금지. 의심 PR만 수동 호출.
npm run dev 실행 필요playwright install chromium)GEMINI_API_KEY 환경변수 or forge .envBoris는 "Chrome 확장 + Claude Desktop 내장 브라우저"를 추천. 우리 환경 제약:
결론: Playwright + Gemini Vision 조합이 같은 가치(코드→실행→스크린샷→분석 closed loop)를 WSL에서 달성. Chrome 확장은 대화형 UX 이점만 있고, 자동화 효과는 이 스킬이 동등.
/playwright-parallel-test와 연계해서 사용자 플로우(로그인→결제→확인) 검증 후 스크린샷 캡처pixel-diff-gate.sh)는 이미 본문에 있으나, ad-hoc visual-loop 호출 컨텍스트에서 toHaveScreenshot 베이스라인을 실제로 생산하는 경로는 아직 미배선(현재는 qa/forge-fix 테스트 컨텍스트에서만 존재) — visual-loop 자체 캡처 흐름에 베이스라인 생산·저장 단계를 추가하는 작업이 남아있다.# 기본 (3 viewport)
/visual-loop http://localhost:3000/dashboard
# 모바일만
/visual-loop http://localhost:3000/checkout --viewport=mobile
# 카드게임 프로젝트 예시 (PC 버전)
/visual-loop http://localhost:5173/game/<project> --viewport=desktop
| 증상 | 원인 | 해결 |
|---|---|---|
playwright-cli: command not found | Playwright 미설치 | npm install -g @playwright/cli && playwright install chromium |
ECONNREFUSED localhost:3000 | Dev server 미기동 | 별도 터미널에서 npm run dev 후 재실행 |
| Gemini API 429 rate limit | 과다 호출 | --viewport=mobile 등으로 축소, 10초 sleep 삽입 |
| 스크린샷 빈 화면 | JS 렌더링 대기 부족 | Playwright --wait-until networkidle 옵션 |
| Evaluator 스폰 실패 | Agent 도구 미허용 | allowed-tools에 Agent 포함 확인 (frontmatter) |
출처 및 관련 문서:
forge-outputs/01-research/articles/2026-04-17/2026-04-17-yozm-wishket-com-boris-cherny-15-claude-code-features-analysis.md/screenshot-analyze (Gemini Vision), /playwright-cli (브라우저 자동화), /playwright-parallel-test (E2E)실패 시 [[pev-self-correction]] 적용
병렬/다단계 실행 = Workflow 도구로 컨텍스트 격리 + resume 지원. 패턴: 3 viewport Gemini Vision parallel().
실행: Workflow({ script: Bash("cat ~/.claude/skills/visual-loop/workflow.js") })
CLAUDE_CODE_DISABLE_WORKFLOWS=1 시 기존 방식 fallback.
⚠️ Phase 0 전제: Codex/Gemini Vision용 approve-worker 토큰 외부 선발행 필수 (Workflow는 셸 직접 호출 불가).
Multi-worker 검수 스킬 (Codex + Gemini Double / Opus + Codex + Gemini Triple). 단일 Codex 검수 대비 100% 보완 카테고리 커버. 트리거: /cr-multi, /cr-double, /cr-triple, plan/spec 저장 후 자동(CR_MULTI_AUTO=on), plateau 3회 자동 승격.
Forge 하네스 읽기전용 레거시 감사: 낡은룰/중복/과대 전역컨텍스트/넓은 Skill/불필요 Hook·MCP/제품중복 분류. 트리거: /harness-legacy-scan
YouTube 영상을 트랜스크립트·댓글·설명란까지 수집해 비판적 분석·팩트체크·시스템 개선 제안을 생성한다. 사용자가 YouTube URL을 보내거나 영상 분석을 요청할 때 사용한다.
REST API 엔드포인트 HTTP 레벨 E2E 자동 테스트. Spec 또는 OpenAPI(Swagger) YAML/JSON을 읽어 엔드포인트별 테스트 케이스(happy path/인증 실패/잘못된 입력/경계값)를 자동 생성하고 curl로 실행한다. 응답 스키마를 OpenAPI 스펙과 대조해 드리프트를 감지한다. /qa 스킬이 서버/API 프로젝트 감지 시 자동 트리거. 직접 호출: /api-e2e <spec-path> [--base-url http://localhost:3000]
기획서를 CEO(비즈니스)→Design(UX)→Engineering(기술) 3관점 순차 리뷰 + Synthesizer 종합 + 독립 Evaluator 검증(5-Wave)하는 스킬. Phase 3 에이전트 회의 후 자동 트리거. (적대적 Codex 검수는 cr-triple/codex-review 별도 게이트.)
PR 생성 전 develop 대비 feature 브랜치의 성능을 비교하는 스킬. 번들 크기, 테스트 시간, API 응답 시간을 측정. P7 PR 생성 전 자동 트리거.