LogoSkills

/cc-coui-sync:status — 캔버스는 지금 어느 코드를 그리고 있나

Figma 플러그인이 지금 그리는 것이 어느 코드인지를 6행 표로 답합니다 — 번들에 구워진 소스 해시와 작업 트리·main 을 대조하고, components.json 신선도·main 에 도달하지 못한 흡수 브랜치·재촬영 잔여·마지막 Generate 시각을 함께 잽니다. 재생성했더니 컴포넌트 모습이 달라졌을 때, 흡수 작업을 시작하거나 끝낼 때, 캔버...

/cc-coui-sync:status — 캔버스는 지금 어느 코드를 그리고 있나#

항목내용
실행 명령/cc-coui-sync:status
분류cocode design
난이도●●○ 보통
MCP 서버figma

한마디로#

디자인 시스템의 Figma 라이브러리는 코드에서 생성됩니다. 그래서 "지금 캔버스에 보이는 것"은 누군가의 컴퓨터에 빌드돼 있는 코드의 사진입니다. 이 커맨드는 그 사진이 어느 시점의 코드인지를 재서 표 하나로 보여줍니다 — 아무것도 고치지 않고 읽기만 합니다.

누가·언제 쓰나요#

  • 재생성했더니 컴포넌트 모습이 예전으로 돌아갔을 때 — 원인이 대개 여기 있습니다
  • 흡수 작업을 시작하기 전(무엇이 이미 반영돼 있나) · 끝낸 뒤(정말 도달했나)
  • "코드와 캔버스가 맞나요?" 라는 질문에 인상이 아니라 근거로 답해야 할 때

왜 이 커맨드가 있나#

코드가 정본이고 캔버스는 재생성물인 구조에서, "머지됐는가 / 빌드됐는가 / 릴리즈됐는가" 는 서로 다른 세 질문인데 화면에는 구별되어 나타나지 않습니다. 실제로 두 달치 흡수 작업이 머지되지 않은 브랜치에 살아 있는 동안 플러그인은 그 브랜치에서 빌드되어 있었고, 그 상태는 "재생성했더니 컴포넌트 모습이 달라졌다" 라는 질문으로만 드러났습니다 — 원인 추적에 브랜치별 해시 대조가 필요했습니다. 이 표가 있었으면 그 질문은 질문이 되기 전에 화면에 있었습니다.

(릴리즈는 이 표에 없습니다 — 플러그인은 배포 패키지가 아니라 그 컴퓨터의 저장소를 읽으므로, 릴리즈 여부는 캔버스에 한 픽셀도 영향을 주지 않습니다.)

실행 위치#

coco-de/coui 레포 루트. 이 커맨드가 세는 값은 전부 그 레포의 것입니다 — 다른 레포에서 돌리면 경로가 없어 아무것도 재지 못합니다. Figma MCP 는 5단계에만 필요하며, 없으면 그 행만 측정 불가 로 표기하고 계속합니다.

이 파일은 절차만 권위를 가집니다. 엔지니어링 룰의 정본은 coui 레포의 .claude/rules/ 이며(매 세션 자동 로드) 충돌 시 그쪽이 우선합니다.


1. 번들 출처 — code.js 는 어느 소스에서 빌드됐나#

번들에 구워진 해시를 꺼낸다 (code.js 는 gitignore 대상이라 없을 수 있다):

grep -oE  ' kSharedSourceHash = true \?  " [0-9a-f]{16} " '   tools/figma-token-sync/code.js \
  | grep -oE  ' [0-9a-f]{16} '
  • 파일이 없으면 → 빌드된 적 없음 — Generate 를 돌리려면 npm run build 필요 로 판정하고 다음 단계로.
  • 매치가 없으면 → dev 폴백 — 주입 없이 빌드됨, 무효화 죽음 으로 판정한다. 이 상태는 check_shared_source_hash.sh 가 CI 에서 막는 바로 그것이다.

이제 후보 ref 들의 소스 해시를 계산해 대조한다. 계산식은 빌드 스크립트(tools/figma-token-sync/package.jsonbuild)와 글자 그대로 같아야 한다 — 다른 식으로 재구현하면 두 계산이 갈라지는 날 이 표가 거짓을 말한다:

