/cc-coui-sync:absorb — 디자이너가 무엇을 바꿨는지 먼저 안다#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /cc-coui-sync:absorb |
| 분류 | cocode design |
| 난이도 | ●●● 높음 |
한마디로#
디자이너가 Figma 에서 컴포넌트를 고치면 코드는 그걸 모릅니다. 이 커맨드는 디자이너가 플러그인으로 내려준 캔버스 스냅샷을 지난번 것과 대조해 "무엇이 달라졌는지" 만 찾아 이슈 하나로 남깁니다. 고치지는 않습니다 — 사람이 봐야 할 차이가 있는지 없는지, 그 신호 하나가 산출물입니다.
누가·언제 쓰나요#
- 스케줄 자동화가 주기적으로 (디자이너가 스냅샷을 떨궈 두면, 사람이 더 손대지 않아도 캔버스 변화가 이슈로 올라옵니다)
- 흡수를 시작하기 전에 "무엇이 바뀌었나" 를 목록으로 받고 싶을 때
이슈가 올라오면 그 다음은 /cc-coui-sync:absorb-apply 가 이어받습니다.
실행 위치#
coco-de/coui 레포 루트에서 시작한다. 스크립트 실행만 tools/figma-token-sync
로 내려간다. 인자를 받지 않는다.
이 커맨드는 읽고 보고할 뿐 아무것도 고치지 않는다. Dart 소스를 편집하지 않고, 토큰 값을 코드로 옮기지 않고, 베이스라인을 갱신하지 않는다. 산출물은 "사람이 봐야 할 차이가 있다/없다" 라는 신호 하나다.
이 파일은 절차(orchestration) 만 권위를 가진다. 이슈 제목의 closing-keyword 규칙은 coui 레포
.claude/rules/process/workflow.md, 문서 작성 규칙은.claude/rules/principles/writing-rules.md가 정본이며 충돌 시 그쪽이 우선한다.
1. 안전 인터록 — 파일 키가 확인되지 않았으면 즉시 중단#
tools/figma-token-sync/reverse/config.json 을 읽는다.
fileKeyStatus 가 정확히 "confirmed" 가 아니면 여기서 멈춘다. 스크립트를 부르지 않고, 이슈를 만들지 않고, "파일 키가 확인되지 않아 중단했다 — 디자이너가 이 키가 정본 라이브러리 파일인지 확인한 뒤
fileKeyStatus 를 confirmed 로 바꿔야 한다" 고 보고하고 끝낸다.
왜: 확인되지 않은 키는 엉뚱한 파일을 가리킬 수 있다. 그 상태로 걸어가면 그 파일의 구조가 baseline 과 전부 다르므로, 자동화는 "디자이너가 모든 것을 바꿨다" 는 거대한 거짓 이슈를 만든다. 그 이슈는 틀렸다는 것조차 읽어봐야 알 수 있고, 한 번 겪은 사람은 다음 이슈도 안 읽는다 — 자동화의 신뢰를 첫 실행에 태워버리는 경로다. 키가 맞다는 것은 코드가 판정할 수 없고 사람만 판정할 수 있으므로, 그 판정을 파일에 기록하기 전까지는 걷지 않는다.
이 인터록은 스냅샷 경로에서 더 중요하다. 개발용 플러그인은 figma.fileKey 를 받지 못해(Figma 가 조직 비공개 플러그인에만 준다) 스냅샷의
fileKey 가 null 이고, 그러면 스크립트의 "다른 파일에서 온 스냅샷" 검사가 작동하지 않는다. 파일 정체성을 말해 주는 것은 이 설정뿐이다.
같은 파일에서 pages 를 꺼내 들고 간다 — null 이면 "모든 페이지", 배열이면 그 페이지 id 만 대조한다.
2. 스냅샷 찾기 — 디자이너가 떨궈둔 구조 파일#
tools/figma-token-sync/reverse/incoming/ 에서 structure-*.json 을 파일 목록으로
찾는다.
ls tools/figma-token-sync/reverse/incoming/structure-*.json 2 > /dev/null | sort | tail -1
이름은 structure-<UTC 시각>.json 이라 사전순 마지막이 곧 최신이다. 그 하나만 쓴다 — fidelity 채널이 여러 파일 중 마지막 하나만 읽는 것과 같은 규칙이다.
왜 MCP 로 걷지 않는가: 이 파일에서 MCP get_metadata 의 페이지 목록 호출은 Cover 한 장만 돌려주고(알려진 버그), 193 페이지를 한 장씩 걷으면 하루 예산 200회 · 한 실행 상한 60회를 넘기며, 그 XML 전부가 에이전트의 컨텍스트를 지나야 한다. 실제로 그 경로로 대조된 것은 Cover 한 장 · 노드 7개였고, 컴포넌트 페이지 133장은 아무도 보지 않았다. 플러그인의
구조 → .json 은 파일 안에서 돌아 예산도 컨텍스트도 쓰지 않고 전 페이지(193 · 노드 65,000여 개)를 몇 초에 읽는다. 그래서 이 채널의 캡처도 fidelity 처럼
디자이너가 떨군다. 무인인 것은 걷기가 아니라 대조다.
하나도 없으면 스크립트를 부르지 말고 고정 문구를 만든다#
structure-*.json 이 없으면 4단계를 건너뛴다. 대신 아래 문구를 만들어 7단계 본문의 ## 구조 드리프트 아래에 그대로 싣는다. 마지막으로 스냅샷이 들어온 날짜를 먼저 구한다:
git log -1 --format=%cs -- ' tools/figma-token-sync/reverse/incoming/structure-*.json '
출력이 있으면 그 날짜를, 비어 있으면 아직 한 번도 없음 을 넣는다.
⚠️ **이번 주기에는 구조 스냅샷이 없었다 — 이것은 통과가 아니다.**
`reverse/incoming/` 에 `structure-*.json` 이 없어 Channel A 를 실행하지 않았다. 따라서
**페이지 · 컴포넌트 · 변형의 추가 · 삭제 · 이름 변경 · 이동 · 크기 변경은 이번 주기에
아무도 검사하지 않았다.** 이 항목이 비어 있는 것은 " 이상 없음 " 이 아니라 " 정보 없음 " 이다.
마지막으로 스냅샷이 들어온 날: < YYYY-MM-DD | 아직 한 번도 없음 >
이 상태를 끝내려면: 디자이너가 Figma 데스크톱에서 플러그인의 **구조 → .json** 을
실행해 받은 파일을 `reverse/incoming/` 에 커밋한다.
왜 고정 문구인가: 5단계(fidelity)와 같은 이유다 — 제목만 두고 아래를 비우면 읽는 사람은 구조까지 검사됐고 깨끗했다고 읽는다.
스냅샷의 나이는 리포트가 말한다 — 여기서 거르지 않는다#
오래된 스냅샷을 이 단계에서 버리지 않는다. 스크립트가 리포트 첫머리에 capturedAt 과 "N일 전" 을 싣고, 리포트는 그 시점의 캔버스에 대한 진술이라고 명시한다. 어제 스냅샷과 석 달 전 스냅샷은 같은 문장으로 읽히면 안 되지만, 그 구별은 날짜를 싣는 것으로 한다 — 상한을 두면 그 상한 아래의 낡음이 "최신" 으로 읽힌다.
다만 마지막 보고에 그 날짜를 싣는다. 한 번의 실행으로는 알 수 없지만, 주기마다 남은 날짜는 "스냅샷이 들어오지 않고 있다" 를 기록만이 보여준다.
3. 승격 전 대조는 이 커맨드의 일이 아니다#
베이스라인이 아직 MCP 캡처(capturedBy: figma-mcp:get_metadata)이거나 비어 있으면, 스크립트는 다른 경로의 캡처와의 비교를 종료 코드 2 로 거부한다 — 타입 어휘 · 반올림이 같다는 보장이 없어 그 차이가 전부 "디자이너의 변경" 으로 나가기 때문이다. 그 상태는 인프라 실패로 보고하고 끝낸다. 전환은 사람이
--update-baseline 으로 한 번 한다(8단계).
4. 구조 차분#
cd tools/figma-token-sync & & npx tsx reverse/diff_structure.ts \
--snapshot=reverse/incoming/structure- < stamp > .json \
--out=/tmp/figma-structure-diff.md
--snapshot 에는 2단계에서 고른 그 파일 하나를 준다. --raw-dir 과 같이 주지 않는다 — 스크립트가 거부한다.
종료 코드를 기록해 둔다. 여기서는 이슈를 만들지 않는다 — 판단은 두 채널이 다 끝난 뒤 6단계에서 한 번에 한다.
| exit | 뜻 |
|---|---|
0 | 조치할 차이 없음 |
1 | 차이 있음 — 사람이 봐야 함 |
2 | 인프라 실패 (파일 없음/읽기 실패/형태 검증 실패/모집단 붕괴/캡처 경로 불일치) |
0 이면서 "baseline 이 비어 있다(첫 실행)" 는 메시지가 나오면, 이슈는 만들지 않되 보고에는 남긴다 — 사람이 베이스라인을 승격할 시점이라는 뜻이다(8단계).
5. 디자이너가 떨궈둔 fidelity 리포트#
tools/figma-token-sync/reverse/incoming/ 에 fidelity-*.json 이 있는지 먼저 파일 목록으로 확인한다.
하나도 없으면 스크립트를 부르지 말고 이 단계를 통째로 건너뛴다. 대신 아래 "리포트 없음 고정 문구"를 만들어 들고 6단계로 간다.
왜 부르지 않는가: 그 스크립트는 빈 디렉터리를 2(인프라 실패)로 취급한다 — 사람이 직접 부를 때는 그게 옳다("리포트가 없다"는 "결함이 없다"가 아니라 "아무도 검사하지 않았다"이므로 침묵을 통과로 읽으면 안 된다). 하지만 크론은 매일 기회주의적으로 들여다볼 뿐이라, 디자이너가 아직 아무것도 떨구지 않은 평범한 날마다 인프라 실패를 보고하게 된다. 매일 우는 알람은 곧 아무도 안 듣는 알람이 된다.
건너뛰었으면 침묵이 아니라 고정 문구를 만든다#
스크립트를 부르지 않는 것과 아무 말도 하지 않는 것은 다르다. 건너뛴 실행은 반드시 아래 문구를 만들어 7단계 본문의 ## Fidelity
아래에 그대로 싣는다. 마지막으로 리포트가 들어온 날짜를 먼저 구한다:
git log -1 --format=%cs -- tools/figma-token-sync/reverse/incoming/
출력이 있으면 그 날짜를, 비어 있으면 아직 한 번도 없음 을 넣는다(그 디렉터리에 리포트가 커밋된 적이 없다는 뜻이다).
⚠️ **이번 주기에는 디자이너 리포트가 없었다 — 이것은 통과가 아니다.**
`reverse/incoming/` 이 비어 있어 Channel B 를 실행하지 않았다. 따라서
**색 · 토큰 바인딩 · 타이포그래피 · radius · shadow 는 이번 주기에 아무도 검사하지
않았다.** 이 항목이 비어 있는 것은 " 이상 없음 " 이 아니라 " 정보 없음 " 이다.
마지막으로 리포트가 들어온 날: < YYYY-MM-DD | 아직 한 번도 없음 >
이 상태를 끝내려면: 디자이너가 Figma 데스크톱에서 플러그인의 **충실도 리포트
내보내기**를 실행해 받은 JSON 을 `reverse/incoming/` 에 커밋한다.
왜 고정 문구인가: ## Fidelity 제목만 두고 아래를 비우면, 그 이슈를 여는 사람은 색까지 검사됐고 깨끗했다고 읽는다. 그건
ingest_fidelity.ts 가 빈 디렉터리를 실패로 거부하면서까지 막으려는 바로 그 오독인데, 크론이 그 거부를 우회하면서 진술까지 같이 흘려버리면 자동화가 스스로 그 오독을 만들어내는 셈이 된다. 날짜를 함께 싣는 이유는
부재가 얼마나 오래됐는지가 부재 자체보다 중요하기 때문이다 — 어제 없었던 것과 석 달째 없는 것은 같은 문장으로 읽히면 안 된다.
fidelity-*.json 이 하나 이상 있으면 위 고정 문구는 필요 없다. 스크립트를 한 번만 호출한다 (디렉터리를 스스로 훑으므로 파일마다 부르지 않는다):
cd tools/figma-token-sync & & npx tsx reverse/ingest_fidelity.ts \
--dir=reverse/incoming \
--out=/tmp/figma-fidelity.md
종료 코드 규약은 4단계와 동일하다 (0 차이 없음 / 1 차이 있음 / 2 인프라 실패).
fidelity-*.json 이 여러 개면 스크립트는 사전순 마지막 하나만 읽고, 몇 개 중 어느 파일을 읽었는지를 리포트 본문 맨 위에 스스로 싣는다.
따로 옮겨 적을 것이 없다.
왜 본문인가: 자동화가 사람에게 건네는 것은 이슈 본문 하나뿐이라, stderr 로만 한 말은 아무에게도 닿지 않는다. 리포트의 범위를 좁히는 사실(읽지 않은 파일 · 부분 스윕 필터 · 생성 시각 · 상한에서 잘린 표)은 전부 본문 안에 있다.
이 채널은 디자이너가 Figma 데스크톱에서 플러그인을 돌려 내려받은 산출물이다. 무인 크론이 볼 수 없는 축(색 · 토큰 바인딩 · 타이포그래피 · radius · shadow)은 여기서만 답이 나온다.
6. 판정 — 이슈는 최대 한 개#
두 채널은 입력도 실패 양상도 서로 독립이므로 따로 판정하고, 이슈만 하나로 합친다.
리포트가 없어 건너뛴 채널은 판정에서만 빠진다 — 이슈를 만들지 말지는 실제로 돌린 채널의 코드로 정한다. 둘 다 건너뛰었으면 이슈 없음, "두 채널 모두 입력 없음" 만 보고한다. 다만 이슈를 만들게 되면 5단계의 "리포트 없음 고정 문구" 를 본문에 반드시 싣는다. 판정에서 빠지는 것과 본문에서 빠지는 것은 다르다.
| 두 채널의 종료 코드 | 결과 |
|---|---|
둘 다 0 | 이슈 없음. "변화 없음" 만 보고 |
어느 쪽이든 1 이 있고 2 는 없음 | 이슈 1개 생성 |
1 과 2 가 섞임 |
이슈 1개 생성 — 본문 맨 위에 "이 실행에서 <채널> 은 실패했다 — 이 이슈는 부분 신호다" 를 적는다 |
2 는 있고 1 은 없음 | 이슈 없음. 인프라 실패로 보고 |
종료 코드는 이슈를 만들지 여부만 정한다. 이슈를 만들게 되면 본문에는 실제로 실행된 채널의 리포트를 종료 코드와 무관하게 전부 싣는다.
1 인데 --out 파일이 없으면 그건 차이가 아니라 실패다#
채널이 1 로 끝났으면 그 채널의 --out 파일이 존재하는지 확인한다. 없으면 그 채널을 2
로 취급한다.
test -s /tmp/figma-structure-diff.md # 4단계를 돌렸으면
test -s /tmp/figma-fidelity.md # 5단계를 돌렸으면
왜: 1 은 두 스크립트가 "차이를 찾았다" 는 뜻으로 의도해서 내는 코드지만, Node 는 처리되지 않은 예외로 죽을 때도
1 로 끝난다. 두 스크립트는 그 혼동을 알고 파일 쓰기·읽기 실패를 전부 2 로 감싸두었는데, 그 감싸기 밖에서 죽는 경로가 하나 남는다 — 모듈이
적재되는 동안 던지는 경우다(공유 서식 모듈은 예산 산술이 어긋나면 그렇게 던진다). 그때 스크립트는 리포트를 쓰지 못한 채 1
로 끝나고, 이 커맨드는 그 1 을 발견으로 읽어 존재하지 않는 파일로 이슈를 만들려 든다.
파일의 존재는 그 구분을 정확히 가른다: 의도한 1 은 언제나 리포트를 먼저 쓰고 끝나기 때문이다.
왜 0 인 채널의 리포트도 싣는가: 그 채널의 ✅ 는 아무 조건 없는 통과가 아니라 범위가 딸린 주장이다. fidelity 리포트는 자기 본문에 언제 생성됐는지 · 필터가 걸린 부분 스윕인지 · 드롭 디렉터리의 몇 개 중 하나만 읽었는지를 싣는데, 그 ✅ 를 "할 말이 없다" 한 줄로 갈음하면 그 조건들이 통째로 사라진다. 반년 전 리포트의 ✅ 가 이번 주기의 보증으로 읽히는 것 — 5단계가 리포트
부재에 대해 막으려는 오독과 정확히 같은 오독이고, 리포트가 있을 때 그것을 버려서 만들 이유가 없다.
왜 2 로는 이슈를 만들지 않는가: 깨진 파이프라인은 디자인 변경이 아니다. 그것을 디자인 이슈로 등록하면 이 라벨의 이슈 절반이 "자동화가 또 고장남" 이 되고, 사람은 제목만 보고 닫기 시작한다. 그러면
진짜 드리프트가 왔을 때도 닫힌다 — 이 자동화가 존재하는 유일한 이유가 그 순간인데.
왜 섞였을 때는 신호를 버리지 않는가: 구조 워크가 깨졌다는 사실은 fidelity JSON 이 잘 파싱됐는지에 대해 아무 말도 하지 않는다. 멀쩡한 채널의 발견을 다른 채널의 고장 때문에 버릴 이유가 없다. 다만 읽는 사람이 그 이슈를 완전한 목록으로 오해하면 안 되므로 부분 신호임을 맨 위에 적는다.
7. 이슈 생성#
본문을 조립하고 · 크기를 재고 · 만든다. 세 단계를 나누는 이유는 마지막 단계가 실패하는 방식이 조용하기 때문이다 — 상한을 넘긴 본문은 gh
가 거부하고, 그러면 두 채널이 찾아낸 것이 아무에게도 닿지 않은 채 실행이 끝난다. 실패한 것은 이슈 생성 한 번인데 사라지는 것은 그날의 발견 전부다.
7.1 본문 조립#
두 실행의 이슈가 같은 모양이어야 사람이 훑을 수 있다. 순서 고정:
< 부분 신호 경고 — 해당될 때만 >
## 구조 드리프트 (디자이너 · 플러그인 구조 스냅샷)
< 스크립트를 돌렸으면: diff_structure.ts 의 markdown 출력 그대로 >
< 스냅샷이 없어 4단계를 건너뛰었으면: 2단계의 " 스냅샷 없음 고정 문구 " 그대로 >
## Fidelity (디자이너 · 플러그인 리포트)
< 스크립트를 돌렸으면: ingest_fidelity.ts 의 markdown 출력 그대로 — 종료 코드가 0 이어도 싣는다 >
< 리포트가 없어 5단계를 건너뛰었으면: 그 단계의 " 리포트 없음 고정 문구 " 그대로 >
## 이 이슈가 볼 수 없는 것
구조 채널은 구조와 기하(id · 이름 · 타입 · x · y · width · height)만 본다.
색·토큰 바인딩·타이포그래피·radius·shadow 는 무인으로 읽을 수 없다.
레포 루트에서 조립한다:
BODY=/tmp/figma-absorb-body.md
: > " $BODY "
# 부분 신호 경고 — 한 채널이 2 로 끝났을 때만 (6단계 표 3행)
# printf ' %s\n\n ' ' ⚠️ 이 실행에서 ` < 채널 > ` 은 실패했다 — 이 이슈는 부분 신호다. ' > > " $BODY "
printf ' ## 구조 드리프트 (디자이너 · 플러그인 구조 스냅샷)\n\n ' > > " $BODY "
cat /tmp/figma-structure-diff.md > > " $BODY " # 건너뛰었으면 2단계 고정 문구를 대신 쓴다
printf ' \n\n## Fidelity (디자이너 · 플러그인 리포트)\n\n ' > > " $BODY "
cat /tmp/figma-fidelity.md > > " $BODY " # 건너뛰었으면 5단계 고정 문구를 대신 쓴다
printf ' \n\n## 이 이슈가 볼 수 없는 것\n\n구조 채널은 구조와 기하(id · 이름 · 타입 · x · y · width · height)만 본다.\n색·토큰 바인딩·타이포그래피·radius·shadow 는 무인으로 읽을 수 없다.\n ' > > " $BODY "
채널이 2 로 끝났으면 그 리포트 파일은 없다 — 스크립트가 인프라 실패에서 멈추고 아무것도 쓰지 않기 때문이다. cat
이 실패하도록 두지 말고, 그 절 아래에 무엇이 실패했는지 한 문단으로 적는다. 절을 비우면 읽는 사람은 그 채널이 깨끗했다고 읽는다.
두 채널의 출력은 그대로 싣는다 — 요약하거나 표를 줄이지 않는다. 각 스크립트는 자기 예산 안에서 잘라내며 몇 행을 감췄는지 문장으로 적는다. 그 문장을 지우면 잘린 리포트가 완전한 목록으로 읽힌다.
어떤 제목도 아래를 비운 채 내보내지 않는다. 할 말이 없는 채널은 "할 말이 없다" 를 문장으로 적는다. 빈 절은 읽는 사람에게 침묵이 아니라 깨끗한 검사 결과로 보이고, 그 오독은 절을 아예 빼는 것보다 나쁘다 — 검사가 돌았다는 인상까지 같이 주기 때문이다.
마지막 문단을 매번 싣는 왜: 이 이슈에 색 관련 항목이 없다는 것이 "색은 멀쩡하다" 는 뜻으로 읽히면 안 된다. 무인 채널이 색을 볼 수 없다는 사실을 이슈 자신이 말하지 않으면, 없는 보장을 있다고 믿게 된다.
7.2 크기 검증 — 스크립트를 믿지 말고 잰다#
LIMIT=65536
BYTES=$(wc -c < " $BODY " )
echo " 본문 ${BYTES} / ${LIMIT} 바이트 "
wc -c 는 바이트를 센다. 글자 수로 재면 안 된다 — 이 본문은 한국어라 한 글자가 3 바이트이고, 글자로 세면 실제 크기를 3배 가까이 낮잡는다. 상한이 바이트면 바이트로 잰다.
왜 상한이 있나: GitHub 이슈 본문은 65,536 바이트를 넘으면 거부된다. 그 본문이 이 발견들이 사람에게 닿는 유일한 경로라, 상한이 없으면 스윕이 크게 잡을수록 아무도 아무것도 못 보게 된다 — 결함이 많을수록 조용해지는 자동화다.
왜 여기서 또 재는가: 두 스크립트는 각자 자기 예산 안에서 잘라내고, 두 예산과 이 절의 감싸는 글 몫(ISSUE_WRAPPER_RESERVE_BYTES)을 합친 값이 상한을 넘지 않는다는 것은
reverse/report_format.ts 가 로드 시점에 단언한다. 그래서 정상 경로에서 이 검사는 항상 통과한다 — 그게 이 검사를 두는 이유다. 통과하지 않으면 그것은 본문이 큰 게 아니라
잘라내기가 동작하지 않았다는 뜻이고, 그 사실은 이슈가 거부되어 아무것도 안 남는 형태가 아니라 문장으로 남아야 한다. 산술을 한 곳에서 맞춰두는 것과, 그 산술이 실제로 지켜졌는지 확인하는 것은 다른 일이다.
넘겼으면 — 이슈를 포기하지 않는다. 발견은 실재하고(6단계가 이미 1 을 봤다), 그것이 사라지는 것이 이 상한이 막으려던 바로 그 결과다. 순서대로 한다:
-
각 채널 리포트의 바이트 수를 자기 예산과 비교한다. 넘긴 쪽이 원인이다. 두 예산은 공유 모듈이 들고 있으므로 거기서 읽는다 — 여기 숫자를 옮겨 적으면 둘이 갈라진다:
cd tools/figma-token-sync & & npx tsx -e " const m = require( ' ./reverse/report_format.ts ' ); console.log( ' A ' , m.CHANNEL_A_BUDGET_BYTES, ' B ' , m.CHANNEL_B_BUDGET_BYTES); " wc -c /tmp/figma-structure-diff.md /tmp/figma-fidelity.md 원인인 절의 본문만 아래 대체 문단으로 갈아끼운다. 제목은 남긴다.
다시 잰다. 그래도 넘으면 나머지 채널 절도 같은 문단으로 갈아끼운다. 두 절이 모두 대체되면 남는 것은 감싸는 글뿐이라 반드시 상한 아래로 내려간다.
본문 맨 위에 "이 이슈는 리포트 본문이 상한을 넘겨 일부를 들어낸 채 만들어졌다" 를 적는다. 이슈는 반드시 만든다.
대체 문단:
⚠️ **이 채널의 리포트가 본문에 실리지 못했다.**
리포트가 < N > 바이트로 이 채널의 예산 < BUDGET > 바이트를 넘겼고, 그대로 실으면 이슈
본문 상한(65,536 바이트)을 넘겨 **이슈 자체가 거부된다.** 발견이 통째로 사라지는 것보다
이 문단이 낫다고 판단해 본문만 들어냈다.
리포트를 읽으려면 레포에서 직접 돌린다:
cd tools/figma-token-sync & & npx tsx < 이 채널의 스크립트 > < 4·5단계와 같은 플래그 >
그리고 이 상태 자체가 파이프라인 결함이다 — 스크립트는 자기 예산 안에서 잘라내고
몇 행을 감췄는지 적게 되어 있다. 예산을 넘겼다는 것은 그 잘라내기가 동작하지 않았다는 뜻이다.
넘긴 사실은 마지막 보고에도 남긴다. 이슈에만 적으면 그 이슈를 여는 사람은 알지만 파이프라인을 고치는 사람은 모른다.
7.3 이슈 만들기#
gh issue create \
--title " chore(figma): 디자인 구조 드리프트 — $(date -u +%Y-%m-%d) " \
--label " design-system,tooling,p2 " \
--body-file " $BODY "
gh 가 실패하면(권한 · 네트워크 · 라벨 없음) 인프라 실패로 보고하고, 본문 파일 경로를 보고에 남긴다. 조립된 본문은 그 실행의 유일한 산출물이라 경로를 잃으면 다시 만들려면 MCP 예산을 또 쓴다.
제목 규칙 — closing keyword 를 이슈 번호에 붙이지 않는다#
제목의 conventional-commit type 에는 반드시 scope 를 붙인다 (chore(figma):).
왜: close(s|d) / fix(es|ed) / resolve(s|d) 바로 뒤에
#N 이 오면 GitHub 이 그 이슈를 자동으로 닫는다. 이 레포에서 fix: #N — ... 제목이 본문에 "Part of #N" 이라고 적혀 있었는데도 이슈를 닫아버린 사고가 실제로 있었고, scope 를 끼워
fix(web): #N 으로 만들면 인접이 깨져 닫히지 않는다(.claude/rules/process/workflow.md). chore
는 애초에 closing keyword 가 아니라 지금 형태로는 위험이 없지만, 누군가 나중에 type 만 fix 로 바꿔도 살아남는 것은 scope 다 — 그래서 scope 를 습관으로 고정한다.
리포트 본문에 이슈 번호를 인용해야 하면 Part of #N 처럼 키워드가 아닌 표현만 쓴다.
8. 절대 하지 않는 것#
- Dart 소스를 편집하지 않는다. 이 커맨드의 산출물은 이슈 하나이며 코드 변경은 0이다.
-
--update-baseline을 자동으로 돌리지 않는다. 베이스라인 승격은 사람의 결정이다.- 왜: 베이스라인을 올린다는 것은 "디자이너가 한 그 변경이 이제부터 정답" 이라고 선언하는 것이다. 자동화가 그것을 대신 하면, 자동화는 매 실행 자기가 방금 본 것을 정답으로 삼으므로 어떤 드리프트도 영원히 보고되지 않는다 — 항상 초록인 채로 아무것도 지키지 않는 가드가 된다.
- 사람이 승격하는 방법: 이슈의 diff 를 읽고 그 변경에 동의한 뒤,을 돌리고
cd tools/figma-token-sync npx tsx reverse/diff_structure.ts --snapshot=reverse/incoming/structure- < stamp > .json --update-baselinereverse/baseline.structure.json을 커밋한다. 커밋 메시지에 무엇에 동의했는지 남긴다. - 이슈가 보여준 그 파일로 승격한다. 리포트 첫머리의
출처 파일이 그 스냅샷이다. 그 뒤 새 스냅샷이 들어왔다면 동의한 내용과 승격하는 내용이 다를 수 있으니 diff 를 한 번 더 보고 올린다. - 승격 실행은 이전 베이스라인과의 diff 를 함께 내는데, 캡처 경로가 바뀌는 첫 승격의 그 diff 는 전환 소음이라 판정에 쓰지 않는다.
- 스냅샷 파일을 손보지 않는다. 스크립트가 형태를 검증하고, 손댄 파일은 어긋난 자리에서 종료 코드 2 로 멈춘다.
보고 형식#
이슈를 만들었든 안 만들었든, 실행 끝에 다음을 보고한다:
- 인터록 통과 여부 (
fileKeyStatus) -
읽은 스냅샷 파일 이름과
capturedAt(2단계 — 스냅샷이 들어오지 않고 있다는 것은 주기 간 기록만이 보여준다), 리포트가 말한 페이지 수 · 노드 수 - 구조 채널의 종료 코드, 또는 건너뜀 + 마지막 스냅샷 날짜
- fidelity 채널의 종료 코드, 또는 건너뜀 + 마지막 리포트 날짜 (건너뛴 실행은 종료 코드가 없다)
- 조립한 본문의 바이트 수와 상한 대비, 그리고 상한을 넘겨 들어낸 절이 있으면 어느 절인지 (7.2 — 이건 이슈가 아니라 파이프라인의 결함이라 이슈를 여는 사람이 아니라 이 보고를 읽는 사람에게 닿아야 한다)
- 생성한 이슈 URL, 또는 만들지 않은 이유