PR 분류
목표
열린 PR의 백로그를 단일 집중 세션에서 병합 가능한 세트로 전환합니다. 추적 가능하고 재개 가능한 계획(<VERSION>_PR_TRIAGE.md)을 생성한 다음, 이를 실행합니다. 필요한 경우 리베이스하고, 격리 안전 배치로 병합하고, 병합 후 후속 조치를 적용하며, 대체되거나 부분적으로 적용 가능한 PR을 작성자에게 크레딧을 주며 종료합니다.
이 스킬은 draft-release-notes 및 release-bump와 함께 사용됩니다. 먼저 분류하고, 새로운 main에 대해 초안 노트를 작성한 다음, 릴리스를 진행합니다.
사용 시기
- 10개 이상의 열린 PR이 누적된 마이너 또는 메이저 릴리스 전
- 병합을 차단하지 않으면서 무엇이 릴리스되는지에 대한 내러티브를 유지하려는 경우
- 모든 PR을 깊이 검토할 수는 없지만 중요한 부분을 빠르게 병합해야 하는 경우
전제 조건
ghCLI가 저장소에 인증되어 있어야 함- PR 검토 전용 작업 트리(main 작업 트리를 기여자 브랜치 체크아웃으로 오염시키지 않음)
- 대상 버전에 대한 명확성 — 분류 문서는 버전 이름을 따서 명명됨 (예:
0.4.0_PR_TRIAGE.md)
워크플로
1. 격리된 PR 검토 작업 트리 설정
git worktree list # 오래된 작업 트리가 있는지 먼저 확인
git worktree prune
git worktree add ../voicebox-pr-review -b pr-review-<VERSION> main
주 작업 트리는 릴리스 준비 작업(체인지로그 초안, main에 직접 후속 조치)에 사용합니다. 검토 작업 트리는 gh pr checkout에 사용합니다. 각 체크아웃은 HEAD를 기여자 브랜치로 이동시키는데, 이는 주 작업 트리에서 수행하고 싶지 않은 작업입니다.
2. 모든 열린 PR에 대한 메타데이터 수집
gh pr list --state open --limit 50 --json \
number,title,author,isDraft,mergeable,mergeStateStatus,files,additions,deletions,reviewDecision,statusCheckRollup,maintainerCanModify \
--jq '.[] | {num: .number, title, author: .author.login, mergeable, state: .mergeStateStatus, canModify: .maintainerCanModify, changes: "+\(.additions)/-\(.deletions)", files: [.files[].path]}'
각 PR에 대해 다음 정보가 필요합니다:
- 크기 (
+추가/-삭제) - 병합 가능 상태 (
CLEAN,UNSTABLE,DIRTY= 충돌,UNKNOWN= GitHub에서 아직 계산 중) - 브랜치에 유지보수자 편집이 허용되는지 여부 (나중에 작성자를 위해 리베이스할 때 필요)
- 변경된 파일 경로 (PR 간 중복을 찾는 데 도움)
UNKNOWN은 main에 푸시 직후에 흔히 발생합니다. 그냥 병합을 시도하고 확인하세요.
3. 계층으로 분류
각 PR을 정확히 하나의 버킷에 정렬합니다:
계층 1 — 병합: 작고, 병합 가능하며, 실제 버그를 수정하고, CI가 깨끗하며, 검토 비용이 낮습니다. 한 줄짜리, 의존성 완화, 대상 안전 강화. 쉬운 승리입니다.
계층 2 — 후보, 검토 필요: 중간 크기(50-200줄), 더 넓은 표면적을 다루며, 타당해 보이지만 자세히 읽어야 합니다. 제품 방향에 맞는 새로운 사용자 대상 기능.
대체: 수정 또는 기능이 이미 병합된 내용으로 해결되었습니다. 대체 PR을 가리키는 설명과 함께 종료합니다. 신중하게 확인하십시오. "비슷한 제목"은 증거가 아닙니다. 실제 diff를 비교하십시오.
다음 릴리스로 연기: 큰 기능, 더티 충돌, 드래프트 PR, 릴리스 파이프라인에 위험을 초래할 수 있는 모든 것. 속도전에서 병합하지 마십시오. 집중적인 주의가 필요합니다.
4. 분류 문서 작성
PR 검토 작업 트리 루트에 <VERSION>_PR_TRIAGE.md를 생성합니다. 구조:
# <Repo> <VERSION> — PR 분류
<VERSION>에 어떤 열린 PR이 병합되는지 추적하기 위한 작업 문서. 릴리스가 끝나면 삭제.
마지막 업데이트: <DATE>
## 진행 상황
**계층 1: 0 / N 병합됨**
**계층 2: 0 / M 처리됨**
**대체 분류: 보류 중**
---
## <VERSION>에 병합 — 중요 버그 수정
| PR | 상태 | 크기 | 수정 내용 | 필수 이유 |
|---|---|---|---|---|
| [#123](url) | [ ] | +5/-0 | ... | ... |
## 강력 후보 — 빠른 검토 필요
| PR | 상태 | 크기 | 요약 |
|---|---|---|---|
## 대체로 종료
| PR | 상태 | 이유 |
|---|---|---|
## <NEXT_VERSION>로 연기
- [#xxx](url) ... — 이유
---
## 공격 순서
1. 대체 PR 종료 (한 줄 설명)
2. 의존성 없는 배치로 계층 1 병합 — 파일 경로가 겹치지 않는지 확인
3. 계층 2를 개별적으로 검토
4. 모든 내용을 반영하기 위해 `draft-release-notes` 재실행
5. `release-bump` 실행
진행 상황 헤더가 가장 중요한 부분입니다. 점수판 역할을 하며 세션이 중단되어도 깔끔하게 재개할 수 있습니다.
5. PR별 루프 실행
계층 1 / 계층 2 목록의 각 PR에 대해:
a. 검토 작업 트리에서 체크아웃:
cd ../voicebox-pr-review
git checkout pr-review-<VERSION> # 중립 베이스로 리셋
gh pr checkout <N>
b. main..HEAD가 아닌 실제 커밋 읽기:
git show HEAD # PR의 실제 변경사항
git show --stat HEAD # 변경된 파일 + 줄 수
PR 브랜치가 main보다 오래된 경우 git diff main..HEAD로 검토하지 마십시오. 해당 diff에는 PR이 포크된 후 main에 적용된 모든 커밋이 - (삭제) 줄로 포함됩니다. 3줄짜리 PR이 700줄짜리 되돌리기처럼 보일 수 있습니다. 이는 PR을 잘못 판단하는 가장 쉬운 방법입니다.
c. 우려 사항 평가: 정확성, 범위, 이미 병합된 작업과의 상호 작용, 버전 호환성 (예: 아직 고정하지 않은 종속성 버전이 필요한 API를 사용할 수 없음).
d. 브랜치가 main보다 뒤쳐져 있으면 리베이스:
git fetch origin main
git rebase origin/main
이는 squash 병합 전에 필수적입니다. GitHub의 squash는 diff(PR-head, merge-base)를 계산합니다. 오래된 브랜치에서는 해당 diff에 모든 중간 커밋을 되돌리는 내용이 포함됩니다. 리베이스는 병합 베이스를 앞으로 이동시켜 squash가 깔끔해지도록 합니다.
e. 유지보수자 편집이 허용되는 경우, 리베이스된 브랜치를 기여자 포크로 푸시:
git remote add <author> https://github.com/<author>/<repo>.git
git fetch <author> <branch> # 먼저 그들의 ref를 가져옴
git push <author> HEAD:<branch> --force-with-lease
이렇게 하면 GitHub의 PR UI가 리베이스된 상태와 동기화되고 GitHub 측에서 병합이 깔끔해집니다.
f. 병합:
gh pr merge <N> --squash
g. 분류 문서 업데이트 — 체크박스를 ✅ merged <sha>로 변경합니다 (gh pr view <N> --json mergeCommit --jq '.mergeCommit.oid[0:7]'의 짧은 SHA 사용). 진행 상황 헤더를 업데이트합니다.
6. 소소한 수정 사항 배치 처리
≤5줄 변경, 깨끗한 CI, 겹치지 않는 파일 경로, 명백히 올바른 의도(예: 한 줄 의존성 완화, 환경 변수 추가, 임포트 경로 수정)의 PR은 PR별 검토 절차 없이 단일 루프에서 병합할 수 있습니다:
for pr in 425 384 416 429; do
echo "=== PR $pr 병합 중 ==="
gh pr merge $pr --squash
done
이후 각 PR이 깔끔하게 병합되었는지 확인합니다:
for pr in 425 384 416 429; do
gh pr view $pr --json state,mergeCommit --jq "{pr: $pr, state, sha: .mergeCommit.oid[0:7]}"
done
7. 병합 후 후속 조치
때로는 알려진 사소한 문제(예: 불완전한 dtype 맵, 오래된 센티널 정리)가 있음에도 PR을 병합할 가치가 있습니다. 병합을 차단하지 말고, 바로 일반 브랜치 + PR로 후속 조치를 적용하십시오:
cd <main-worktree>
git pull --ff-only origin main
git checkout -b fix/<short-name>
# 편집...
git commit -m "fix(<area>): <한 줄 설명>"
git push -u origin fix/<short-name>
gh pr create --title "..." --body "#<N>에 대한 후속 조치. ..."
두 SHA를 모두 분류 문서에 기록합니다 (✅ merged <pr-sha> + follow-up <pr>).
main에 직접 푸시 예외: 명시적이고 범위가 지정된 정책(예: "릴리스 속도전") 하에서만 허용됩니다. 기본값으로 사용하지 마십시오.
8. 대체: 크레딧을 언급하는 설명과 함께 종료
gh pr close <N> --comment "종료합니다 — 병합된 #<M>으로 대체되었습니다. <간단한 설명>. 감사합니다!"
먼저 diff를 확인하십시오. "비슷한 제목"만으로는 충분하지 않습니다. PR이 부분적으로 대체된 경우(진단은 맞지만 변경 사항의 절반만 여전히 필요한 경우), 대신 부분 적용을 수행하십시오.
9. 부분 적용 패턴
PR에 가치 있는 변경 사항과 문제가 있는 변경 사항이 함께 포함된 경우:
cd <main-worktree>
git pull --ff-only origin main
# PR 브랜치에서 특정 파일 체리픽
git checkout <pr-commit-sha> -- <file1> <file2>
# 스테이징된 변경 사항 검토, 필요에 따라 조정
git diff --cached
# 대량으로 바꾸고 싶지 않은 파일에 외과적 편집 적용
# (예: PR의 파일이 보존해야 하는 최근 main 커밋보다 오래된 경우)
# 원저작자에게 크레딧을 주는 트레일러로 커밋
git commit -m "$(cat <<'EOF'
<제목>
<무엇을 유지하고 무엇을 삭제했는지 설명하는 본문>
Co-Authored-By: <author> <[email protected]>
EOF
)"
git push ... # 브랜치 + PR (main에 직접 푸시 예외가 아닌 경우)
그런 다음 무엇이 적용되고 무엇이 삭제되었는지 설명하고 커밋 SHA를 참조하는 설명과 함께 PR을 종료합니다.
10. 문서를 최신 상태로 유지
모든 병합, 모든 종료, 모든 후속 조치 → <VERSION>_PR_TRIAGE.md를 업데이트합니다. 문서는 세션 로그입니다. 중단되었다가 내일 재개하면 문서가 "내가 어디까지 했지?"에 대한 유일한 진실 공급원입니다.
11. 분류가 완료되면
- 문서의 모든 PR에 최종 상태가 있습니다 (✅ merged / ✅ closed / deferred)
- 진행 상황 헤더는 각 계층에 대해 N/N을 표시합니다.
- 다음에 실행할 스킬은
draft-release-notes(새로운 main에 대해[Unreleased]를 재생성), 그 다음release-bump입니다.
릴리스가 출시된 후 분류 문서를 삭제하거나 버전 기록에 보관할 수 있습니다.
주의사항
- 오래된 브랜치의
main..HEAD는 거짓말을 합니다. 브랜치가 분기된 이후 main에 추가된 모든 것을 삭제로 표시합니다. PR의 실제 커밋은 항상git show HEAD로 검토하십시오. - 리베이스되지 않은 브랜치를 Squash 병합하면 중간 작업이 되돌려집니다. Squash는
diff(PR-head, merge-base)를 계산합니다. 리베이스는 병합 베이스를 앞으로 이동시킵니다. mergeable=UNKNOWN은 일시적입니다. GitHub가 푸시 후 다시 계산하고 있습니다. 그냥 병합을 시도해 보십시오.- 라우트 순서가 중요합니다 (FastAPI 및 유사한 경우):
DELETE /history/failed는DELETE /history/{id}이전에 등록되어야 합니다. 그렇지 않으면 매개변수화된 경로가"failed"를 ID로 소비합니다. - Apple의
-weak_framework는 동일한 프레임워크에 대해 순서에 관계없이-framework를 재정의합니다. 종속성이 선택 사항을 하드 링크하는 경우cargo:rustc-link-arg=-Wl,-weak_framework,Name을 통해 사용하십시오. - 종속성 버전 최소값은 적용할 수 있는 것을 제한합니다.
torch_dtype=→dtype=와 같은 kwarg 이름 변경을 수락하기 전에 최소 버전 핀이 이를 지원하는지 확인하십시오. 때로는 PR의 절반만 체리픽하는 것이 올바른 방법입니다. cpal::Stream및 유사한!Send오디오 타입은await지점이나spawn_blocking을 교차할 수 없습니다. 때로는 "이상적이지는 않지만 올바른" 동기 대기가 가능한 최상의 수정입니다. 플래그를 지정하되 차단하지 마십시오.- PyTorch nightly 빌드는 릴리스에 사용할 수 없습니다. 비결정적이며 실행 간에 회귀할 수 있습니다. PR이 GPU 문제를 해결하기 위해 nightly로 전환할 것을 제안하는 경우,
TORCH_CUDA_ARCH_LIST=...+PTX를 선호하거나 안정적인 지원을 기다리십시오.
정식 명령어 참조
# 대량 PR 메타데이터
gh pr list --state open --limit 50 --json number,title,author,mergeable,mergeStateStatus,additions,deletions,maintainerCanModify,files
# 단일 PR 상세 보기
gh pr view <N> --json body,author,headRefName,baseRefName,mergeable,maintainerCanModify,files,statusCheckRollup
# 브랜치-vs-main diff가 아닌 실제 커밋
git show HEAD
git show --stat HEAD
gh pr diff <N>
# 기여자 브랜치를 현재 main으로 리베이스
git fetch origin main && git rebase origin/main
# 리베이스를 기여자 포크로 푸시 (maintainerCanModify=true 필요)
git remote add <author> https://github.com/<author>/<repo>.git
git fetch <author> <branch>
git push <author> HEAD:<branch> --force-with-lease
# 병합
gh pr merge <N> --squash
# 분류 문서용 병합 SHA 확인
gh pr view <N> --json state,mergeCommit --jq '{state, sha: .mergeCommit.oid[0:7]}'
# 대체 종료
gh pr close <N> --comment "종료합니다 — 병합된 #<M>으로 대체되었습니다. 감사합니다!"
참고 사항
- 오래된 브랜치를
main..HEAD로 검토하지 마십시오. 이 스킬에서 가장 중요한 한 줄입니다. - 분류 문서는 세션 상태입니다. 문서를 잃으면 세션을 잃습니다. 모든 작업 후에 업데이트하십시오.
- 부분 적용에서도 기여자에게 크레딧을 제공하십시오.
Co-Authored-By:트레일러와 적용된 커밋에 연결되는 종료 설명을 사용하십시오. - 완벽함이 출시를 막지 못하게 하십시오. "깨짐"에서 "약간 알려진 문제가 있지만 작동함"으로 바뀌는 수정은 엄격한 개선입니다. 문제에 플래그를 지정하고, 후속 조치를 등록하고, 수정 사항을 병합하십시오.