# 작업 트리
( cd tools/figma-token-sync  & &   \
  find src code.ts -name  ' *.ts '   -not -path  ' src/data/* '   | sort | xargs cat | shasum -a 256 | cut -c1-16 )

# origin/main — 작업 트리를 건드리지 않는 임시 worktree 로
git fetch origin main -q
git worktree add --detach /tmp/fss-main origin/main -q 2 > /dev/null || true
( cd /tmp/fss-main/tools/figma-token-sync  & &   \
  find src code.ts -name  ' *.ts '   -not -path  ' src/data/* '   | sort | xargs cat | shasum -a 256 | cut -c1-16 )
git worktree remove --force /tmp/fss-main

왜 worktree 인가: git stash / git checkout 으로 오가는 대조는 사용자의 작업 트리를 건드린다 — 이 커맨드는 읽기 전용이라는 약속을 그 방식으로는 지킬 수 없다.

판정: 구운 해시가 작업 트리와 일치 / origin/main 과 일치 / 둘 다 아님. 셋째가 가장 중요한 답이다 — 캔버스가 지금 저장소 어디에도 없는(혹은 다른 브랜치의) 코드로 그려지고 있다는 뜻이므로, 그때는 로컬 브랜치들을 같은 식으로 순회해 어느 ref 인지까지 찾아 적는다.

src/data/ 는 계산에서 빠진다 — 즉 이 해시는 빌더 로직의 출처만 답한다. 데이터(components.json)의 신선도는 2단계가 따로 답한다. 둘은 독립적으로 낡을 수 있고, 실제로 서로 다른 가드가 지킨다.

2. components.json — 데이터는 신선한가#

bash scripts/guards/structure/check_figma_export_fresh.sh

통과/실패를 그대로 적는다. 실패면 처방도 가드 출력에 있다 (export 재실행).

3. main 미도달 흡수 커밋 — 좌초 감지#

현재 브랜치와, 원격의 다른 브랜치들 중 컴포넌트/플러그인 경로를 건드리고 main 에 없는 커밋을 센다:

git fetch origin -q
# 현재 브랜치
git log --oneline origin/main..HEAD -- \
  packages/coui_core packages/coui_flutter packages/coui_web tools/figma-token-sync | cat

# 원격 브랜치 — 최근 30일 내 움직였고, 그 브랜치의 PR 상태로 분류
gh pr list --state all --limit 1000 --json headRefName,state,number  >   /tmp/fss-prs.json
cutoff=$(( $(date +%s) - 30*24*3600 ))
git for-each-ref --format= ' %(committerdate:unix) %(refname:short) '   refs/remotes/origin \
  | while read -r ts b; do
      [  " $ts "   -lt  " $cutoff "   ]  & &   continue
      case  " $b "   in origin/main|origin/HEAD|origin/chore/preset-baseline-*|origin/chore/golden-baseline-*) continue;; esac
      n=$(git rev-list --count origin/main.. " $b "   -- \
            packages/coui_core packages/coui_flutter packages/coui_web tools/figma-token-sync 2 > /dev/null)
      [  " ${n:-0} "   -gt 0 ] || continue
      short=${b#origin/}
      info=$(jq -r --arg h  " $short "   \
         ' [.[] | select(.headRefName==$h)]
         | if length==0 then  " NONE " 
            else (sort_by(.state== " OPEN " ) | last |  " \(.state) #\(.number) " ) end '   /tmp/fss-prs.json)
      case  " $info "   in
        MERGED*|CLOSED*) ;;                      # 랜딩됐거나 폐기됨 — 죽은 브랜치
        OPEN*) echo  " 진행중  $b: $n ($info) " ;;
        NONE)  echo  " 좌초⚠️  $b: $n (PR 없음) " ;;
      esac
    done

왜 두 필터인가: 이 레포에는 끝난 작업의 브랜치가 수백 개 남아 있다. 날짜 컷 하나만으로는 부족했다 — 실측에서 30일 컷이 178줄을 남겼고 그중 살아 있는 것은 4줄이었다(머지된 PR 의 브랜치가 삭제되지 않고 남으면 committerdate 는 최근이다). 브랜치가 살아 있느냐는 PR 상태가 직접 답한다: MERGED/CLOSED 면 죽은 것이고, OPEN 이면 진행 중, PR 자체가 없으면 그것이 좌초다. 같은 브랜치 이름으로 PR 이 여럿이면(재타겟 실패로 다시 연 경우) OPEN 을 우선한다.

