/design-md skill — orchestration body

SkillMedia

design-md is a library of design skills that lets your AI create and edit app UI designs. Once added, your AI can generate interface designs and refine them through edits, following an open standard that works with common coding agents. The result is app UI designs produced directly by your AI instead of starting from a blank screen.

Available today. Use it from your connected AI after setup.

Add design-md to your AI, then ask it to design or revise an app screen. It will use the skill library to produce and edit the UI.

Then ask your AI: use the /design-md skill — orchestration body skill

What your AI can do with it

  • Generate app UI designs
  • Edit existing UI designs
  • Work alongside coding agents like Gemini CLI, Claude Code, and Cursor
  • Follow the Agent Skills open standard for compatibility

What this skill tells your AI

The instructions your AI receives, as published by caesiumy/ko-design-md in .claude/skills/design-md/SKILL.md and read by ahel’s review.

This skill builds a complete catalog entry through a 5-subagent pipeline with one user checkpoint. The pipeline is heavy (research, drafting, two review loops) so resumability matters: each stage's artifact lives on disk in .claude/cache/design-md/{slug}/ and the next stage reads from there. State is encoded by file presence — no separate state.json needed for v1.

Pipeline shape

[INTAKE] → research-collector → design-md-author ⇄ design-md-reviewer (loop ≤3)
                                                          ↓ score≥8 or N=3
                                                    [USER CHECKPOINT]
                                                          ↓ approve
                                                [WRITE_MD] → [TOKENS]
                                                          ↓
                                  preview-html-author ⇄ preview-html-reviewer (loop ≤3, non-blocking)
                                                          ↓
                                                   [WRITE_PREVIEW]
                                                          ↓
                                                       [BUILD_OG]
                                                          ↓
                                                       [VERIFY]
                                                          ↓
                                                         END

Loop termination: design loop is blocking — score must reach 8/10 within 3 iterations or the user decides at the checkpoint. Preview loop is non-blocking — proceed with warning if score < 8 at iteration 3.

Key reference files (read these before dispatching subagents that need them):

  • .claude/skills/design-md/references/stitch-format.md
  • .claude/skills/design-md/references/design-md-template.md
  • .claude/skills/design-md/references/rubric-design.md
  • .claude/skills/design-md/references/rubric-preview.md

Stage 1 — Preflight

Verify the working environment before doing anything user-visible.

  1. Bash: pwd to capture the absolute repo root. Hold this value as ${repo_root} in your reasoning and substitute it literally into every later Bash command and dispatch prompt that touches a repo path. The shell preserves cwd across calls, but pinning the absolute path makes Stage 8/10/11 robust to any inadvertent cd.
  2. Bash: date +%Y-%m-%d to capture today's date. Hold this value as ${today} in your reasoning. Stage 6a passes this to the author for both the last_updated and created_at frontmatter fields — a brand-new entry is added and last-updated on the same day. The project's date validator at src/lib/content-parser.ts rejects any other format, and missing-created-at in src/lib/draft-validator.ts blocks a draft that omits created_at.
  3. Read ${repo_root}/package.json. If "name" is not exactly "ko-design-md", abort with: "이 스킬은 ko-design-md 레포 안에서만 동작합니다. 현재 디렉터리: ${repo_root}". Do not proceed.
  4. Verify ${repo_root}/src/lib/content-types.ts is readable. If not, abort.
  5. Read ${repo_root}/src/lib/content-types.ts and extract the live CATEGORIES const. Use this as the source of truth for the intake category picker (do NOT hardcode the enum from memory — it can drift).

Stage 2 — Conversational intake

Use a single AskUserQuestion form with these 4 questions (multi-select where indicated):

  1. 브랜드명 (text via "Other" → custom input): e.g. "토스", "당근", "구름". Use the Korean company/brand display name as it should appear in the name frontmatter, not the design system product name. If research later surfaces a distinct design system name (e.g. "SEED Design", "Vapor UI"), the author stores that in optional design_system_name.
  2. 참고 URL (text via "Other"): comma-separated URLs. 2개 이상 권장 — 1개만 입력 시 research-collector가 INSUFFICIENT_SOURCES로 중단할 수 있고, 그 경우 스크린샷 보강 필요. Brand homepage, design system page, blog post about their UI, etc.
  3. 카테고리 (single-select): all values from CATEGORIES const, in order. Last option is etc.
  4. 언어 (single-select): ko (한국어 본문), en (English body), both (두 파일 생성). Default ko recommended.

