Communitygithub.com

Afresto-Next/afresto-claude-skills

아프레스토 넥스트(backend Go, web React, mobile RN/Expo) 배포 작업을 위한 스킬입니다. Git 워크플로우, CI/CD, 품질 게이트, 마이그레이션을 다룹니다.

afresto-claude-skills란 무엇인가요?

이 스킬은 아프레스토 넥스트 프로젝트의 안정적인 배포를 보장하기 위해 설계되었습니다. **멀티 에이전트 환경**에서의 Git 충돌 방지, Go 품질 게이트(gofmt, go build/vet 등), 그리고 **GitHub Issues 기반 작업 흐름**을 강제합니다. **Claude Code, Cursor, Codex** 등 여러 AI 에이전트가 동시에 같은 저장소에서 작업할 때 발생하는 문제를 해결하며, Git 작업 트리 오염, 브랜치 전환, 스테이징 오류를 방지합니다. 배포는 **backend Go**(GHCR → Watchtower), **web React**(Cloudflare Workers), **mobile** (EAS OTA)로 구성되며, 모든 배포 전에 사용자 승인을 요구합니다. 핵심 규칙으로는 `main` 브랜치 직접 커밋 금지, 작업 전 GitHub Issue 생성, 선택적 Git 스테이징(selektif staging)이 포함됩니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add Afresto-Next/afresto-claude-skills

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Deploy Afresto Next

스택: backend Go (push→GHCR→Watchtower ~5분) · web React (Vite→Cloudflare Worker, 수동 wrangler deploy) · mobile RN/Expo, 별도 저장소 <repo-mobile> (EAS OTA). 프로덕션 배포는 항상 사용자 허가를 먼저 받아야 합니다 (매번 사용자가 승인).

🔴 규칙 #1 — 이 저장소는 여러 AGENT TAB이 동시에 사용함

git status에 다른 세션의 수정된 파일이 자주 표시됩니다. 따라서:

  • git add -a / git commit -a / git add . 하지 마세요 — 다른 세션의 작업 중인 파일까지 커밋에 포함됩니다.
  • 선택적으로(stage selektif) 자신의 파일만 추가한 후, 확인: git diff --cached --name-only — 다른 세션의 파일이 없는지 확인 (특히 web/src/pages/ChatPage.tsx, backend/internal/db/dbgen/models.go가 당신이 재생성한 것이 아니라면 자주 섞입니다).
  • 두 세션이 공유하는 파일(예: ExamPage)에서 당신의 hunk만 원한다면: stash/reset하지 마세요 (다른 에이전트의 작업을 빼앗게 됩니다). 대신 Git 인덱스를 blob으로 편집하세요: git show :path > tmp → tmp에서 그들의 hunk를 제거 → SHA=$(git hash-object -w tmp) → git update-index --cacheinfo 100644,$SHA,path. 디스크의 파일은 변경되지 않습니다.
  • 🚨 Bash의 cwd 도구가 이전 호출의 cd .../web (또는 worktree)에서 유지될 수 있습니다. 그러면 git add backend/...가 pathspec did not match 오류를 내거나 잘못된 트리를 스테이징합니다. 선택적 스테이징 전에 **cd <repo-root> &&**를 먼저 실행하세요 (또는 절대 경로 사용). git diff --cached --name-only가 올바른 루트 상대 경로(../backend, src/...가 아닌)를 표시하는지 확인하세요.
  • 배포 후: git status를 확인하여 다른 세션의 작업 파일이 그대로 있는지 확인하세요.

Go 품질 게이트 (backend 커밋 전)

Laravel의 Pint + PHPStan에 해당하는 Go 버전입니다. backend 파일을 스테이징하기 전에 로컬에서 실행하세요:

  • 포맷: cd backend && gofmt -l . → 비어 있어야 함. 내용이 있으면 → gofmt -w . (자동 수정), 결과를 스테이징하세요. (pint --test에 해당)
  • 빌드 + vet: go build ./... && go vet ./...가 통과해야 함.
  • 정적 분석 (목표): golangci-lint run ./... (gofmt/govet/staticcheck/errcheck/ineffassign/unused/misspell 포함). phpstan analyse에 해당.
  • ⚠️ 상태: CI 게이트 아직 활성화되지 않음. 아직 backend/.golangci.yml 또는 .github/workflows/ci.yml이 없으며, staticcheck도 구버전 → 따라서 포맷+빌드+vet은 수동으로 진행. CI에서 강제 적용 및 전체 설정 계획: docs/plan/00-tooling-quality-gate.md. 생성된 코드 internal/db/dbgen은 lint에서 제외.
  • 변경 사항에 테스트 포함 → afresto-testing 스킬 (go test ./...; TEST_DATABASE_URL 없으면 integration은 건너뜀).

