Figma 퍼블리싱 파이프라인 04. 일관성 해결과 아키텍처 재정의
토큰 문제를 잡고 나니 다른 게 보였다. 같은 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.json과 src/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가 읽고 판단하는 이상 결과는 흔들렸다. 판단이 필요한 자리를 표로 바꾸고 나서야 같은 입력에 같은 출력이 나왔다.