Then ask three follow-up text inputs:

  • 스크린샷 경로 (optional) — comma-separated absolute paths to screenshot files. The user can type "없음" to skip.

  • 로고 자산 경로 (optional) — an existing local file path for a brand logo. Accept only .svg, .png, .webp, or .avif. The user can type "없음" to skip.

    CRITICAL — pick a small square symbol mark, NOT a wordmark. Two square slots in the site consume this asset: the catalog grid card (~48–96 px on screen) AND the OG image's top-left brand mark (32×32 px in the 1200×630 social card, see src/og/template.tsx). The OG renderer (Satori) has limited object-fit support, so a non-square asset is stretched into the 32×32 box rather than letterboxed — the catalog card has the same constraint at its own scale. Choose accordingly:

    • ✅ Pick the brand's standalone symbol / mark / favicon shape with a transparent background — e.g. SOCAR's angular blue mark, Toss's curved oval lens, Gmarket's circular G, Baemin's symbol. Match the style of existing public/logos/{toss,socar,baemin,…}.png (square, no text, no baked-in frame).
    • ❌ Avoid the horizontal wordmark / lockup (the brand name written out, e.g. "Gmarket", "toss", "쏘카") — wordmarks render too small in the grid card or break its aspect.
    • ❌ Avoid iOS-squircle / framed app icons with a rounded gradient background baked in — that frame conflicts with the catalog card's own background. Prefer the unframed symbol form.
    • When a bundle provides multiple variants (e.g. logo-brand.png wordmark vs logo-circular-g.png symbol vs logo-app-icon.png framed), the unframed symbol is correct for the catalog grid. Filename hints for the GRID-WRONG forms: *wordmark*, *logotype*, *-brand*, *-horizontal*, *-app-icon* (framed). Filename hints for the GRID-RIGHT form: *-symbol*, *-mark*, *-circular*, *-icon* (when unframed), or a generic {slug}.png that is already a symbol.

    Optional second asset — wordmark/logotype for the preview hero. The catalog grid uses the symbol, but the preview HTML hero (public/preview/{slug}/preview.html) has room for a richer brand lockup with the brand name visible. If the source provides BOTH a symbol AND a horizontal wordmark/logotype, capture both paths. Stage 4a will place the wordmark at public/logos/{slug}-logotype.{ext} (matching the existing toss-logotype.png convention), and the preview-html-author renders the wordmark in the hero where there is space. The grid card always uses the symbol; the wordmark has no frontmatter field — it stays a site-internal preview-only asset (the design.md's logo frontmatter URL still points to the symbol so the file remains portable outside ko-design-md).

  • 디자인 시스템 문서 사이트 URL (optional) — if the brand publishes its design system as a documentation website (not only Figma), the root URL of that site (e.g. https://socarframe.socar.kr/). Stage 4b crawls it into a research corpus. The user can type "없음" to skip.

Capture the answers as: brand_name, source_urls (parsed array), category, lang, screenshot_paths (parsed array, may be empty), logo_asset_path (string or empty), docs_site_url (string or empty).

Screenshot path preflight: for each path in screenshot_paths, run Bash: [ -f "$path" ]. If any path is missing, surface the missing list to the user and re-prompt the screenshot question. This avoids research-collector failing silently mid-read.

Logo path preflight: if logo_asset_path is not empty/없음, run Bash: [ -f "$logo_asset_path" ] and verify the extension matches svg|png|webp|avif. If missing or unsupported, surface the problem and re-prompt the logo question. Do not download logos from the web.

Stage 3 — Slug derivation + conflict resolution

Derive slug from brand_name:

  1. NFD-normalize and strip diacritics/non-ASCII.
  2. Lowercase, replace [^a-z0-9]+ with -, trim leading/trailing -.
  3. If the result is empty (Korean-only brand with no Latin form), prompt the user via AskUserQuestion for an explicit slug. Question wording: "slug은 영문 소문자/숫자/하이픈만 가능합니다 (예: toss, karrot-market)." Validate the user's input matches ^[a-z0-9-]+$; on mismatch, re-prompt.

Check for conflicts via Bash (ls services/{slug}.md 2>/dev/null and ls services/{slug}.en.md 2>/dev/null):

  • No conflict → proceed.
  • Conflict → AskUserQuestion:
    • "다른 slug 사용" → user provides a new slug, recheck.
    • "기존 항목 업데이트" → set mode = update. The pipeline still runs but final write overwrites.
    • "취소" → abort.

Stage 4 — Cache setup

Create the staging directory:

mkdir -p .claude/cache/design-md/{slug}

This directory holds all intermediate artifacts. It's gitignored (.claude/cache/ was added to .gitignore when the skill was installed) so partial work won't leak into PRs.

Stage 4a — Logo asset resolution

Before resolving paths — verify the logo asset is the right FORM. The catalog grid card uses a small square logo slot, so the chosen asset MUST be a symbol / mark / favicon shape (transparent background, no text), NOT a horizontal wordmark and NOT an iOS-squircle app icon with a baked-in background. When auto-picking from a bundle/zip that contains multiple variants, prefer filenames matching *-symbol*, *-mark*, *-circular*, or unframed *-icon*; reject filenames matching *wordmark*, *logotype*, *-horizontal*, *-brand* (often the wordmark), or *-app-icon* (often the iOS-squircle framed form). If only a wordmark variant is available, prompt the user to confirm before placing it — wordmarks are a known catalog-grid mismatch (see Stage 2's logo intake rule and the existing public/logos/{toss,socar,baemin,...}.png reference). This check applies inside step 1 below.

Resolve two logo values before dispatching author agents — different downstream concerns need different forms:

  • logo_url — fully-qualified URL like https://getdesign.kr/logos/toss.png. Goes into design.md frontmatter, where it must stay meaningful when the file is copied outside the ko-design-md site (PRD User Story 1 — vibe-coding flow).
  • logo_src_path — site-relative path like /logos/toss.png. Goes into preview HTML <img src>, which is only ever loaded inside the catalog site's iframe. Keeping it relative avoids making dev/staging depend on the production-domain asset.

Both either co-exist (logo found) or are simultaneously empty (no logo).

The canonical site origin is https://getdesign.kr. Change this constant in one place only — this paragraph — if the origin ever moves.

  1. If logo_asset_path was provided:
    • Verify it exists and has a supported extension (svg, png, webp, avif).
    • If it already lives under ${repo_root}/public/logos/, set logo_src_path = /logos/{basename} and logo_url = https://getdesign.kr/logos/{basename}.
    • Otherwise copy it to ${repo_root}/public/logos/{slug}.{ext} and set logo_src_path = /logos/{slug}.{ext} and logo_url = https://getdesign.kr/logos/{slug}.{ext}. This is allowed only for user-supplied local logo assets.
  2. If no logo path was provided, auto-detect the first existing file in public/logos/{slug}.{svg,png,webp,avif} (in that order) and set logo_src_path = /logos/{slug}.{ext} and logo_url = https://getdesign.kr/logos/{slug}.{ext}.
  3. If nothing is found, set both to an empty string and continue. The entry may ship without a logo, but Stage 13 must report the missing logo TODO.
  4. Optional wordmark / logotype for the preview hero. If a wordmark variant was captured at Stage 2 (a horizontal lockup that contains the brand name as text — e.g. logo-brand.png, *-logotype.svg), copy it to ${repo_root}/public/logos/{slug}-logotype.{ext} and set logo_wordmark_src_path = /logos/{slug}-logotype.{ext}. If no wordmark was captured at intake but a file already exists at public/logos/{slug}-logotype.{svg,png,webp,avif}, auto-detect it (same precedence order as the symbol). Otherwise set logo_wordmark_src_path = "". There is NO frontmatter URL for the wordmark — it is a site-internal preview-only asset; the design.md logo field always references the symbol so the file remains portable outside ko-design-md.

The auto-detect pattern for the catalog grid logo is exactly public/logos/{slug}.{svg,png,webp,avif}; for the optional wordmark it is public/logos/{slug}-logotype.{svg,png,webp,avif}. When the symbol values are non-empty, every later stage must preserve them exactly — design-md-author writes logo_url verbatim into frontmatter, preview-html-author embeds logo_src_path as <img src> (or logo_wordmark_src_path in the hero when that is non-empty), and the Stage 10 grep checks match each file against the appropriate form.

Stage 4b — Docs-site crawl (conditional)

If docs_site_url is empty or "없음", skip this stage and set crawl_corpus_path = "none".

Otherwise, crawl the brand's documentation site into the cache directory so research-collector can use it as a primary source. This runs the docs-crawler skill's engine — a sitemap-driven crawl with a JS-render fallback that also localizes images (external and inline base64) into crawl/images/, so the cached corpus is self-contained:

cd "${repo_root}" && pnpm crawl:docs "${docs_site_url}" --out "${repo_root}/.claude/cache/design-md/{slug}"

The crawl writes crawl-corpus.md (the merged corpus) plus crawl/pages/*.md, the downloaded crawl/images/, and crawl/manifest.json into the (gitignored) cache directory. The first crawl of a JavaScript-rendered site auto-installs a headless browser (~150MB, one-time).

After it returns, verify the corpus landed:

[ -s "${repo_root}/.claude/cache/design-md/{slug}/crawl-corpus.md" ] && echo CORPUS_OK || echo CORPUS_MISSING
  • CORPUS_OK → set crawl_corpus_path = ${repo_root}/.claude/cache/design-md/{slug}/crawl-corpus.md.
  • CORPUS_MISSING, or the crawl exited non-zero → the crawl failed. It is best-effort: research can still proceed from source_urls. AskUserQuestion: "문서 사이트 크롤 실패 — (a) 다시 시도 / (b) 크롤 없이 진행 / (c) 취소". On "다시 시도" re-run the crawl; on "크롤 없이 진행" set crawl_corpus_path = "none"; on "취소" abort with the resume path.

Stage 5 — Research (research-collector)

Dispatch via Agent tool with subagent_type: "research-collector". Pass this prompt:

Research the brand "{brand_name}" (slug: {slug}) for ko-design-md catalog onboarding.

source_urls: {comma-separated URLs}
screenshot_paths: {comma-separated paths or "none"}
crawl_corpus_path: {crawl_corpus_path from Stage 4b — absolute path to crawl-corpus.md, or "none"}
category: {category}
lang: {lang}
cache_dir: {absolute path to .claude/cache/design-md/{slug}/}

Follow your agent definition. If crawl_corpus_path is not "none", read that corpus first as your primary source. Write exactly one file at {cache_dir}/research.md with the cited-claims structure. Halt with INSUFFICIENT_SOURCES only if crawl_corpus_path is "none" AND fewer than 2 URLs return 2xx.

After the agent returns, Read {cache_dir}/research.md.

  • If the first line of ## Sources is INSUFFICIENT_SOURCES, surface this to the user via AskUserQuestion with options: "URL 추가 입력" / "스크린샷 경로 추가" / "취소". On URL/screenshot addition, re-dispatch research-collector with the augmented inputs.
  • Section sanity check: Bash: grep -c '^## ' {cache_dir}/research.md. Expected output is 9 (one per documented H2 section). If less than 9, the agent silently produced a malformed file — re-dispatch with an instruction prefixed: "Your previous research.md was malformed (only N sections found). Produce ALL 9 H2 sections in the documented order, even if some are (no public evidence found)."
  • Otherwise, proceed.

Stage 6 — Draft + design.md review loop

Iteration counter N = 1. Loop:

6a. Dispatch design-md-author

Via Agent with subagent_type: "design-md-author". Pass:

Author a Stitch v0.1-format design.md draft for "{brand_name}".

cache_dir: ${repo_root}/.claude/cache/design-md/{slug}/
slug: {slug}
name: {brand_name}
category: {category}
lang: {lang}
today: {today as YYYY-MM-DD}
logo_url: {logo_url or "none"}
research_path: ${repo_root}/.claude/cache/design-md/{slug}/research.md
prior_review_path: ${repo_root}/.claude/cache/design-md/{slug}/review-{N-1}.json or "none" on first pass
format_reference_path: ${repo_root}/.claude/skills/design-md/references/stitch-format.md
template_path: ${repo_root}/.claude/skills/design-md/references/design-md-template.md
demo_paths: (none — leave empty by default; pass an existing ${repo_root}/services/*.md only if a stylistic peer genuinely fits the new brand. The early _demo-*.md fixtures have been removed.)

Follow your agent definition. Write {cache_dir}/draft.md.

Bilingual variant: when the user chose both from intake, replace the lang: {lang} line with two lines:

primary_lang: ko
secondary_lang: en

The author then writes both draft.md (lang=ko) and draft.en.md (lang=en) in one pass, per its agent definition. Adjust the trailing Write {cache_dir}/draft.md line to Write {cache_dir}/draft.md AND {cache_dir}/draft.en.md.

After return, verify {cache_dir}/draft.md exists and is non-empty. If missing, the author failed — log the issue, retry once with the same prompt; if still missing, abort with a diagnostic message.

6a2. Deterministic draft gate (machine validation)

Before spending a reviewer dispatch, run the draft validator — it covers every mechanically checkable rubric item (frontmatter round-trip, section presence/order, OKLCH-only token values, [src:N]/References integrity, expected logo) so the reviewer model never has to "grep mentally":

cd "${repo_root}" && pnpm validate:draft .claude/cache/design-md/{slug}/draft.md \
  --slug {slug} --expected-logo {logo_url or none} --lang {lang} \
  --iteration {N} --json-out "${repo_root}/.claude/cache/design-md/{slug}/review-machine-{N}.json"
  • Exit 0 → proceed to 6b, passing the machine report path (see the 6b prompt).
  • Exit 1 (block issues) → do NOT dispatch the reviewer. Re-dispatch 6a with prior_review_path = the review-machine-{N}.json above (its issues[] uses the same severity/section/fix shape the author already consumes). Machine retries use a sub-counter K (max 2) and do not increment N — machine fixes are cheap and must not consume the semantic-review budget.
  • K exhausted with blocks remaining → dispatch 6b anyway; the reviewer receives the failing machine report and the normal loop/checkpoint rules take over (no new termination path).

Bilingual runs: after the primary draft passes, gate draft.en.md the same way with --lang en (write to review-machine-en.json) before the 6d companion review.

6b. Dispatch design-md-reviewer

Via Agent with subagent_type: "design-md-reviewer". Pass:

Score the draft.md at {cache_dir}/draft.md against the rubric.

cache_dir: {abs path}/.claude/cache/design-md/{slug}/
draft_path: {cache_dir}/draft.md
research_path: {cache_dir}/research.md
content_types_path: {abs path}/src/lib/content-types.ts
rubric_path: {abs path}/.claude/skills/design-md/references/rubric-design.md
expected_logo_url: {logo_url or "none"}
machine_report_path: {cache_dir}/review-machine-{N}.json
iteration_n: {N}
output_path: {cache_dir}/review-{N}.json

Follow your agent definition. Write exactly one file at output_path.

The machine report has already verified the deterministically checkable items
(frontmatter round-trip, section presence/order, hex/rgba token scan,
[src:N]/References integrity, expected logo). Do not re-verify those — spend
your review on judgment items: Brand fidelity semantics against research.md,
Voice/tone, and cross-section token contradictions.

After return, Read {cache_dir}/review-{N}.json.

6c. Loop decision

  • If review.passed && review.score >= 8 → exit loop, go to step 6d.
  • Else if N < 3N += 1, go back to 6a (the author will read review-{N-1}.json and revise).
  • Else (N == 3 and not passed) → exit loop with a warn flag; go to step 6d. The user will see the failed verdict at the checkpoint and decide.

6d. Bilingual companion review (only when lang == "both")

If the user chose both, after the primary loop exits, run a single-pass review on draft.en.md. Dispatch design-md-reviewer once more:

Score the draft.en.md at ${repo_root}/.claude/cache/design-md/{slug}/draft.en.md against the rubric.

cache_dir: ${repo_root}/.claude/cache/design-md/{slug}/
draft_path: ${repo_root}/.claude/cache/design-md/{slug}/draft.en.md
research_path: ${repo_root}/.claude/cache/design-md/{slug}/research.md
content_types_path: ${repo_root}/src/lib/content-types.ts
rubric_path: ${repo_root}/.claude/skills/design-md/references/rubric-design.md
expected_logo_url: {logo_url or "none"}
iteration_n: 1
output_path: ${repo_root}/.claude/cache/design-md/{slug}/review-en.json

Follow your agent definition (Bilingual companion mode at bottom of definition).

Read the resulting review-en.json. Apply a relaxed pass criterion: ship the .en.md only if rubric[0].earned == 3 (Schema validity full) AND rubric[1].earned == 2 (Section coverage full). Other items contribute to the user-facing score but do not block. The user sees both reviews at Stage 7.

Stage 7 — User checkpoint

This is the only mandatory user gate. Show the user:

  1. The current draft.md content (read it and display the full file inline, formatted as markdown — paste in code fences).
  2. The latest review-{final}.json verdict — extract score, passed, verdict, and bullet the issues array.
  3. If iteration > 1, show a brief diff highlight: "Iter 1 score: X → Iter {final} score: Y" plus the top 1–2 issues that improved between iterations (compare review-1.json.issues and review-{final}.json.issues).
  4. If lang == "both": also display draft.en.md content + review-en.json verdict. Highlight whether Items 1 and 2 reached full points (the gate for shipping the .en.md). If not, surface the specific issues so the user can request a revision pass.

Then AskUserQuestion:

OptionEffect
"승인하고 계속"Approve as-is. Proceed to Stage 8.
"수정 사항 알려주고 한 번 더"User provides feedback in the "Other" custom input. Append the user's notes to the prior review-N.json's issues array (with severity: block) and re-dispatch the author for one more revision. After this extra revision, run the reviewer once more, then return to checkpoint with the new draft.
"취소"Abort. Cache dir is left intact. Print: "취소되었습니다. 재개하려면 cache 디렉터리에서 작업을 이어가세요: .claude/cache/design-md/{slug}/"

Stage 8 — Write design.md to services/

After approval:

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
45
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
design-md
Source
github.com/caesiumy/ko-design-md