본문으로 건너뛰기

Figma 퍼블리싱 파이프라인 07. v5: LLM이 비교하지 않는 설계

·12 min read·7 / 7

"비교는 기계가 하고, LLM은 설명한다."

Figma 시안을 코드로 옮기는 작업에서 가장 큰 병목은 "LLM이 Figma JSON과 코드를 직접 비교하는 것"이다. 추측이 끼어들고, sublayer를 생략하고, 색상을 틀리게 읽는다. v5는 이 문제를 구조적으로 해결한다.


왜 만들었나

기존 v4까지의 문제:

  • Figma JSON + 코드를 LLM에 던져서 비교 → 부정확
  • 스타일 추측 기반 → 색상, 간격 오류 빈번
  • sublayer 생략 가능성 → 누락된 컴포넌트

v5의 해법은 단순하다. LLM이 비교하는 구간을 없앤다.

Figma JSON → Design IR
Code       → Code IR

         Mapping Engine

          Diff Engine

           diff.json

  LLM (설명 / 설계 / 코드 생성)

비교는 규칙 기반 Python 엔진이 하고, LLM은 결과를 읽고 코드만 수정한다.


전체 구조 (13 Phase)

Phase 03  : Python (0토큰)  ── Figma REST API 수집
Phase 48  : Python (0토큰)  ── IR 생성 + 정규화 + 비교
Phase 911 : LLM (~3,500토큰) ── 리포트 + 설계 + 코드 수정
Phase 1213: Shell  (0토큰)  ── 검증 + 정리

전체 토큰의 95%를 차지하던 "비교" 구간이 0토큰이다.


Phase 0: Figma 원본 수집

Figma URL에서 fileKeynodeId를 파싱해서 REST API 1회 호출.

GET https://api.figma.com/v1/files/{fileKey}/nodes?ids={nodeId}
X-FIGMA-TOKEN: $FIGMA_TOKEN

전체 노드 트리를 /tmp/figma_v5_raw.json으로 저장. LLM 없음, 순수 HTTP.


Phase 1: 섹션 선택

raw.json의 직접 자식 노드를 분류해서 사용자에게 제시한다.

[v] 마케터 정보 (7개 노드)
[v] 광고 정보 (8개 노드)
[ ] 콘텐츠 게시 채널 (6개 노드)

multiSelect 방식으로 원하는 섹션만 골라서 이후 분석 범위를 제한한다. 전체 페이지를 분석하지 않아도 된다.


Phase 2: 선택 섹션 완전 분석

Phase 1에서 선택된 nodeId마다 REST API를 개별 호출해서 모든 속성을 추출한다.

추출 항목:

  • 기하학: x, y, width, height, rotation
  • 레이아웃: layoutMode, padding(상하좌우), gap
  • 스타일: fills, strokes, fontSize, fontWeight, cornerRadius
  • 컴포넌트: variant, colorPalette 등 props
  • 상태: visible, hidden, opacity
  • 자식 노드 재귀 전부

Phase 1 → Phase 2 연결은 자동이다. semantic_areas에서 nodeId 배열을 파싱해서 자동 전달.


Phase 3: 에셋 추출

exportSettings가 있는 노드만 추려서 다운로드한다.

  • 아이콘 → public/icons/
  • 이미지 → public/images/
  • 매니페스트 → asset_manifest.json

수동 다운로드 없음.


Phase 4: Design IR 생성 (핵심)

Figma JSON을 정규화된 중간 표현(IR) 으로 변환한다. 이게 v5의 핵심이다.

type DesignIRNode = {
  nodeId: string
  name: string
  role: 'text' | 'button' | 'input' | 'container' | 'image' | 'icon' | ...
  visible: boolean          // 숨겨진 노드도 포함
  opacity: number
 
  textContent?: {
    value: string
    fontSize: number        // 정규화된 px
    fontWeight: number
    color: string           // 정규화된 hex
    lineHeight: number
  }
 
  layout: {
    mode: 'flex' | 'grid' | 'absolute' | 'none'
    direction?: 'row' | 'column'
    gap?: number
    padding: { top, right, bottom, left }
    width?: number
    height?: number
  }
 
  style: {
    color?: string
    backgroundColor?: string
    borderRadius?: number
    borderColor?: string
  }
 
  state: {
    variant?: string
    disabled?: boolean
  }
 
  children: DesignIRNode[]
}