🔴 규칙 #00 — 의미 있는 작업은 먼저 GitHub Issue로 등록 (2026년 9월 5일부터)

규칙 #0 (브랜치 + PR) 이전에 적용: 코드를 전혀 건드리기 전에.

목적은 형식적이지 않습니다. 대화로 들어온 요청은 두 당사자만 알게 됩니다 — 팀은 읽을 수 없고, 소유자는 할당할 곳이 없으며, 모듈은 누가 무엇을 하고 있는지 흔적을 남기지 않습니다.

Issue가 필수인 경우

새 기능 · 학교에서 볼 수 있는 변경 · DB 스키마 변경 · 제품 결정이 필요한 모든 것 · 두 개 이상의 PR이 필요한 작업.

Issue가 필요 없는 경우

명확하고 좁은 버그 · 텍스트/레이블 수정 · 문서 · PR 리뷰 코멘트의 직접적인 후속 조치.

의심되면 → issue를 만드세요. 저렴하고, 닫을 수 있습니다. 이 기준이 규칙을 살아있게 합니다: 모든 사소한 요청이 issue가 되면, 사람들은 조용히 이를 무시하기 시작하고, 한 번 무시해도 결과가 없으면 규칙은 죽습니다.

에이전트가 할 일

  1. 코드를 충분히 탐색하여 "초기 발견"을 채우고 blocker를 찾으세요 — 추측하지 마세요.
  2. .github/ISSUE_TEMPLATE/pekerjaan.md 템플릿을 사용해 issue를 만들고, modul:* 레이블을 붙이세요 (모듈별 기록을 형성합니다). 결정이 필요한 사항이 있으면 butuh-keputusan도 추가하세요.
  3. 멈추세요. 코딩하지 말고, 브랜치를 만들지 말고, 자신을 할당하지 마세요. 할당은 소유자의 행동입니다. 팀이 피드백을 제공하거나 아이디어를 수정할 공간도 필요합니다.
  4. 소유자에게 issue 번호와 함께 결정이 필요한 사항(있는 경우)을 보고하세요.

🔑 소유자는 "지금 작업"이라고 말함으로써 이것을 건너뛸 수 있습니다 — issue는 계속 기록으로 만들고, 작업은 진행됩니다. 이 규칙은 기본값을 규정할 뿐, 가두려는 것이 아닙니다.

작업 중간에 새로 발견된 Blocker

계속하기 전에 알리세요: issue에 코멘트 (팀도 볼 수 있도록) 그리고 소유자에게 전달하세요. 결정이 작업 중단으로 이어지면 → butuh-keputusan 레이블을 추가하세요. 자신의 해석을 조용히 선택하고 계속 진행하지 마세요 — 그렇게 하면 "완료"되었지만 잘못된 작업이 탄생합니다.

고리 닫기

PR 본문에 Closes #<번호> 를 반드시 포함하세요. 이것이 issue → PR → 사람을 연결하여, gh issue list --label modul:ujian --state all이 그 모듈에서 누가 무엇을 하고 있는지 기록하게 됩니다. 이 줄이 없으면 흔적이 끊깁니다.

🔴 규칙 #0 — main에 직접 commit/push하지 마세요 (4인 팀, 2026년 7월 25일부터)

모든 작업은 브랜치 + Pull Request를 통해; 리뷰어만 main에 병합합니다 (병합 = 배포 트리거).

  1. 시작 전: git checkout main && git pull — 새 마이그레이션이 있으면 먼저 실행하세요.
  2. 코드를 건드리기 전에 주제별 브랜치를 만들거나 체크아웃하세요:
    • 기능 → feat/<이름> · 버그 → bugfix/<이름> · issue가 있는 버그 → 새 브랜치 + issue 학습.
  3. 선택적 커밋 → git push -u origin <브랜치> (main이 아닌).
  4. main으로 PR 열기: gh 로그인 시 gh pr create; 아니면 사용자에게 URL 제공 https://github.com/Afresto-Next/next/compare/main...<브랜치>?expand=1.
  5. 리뷰어 승인 & 병합 → 배포. 직접 PR 병합 금지.
  • 아래 "Push backend / Deploy WEB" 부분은 PR이 main에 병합된 후 실행되는 메커니즘입니다 (보통 배포 담당자가 수행), main에 직접 push 허용이 아닙니다.
  • 스킬 변경도 PR을 통해 (CODEOWNERS).