왜 세는가: 좌초된 흡수는 스스로 알리지 않는다. 브랜치에서 빌드해 Generate 를 돌리는 동안은 캔버스가 옳아 보이고, 누군가 main 에서 다시 빌드하는 순간 되돌아간다 — 그 되돌아감이 이 표가 미리 보여주려는 것이다. PR 없음OPEN 은 다르게 읽는다: 전자는 아무도 리뷰를 기다리지 않는 진짜 좌초, 후자는 랜딩 대기 중이라 곧 해소될 상태.

4. 재촬영 잔여 — dispatch 됐지만 아직 안 실린 baseline#

for b in $(git for-each-ref --format= ' %(refname:short) '   refs/remotes/origin \
             | grep -E  ' origin/chore/(preset|golden)-baseline- ' ); do
  # 그 브랜치의 승격 커밋 패치가 main 에 이미 있으면(cherry-pick 완료) 정리 대상일 뿐이다
  if [  " $(git cherry origin/main  " $b "   | grep -c  ' ^+ ' ) "   -gt 0 ]; then
    echo  " $b: 미수용 — cherry-pick 대기 " 
   fi
done

재촬영 워크플로(preset-baseline.yml · golden-baseline.yml) 는 브랜치를 밀어두고 사람이 옮기는 구조라, 잊힌 산출물이 원격에 조용히 쌓일 수 있다. 이 행이 그것을 센다.

5. 마지막 Generate 발자국 — (Figma MCP 가용 시)#

실행은 자기 위치를 문서에 남긴다 — figma.root 의 sharedPluginData, 네임스페이스 coui, 키 last-step, 값 단계|epoch-ms. use_figma 로 읽는다 (읽기 전용 스크립트, 반드시 figma-use 스킬 로드 후):

const v = figma.root.getSharedPluginData( ' coui ' ,  ' last-step ' );
return { lastStep: v || null };

값을 단계 · <사람이 읽는 시각> 으로 표기한다. 비어 있으면 발자국 없음 (이 문서에서 Generate 이력 없음). MCP 가 없으면 측정 불가 (Figma MCP 미연결).

: "마지막으로 캔버스를 그린 게 언제·어디까지였나" 는 저장소 어디에도 없고 문서에만 있다. 죽은 실행의 마지막 위치를 읽으라고 만들어진 값이지만, 살아 끝난 실행의 시각 증거로도 그대로 쓰인다.

6. 로컬 SDK — 이 머신에서 무엇이 가능한가#

flutter --version 2 > /dev/null | head -1
grep -m1  ' flutter: '   pubspec.yaml

로컬이 워크스페이스 floor 미만이면: 로컬 flutter test 불가 — 검증은 CI · 재촬영은 workflow dispatch. 이 한 줄이 있어야 "왜 로컬에서 안 도냐" 를 매번 다시 밟지 않는다.


출력 형식#

측정을 마치면 이 표 하나로 보고한다. 각 행은 위 단계의 실측값이어야 하며, 실행하지 않은 단계를 추정으로 채우지 않는다:

| 항목                  || 판정 |
|----------------------|----------------------------|------|
| 번들 출처             |  < hash >   =  < ref >   / 불일치     |/⚠️ |
| components.json      | fresh / stale               |/⚠️ |
| main 미도달 흡수      | N 커밋 ( < 브랜치 > )           |/⚠️ |
| 재촬영 잔여           | 없음 /  < 브랜치 >               |/⚠️ |
| 마지막 Generate       |  < 단계 >   ·  < 시각 >   / 측정 불가  | ℹ️   |
| 로컬 SDK             |  < 버전 >   (floor  < 요구 > )       |/⚠️ |

⚠️ 가 하나라도 있으면 그 행 밑에 다음 한 걸음(정확한 명령 또는 어느 커맨드로 이어지는지 — 흡수 잔여면 /cc-coui-sync:absorb-apply)을 한 줄로 붙인다.