숨겨진 노드도 visible: false로 포함하는 것이 중요하다. 기존 파이프라인은 숨긴 노드를 생략해서 코드와 비교할 때 "누락"으로 잘못 판정하는 경우가 있었다.


Phase 5: Code IR 생성

현재 코드베이스의 TSX/JSX 파일을 AST 파싱해서 Design IR과 동일한 구조로 변환한다.

// 프로젝트 코드
<Text fontSize="md" color="grey.900">제목</Text>
<Box px={4} gap="2">...</Box>
// Code IR
{
  "role": "text",
  "textContent": { "fontSize": 16, "color": "#1b1c1d" },
  "layout": { "padding": { "left": 16, "right": 16 }, "gap": 8 }
}

Chakra UI props, Tailwind 클래스, 디자인 토큰 모두 해석해서 실제 값으로 변환한다.


Phase 6: Normalize

Design IR과 Code IR 양쪽의 모든 값을 동일 단위로 통일한다. 이 단계 없이는 비교가 불가능하다.

입력통일 값
px={4}, padding="16px", gap="2"16
"gray.700", #374151, rgb(55,65,81)"#374151"
"text-sm", fontSize="14px", fontSize={14}14
rounded="md", borderRadius={6}6

Chakra UI의 1 unit = 4px 규칙, 토큰 → hex 변환 등이 여기서 처리된다.


Phase 7: Mapping Engine

정규화된 Design IR ↔ Code IR을 1:1 매핑한다. 신뢰도(confidence)를 포함해서 매핑하는 것이 핵심이다.

7단계 우선순위:

순위방법confidence
1텍스트 exact match1.0
2role + 구조 일치0.95
3컴포넌트 이름 exact match0.85
4구조 Fingerprint (자식 수 + 타입 패턴)0.75
5LLM fallback0.5
{
  "mappings": [
    {
      "designNodeId": "123:456",
      "codeNodePath": "Table > Row[1] > Cell[2]",
      "confidence": 0.95
    }
  ]
}

LLM fallback은 위 4단계가 모두 실패했을 때만 발동한다. 대부분의 경우 0토큰.


Phase 8: Diff Engine (핵심)

매핑된 쌍을 정규화된 값으로 정확 비교한다. LLM이 전혀 개입하지 않는다.

8가지 diff 타입:

Type설명Severity
missing_in_codeFigma에 있는데 코드에 없음HIGH
extra_in_code코드에만 있고 Figma에 없음MEDIUM
text_mismatch텍스트 내용 다름HIGH
color_mismatch색상 다름MEDIUM
spacing_mismatch간격/패딩 다름MEDIUM
font_mismatch폰트 크기/굵기 다름MEDIUM
asset_mismatch에셋 참조 다름HIGH
visibility_mismatchvisible 상태 다름MEDIUM
{
  "summary": { "total": 15, "high": 3, "medium": 8, "low": 4 },
  "diffs": [
    {
      "type": "spacing_mismatch",
      "field": "gap",
      "design": 16,
      "code": 12,
      "severity": "MEDIUM"
    }
  ]
}

출력: diff.json. 이게 이후 모든 LLM 단계의 입력이 된다.


Phase 9: LLM 비교 리포트 (~500토큰)

diff.json을 사람이 읽을 수 있는 형식으로 변환하는 유일한 역할.

# 비교 리포트
 
## Summary
- 총 불일치: 15개
- HIGH: 3개 (즉시 수정 필수)
- MEDIUM: 8개 (권장)
- LOW: 4개 (선택)
 
## HIGH Priority
| 컴포넌트 | 문제 | 해결책 |
|---------|------|--------|
| Title   | missing_in_code | 추가 필요 |
 
## MEDIUM Priority
| 컴포넌트 | 문제 | Design | Code | 해결책 |
|---------|------|--------|------|--------|
| Button  | font_mismatch | 16px | 14px | fontSize 수정 |

Phase 10: 설계서 작성 (~1,000토큰)

