본문으로 건너뛰기

Figma 퍼블리싱 파이프라인 04. 일관성 해결과 아키텍처 재정의

·9 min read·4 / 12

토큰 문제를 잡고 나니 다른 게 보였다. 같은 Figma 시안을 같은 명령으로 돌리는데 매번 다른 코드가 나왔다.

세 번 돌리면 세 가지 코드가 나왔다

같은 배지 하나를 두고 이렇게 갈렸다.

// 첫 번째 — 프로젝트 컴포넌트를 모르고 라이브러리 기본값 사용
<Badge colorScheme="blue">소진</Badge>
 
// 두 번째 — 컴포넌트는 찾았는데 색상을 추측
<StatusBadge color="green" label="소진" />
 
// 세 번째 — 맞음
<StatusBadge color="blue" label="소진" />

원인은 단순했다. AI는 요청마다 처음부터 판단한다. 이 Figma 노드에 어떤 컴포넌트를 쓸지, 배경색 #e9f2ff가 blue인지 green인지, import를 어디서 할지를 매번 새로 답한다. 프로젝트를 기억하고 있는 게 아니라 그때그때 추론하는 것이라, 추론이 흔들리면 결과도 흔들린다.

판단을 잘하게 만드는 대신 판단할 게 없게 만들었다

규칙 문서를 더 잘 쓰는 방향은 포기했다. 사람이 읽는 마크다운 규칙 대신, 스크립트가 읽는 JSON 매핑 파일을 두기로 했다.

AI가 이 색이 뭘까 고민하는 자리에, 스크립트가 미리 답을 넣어두는 구조다.

매핑 규칙을 JSON 한 파일에 모았다

.claude/design-system-map.json 하나에 전부 넣었다.

instanceMap은 Figma 컴포넌트와 코드 컴포넌트를 잇는다. Figma에서 재사용 가능한 UI 조각을 인스턴스(INSTANCE)라고 부르는데, React 컴포넌트를 가져다 쓰는 것과 같은 개념이다.

{
  "instanceMap": {
    "Status Badge": {
      "component": "StatusBadge",
      "import": "@/components/status-badge",
      "defaultProps": {},
      "colorResolve": "fillsToStatusBadge"
    }
  }
}

Figma에서 "Status Badge"를 만나면 StatusBadge를 쓰고 import는 @/components/status-badge에서 한다. 여기엔 판단이 없다.

fillsToStatusBadge는 배경색을 prop 값으로 바꾼다. 컴포넌트는 color="blue"를 받는데 Figma에서 넘어오는 건 #eff7ff 같은 hex라, 그 사이를 이어줄 표가 필요하다.

{
  "fillsToStatusBadge": {
    "#eff7ff": "blue",
    "#e0ffeb": "green",
    "#fef2e3": "yellow"
  }
}

hexContextMap은 같은 색이 쓰이는 자리에 따라 다른 토큰으로 가야 하는 경우를 처리한다. #e5e7eb가 배경이면 background.basic.4, 테두리면 border.basic.2다. 색만 봐서는 구분할 수 없고 어느 속성에 들어가는지를 같이 봐야 한다.

{
  "hexContextMap": {
    "background": { "#e5e7eb": "background.basic.4" },
    "border": { "#e5e7eb": "border.basic.2" }
  }
}

importPaths는 AI가 가장 자주 틀리던 곳이다. 컴포넌트 이름은 맞게 쓰는데 경로를 그럴듯하게 지어내는 일이 잦았다.

{
  "importPaths": {
    "StatusBadge": "@/components/status-badge",
    "Select": "@/components/select"
  }
}

pageTemplates는 화면 유형 판별까지 표로 옮긴 것이다. 이 시안이 테이블 페이지인지 대시보드인지를 지표 노드로 판정하고, 그 유형에 필요한 구조와 import를 미리 정해둔다.

{
  "pageTemplates": {
    "table": {
      "structure": "Header → Layout → Controls + Table",
      "requiredImports": [
        { "component": "PageHeader", "from": "@/components/page-header" }
      ],
      "indicators": ["Table", "Header Row", "Row", "Pagination"]
    }
  }
}

이 맵을 손으로 쓸 필요는 없다. token.jsonsrc/components/를 스캔해서 만들어준다.

python3 ~/.claude/scripts/init_design_system_map.py --project-root /path/to/project

토큰 파일에서 색상 패턴을 뽑고, 시맨틱 토큰 이름으로 용도를 분류하고, 컴포넌트 파일을 훑어서 Figma 이름과 코드 컴포넌트를 짝지어준다.

한 요청에 너무 많은 일을 시키고 있었다

일관성 문제를 잡고 나니 더 근본적인 게 보였다. 한 번 실행할 때 Figma 분석, 자산 추출, 섹션 분할, 코드 생성, 스타일 적용, 기존 코드 수정, 검증, 재수정까지 전부 하고 있었다. 20~30분이 걸렸고 토큰도 계속 늘었고 결과도 매번 달랐다.

여기서 방향을 바꿨다. 더 똑똑하게 만드는 대신 덜 하게 만들기로 했다. 특히 코드를 새로 만드는 일과 기존 코드를 고치는 일은 완전히 다른 문제인데, 그 둘을 한 흐름에 섞어둔 게 컸다.

대상 단위도 페이지 전체에서 컴포넌트 단위로 내렸다. 파일을 찾아 헤매는 탐색 단계와 자동 수정 루프, 복잡한 검증 단계를 걷어냈다.

구조는 이렇게 남았다.

~/.claude/
├── CLAUDE.md
├── commands/
│   ├── publish-init.md
│   └── publish.md
└── tools/
    └── figma-extract.js
구성역할
CLAUDE.md환경 정의
publish-init프로젝트 분석
publish퍼블리싱 실행
figma-extract.js데이터 처리

프로젝트 분석은 publish-init으로 한 번만 하고, 이후 publish는 그 결과를 재사용한다. 매 실행마다 프로젝트를 다시 파악하던 걸 없앤 것이다.

단계 사이는 전부 IR(Intermediate Representation, 원본과 결과물 사이에 두는 중간 표현)로 주고받게 통일했다. 각 단계가 IR을 만들거나 소비하거나 검증하는 셋 중 하나만 하니 단계를 추가해도 구조가 흔들리지 않는다.

막힌 지점과 원인

문제원인해결
같은 시안에서 매번 다른 코드가 나옴AI가 컴포넌트·색상·경로를 요청마다 새로 추론매핑을 design-system-map.json으로 고정
같은 hex가 잘못된 토큰으로 변환됨색상만 보고 용도를 구분할 수 없음hexContextMap으로 속성별 분기
import 경로를 지어냄경로에 대한 근거가 없어 추론에 의존importPaths에 명시
실행에 20~30분, 결과가 불안정생성과 수정, 검증을 한 흐름에 전부 포함생성만 남기고 수정 루프·탐색·검증 분리

규칙 문서를 아무리 잘 써도 AI가 읽고 판단하는 이상 결과는 흔들렸다. 판단이 필요한 자리를 표로 바꾸고 나서야 같은 입력에 같은 출력이 나왔다.