LogoSkills

/cc-coui-sync:absorb — 디자이너가 무엇을 바꿨는지 먼저 안다

디자이너가 플러그인으로 내려준 Figma 구조 스냅샷을 baseline 과 차분해 이슈 하나로 보고합니다 — 페이지·컴포넌트·변형 축의 추가/삭제/rename/이동을 찾아 '사람이 봐야 할 차이가 있다/없다' 신호를 남기고, 아무 코드도 고치지 않습니다. 스케줄 자동화로 캔버스 드리프트를 주기 감시할 때, 디자이너가 무엇을 바꿨는지 먼저 알아야 할 때...

/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" 가 아니면 여기서 멈춘다. 스크립트를 부르지 않고, 이슈를 만들지 않고, "파일 키가 확인되지 않아 중단했다 — 디자이너가 이 키가 정본 라이브러리 파일인지 확인한 뒤 fileKeyStatusconfirmed 로 바꿔야 한다" 고 보고하고 끝낸다.

: 확인되지 않은 키는 엉뚱한 파일을 가리킬 수 있다. 그 상태로 걸어가면 그 파일의 구조가 baseline 과 전부 다르므로, 자동화는 "디자이너가 모든 것을 바꿨다" 는 거대한 거짓 이슈를 만든다. 그 이슈는 틀렸다는 것조차 읽어봐야 알 수 있고, 한 번 겪은 사람은 다음 이슈도 안 읽는다 — 자동화의 신뢰를 첫 실행에 태워버리는 경로다. 키가 맞다는 것은 코드가 판정할 수 없고 사람만 판정할 수 있으므로, 그 판정을 파일에 기록하기 전까지는 걷지 않는다.

이 인터록은 스냅샷 경로에서 중요하다. 개발용 플러그인은 figma.fileKey 를 받지 못해(Figma 가 조직 비공개 플러그인에만 준다) 스냅샷의 fileKeynull 이고, 그러면 스크립트의 "다른 파일에서 온 스냅샷" 검사가 작동하지 않는다. 파일 정체성을 말해 주는 것은 이 설정뿐이다.

같은 파일에서 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개 생성
12 가 섞임 이슈 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 을 봤다), 그것이 사라지는 것이 이 상한이 막으려던 바로 그 결과다. 순서대로 한다:

  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
    
  2. 원인인 절의 본문만 아래 대체 문단으로 갈아끼운다. 제목은 남긴다.

  3. 다시 잰다. 그래도 넘으면 나머지 채널 절도 같은 문단으로 갈아끼운다. 두 절이 모두 대체되면 남는 것은 감싸는 글뿐이라 반드시 상한 아래로 내려간다.

  4. 본문 맨 위에 "이 이슈는 리포트 본문이 상한을 넘겨 일부를 들어낸 채 만들어졌다" 를 적는다. 이슈는 반드시 만든다.

대체 문단:

⚠️ **이 채널의 리포트가 본문에 실리지 못했다.**

리포트가  < 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-baseline
      
      을 돌리고 reverse/baseline.structure.json 을 커밋한다. 커밋 메시지에 무엇에 동의했는지 남긴다.
    • 이슈가 보여준 그 파일로 승격한다. 리포트 첫머리의 출처 파일 이 그 스냅샷이다. 그 뒤 새 스냅샷이 들어왔다면 동의한 내용과 승격하는 내용이 다를 수 있으니 diff 를 한 번 더 보고 올린다.
    • 승격 실행은 이전 베이스라인과의 diff 를 함께 내는데, 캡처 경로가 바뀌는 첫 승격의 그 diff 는 전환 소음이라 판정에 쓰지 않는다.
  • 스냅샷 파일을 손보지 않는다. 스크립트가 형태를 검증하고, 손댄 파일은 어긋난 자리에서 종료 코드 2 로 멈춘다.

보고 형식#

이슈를 만들었든 안 만들었든, 실행 끝에 다음을 보고한다:

  • 인터록 통과 여부 (fileKeyStatus)
  • 읽은 스냅샷 파일 이름과 capturedAt (2단계 — 스냅샷이 들어오지 않고 있다는 것은 주기 간 기록만이 보여준다), 리포트가 말한 페이지 수 · 노드 수
  • 구조 채널의 종료 코드, 또는 건너뜀 + 마지막 스냅샷 날짜
  • fidelity 채널의 종료 코드, 또는 건너뜀 + 마지막 리포트 날짜 (건너뛴 실행은 종료 코드가 없다)
  • 조립한 본문의 바이트 수와 상한 대비, 그리고 상한을 넘겨 들어낸 절이 있으면 어느 절인지 (7.2 — 이건 이슈가 아니라 파이프라인의 결함이라 이슈를 여는 사람이 아니라 이 보고를 읽는 사람에게 닿아야 한다)
  • 생성한 이슈 URL, 또는 만들지 않은 이유