실행 순서

1. 커밋 (Bash 도구 = Git Bash / sh, PowerShell 아님)

  • 여러 줄 메시지: heredoc git commit -q -F - <<'EOF' … EOF. @'…'@를 사용하지 마세요 (PowerShell here-string으로, 제목이 @ ...로 오염됨).
  • 메시지 끝: Co-Authored-By: Claude Opus 4.8 <[email protected]>.
  • "CRLF will be replaced by LF" 경고는 정상, 무시하세요.
  • 당신의 작업 브랜치 (feat/* · bugfix/*)에 커밋, main이 아님 (규칙 #0 참조). 사용자가 요청할 때 커밋.

🔴 git add -A / git add <dir>/ 하지 마세요 — 이미 3번 사용자 파일을 휩쓸었습니다

이 저장소에는 추적되지 않은 많은 사용자 작업 파일이 있습니다: docs/bskap/ (이미지+PDF), docs/analisis butir soal/, elibrary/, cloudflare/**/.wrangler/ (계정 캐시). 한 번 -A로 모든 것이 포함됩니다.

실제 사례: git add docs/가 BSKAP PDF/xls/이미지를 포함 (7월 25일 & 8월 2일); git add -A가 446개 파일을 포함, wrangler-account.json도 포함 (8월 9일).

git add path/ke/berkas1 path/ke/berkas2        # 항상 하나씩
git commit -q -F - <<'EOF'
...
EOF
git show --name-only --format="" HEAD | wc -l   # 의도한 것과 일치하는지 확인?

숫자가 맞지 않으면: git reset --soft HEAD~1 && git reset, 올바르게 다시 스테이징. 이미 자신의 브랜치에 푸시했다면, git push --force-with-lease는 안전합니다.

2. DB 마이그레이션 — TIP 커밋에 필수

.github/workflows/migrate.yml이 git diff --name-only HEAD~1 HEAD (마지막 커밋만)로 마이그레이션을 감지하며, "Build & Push Docker Images" 성공 후 (workflow_run) 실행됩니다.

  • 푸시에 마이그레이션이 포함된 경우 → 마이그레이션 반드시 TIP 커밋에 있어야 함 (마이그레이션+코드가 하나의 커밋에 있어도 안전).
  • 마이그레이션은 빌드 후 self-hosted runner VM에서 자동으로 실행됩니다. run --rm migrate 동기화 → 오류 시 job이 심하게 실패 (조용하지 않음).
  • 마이그레이션 확인: 해당 커밋의 Actions → "Migrate DB" 녹색 탭 확인 (gh CLI 사용 가능: gh run list --workflow="Migrate DB (self-hosted)" --limit 1 — 에이전트가 직접 확인 가능, 사용자에게 요청할 필요 없음). Watchtower가 다음 폴링(~5분)에서 api 이미지를 교체하며, 거의 항상 마이그레이션 완료 후에 발생.
  • 마이그레이션 푸시 전, 로컬 DB(v102 = 프로덕션)에서 psql BEGIN; … ROLLBACK;으로 SQL을 확인하세요. afresto-db-change 스킬 / 엔지니어링 함정 §4 참조.

🔴 마이그레이션 번호: 브랜치 생성 시점이 아닌, 병합 직전에 다시 확인

팀원이 당신의 브랜치가 열려 있는 동안 다른 마이그레이션 시리즈를 병합할 수 있습니다. 중복 번호는 goose가 sortAndConnectMigrations에서 패닉을 일으킵니다 — SQL 오류가 아니므로 쉽게 잘못 읽힙니다. ✅ 데이터 손상 없음: 패닉은 마이그레이션 목록을 수집할 때 발생하며, 하나도 실행되기 전입니다.

실제 사례 (2026년 8월 9일): 브랜치 생성 시 00138 선택; 병합 시 팀이 이미 00138–00140을 사용 중. 첫 번째 수정은 00140으로 이름을 바꾸었지만 여전히 충돌 — 다시 추측했기 때문. 추측하지 말고 계산하세요:

git pull                                        # 필수, 팀의 마이그레이션을 확인하기 위해
MAX=$(ls backend/migrations/ | sed 's/_.*//' | sort -n | tail -1)
NEXT=$(printf "%05d" $((10#$MAX + 1)))
ls backend/migrations/ | sed 's/_.*//' | sort | uniq -d   # 반드시 비어 있어야 함

이미 잘못되었다면: git mv로 새 번호로 변경, 작은 PR, 병합, 그런 다음 Actions 탭에서 "Migrate DB (self-hosted)" 워크플로를 다시 실행하세요.

3. Backend 푸시 (PR이 main에 병합된 후 — 규칙 #0 참조)

PR 병합 → main 업데이트 → GHCR → Watchtower ~5분 후 이미지 교체. backend 변경 없음 = Watchtower 기다릴 필요 없음. (개발 중: git push -u origin <브랜치> → PR — main에 푸시하지 마세요.)

⚠️ backend 병합 후: 502 확인 (이미 6번 재발)

api/worker 컨테이너 재생성은 자주 전체 중단을 유발합니다 — 모든 컨테이터가 Up이지만 모든 경로가 502. 실질적으로 모든 backend 병합은 주사위 던지기입니다.

curl -s -o /dev/null -w "%{http_code}\n" --max-time 15 https://next.afresto.co/api/v1/auth/me
# 401 = 정상 (인증 필요) · 502 = 중단

502인 경우 → Actions → "Ops — pulihkan API" → restart-docker (SSH 없이, 1분 미만 복구). 새로 배포된 기능을 분석하지 마세요: 502 + 컨테이너 Up = Docker 데몬 재시작. 세부 사항: docs/ops-auto-pulih.md.

4. WEB 배포 — 깨끗한 WORKTREE에서 (여러 agent tab 때문에)

npm run build는 전체 working tree를 읽습니다 → 다른 세션의 작업 중인 파일도 함께 게시됩니다. 따라서 당신의 커밋에서 worktree로 빌드 및 배포:

rm -rf /c/wtd 2>/dev/null; git worktree add --detach /c/wtd <SHA-커밋>
cd /c/wtd && git status --short   # 반드시 비어 있어야 함 (깨끗)
cd /c/wtd/web && npm ci && npm run build
cd /c/wtd/cloudflare/web-worker && npx wrangler deploy
cd <repo-projectTwo> && git worktree remove --force /c/wtd && git worktree prune
  • 🔥 Worktree 경로는 반드시 짧아야 (/c/wtd) — 긴 PDF 이름의 DKI 학사 일정 파일과 결합되어 Windows에서 "Filename too long" 오류 발생.
  • 필요한 경우 번들이 올바른지 확인: grep -rl "<기능 표식>" /c/wtd/web/dist/assets/*.js — 하지만 거짓 양성 주의 (일반 표식은 다른 기능에서도 나올 수 있음).
  • Cloudflare는 자산을 ~20초 캐시 → Ctrl+F5로 테스트. (200이지만 크기가 ~397바이트면 fallback, 실제 자산 아님.)

5. Mobile OTA — 별도 저장소, --environment 필수

cd "<repo-mobile>"
npx eas update --branch preview --environment preview --message "…" --non-interactive
  • 🔥 --environment 필수 (preview/production) — 없으면 잘못된 환경 업데이트/실패.
  • 모바일 저장소 GitHub 리모트 있음 (Afresto-Next/afresto-next-mobile) → 규칙 #0 적용: 브랜치 → PR → 병합, main에 직접 푸시 금지.
  • OTA는 JS/자산만 변경. 네이티브 모듈 변경 → OTA로 CRASH → 다시 빌드 필수.

🔥 eas update는 WORKING TREE를 보내며, 커밋이 아님

4단계의 npm run build와 정확히 동일 — 전체 작업 디렉터리가 번들에 포함되며, 다른 세션의 작업 중인 파일도 포함됩니다. 게시 결과의 Commit <hash>* 줄에서 * = 더티 트리.

게시 직전에 반드시 출력하고, 결과의 Commit 줄과 일치시켜 확인:

echo "다음에서 게시: $(git branch --show-current) @ $(git log --oneline -1 --format=%h)"
git log --oneline HEAD..origin/main   # 비어 있음 = 다른 사람의 작업이 남아 있지 않음
  • main에서 게시하세요, 기능 브랜치가 아닌. 브랜치에서 게시하면, 이미 병합된 다른 사람의 작업이 채널에서 사라집니다 — OTA는 전체 번들을 교체하며, 패치하지 않습니다.
  • 프로덕션: 먼저 다른 세션의 커밋되지 않은 변경 사항을 치워두세요 (git stash push -- <파일> … git stash pop). 프로덕션은 검토되지 않은 코드를 포함해서는 안 됩니다.

🚨 작업 중간에 브랜치가 변경될 수 있음 (여러 agent tab, 하나의 클론)

2026년 8월 20일 사건: preview 게시는 분명히 main @ d0938fa에서; 다음 명령어는 HEAD가 이미 다른 브랜치 @ 8126a50에 있음을 발견 — 같은 클론에서 다른 세션이 브랜치를 전환한 것. 프로덕션에서 3개 PR의 코드를 더 오래 실행했으며, 단 하나의 오류도 없이.

➡️ 브랜치가 명령어 사이에 동일하다고 가정하지 마세요. 매번 다시 출력하세요 (위 명령어). ➡️ 진정한 예방: OTA를 WORKTREE에서 수행, 4단계의 웹 배포와 동일하게 — 다른 세션이 브랜치를 변경할 수 없습니다:

rm -rf /c/wtm 2>/dev/null; git worktree add --detach /c/wtm <SHA-커밋>
cd /c/wtm && git status --short          # 반드시 비어 있어야 함
npm ci && npx eas update --branch <ch> --environment <env> --message "…" --non-interactive
cd <repo-mobile> && git worktree remove --force /c/wtm && git worktree prune

🪤 Worktree 경로 짧게 (/c/wtm) — 4단계와 같은 이유. 🪤 worktree에서 npm ci는 시간이 걸림; 작은 OTA의 경우 메인 클론에서 그대로 진행해도 되지만 위의 브랜치+커밋 확인을 실행하고 결과가 일치하는지 확인하세요.

🏢 GHCR 및 GitHub 조직 — 저장소가 이제 Afresto-Next 조직에 있음

저장소 (next, afresto-next-mobile, afresto-claude-skills)가 ristology/* → Afresto-Next 조직으로 이전됨 (2026년 7월 20일). 전체 전송 런북: docs/runbook-transfer-next-org.md.

  • 🚨 저장소 조직 이전 = GHCR 경로가 소유자를 따라감 → Watchtower가 조용히 멈춤. CI는 이미지에 ghcr.io/${GITHUB_REPOSITORY_OWNER}/afresto-next-{api,web,migrate} 태그를 붙임 — 소유자는 저장소 소유자를 따릅니다. 설정이 변경되지 않으면 Watchtower는 이전 경로를 계속 폴링하다가 새 이미지를 찾지 못합니다.
  • 수정: docker-compose.yml에서 image: 값을 업데이트하고, docker compose pull을 다시 실행하세요. 그런 다음 Watchtower를 재시작: ( docker restart watchtower 또는 SIGHUP 시그널).
  • 🚨 GITHUB_REPOSITORY_OWNER는 조직 이전 후에도 CI 내에서 자동으로 업데이트됩니다. 수동으로 수정할 필요 없음. 단, 이전 경로를 수동으로 덮어쓰지 않았는지 확인하세요.
  • 개인 저장소가 있는 경우 (ristology/*) 동기화되지 않으며 작업이 게시되지 않습니다.

배포 확인 목록

  • backend 배포 후 502 체크 수행
  • 웹: worktree가 깨끗하고 빌드가 올바른 커밋에서 생성되었는지 확인
  • mobile: 올바른 브랜치/커밋에서 게시, --environment 포함
  • 마이그레이션: 번호가 고유하고 TIP 커밋에 포함되어 있는지 확인
  • Git 작업: 선택적 스테이징, 변경 사항 확인, 다른 세션의 파일 손상 없음
  • 모든 규칙 준수 (#00, #0, #1)

관련 스킬