| name | omd:harness |
| description | 화면 전체나 신규 surface를 처음부터 디자인할 때의 진입점 — Discovery→Wireframe→Components→Microcopy→Validation 파이프라인을 omd-master 오케스트레이터로 실행. 트리거: '랜딩페이지', '랜딩 페이지', '랜딩 만들어줘', '홈 화면', '첫 화면', '프로토타입', '그럴싸한', '구색 갖춰', 'first screen', 'first impression', 'landing page', 'landing', 'prototype', 'MVP UI', 'home', 'production-ready', 'wireframe to production', '랜딩 처음부터', 'production-ready', '一からデザイン', '從頭設計'. 자연어 발화('그럴싸한 랜딩 만들어줘', 'MVP UI 잡아줘', '프로토타입이라도 구색 갖춰서')에도 자동 트리거. 단일 컴포넌트 수정은 omd:apply. |
omd:harness — Design Harness Entry
이 스킬은 omd-master 오케스트레이터를 호출하는 단일 진입점이다. 본 스킬은 launcher + 사전체크 + run 디렉토리 부트스트랩 책임만 가지고, phase 로직은 agents/omd-master.md에 있다.
CLI 의존 없음. 모든 부트스트랩은 Bash + Write 툴로 직접 실행한다.
트리거
/omd-harness <task> 명시 호출
- 사용자가 자연어로 "디자인 하네스 / 시니어 디자이너처럼 / 알아서 디자인" 요청
Step 0 — task 추출
슬래시에 task 같이 적었으면 (/omd-harness 물 음용 유도 메인 화면) 그 자연어 부분이 task. 빈 슬래시면 한 번 묻기:
어떤 디자인 작업을 진행할까요?
shape: "[도메인] + [톤/스타일] + [핵심 화면]" — 예: "토스 스타일 가족용 식단 앱 메인 화면"
Step 1 — Subagent registration 자동복구 (v1.6.0+)
omd-master subagent가 이 세션에 dispatch 가능한지 verify. Agent tool의 사용 가능 subagent 목록에 omd-master가 있으면 Step 2로. 없으면 게이트로 막지 말고 자동복구:
1.1 — Agent 폴더 detect
다음 폴더 중 가장 먼저 존재하는 것을 target:
.claude/agents/ (claude-code agent)
.codex/agents/ (codex-cli agent)
.agents/ (openhands / generic)
없으면 .claude/agents/를 mkdir로 생성 (claude-code default).
1.2 — 패키지 source 위치 확인 + 복구 Write
OMD_DIR=$(npm root -g)/oh-my-design-cli
[ -d "$OMD_DIR" ] || OMD_DIR=$(npm root)/oh-my-design-cli
SRC="$OMD_DIR/agents/omd-master.md"
TARGET=".claude/agents/omd-master.md"
[ -f "$SRC" ] && cp "$SRC" "$TARGET" && echo "RECOVERED" || echo "SRC_MISSING"
성공 시 사용자에게 한 줄:
omd-master subagent를 ${TARGET}에 복구했어요. /agents 한 번 실행하거나 다음 turn에서 자동으로 잡힙니다. 계속 진행할게요.
1.3 — 복구도 실패한 경우 (SRC_MISSING / cp 권한 실패)
기존 안내 fallback:
omd-master subagent 자동복구가 실패했어요 (패키지 source 누락 또는 권한 문제).
해결 (가장 빠른 순서):
1. `npx oh-my-design-cli@latest install-skills --all` 재실행 → /omd-harness 재호출
2. /agents 실행 → omd-* 목록 확인 → 안 보이면 Claude Code 재시작
복구 성공 시에도 main agent가 같은 turn에 master를 dispatch 못하는 경우가 있으니, Step 4 spawn 시 한 번 더 verify해서 실패하면 사용자에게 "다음 turn에서 자동 재발동" 안내 후 Step 2-3 산출물(ctx-prime.json + 페르소나 답)만 보존하고 종료.
Step 2 — Run 디렉토리 부트스트랩 (인라인 Bash)
이전엔 omd harness "<task>" --internal CLI를 호출했지만 1.0.0부터는 스킬이 직접 한다. 결정론적 hard verify gate:
2.1 기존 run 재사용 체크
ls -t .omd/runs 2>/dev/null | head -1
출력 있으면 그 디렉토리의 task.md를 Read해서 사용자 task와 의미적으로 일치하는지 확인. 일치하면 그 run 재사용 — Step 3으로 점프.
2.2 신규 run 부트스트랩
다음을 반드시 정확히 이 순서로 Bash 툴로 실행:
TS=$(node -e "console.log(new Date().toISOString().replace(/[:.]/g,'-'))")
SLUG=$(node -e "
const s = process.argv[1].toLowerCase().trim()
.replace(/[^a-z0-9가-힣\s-]+/g,'')
.replace(/\s+/g,'-')
.replace(/-+/g,'-')
.replace(/^-|-$/g,'');
console.log(s.slice(0,40) || 'untitled');
" "<EXTRACTED_TASK>")
RUN_ID="run-${TS}-${SLUG}"
RUN_DIR=".omd/runs/${RUN_ID}"
mkdir -p "${RUN_DIR}"/{wireframes,components,assets/briefs,assets/fallback,assets/pinterest-refs,eval/screenshots,persona-feedback,handoff,checkpoints}
cat > "${RUN_DIR}/task.md" <<EOF
# Harness Task
<EXTRACTED_TASK>
---
- run_id: \`${RUN_ID}\`
- started_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
- cwd: \`$(pwd)\`
EOF
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] run initialized" > "${RUN_DIR}/run.log"
mkdir -p .omd
[ -f .omd/.gitignore ] || printf "runs/\ncache/\n" > .omd/.gitignore
INDEX=".omd/runs/INDEX.md"
[ -f "${INDEX}" ] || cat > "${INDEX}" <<EOF
# Harness Runs Index
One line per run. Append-only.
EOF
TASK_ONELINE=$(echo "<EXTRACTED_TASK>" | tr '\n' ' ' | cut -c1-120)
echo "- $(date -u +%Y-%m-%dT%H:%M:%SZ) \`${RUN_ID}\` — ${TASK_ONELINE}" >> "${INDEX}"
echo "RUN_DIR=${RUN_DIR}"
echo "RUN_ID=${RUN_ID}"
2.3 Hard verify gate (master spawn 차단 조건)
부트스트랩 다음, master spawn 전에 반드시:
test -d "${RUN_DIR}" && test -f "${RUN_DIR}/task.md" && echo "OK" || echo "FAIL"
OK가 출력되지 않으면 master는 절대 spawn하지 않는다. 사용자에게:
하네스 부트스트랩이 실패했어요 (run dir or task.md 누락). 디스크 권한·경로 문제일 수 있어요. 다시 시도하거나 .omd/ 디렉토리를 정리해주세요.
이 gate를 통과해야만 Step 2.5로.
Step 2.5 — CTX-PRIME — 코드베이스 자동 분석 (v1.6.0+)
reference를 고르라고 사용자에게 묻기 전에 먼저 레포를 본다. 사용자가 듣고 싶은 첫 문장은 "이 레포 분석했어요 — Next.js 14 + 토스 블루 + 4개 surface" 같은 진단이지 "어느 레퍼런스 골라드릴까요?"가 아니다.
2.5.1 — ctx-prime helper 실행
OMD_DIR=$(npm root -g)/oh-my-design-cli
[ -d "$OMD_DIR" ] || OMD_DIR=$(npm root)/oh-my-design-cli
HELPER="$OMD_DIR/scripts/ctx-prime.cjs"
[ -f "$HELPER" ] || { echo "CTX_PRIME_MISSING"; exit 0; }
node "$HELPER" "$(pwd)" "${RUN_DIR}"
성공 시 ${RUN_DIR}/ctx-prime.json 생성. ~12-50ms (typical repo).
CTX_PRIME_MISSING (구버전 CLI) → Step 3로 직진 (legacy path).
2.5.2 — ctx-prime.json Read + 사용자 picker 게이트
Read 툴로 ${RUN_DIR}/ctx-prime.json 로드. 다음 필드만 사용자에게 한 줄로 brief:
stack.framework, stack.kind, brand_signal.dominant_color_hex
surface_inventory.length (몇 개 surface 발견)
brand_signal.language (ko / en / ja)
AskUserQuestion 1개를 다음 shape로:
question: "이 레포 분석했어요 — {framework} + {dominant_color_hex} 베이스 + {N}개 surface ({language} 카피). 이번 작업의 1차 타깃 페르소나는?"
header: "Audience"
options: ctx-prime.audience_hypothesis 상위 3개 → label/description 매핑
- audience_hypothesis[0]: label + "(추천)", description = evidence
- audience_hypothesis[1]: label, description = evidence
- audience_hypothesis[2]: label, description = evidence (없으면 생략)
(AskUserQuestion이 자동 "Other" 추가하므로 자유 입력 페르소나도 가능.)
사용자 답을 ctx-prime.json에 confirmed_audience 필드로 merge (Edit 또는 Write):
{
"confirmed_audience": "외부 트래픽 — SEO/conversion 우선, 톤 일탈 허용"
}
2.5.3 — Interview-lite (2-4 picker 묶음)
페르소나 확정 직후 AskUserQuestion 1번 더, 최대 4개 question 묶음. ctx-prime 결과를 활용해 picker option을 동적 구성:
Question 1 — exit_scope:
- "단일 화면만 (한 surface 깊이)"
- "풀 랜딩 (hero + features + CTA + footer)" — 추천 (대부분의 경우)
- "다중 surface (랜딩 + product preview + docs)"
Question 2 — wow moment:
- ctx-prime.wow_moment_candidates 상위 3개 + "Other"
Question 3 — primary CTA:
- "Sign-up / Get started" — 추천 if audience=외부
- "Book demo / Contact sales"
- "GitHub star / View source"
- "View docs / Read more"
Question 4 — visual grounding:
- "Live reference capture (느림, 정확)" — 추천 if exit_scope=풀랜딩
- "Catalog-only (빠름, generic)"
답을 ${RUN_DIR}/handoff/.handoff.json에 prefilled_slots로 적재:
mkdir -p "${RUN_DIR}/handoff"
cat > "${RUN_DIR}/handoff/.handoff.json" <<EOF
{
"state": "PROPOSE_PLAN",
"prefilled_slots": {
"audience": "<confirmed_audience>",
"exit_scope": "<answer 1>",
"wow_moment": "<answer 2>",
"cta_primary": "<answer 3>",
"visual_grounding": "<answer 4>"
},
"ctx_prime_ref": "ctx-prime.json",
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
이 시점부터 master는 SLOT_GATE를 건너뛰고 PROPOSE_PLAN으로 직행한다 (master INTAKE 분기 참고).
2.5.4 — Backward compatibility
ctx-prime.json 누락 또는 사용자가 picker에서 "Other → 알아서 골라줘" 답하면 Step 3 (reference picker) 그대로 진행. prefilled_slots 없으면 master는 legacy SLOT_GATE 흐름.
Step 3 — DESIGN.md 존재 확인 + reference 의미 매칭
프로젝트 루트에 DESIGN.md 없으면 reference를 직접 추천한다. 외부 API 호출 없음.
3.1 카탈로그 로드
다음 파일을 Read 툴로 전체 로드:
.claude/data/reference-fingerprints.json — reference fingerprint (tone keywords, visual theme, antipatterns, signature motion, has_personas, category)
.claude/data/reference-tags.md — 사람-읽기용 keyword 매트릭스
.claude/data/vocabulary.json — controlled vocab
.claude/data/에 없으면 node_modules/oh-my-design-cli/data/ 또는 패키지 root data/ 에서 fallback.
3.2 사용자 task 분석 (silent)
- controlled-vocab 키워드 추출 (예: "헬스/웰니스 / calm-blue / 차분" →
[calm, minimal, approachable, warm])
- 명시 brand hint (예: "토스 같은" →
["toss"])
- 카테고리 추측 (Consumer / Productivity / Fintech / AI / Developer Tools / Design Tools / Automotive / Aerospace / SaaS / Enterprise)
3.3 점수 계산 (in-head, 결정론적)
- 각 ref의
tone_keywords ∩ task keywords → 1점/매칭
- brand hint match → +5점
- 카테고리 일치 → +1점
- top 5 정렬
3.4 검증 (hallucination 방지)
추천하는 모든 id는 reference-fingerprints.json의 items[].id에 반드시 존재해야 한다. 없는 id는 만들어내지 않는다.
3.5 사용자에게 제시 (자연어 prose)
라벨 없이, 추천을 statement로:
DESIGN.md가 없어서 reference 한 개를 골라 부트스트랩할게요. <task 핵심 한 줄>을 보니 <top1.id>가 가장 잘 맞을 것 같아요 — <visual_theme 핵심 + 매칭 키워드 1-2개를 한 줄로>.
이대로 가시려면 go (또는 <top1.id>).
다른 후보: <top2.id> (한 줄 이유) · <top3.id> (...) · <top4.id> (...) · <top5.id> (...)
본인이 아는 다른 reference면 한 줄로 id만 (예: vercel) — reference 카탈로그에 없으면 알려드립니다.
3.6 사용자 응답 처리
go 또는 reference id (top-5 안) → 그 id로 master spawn
- 다른 reference id (top-5 밖이지만 카탈로그 안) → 동일하게 진행
- 카탈로그에 없는 id → "해당 id는 reference 카탈로그에 없어요. top-5 중에서 골라주세요."
중단 → 종료
3.7 라이브 reference capture (선택)
선택된 reference의 라이브 사이트에서 디자인 토큰 + 시각 reference + 폰트를 가져오면 master phase 정확도가 크게 올라간다. 단일 mode (이전의 clone/inspired 갈래는 v1.3.3에서 폐기 — 시각 동일성은 brand creative work을 가져와야 가능하고, 그건 IP 영역).
사용자에게 한 줄로 묻기:
<id> 라이브 자료를 가져올까요? (토큰·구조 cue·폰트·hero screenshot)
가져옴: 컬러·radius·간격·폰트(open-source CDN)·구조 cue(carousel/CTA 모양/nav)·voice register
가져오지 않음: brand mascot 일러스트·마케팅 사진·로고(사용자 자체 자산으로 시작)
답: yes / skip
yes → omd:reference-capture skill 호출 (Skill 툴) → 끝나면 Step 4로
skip → 바로 Step 4
omd:reference-capture는 LICENSE-NOTE.md / attribution.md / fonts.json / structure.json 작성. 모든 brand-identifying 자산은 assets/_reference/<id>/에 reference 용도로 보존되며 사용자 product DOM에는 들어가지 않는다.
Step 3.8 — Surface signal 추출 (master prompt에 한 줄 전달)
사용자 task에서 surface idiom 신호 추출. 이 신호는 master가 reference-capture 자료 중 어떤 부분을 더 무겁게 볼지 결정한다.
키워드 → surface 매핑
| 키워드 (KR/EN/JP/TW) | surface_signal |
|---|
랜딩, 홈, 메인, landing, home, main, marketing, 홍보, 프로모션, 「ランディング」, 首頁 | marketing |
대시보드, 앱, 화면, 설정, 관리, dashboard, app, settings, console, admin, 「ダッシュボード」, 儀表板 | product |
문서, 가이드, docs, documentation, help, 「ドキュメント」, 文件 | docs |
온보딩, 시작하기, 가입, onboarding, signup, 「オンボーディング」, 註冊 | onboarding |
| (위에 매치 없음) | null (master 자유 판단) |
Step 4 prompt에 포함되어야 할 필드
기존 <RUN_DIR + task + chosen_ref_id> 에 surface_signal과 reference-capture 자료 경로를 명시:
RUN_DIR: <path>
task: <user task>
chosen_ref_id: <id>
surface_signal: marketing | product | docs | onboarding | null
reference_capture_dir: assets/_reference/<id>/ | null
reference_capture_dir이 존재하면 master는 그 디렉토리의 tokens.json, structure.json, screenshots/*.png 를 모두 활용한다 (canonical DESIGN.md만 보지 말 것).
Step 4 — Master 호출 (handoff loop)
Subagent (master)는 AskUserQuestion 직접 호출 불가 (main-thread 전용). file-based handoff 패턴으로 돌린다.
Master에게 강제되는 시각 grounding 규칙
master에게 전달되는 prompt 첫 단락은 반드시 다음을 포함:
시각 grounding 규칙 (위반 = regression):
1. reference_capture_dir이 null이 아니면, master의 Component phase 진입 전에
`<reference_capture_dir>/screenshots/hero-desktop.png`을 Read 툴로 **이미지로**
읽는다. 텍스트 분석만으로 끝내지 말 것 — 실제 hero composition
(carousel/illustration/data-card/text-only 등)을 시각 inspect 후 진행.
2. `<reference_capture_dir>/structure.json`에 기록된 hero.type, cta.dominant_shape,
nav.structure, page.viewport_heights를 wireframe + component 결정에 반영.
surface_signal과 structure.json이 일치하면 그대로, 어긋나면 surface_signal 우선.
3. `<reference_capture_dir>/tokens.json`의 live_overrides가 있으면 그 값을 우선
사용. canonical DESIGN.md 값은 voice/principles/motion philosophy에만 절대 권위.
4. `<reference_capture_dir>/fonts.json`의 `live_observed: true` 항목은 생성 HTML
`<head>`에 반드시 `html_link` 그대로 박을 것. 누락 시 시스템 fallback으로
렌더되어 결과가 spec과 silently 어긋남 (예: Pretendard 미로드 → 둥근 시스템 폰트).
canonical DESIGN.md가 언급한 폰트라도 `live_observed: false`면 적용 금지
(대표 함정: "BM JUA accent only" 같은 spec 문구를 잘못 적용해서 둥근 디스플레이체로 헤더 렌더).
5. **사용자 product 결과물 자산 정책** (단일 mode — 이전 clone/inspired 갈래는 v1.3.3 폐기):
- **brand creative work (mascot 일러스트·마케팅 사진·고유 ornament)는 product DOM 미사용.** captured logo/screenshot은 `assets/_reference/<id>/` 안에 reference로만 보존.
- 마케팅 카피·tagline·slogan은 라이브 사이트에서 verbatim 인용 금지. voice register + 문장 길이만 따르고 텍스트는 사용자 프로젝트 맥락에서 새로 작성.
- **헤더 logo = product name 워드마크만** (v1.3.7부터 — 별도 icon/shapes mark 금지):
```html
<a class="logo" href="/" style="display:inline-flex;align-items:center;text-decoration:none;">
<span class="logo-wordmark">{{PRODUCT_NAME}}</span>
</a>
<style>
.logo-wordmark{
font-family:"Bricolage Grotesque","Space Grotesk","DM Serif Display","Fraunces",-apple-system,sans-serif;
font-size:22px;font-weight:700;letter-spacing:-0.04em;
color:var(--color-text-heading);
}
</style>
```
- **그냥 폰트 + 텍스트**가 logo 역할. shapes mark / DiceBear icon / svg 마크 추가 금지 — 사용자가 짜증나는 "AI가 만든 generic 로고" 회피.
- 디스플레이 폰트는 `<head>`에 `<link rel="stylesheet">` 의무 로드. brand vibe별 선호:
- cool / minimal / fintech serious → **Space Grotesk** 또는 **Bricolage Grotesque** (geometric, modern sans)
- warm / playful / community → **Bricolage Grotesque** (variable, 친근)
- editorial / luxury / serif → **DM Serif Display** 또는 **Fraunces** (display serif)
- CDN URLs (omd:asset-fetch §6 참고, 모두 SIL OFL):
- `https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@400;500;600;700;800&display=swap`
- `https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap`
- `https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&display=swap`
- `https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300..900&display=swap`
- **{{PRODUCT_NAME}} 결정 logic**:
1. 사용자 prompt에 명시된 product name 있으면 그걸로 (예: "내 가계부 앱 만들어줘" → 사용자에게 "이름은 뭘로 할까요? skip하면 placeholder"라고 한 줄 묻기 — 답 받으면 그대로, skip이면 step 2)
2. 사용자 응답 없거나 임의 placeholder 필요 → **brand vibe 맞는 generic placeholder 1개**:
| brand vibe | placeholder 후보 (1개 선택) |
|---|---|
| cool / fintech / minimal | `Folio`, `Mint`, `Nord`, `Vector`, `Norma`, `Atlas` |
| warm / conversational / friendly | `Pop`, `Spark`, `Halo`, `Cosy`, `Hue` |
| community / local / marketplace | `Townie`, `Hood`, `Plot`, `Block`, `Brick` |
| editorial / commerce / curated | `Folio`, `Curator`, `Volume`, `Edit` |
| mobility / service | `Lane`, `Trip`, `Glide`, `Loop` |
| productivity / advisor | `Compass`, `Norm`, `Plan`, `Beacon` |
3. 선택된 이름을 `<title>`, hero copy `[YOUR PRODUCT NAME]` 자리, footer brand 자리 등 **product 전체에 일관 적용** — 사용자가 swap할 때 grep 한 번으로 끝나도록 단일 토큰 유지.
- 사용자에게 한 줄 알림 (생성 후): "Product name으로 `Folio` 사용 — 본인 이름으로 바꾸려면 `landing.html`에서 `Folio` grep replace."
6. **자산 라이브러리 — 검증된 CDN URL만 사용** (mandatory):
**각 항목 옆 timestamp는 마지막 200 OK 확인 일자.** 이 리스트에 있는 URL은 generator가 즉시 사용 가능. 이 리스트에 없는 자산은 generator가 임의로 URL 추측·생성 금지.
**CHARACTERS / AVATARS (hero 캐릭터 자리, CC0)**:
- DiceBear notionists — `https://api.dicebear.com/9.x/notionists/svg?seed=<seed>&size=<n>` ✓ 2026-05-14
- DiceBear lorelei — `https://api.dicebear.com/9.x/lorelei/svg?seed=<seed>&size=<n>` ✓ 2026-05-14
- DiceBear personas — `https://api.dicebear.com/9.x/personas/svg?seed=<seed>&size=<n>` ✓ 2026-05-14
- DiceBear 전체 스타일 카탈로그: `notionists | notionists-neutral | lorelei | personas | adventurer | avataaars | bottts | croodles | fun-emoji | thumbs` — 모두 CC0
- `seed` 값은 brand-relevant 토큰 사용 (예: `banksalad-advisor-001`) — deterministic 출력 보장
**ICONS (product 카테고리·feature 카드·nav)**:
- Lucide (ISC) — `https://cdn.jsdelivr.net/npm/lucide-static@latest/icons/<name>.svg` ✓ 2026-05-14
- Heroicons (MIT) — `https://cdn.jsdelivr.net/npm/heroicons@2/24/outline/<name>.svg` ✓ 2026-05-14
- Tabler Icons (MIT) — `https://cdn.jsdelivr.net/npm/@tabler/icons@latest/icons/<name>.svg` ✓ 2026-05-14
**PATTERNS / BACKGROUNDS**:
- Hero Patterns (CC BY 4.0) — `https://heropatterns.com` ✓ 2026-05-14 (도큐 보고 inline SVG 복사)
- 원형 그라데이션 ornament은 `radial-gradient + filter: blur()` CSS로 generator가 직접 생성 (brand-specific creative work 아님)
**FONTS (open-source)**:
- Pretendard (SIL OFL) — `https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css` ✓ 2026-05-14
- Wanted Sans (SIL OFL) — `https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@latest/packages/wanted-sans/fonts/webfonts/static/complete/WantedSans.css` ✓ 2026-05-14
- Noto Sans KR (SIL OFL) — `https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap` ✓ 2026-05-14
- Inter (SIL OFL) — Google Fonts
**이전 버전에 있던 미검증 항목 (skill 제거됨)**: Humaaans github raw / Open Peeps github raw / unDraw direct CDN / Storyset CDN — 직접 SVG URL 패턴 미공식, 404 빈번. 다시 등재하려면 verify 후 timestamp 부착.
**Fetch 시 절차**:
- 자산 1회 `curl -sIL -o /dev/null -w "%{http_code}"` 로 200 확인
- 결과물 `attribution.md` 에 사용된 URL + 라이선스 + fetch 일자 명시
- `<!-- asset-source: <CDN url>, license: <license>, status: fetched|fallback -->` 코멘트를 HTML에 박을 것
**CDN fetch 실패 fallback 정책 (위반 = regression)**:
- **금지**: 핸드드로잉 inline SVG 캐릭터 합성 (stick figure, 손으로 그린 face 등 — sub-agent가 "curl 못 한다" 핑계 도피 안티패턴). **v1.3.4의 Karrot에서 다시 발생함 — 이번에 gate를 강화.**
- **허용 fallback (우선순위 순)**:
1. 동일 카테고리의 다른 검증 URL 시도 (DiceBear notionists 실패 → lorelei → personas)
2. 모두 실패 시 brand-color 그라데이션 placeholder box:
```html
<div class="hero-character-fallback"
style="aspect-ratio:1; background:linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%); border-radius:24px; display:flex; align-items:center; justify-content:center;">
<span style="color:#fff; font-size:14px; opacity:.7;">[CHARACTER IMAGE]</span>
</div>
```
3. 사용자에게 한 줄로 알림: "CDN 자산 fetch 실패, brand-color placeholder 사용."
- **사용자 product DOM에 amateur SVG는 절대 들어가지 않음.**
**Handcraft gate (v1.3.5 신설 — Karrot 사고 재발 방지)**:
- hero-character.svg 파일을 emit하기 직전 **상단에 의무 코멘트**:
```xml
<!-- omd-asset: source=https://api.dicebear.com/9.x/<style>/svg?seed=<seed>, license=CC0, fetched=<ISO-date> -->
```
- 이 코멘트가 없거나 source URL이 검증 catalog (omd:asset-fetch §2)에 없으면 — generator는 **그 파일 product DOM에 embed 금지**. fallback gradient placeholder 사용.
- product DOM 작성 후 self-audit grep:
- `grep -cE '<path d="M[0-9.,\sCQTLZ\- ]{200,}"' landing.html` (긴 path data = 손으로 그린 anatomy 흔적)
- 차트(.hero-chart) / Lucide 아이콘(viewBox 0 0 24 24) 외에 long path **0** 이어야 함. > 0 이면 sub-agent가 다시 fetch.
- product DOM 안 `<svg>` 인라인은 두 카테고리만 허용:
1. 차트 / 데이터 시각화 (line/bar/area chart — 데이터 패턴, brand-specific X)
2. Lucide / Heroicons / Tabler 아이콘 인라인 (작은 viewBox)
기타 모든 시각 자산은 catalog `<img>` URL 통해 로드. **catalog 외 임의 SVG 그리기 금지.**
자세한 catalog + 검증 gate는 `skills/omd-asset-fetch/SKILL.md` 참조.
7. **CSS 컨테이너 일관성** (위반 = regression):
- 모든 top-level 섹션은 **단일 공유 클래스** `.container-inner` (또는 동등한 이름)을 사용.
- 그 클래스 한 곳에 `max-width: <N>px; margin: 0 auto; padding: 0 24px` 정의. **섹션별로 padding 값 다르게 정의 금지** — header / hero / features / footer 모두 같은 inner 컨테이너로 left/right edge가 정확히 일치해야 함.
- 결과 HTML 작성 후 self-audit: 모든 section의 inner element가 동일한 horizontal padding 적용됐는지 grep으로 확인. 어긋난 곳 있으면 통일.
- 직접 결함 예시 (v1.3.3에서 발생): `.header-inner { padding: 0 24px }` + `.hero-inner { padding: 0 }` → 뷰포트 wide에서 24px offset.
8. **Hero composition decomposition** (위반 = regression):
- **먼저 rule 12 (Hero archetype)에서 archetype 1개 선택**. 모든 archetype은 분리된 elements 원칙 적용.
- 시각 요소는 분리된 element:
- `.hero-character` / `.hero-visual` — img / 또는 inline svg 단일 (DiceBear 등 검증 자산)
- `.hero-chart` — 별도 inline `<svg>` (차트는 brand-specific creative work 아닌 일반 데이터 시각화 패턴)
- `.hero-stat-card` — HTML `<div>` 카드 (숫자·라벨·trend chip)
- `.hero-ornament` — 배경 ornament (CSS radial-gradient + blur)
- `.hero-slide` (carousel archetype용) — 각 slide 분리된 element
- 각 element는 absolute positioning 또는 grid로 조합.
- **금지**: 캐릭터 + 차트 + 데이터 카드를 single SVG 파일 안에 통합 (v1.3.3 결함).
9. **Hero archetype selection** (v1.3.8 신설 — monotone hero 회귀 방지):
v1.3.5~v1.3.7에서 brand가 다른데 hero 구도는 매번 "text-left + character-right + floating cards" 동일 → "와우" 약화. 이 rule부터 sub-agent는 brand vibe + surface signal 기반으로 **7가지 archetype 중 1개를 명시 선택**하고 그에 맞춰 구성.
### archetype catalog
**A. `left-character`** (현행 default)
- 좌측: 카피 + CTA + carousel dot
- 우측: 캐릭터 일러스트 + floating data card 2-3개
- 적합: 핀테크 advisor / 데이터 어드바이저 / B2C 모바일 앱
**B. `center-text`**
- 중앙 정렬 헤드라인 + lead + 2 CTAs
- 아래: 큰 visual (screenshot / video / illustration) 단일
- 좌우 floating card 없음, 시선 단일 축
- 적합: AI / 개발자 도구 / Linear-style minimal SaaS / 검색 중심 portal
**C. `carousel`**
- 풀-블리드 또는 wide carousel — 3-5 slide auto-rotate
- 각 slide: 자체 카피 + visual + CTA (모두 다른 메시지)
- dot pagination + auto 5-7s
- 적합: 커뮤니티 마켓플레이스 / 이커머스 / 다양한 카테고리 promoting
**D. `split-screen`**
- 50/50 그리드: 좌측 = 카피 + CTA / 우측 = 큰 product 이미지 또는 booking widget
- floating card 없음, 큰 우측 단일 visual
- 적합: 모빌리티 (booking strip) / 여행 (검색 widget) / B2B (product 스크린샷)
**E. `editorial-magazine`**
- 풀-블리드 헤로 사진 + overlay 텍스트 (top-strip + cover caption + 큰 headline)
- 잡지 표지 메타포 (Vol.X · Year · Issue 같은 editorial 메타)
- 적합: 큐레이트 커머스 / 패션 / 라이프스타일 / 문화
**F. `dashboard-preview`**
- 좌측 또는 상단: 짧은 카피 + CTA
- 메인 visual: 큰 product UI 스크린샷 (가짜 dashboard mock)
- 시선 = "이게 우리 product 모습" 자체
- 적합: B2B SaaS / analytics / productivity 도구
**G. `quote-led`**
- 큰 인용구 (display serif, 80-120px) — 사용자 testimonial 또는 brand thesis
- 아래: 짧은 보강 카피 + CTA
- 캐릭터 / 차트 / floating 없음 — 단 한 문장의 권위
- 적합: 컨설팅 / 에이전시 / 럭셔리 brand / 사명 강조 surface
### archetype 선택 룰 (brand vibe ↔ archetype)
| brand category / vibe | 1순위 | 2순위 (variation) |
|---|---|---|
| Fintech (advisor) — banksalad, kakaopay, toss | A `left-character` 또는 F `dashboard-preview` | B `center-text` |
| AI / Developer tools — anthropic, claude, vercel | B `center-text` | F `dashboard-preview` |
| Productivity / SaaS — linear, notion, asana | F `dashboard-preview` | B `center-text` |
| E-commerce (curated/editorial) — 29cm, musinsa, ssense | E `editorial-magazine` | C `carousel` |
| Community marketplace — karrot, dcard, mercari | C `carousel` | A `left-character` |
| Mobility / service — socar, uber, lime | D `split-screen` (booking widget) | C `carousel` |
| Travel — airbnb, yanolja, kayak | D `split-screen` (검색 widget) | E `editorial-magazine` |
| Luxury / heritage — ferrari, hermes | G `quote-led` 또는 E `editorial-magazine` | D `split-screen` |
| Consultancy / B2B services | G `quote-led` | B `center-text` |
| Health / wellness — gangnamunni, headspace | B `center-text` 또는 D `split-screen` | A `left-character` |
### 선택 절차
1. brand의 `web/references/<id>/DESIGN.md` §1 (Visual Theme) + fingerprint `category`를 읽고 위 표에서 1순위 archetype 선택
2. 사용자가 같은 brand로 **이미 한 번 실험**했으면 (`.omd/runs/INDEX.md`에 기록) → 2순위 사용해서 variation 제공
3. 사용자가 명시 ("center 정렬로", "carousel로") → 그대로 따름
4. 선택된 archetype을 `experiment-meta.json`의 `hero_archetype` 필드에 명시 (gallery 표시용)
5. 사용자에게 한 줄 알림: "Hero archetype: `center-text` 사용 — Linear-style minimal SaaS 매칭"
### archetype 구현 시 공통 제약
- 모든 archetype은 rule 8 decomposition 따름 (element 분리)
- 모든 archetype은 rule 10 reveal safety net 적용
- 모든 archetype은 rule 5 wordmark-only logo 사용
- 모든 archetype은 rule 6 verified asset catalog만 사용
- archetype별 specific 자산 (예: editorial-magazine의 헤로 photo) — Picsum/Loremflickr 결정적 URL 필수
10. **IntersectionObserver reveal safety net** (v1.3.6 신설, 위반 = regression):
10. **IntersectionObserver reveal safety net** (v1.3.6 신설, 위반 = regression):
이전 batch에서 toss + socar 둘 다 round-2 refinement에서 **동일한 결함을 fix**함 — IntersectionObserver-기반 scroll reveal이 fullpage screenshot 또는 인쇄 모드에서 viewport 밖 element를 `opacity: 0`으로 영구히 hidden시킴. 시스템 결함이라서 skill에 박음:
- reveal 패턴 사용 시 **safety net 의무**:
1. CSS `@keyframes failsafeReveal` 정의:
```css
@keyframes failsafeReveal { to { opacity: 1; transform: none; } }
[data-reveal] { animation: failsafeReveal 0.01s ease 2s forwards; }
html.js-ready [data-reveal] { animation: none; } /* JS ready 시 IO가 주도 */
.is-revealed { opacity: 1 !important; transform: none !important; }
```
2. JS 초기화 시점에 `document.documentElement.classList.add('js-ready')` 추가 → IO 정상 동작 path 진입
3. JS 미로드 / IO 미지원 / fullpage screenshot 시 → 2s 후 자동 reveal (CSS animation forwards)
4. `@media (prefers-reduced-motion: reduce)` 에서는 모든 reveal element를 `opacity: 1` 즉시
- **금지**: `opacity: 0` initial state + IO trigger만 + safety net 없음 — fullpage screenshot 시 빈 섹션 발생
11. **"와우" 수준 polish 체크리스트** (master는 다음 중 최소 5개 구현):
- [ ] 헤더 sticky + scroll 시 subtle backdrop blur or shadow
- [ ] Hero 카르셀 (auto-rotate 5-7s, dot indicator, smooth fade)
- [ ] CTA hover micro-interaction (brand의 motion philosophy 따라 — banksalad면 "lighten on hover")
- [ ] 숫자 count-up 애니메이션 (자산·사용자 수 등 stat에)
- [ ] Scroll reveal stagger (IntersectionObserver, 60ms 간격)
- [ ] 차트 stroke-dashoffset draw-in (financial 도메인이면 mandatory)
- [ ] Feature 카드 hover에 subtle elevation + icon rotate/scale
- [ ] Background ornament — 원형 그라데이션 blur layer (CSS `filter: blur(120px)`로 ambient color wash)
- [ ] `prefers-reduced-motion` 가드 (모든 모션 끔)
- [ ] Footer가 정보 풍부 (4 column, legal·company·resources·contact)
- [ ] 모바일 viewport에서도 깔끔 (768px / 375px 둘 다)
모든 모션은 brand DESIGN.md §15 (Motion & Easing)의 duration scale + easing 토큰 따름.
이 규칙은 Step 4의 master spawn prompt에 매번 prefix로 들어간다 (사용자에게 노출 X — 내부 contract).
루프 의사코드
spawn_count = 0
prompt = "<위 grounding 규칙 prefix> + <RUN_DIR + task + chosen_ref_id + surface_signal + reference_capture_dir>. Phase 1부터 시작."
while spawn_count < 12 (safety cap):
result = Agent({
subagent_type: "omd-master",
description: "Run design harness round N",
prompt: prompt
})
spawn_count += 1
handoff_path = "<RUN_DIR>/.handoff.json"
if not exists(handoff_path):
relay result text to user; halt
handoff = JSON.parse(Read(handoff_path))
if handoff.user_prose:
print handoff.user_prose to user
if handoff.status == "done": halt
if handoff.status == "error": halt + show
if handoff.status == "ask_user":
questions = JSON.parse(Read(handoff.questions_file))
answers = AskUserQuestion({ questions: questions.questions })
answers_file = "<RUN_DIR>/checkpoints/<handoff.checkpoint_id>.answers.json"
Write(answers_file, JSON.stringify({checkpoint_id, answers}))
prompt = "continue checkpoint:" + handoff.checkpoint_id + " — answers at " + answers_file
Safety cap
한 번의 /omd-harness 호출에 최대 12 spawn. 초과 시 사용자에게 escalate ("master가 12 spawn 초과, 멈춥니다 — run dir 보존").
재진입
사용자가 자연어로 "go" / "fix X" 답하면 동일 loop 재시작. master는 .handoff.json 보고 어디까지 갔는지 파악.
사용자 체크포인트 처리
Master가 체크포인트에서 turn을 종료한 후 다음 사용자 메시지가:
- 하네스 컨텍스트 안의 응답 (예: "go", "fix the home screen IA", "stop") → master 재spawn + 그대로 전달
- 다른 작업으로 바뀐 메시지 → run 디렉토리에
paused.flag 생성. 나중에 /omd-harness resume 하면 재개
산출물 위치 (master가 emit, 이 스킬은 안내만)
.omd/runs/run-<ts>-<slug>/
├── task.md
├── brief.md
├── references-cited.md
├── journey.mmd
├── wireframes/
├── DESIGN.md.patch
├── components/
│ ├── manifest.json
│ └── microcopy.json
├── assets/
│ ├── brief.md
│ ├── manifest.json
│ ├── briefs/
│ ├── fallback/
│ └── pinterest-refs/
├── eval/
│ ├── deterministic.json
│ ├── jury.json
│ └── screenshots/
├── persona-feedback/
│ └── <persona>.json
├── critique.md
├── handoff/
│ ├── v0.zip
│ ├── cursor.zip
│ └── subframe.zip
├── run.log
└── postmortem.md
이 스킬이 하지 않는 것
- Phase 로직 실행 (master)
- Sub-agent 직접 spawn (master)
- 사용자 응답 해석/라우팅 (master)
- DESIGN.md 직접 수정 (Phase 5에서 master)
금지
- Master 없이 phase를 직접 수행하지 말 것
- 사용자 체크포인트를 자동 승인하지 말 것
- Run 디렉토리를 임의로 정리/삭제하지 말 것
- Step 2.3 verify gate 통과 전에 master spawn 절대 금지