diff 리포트 기반으로 구체적인 구현 설계서를 만든다.

  • 어느 컴포넌트를 추가/수정할지
  • 변경 전/후 코드 예시
  • 디자인 값 → 프로젝트 토큰 매핑 (#374151grey.700)
  • 구현 우선순위 (HIGH → MEDIUM → LOW)
// 토큰 매핑 예시
const colorMap = {
  "#374151": "grey.700",
  "#111827": "grey.10",
}
const spacingMap = {
  16: "4",  // 4 * 4px
   8: "2",
}

Phase 11: 코드 수정 (~1,500토큰)

설계서를 보고 diff에서 지적된 항목만 수정한다. 전체 파일 재작성이 아니다.

핵심 규칙:

  • diff 항목만 수정 (O)
  • 기존 로직 100% 보존 (O)
  • TypeScript 타입 안정성 유지 (O)
  • 리팩터링 금지 (X)
  • 추측에 의한 수정 금지 (X)

v5.3 변경사항: Phase 11은 더 이상 anthropic 패키지로 별도 API를 호출하지 않는다. /tmp/figma_v5_phase11_context.md를 생성한 뒤 현재 Claude Code 세션이 직접 파일을 편집한다. 별도 API 키 불필요.


Phase 12: 검증

pnpm type-check  # .publishrc.json의 validateCommand 사용

HIGH 항목이 남아 있으면 Phase 11 재실행. type-check 통과 필수.


Phase 13: 정리

rm /tmp/figma_v5_*.json

최종 보고서 출력:

퍼블리싱 완료
- 시작: 15개 불일치
- 완료: 2개 불일치 (LOW만 남음)
- 해결율: 86%
-토큰: ~3,200
-소요시간: 52초

다른 프로젝트에서 쓰는 방법

스킬은 ~/.claude/skills/publish-v5/에 있는 글로벌 스킬이다. 별도 설치 없이 어느 프로젝트에서나 호출 가능하다.

필요한 것

# 환경변수
export FIGMA_TOKEN="your-figma-personal-access-token"
 
# 시스템
# Python 3.9+, Node.js 18+, pnpm

첫 실행 (.publishrc.json 없을 때)

# 프로젝트 루트에서
"퍼블리싱해줘 https://figma.com/design/ABC123/...?node-id=1-2 --v5"
  1. .publishrc.json 탐색 (현재 디렉터리 → git root까지 상위)
  2. 없으면 자동 감지 시작
    • 모노레포면 앱 선택 제시
    • 단일 프로젝트면 현재 디렉터리 분석
  3. framework, componentFramework, tokensPath 자동 감지
  4. .publishrc.json 저장 여부 확인

프레임워크별 지원

Phase 5 Code IR이 다음을 모두 해석한다:

  • Chakra UI: fontSize="md" → 16, px={4} → 16px
  • Tailwind: text-sm → 14, gap-4 → 16px
  • 디자인 토큰: tokens.text.primary → 실제 hex 값

매핑이 안 될 때

"퍼블리싱해줘 [URL] --v5 \
  --map '21074:123=>components/custom-row' \
  --map '21074:456=>components/payment-info'"

토큰 효율

Phase방식토큰
0-8Python (규칙 기반)0
9LLM (리포트)~500
10LLM (설계서)~1,000
11LLM (코드 수정)~1,500
12-13Shell0
합계3,0003,500

v4 대비 토큰은 비슷하지만 정확도가 다르다. v4는 LLM이 Figma JSON과 코드를 동시에 읽고 머릿속에서 비교했다. v5는 Python이 정규화된 값으로 비교한 결과를 LLM에 전달한다.


핵심 요약

항목v4v5
비교 주체LLMPython (Diff Engine)
정규화없음Phase 6에서 통일
매핑 방식LLM 추론7-tier 규칙 + confidence
sublayer 누락가능불가 (재귀 전부 포함)
숨김 노드생략visible: false로 포함
프레임워크프로젝트 종속Chakra/Tailwind 모두 지원
첫 실행 설정수동auto-detect

비교는 데이터 구조 문제다. LLM에게 비교를 맡기면 언어 모델의 한계가 그대로 정확도의 한계가 된다.