1회차실습 235–55분

마일스톤과 라벨

“11월 6일 비공개 베타”라는 목표가 회의록에만 있으면, 그 목표에 남은 업무가 몇 개인지 알려면 회의록과 이슈 목록을 번갈아 봐야 한다. 마일스톤은 출시 목표의 이름, 마감일, 열린 이슈 수, 닫힌 이슈 수를 한 곳에 묶는다. 이 실습은 출시 목표 두 개를 마일스톤으로 만들고, 업무를 분류할 라벨 일곱 개를 만든다.

진행
설명 5분, 시연 5분, 실습 10분
결과
마일스톤 2개, 라벨 7개
명령
gh api, gh label create, gh label list

새 터미널이면 먼저 변수를 불러온다.

cd ~/gh-pm-lab
source pm-lab/env.sh

1gh api로 GitHub 기능 직접 호출하기

gh에는 마일스톤 전용 명령이 없다. 대신 gh api로 GitHub의 REST API를 직접 호출한다. 주소와 필드 이름은 마일스톤 API 문서에 적힌 것을 그대로 쓴다.

부분뜻
--method POST새로 만든다. GET은 조회, PATCH는 수정이다.
"repos/$PM_REPO/milestones"요청할 주소. 실습 저장소의 마일스톤 목록을 가리킨다.
-f title="..."보낼 값. -f 하나에 필드 하나를 적는다.
--jq '...'돌려받은 JSON에서 필요한 값만 꺼낸다.

2출시 마일스톤 두 개 만들기

gh api --method POST "repos/$PM_REPO/milestones" \
  -f title="$PM_MILESTONE" \
  -f description="사전신청부터 운영 확인까지 비공개 베타 검증" \
  -f due_on="2026-11-06T08:00:00Z" \
  --jq '{number, title, due_on}'

gh api --method POST "repos/$PM_REPO/milestones" \
  -f title="M2 공개 런칭" \
  -f description="공개 안내와 운영 대응 준비 완료" \
  -f due_on="2026-11-20T08:00:00Z" \
  --jq '{number, title, due_on}'
{"due_on":"2026-11-06T08:00:00Z","number":1,"title":"M1 비공개 베타"}
{"due_on":"2026-11-20T08:00:00Z","number":2,"title":"M2 공개 런칭"}

due_on은 협정 세계시(UTC) 날짜와 시각이다. 끝의 Z가 UTC를 뜻한다. 08:00 UTC는 한국 시각 17:00이다. 2026-11-06T00:00:00Z로 적으면 한국에서는 11월 6일 09:00이지만, 미국 서부(UTC−8)에서는 11월 5일 16:00로 표시된다. 다른 시간대의 팀원이 있으면 마감 시각을 함께 정한다.

조회

gh api --method GET "repos/$PM_REPO/milestones" \
  -f state=all --paginate \
  --jq '.[] | {number, title, due_on, open_issues, closed_issues}'
{"closed_issues":0,"due_on":"2026-11-06T08:00:00Z","number":1,"open_issues":0,"title":"M1 비공개 베타"}
{"closed_issues":0,"due_on":"2026-11-20T08:00:00Z","number":2,"open_issues":0,"title":"M2 공개 런칭"}

open_issues와 closed_issues는 이 마일스톤에 붙은 이슈의 수다. 지금은 업무가 없으므로 0이다. 마일스톤 제목은 이슈를 등록할 때 그대로 입력하는 값이므로 이후에 바꾸지 않는다.

3라벨 설계: 무엇을 라벨로 두지 않는가

라벨은 업무의 종류(kind:), 담당 영역(area:), 예외 상황(blocked)만 표현한다. Priority와 Status는 라벨로 만들지 않는다.

Priority를 라벨과 Project 필드 두 곳에 두면 다음 상황이 생긴다. LP-03에 P0 라벨이 붙어 있고 Project의 Priority 필드는 P1이다. 둘 중 무엇이 맞는지는 기록만으로 알 수 없고, 누군가 한쪽을 고칠 때마다 다른 쪽도 고쳐야 한다. 이 과정은 Priority와 Status를 Project 필드 한 곳에서만 관리한다.

라벨뜻예
kind:task실행 업무LP-01 ~ LP-06
kind:meeting스프린트 운영 기록Sprint 1 운영 이슈
area:product정책과 기획LP-01 정책 확정
area:design화면과 콘텐츠LP-02 랜딩 페이지
area:engineering구현과 운영LP-03 신청 처리
area:qa검증LP-05 QA
blocked해결을 기다리는 업무정책 미확정으로 멈춘 업무

4라벨 만들기

gh label create "kind:task" -R "$PM_REPO" --color 1D76DB --description "실행 업무"
gh label create "kind:meeting" -R "$PM_REPO" --color D4C5F9 --description "스프린트 운영 기록"
gh label create "area:product" -R "$PM_REPO" --color 0E8A16 --description "정책과 기획"
gh label create "area:design" -R "$PM_REPO" --color C5DEF5 --description "화면과 콘텐츠"
gh label create "area:engineering" -R "$PM_REPO" --color 5319E7 --description "구현과 운영"
gh label create "area:qa" -R "$PM_REPO" --color FEF2C0 --description "검증"
gh label create "blocked" -R "$PM_REPO" --color B60205 --description "해결을 기다리는 업무"
✓ Label "kind:task" created in gildong/launchpad-pm-lab
✓ Label "kind:meeting" created in gildong/launchpad-pm-lab
...
✓ Label "blocked" created in gildong/launchpad-pm-lab

--color는 # 없이 쓰는 16진수 색상 코드다. 색은 팀이 바꿔도 된다.

조회

gh label list -R "$PM_REPO" --limit 100

새 저장소에는 bug, documentation, enhancement 등 기본 라벨 9개가 함께 있으므로 목록에는 16개가 보인다. 기본 라벨은 이 과정에서 쓰지 않지만 지우지 않아도 된다.

  • 마일스톤 조회 결과에 M1 비공개 베타와 M2 공개 런칭 두 줄이 있다.
  • M1의 due_on이 2026-11-06T08:00:00Z이다.
  • 라벨 목록에 kind: 2개, area: 4개, blocked 1개가 있다.
  • Priority와 Status를 라벨로 만들지 않는 이유를 한 문장으로 말할 수 있다.
막히면
증상확인과 해결
Validation Failed, already_exists (마일스톤)같은 제목의 마일스톤이 이미 있다. 조회 명령으로 확인하고 다음 단계로 넘어간다.
Validation Failed, due_on날짜 형식이 틀렸다. YYYY-MM-DDTHH:MM:SSZ 형식인지, T와 Z가 있는지 확인한다.
label with name "kind:task" already exists같은 라벨이 있다. 목록을 확인하고 넘어간다. 색과 설명을 이 교안의 값으로 맞추려면 같은 명령 끝에 --force를 붙여 다시 실행한다.
HTTP 404$PM_REPO가 비어 있거나 틀렸다. echo "$PM_REPO"로 확인하고 source pm-lab/env.sh를 실행한다.

마치며...

저장소에 출시 목표 두 개가 마감일과 함께 생겼고, 업무를 종류와 영역으로 나눌 라벨 일곱 개가 생겼다. 실습 3에서는 이 마일스톤과 라벨을 붙인 첫 업무를 작성한다.