| name | figma-diff-apply |
| description | Figma 변경을 디자이너 검수용 design/* 브랜치에 incremental 적용. /figma-apply 커맨드에서 위임 호출. design-to-ui(7-Step 새 화면 스캐폴딩)와 달리 변경된 컴포넌트만 식별·수정합니다. figma URL 1개(figma-driven) / 2개(diff-driven) 두 모드 지원. 새 화면 변환은 이 스킬 대신 design-to-ui를 사용하세요. |
| license | Apache-2.0 |
| user-invocable | false |
| metadata | {"author":"NAVER","version":"2.0","requires":["figma-asset-download"],"optional":["project-design-system"],"platform":"android"} |
figma-diff-apply
Figma URL의 현재 디자인과 design/* 브랜치의 현 코드를 대조하여 변경된 컴포넌트만 최소 수정으로 반영합니다. 신규 화면 전체 변환이 아니라 iteration(N→N+1) 시점에 사용합니다.
design-to-ui의 7-Step 풀 파이프라인을 호출하지 않습니다. 그 파이프라인은 "없던 화면을 처음 만들 때"에 최적화되어 있어, iteration마다 돌리면 토큰이 낭비되고 의도치 않은 전체 리팩토링 위험이 있습니다.
Prerequisites
- 호출 시점에
design/* 브랜치가 체크아웃되어 있을 것 (/figma-start가 보장)
- Figma Desktop MCP 연결(
get_design_context 사용 가능) 또는 FIGMA_ACCESS_TOKEN 중 하나
project-design-system이 있으면 토큰 매핑에 사용 (없으면 기존 코드 컨벤션 유지)
Phase 0: 경로 판정 (먼저 이걸 정한다)
0-1. 먼저 요청 유형을 가른다. 이 스킬은 "Figma 선언값 ↔ 코드"를 대조하는 도구다. 그 전제가 성립하지 않는 요청에 Phase 1·2를 돌리면 아무 근거도 못 얻고 비용만 쓴다.
| 유형 | 신호 | 처리 |
|---|
| A. 확인·캡처 | "~노출되는지 확인", "~케이스 보여줘", "이미지 저장해줘" — 코드를 바꾸라는 말이 없다 | 이 스킬이 할 일이 없다. Figma 수집·diff 없이 design-qa(또는 캡처)로 넘긴다. 대조 대상이 아예 없으므로 Phase 1·2 전체를 건너뛴다 |
| B. 프롬프트-스펙 | 기준값이 프롬프트에 있고 Figma엔 아직 없다. ① Figma가 표현 못 하는 것을 수치까지 지정("240ms 등속", "즉시 전환") ② 상대 증감("5dp 올려줘") ③ 절대값인데 Figma가 아직 그 값이 아님 | 프롬프트 값이 기준이다. Figma는 diff 원본이 아니라 ⓐ 색·치수 토큰 확인 ⓑ 정지 상태 정합 제약 ⓒ 차이 보고용으로만 쓴다. Figma 선언값으로 요청을 덮어쓰지 말고, 어긋나면 Phase 4 "디자이너 확인 필요"에 올린다 |
| C. Figma 대조 | 기준값이 Figma에 있다 — Figma 쪽이 바뀌었으니 코드를 맞춰 달라는 요청 | 아래 0-2로 진행 |
유형 B의 판정은 추측하지 말고 확인한다 — get_motion_context(nodeId, recursive=true)가 {"nodes":[]}면 Figma에 모션 데이터가 없다는 뜻이고, 그러면 그 요청의 수치는 프롬프트에만 존재한다.
0-2. 유형 C면 규모로 경로를 가른다. 잘못 고르면 20dp 한 줄 바꾸는 데 서브에이전트 4홉이 돌거나, 반대로 화면 전체 변경을 메인이 혼자 훑는다.
| 국소 경로 (기본) | 광범위 경로 |
|---|
| 조건 | 디자이너 프롬프트가 요소와 속성을 지목하고(예: "트랙 리스트 사이 간격 20dp로", "타이틀 색 초록으로"), 대상 파일이 Grep으로 1~2개로 확정됨 | 대상 화면·요소가 프롬프트로 특정되지 않음 / 후보 파일 3개+ / "전체적으로 바뀌었어" 류 / 신규 컴포넌트 추가 |
| Phase 2 | 메인이 직접 (서브에이전트 0개) | Diff Agent 팬아웃 + QA Agent |
| 근거 | 국소 변경에서 팬아웃은 요청과 무관한 오탐을 만들고, QA는 그 오탐을 걷어내는 데 다시 비용을 쓴다 (실측: 607s·202k → 53s·11k, 정확도는 국소 경로가 더 높았음) | 대상이 불명확할 때는 컨텍스트 격리·독립 QA가 실제로 누락을 잡는다 (v6.1: QA 제거 시 통과율 5/5 → 2/5) |
애매하면 국소 경로로 시작하고, Phase 1에서 후보가 3개+로 벌어지면 광범위로 승격한다.
유형 B의 정지 상태 회귀 검증
Figma에 없는 동작을 넣을 때 가장 깨지기 쉬운 것은 정지 상태다. 애니메이션을 얹었더니 시작·끝 프레임이 기존 에셋과 미세하게 달라져 design-qa 오버레이가 전부 어긋나는 식이다. 그래서 유형 B는 구현 후 다음을 픽셀로 확인한다:
- 변경 전 커밋과 변경 후를 각각 같은 정지 상태로 띄운다 (이 프로젝트는
-e state selected 처럼 상태를 인자로 받는다).
- 두 캡처를 diff 해서 차이가 0인지 확인한다.
기존 에셋을 재사용해 점진 노출·클립하는 식으로 구현하면 이 검증이 통과한다. 반대로 글리프·도형을 새로 그려 흉내내면 거의 통과하지 못한다.
Phase 1: 수집
1-1. Figma 스펙
get_metadata(nodeId)
get_design_context(...)
get_variable_defs(nodeId)
get_screenshot(nodeId)
-
get_design_context는 프레임 또는 그 하위 노드로 좁혀서 호출한다. 상위 레벨 노드로는 코드가 나오지 않는다 — 페이지(canvas) 노드는 호출 자체가 실패하고, section 노드는 sparse metadata만 돌아온다("개별 노드로 다시 호출하라"는 안내와 함께). /figma-apply가 받는 링크는 페이지 노드인 경우가 많으므로, get_metadata로 프레임 목록을 먼저 받아 대상 프레임(또는 프롬프트가 지목한 서브노드)을 고른 뒤 호출한다.
-
get_metadata의 좌표·크기는 간격·치수 판정의 결정적 근거다. 형제 노드의 y − (앞 노드 y + height) = 간격. design_context를 프레임마다 다시 뜨지 않고도 한 번의 get_metadata로 모든 프레임의 선언값을 비교할 수 있다.
판정법: 같은 리스트를 담은 프레임들을 나란히 놓고 다음 카드 y − (앞 카드 y + height)를 비교한다. 카드 높이가 같은데 이 값이 프레임마다 다르면 같은 요소가 프레임별로 다르게 선언된 것이다. 코드가 라이트/다크 공용 컴포저블 한 벌이면 한쪽에 맞추는 순간 다른 쪽이 어긋난다.
이런 불일치는 고쳐놓고 넘어가지 말고 Phase 4 "디자이너 확인 필요"에 올려 회신을 받는다.
-
읽은 값은 그 시점의 값이다. Figma는 라이브 문서이고 노드 ID까지 바뀐다 (실측: 같은 세션 1시간 사이에 TrackList gap이 20 → 12로, statusbar 노드가 2009:47 → 176:4로 변했다). 그래서 ① 판정 근거로 쓴 수치는 Phase 4 표에 읽은 값 그대로 남기고, ② 커밋 본문에 들어가 나중에 대조 가능하게 한다. 오래된 발췌를 재사용하지 말고 매번 다시 읽는다.
-
diff-driven 모드(URL 2개): get_design_context를 두 시점 모두 호출해 임시 파일로 저장, Phase 2의 diff 입력으로 사용.
-
MCP 미연결 시: Figma Desktop 앱 실행을 안내(플러그인 CLAUDE.md "Figma MCP 사전 확인" 절). REST fallback은 figma-asset-download/scripts/ 헬퍼.
1-2. 대상 화면·파일 확정
Code Connect 매핑이 있으면 get_code_connect_map(nodeId) 결과를 우선한다.
목표 화면 = 이 Figma 노드에 대응하는 화면 단위. 컨테이너 타입(Fragment / Composable Screen / Activity)은 가리지 않는다. 이후 Phase 3·3.5의 scope 기준 단위가 된다.
매핑 실패한 프레임은 "신규 컴포넌트"로 분류해 Phase 4에 표시한다.
Phase 2: 변경 식별
산출물은 Phase 4와 같은 스키마의 표다 — 분류(Added/Changed/Removed/Moved) | 요소 | Figma 값 | 코드 값 | 위치(file:line). 네 칸이 다 채워지지 않는 행은 넣지 않는다 (그래서 "레이아웃 변경" 같은 행은 성립하지 않는다).
Removed(코드에 있고 Figma에 없음)는 역방향으로 따로 찾는다 — 특히 call-site removal(design_context엔 없는데 코드가 여전히 호출·렌더하는 요소). v6.1 실험에서 재현율 100%로 미식별됐던 항목이라 명시한다.
국소 경로
메인이 직접 대조한다. 서브에이전트를 쓰지 않는다.
같은 값이 여러 곳에 있을 때 노드 주석·구조로 대상을 가려낸다 — 실측에서 리스트 간격 12dp와 헤더 간격 12dp가 값이 같아 혼동 위험이 있었다.
프롬프트가 요청하지 않은 차이가 눈에 띄어도 표에 넣지 않는다. Phase 4의 "그밖에 눈에 띈 점"에 한 줄로만 남기고 적용하지 않는다.
광범위 경로
design_context는 변환 원본이라 메인이 직접 읽는다 — 서브에이전트 요약을 받으면 수치·중첩 구조가 소실된다.
2a. Diff Agent — 후보 파일당 1개, 단일 응답에서 병렬 호출. 각 Agent는 담당 파일 하나만 알고(직접 Read), 다른 Agent의 산출물과 메인의 의사결정은 받지 않는다. 메인은 출력 표만 받는다.
2b. QA Agent — 독립 세션 1개, 2a 컨텍스트 미상속(자기평가 편향 차단). 아래를 cross-check해 하나라도 FAIL이면 전체 FAIL.
- 값 검증 — 표의
Figma 값 칸이 실제 노드 값과 맞는지 직접 재조회해서 확인한다. 2a가 받은 발췌를 그대로 믿지 않는다. (실측 근거: 한 Diff Agent가 stale 발췌를 믿고 20dp로 단정한 반면 다른 Agent는 라이브를 재조회해 12px로 정반대 결론을 냈다. Case ①에서 유일한 실제 회귀 — 이미 정합했던 화면을 깨뜨리는 행 — 도 이 검증이 좌표 재조회로 잡았다.)
- Removed 검증 — 사라진 호출이 누락 없이 잡혔는지
- 매핑 커버리지 — Figma 프레임 80%+
- 스코프 위반 행 — 요청 범위 밖을 건드리는 행 표시. 표시된 행은 Phase 3에서 적용하지 않는다
실측에서 QA가 잡아낸 것의 대부분이 팬아웃이 만든 오탐이었다(6행 중 유효 2행, 1행은 적용하면 정합했던 화면을 회귀). 그래서 QA는 광범위 경로에만 둔다.
2c. 재시도 1회 — FAIL이면 2a를 새 서브에이전트로 재spawn(1차 컨텍스트 재사용 금지 — 같은 세션은 같은 누락을 반복한다). 2차도 FAIL이면 경고를 붙여 Phase 3 진행.
URL 2개 모드 (diff-driven)
두 figma 응답을 unix diff로 비교해 변경 라인만 추출 → 키워드로 코드 Grep → 적용. 결정적 텍스트 비교라 2a 팬아웃을 대체하며 토큰이 크게 줄고 false signal이 없다. 세부 단계는 메인 재량.
QA(2b)는 경로 판정을 따른다 — 국소면 생략, 광범위면 위와 동일.
Phase 3: 최소 변경 적용
- Phase 2 표에 없는 파일·요소는 수정 금지 ("함께 정리" 명목도 안 됨)
- 변경을 목표 화면에 가둔다 (scope 회피 우선) — 다른 화면도 쓰는 값(공통 토큰·공유 컴포넌트)을 바꿔야 하면 목표 화면 국소 해법을 우선한다. 국소 해법이 없거나 본래 전역(디자인 시스템) 변경이면 진행하되 Phase 3.5에 영향 범위를 기록. 단 적용된 디자인을 되돌리는 식의 회피는 금지. (실측 배경: 이전에 sports 작업 중 전역
AppTypography를 고쳐 다른 화면까지 영향이 갔다.)
- 색·치수는
project-design-system이 있으면 토큰으로 매핑, 없으면 기존 컨벤션 유지
- 에셋은
figma-asset-download에 위임한다 — 신규 정적 에셋, diff-driven에서 새로 나온 imgXxx 상수 모두. 동적 비주얼(Canvas·그래프)은 SVG의 실제 파라미터를 보고 적용한다
Phase 3.5: 영향 범위 산출 (scope 안전망)
변경한 값·심볼의 사용처를 Grep해 고유 화면 수를 센다(파일 위치가 아니라 실제 사용처 기준). 2개 이상 화면이 쓰는 변경이면 Phase 4 "영향 범위"에 목록을 기록한다 — 커맨드가 커밋 본문에 넣고, 개발자가 git log로 확인한다.
완벽한 scope 판정이 아니라 명백한 누수를 보이게 하는 최소 그물이다. 차단하거나 자동 되돌림하지 않고, 디자이너에게 scope 판단을 시키지 않는다.
빌드 에러 수정 (커맨드 재위임 시)
/figma-apply가 빌드 게이트에서 실패하면 빌드 에러와 함께 이 스킬을 다시 위임 호출한다. 빌드 실행·재빌드는 커맨드 책임이고 이 스킬은 코드 편집만 한다(gradle 직접 실행 금지). 커맨드가 최대 3회까지 재위임한다.
- 이미 메인에 있는
design_context·Phase 2 표를 재사용한다 — Figma에서 새로 가져오지 않는다
- 적용된 디자인 변경을 되돌려 빌드만 통과시키지 않는다. 누락 에셋은
figma-asset-download로 채우고(참조 삭제 금지), 못 고치면 수정 불가로 보고하고 중단한다 (커맨드가 WIP 커밋으로 개발자에게 인계)
Phase 4: 결과 요약
## figma-diff-apply 결과
### 경로
국소 (서브에이전트 0) / 광범위 (Diff Agent N개 + QA)
### 변경 적용
| 분류 | 요소 | Figma 값 | 코드(변경 전) | 위치 |
|------|------|---------|-------------|------|
### QA 검증 (광범위 경로에서만)
| 기준 | PASS/FAIL |
### 신규 컴포넌트(매핑 실패)
- (없음)
### 영향 범위 (2개+ 화면에 영향 시에만 기록)
- (없음 — 변경이 목표 화면에 한정됨)
### 디자이너 확인 필요
- 같은 요소가 Figma 프레임마다 다른 값으로 선언된 경우 그 목록
- (없으면 생략)
### 그밖에 눈에 띈 점 (적용 안 함)
- (없으면 생략)
금지사항
design-to-ui 7-Step 풀 파이프라인 호출 금지 — 이 스킬의 존재 이유 자체가 그 회피
- 국소 경로에서 서브에이전트 팬아웃 금지 — 요청과 무관한 오탐을 만든다
- 광범위 경로에서 QA FAIL을 경고 없이 통과 금지
- 디자이너가 지정한 값을 Figma 선언값으로 임의 교체 금지 — 둘이 어긋나면 요청대로 적용하고 차이를 Phase 4 "디자이너 확인 필요"에 올린다. 실측에서 Diff Agent가 "5dp 올려줘"(→17dp) 요청을 Figma 선언값 20dp로 덮어쓰라고 결론낸 사례가 있다
- baseline 파일(
.claude/context/ui-spec/*.md) 참조 금지 — 후속 PR(project-ui-spec 도입)에서 다룸