cd ~/gh-pm-lab source pm-lab/env.sh
1범위 변경 요청 기록
상황: Sprint 1 사흘째, 마케팅 담당이 “신청이 어느 광고에서 왔는지 알 수 있게 유입 경로(UTM 값)를 함께 저장해 달라”고 요청했다. 요청을 받으면 먼저 스프린트 목표와 대조한다.
| 선택지 | Sprint 1 목표에 대한 영향 | M1 마감에 대한 영향 |
|---|---|---|
| Sprint 1에 추가 | LP-03 담당이 같은 저장 로직을 고쳐야 한다. LP-03이 이미 막혀 있어 목표일(10-23)을 넘길 가능성이 커진다. | LP-03이 밀리면 LP-05(QA)도 밀린다. |
| Sprint 2에 추가 | 없음 | Sprint 2의 QA 범위가 늘어난다. |
| M2 공개 런칭으로 배정 | 없음 | 없음. 유입 경로 비교는 광고를 시작하는 공개 런칭 이후에 쓰인다. |
이 실습에서는 M2로 배정하고 Priority를 P2로 둔다. 요청 자체는 업무로 등록해 두어야 다음 계획 회의에서 다시 찾을 수 있다.
cat > pm-lab/07-utm.md <<'EOF'
## 배경
마케팅 담당이 사전신청의 유입 경로를 광고 채널별로 비교하려고 한다.
## 완료 조건
- [ ] 신청 시 주소의 utm_source, utm_medium, utm_campaign 값을 함께 저장한다.
- [ ] UTM 값이 없는 신청도 저장된다.
- [ ] 채널별 신청 수를 확인하는 방법이 문서로 있다.
## 검증 근거
UTM 값이 있는 신청과 없는 신청 각 1건의 저장 결과를 댓글로 남긴다.
## 범위 제외
광고 비용 연동과 대시보드는 포함하지 않는다.
EOF
PM_UTM_URL=$(gh issue create -R "$PM_REPO" \
--title "[LP-07] 신청의 유입 경로를 기록한다" \
--body-file pm-lab/07-utm.md \
--label "kind:task,area:engineering" \
--milestone "M2 공개 런칭")
gh project item-add "$PM_PROJECT" --owner "$PM_OWNER" --url "$PM_UTM_URL"
gh project item-edit "$PM_PROJECT" --owner "$PM_OWNER" \
--url "$PM_UTM_URL" --field Priority --value P2
echo "PM_UTM_URL=\"$PM_UTM_URL\"" >> pm-lab/env.sh
printf 'LP-07\t%s\t%s\tcreated\n' "${PM_UTM_URL##*/}" "$PM_UTM_URL" >> pm-lab/backlog-map.tsv
담당자는 아직 정하지 않았다. 다음 주 사전 리포트의 “담당자가 없는 열린 업무”에 LP-07이 나타나므로 계획 회의에서 다시 다루게 된다.
gh issue comment "$PM_SPRINT_URL" --body-file - <<EOF **범위 변경 기록** - 일시: 2026-10-14 15:00 - 추가·제외 업무: $PM_UTM_URL (LP-07) 요청 접수. Sprint 1에 넣지 않고 M2 공개 런칭에 배정했다. - 변경 이유: Sprint 1 목표(신청 최소 흐름 검증)에 필요하지 않다. 유입 경로 비교는 광고를 시작하는 공개 런칭 이후에 쓰인다. - 목표·일정 영향: Sprint 1과 M1 마감에 영향 없음. - 합의자: PM, 마케팅 담당, 개발 담당 EOF
2PR 병합과 LP-01 완료 확인
LP-01의 정책 문서를 검토해 완료 조건 네 개를 확인했다고 가정한다. 팀에서는 검수자가 gh pr review 번호 --approve로 승인한다. 실습에서는 본인이 만든 PR이므로 본인이 승인할 수 없고, 승인 단계 없이 병합한다.
gh pr merge "$PM_PR_URL" --squash gh issue view "$PM_POLICY_URL" --json state,stateReason --jq '"\(.state) \(.stateReason)"'
✓ Squashed and merged pull request gildong/launchpad-pm-lab#7 ([LP-01] 사전신청 정책 문서) CLOSED COMPLETED
PR 본문의 Closes #1 때문에 병합과 동시에 LP-01이 닫혔다. --squash는 PR의 커밋을 하나로 합쳐 기본 브랜치에 반영한다. 작업 브랜치는 GitHub에 남아 있으며, PR 화면의 Delete branch 버튼으로 지울 수 있다.
Status는 따로 확인
이슈가 닫혀도 Project의 Status가 Done으로 바뀌었는지는 Project의 워크플로(Workflows) 설정에 달려 있다. 새 Project는 “Item closed”일 때 Done으로 바꾸는 기본 워크플로가 켜진 상태로 만들어지지만, 설정을 바꾼 Project도 있으므로 값을 확인한다.
gh project item-list "$PM_PROJECT" --owner "$PM_OWNER" --limit 100 --format json \ --jq ".items[] | select(.content.url == \"$PM_POLICY_URL\") | .status"
Done
결과가 Done이 아니면 직접 바꾼다.
gh project item-edit "$PM_PROJECT" --owner "$PM_OWNER" \ --url "$PM_POLICY_URL" --field Status --value Done
검증 근거와 막힘 해소
gh issue comment "$PM_POLICY_URL" \
--body "검수 완료: 완료 조건 4개를 docs/signup-policy.md (PR #${PM_PR_URL##*/})에서 확인했다. 결정 기록은 $PM_SPRINT_URL 에 있다."
gh issue edit "$PM_FORM_URL" --remove-label blocked
gh issue comment "$PM_FORM_URL" \
--body "막힘 해소: LP-01 정책 확정(재신청 시 기존 신청 유지). 저장 로직 작업을 시작할 수 있다."
LP-01 본문의 체크박스는 웹 화면에서 하나씩 눌러 체크한다. LP-01이 닫혀도 LP-03의 blocked 라벨은 그대로 남아 있으므로 직접 지운다. 의존성(Blocked by #1)은 추적 근거로 남겨 둔다.
LP-06(베타 초대 준비)처럼 코드 변경이 없는 업무는 직접 닫는다. 완료와 취소는 닫는 이유로 구분한다. 단순 Closed 개수에는 취소된 업무도 들어가므로, 출시 진행률을 닫힌 이슈 수 하나로 판단하지 않는다.
# 완료 조건을 충족했을 때 gh issue close 6 -R "$PM_REPO" --reason completed \ --comment "초대 대상 100명, 발송 담당, 문의 경로를 정했다. 근거는 위 댓글에 있다." # 하지 않기로 했을 때 gh issue close 6 -R "$PM_REPO" --reason "not planned" \ --comment "공개 런칭과 합쳐 진행하기로 결정했다. 결정 기록: 운영 이슈 주소"
위 두 명령은 예시이며 지금 실행하지 않는다.
3종료 기록과 비교
스프린트 종료 회의 직후, 업무를 다음 스프린트로 옮기기 전에 종료 기록을 저장한다. 시작 기록과 같은 조건으로 조회해야 비교할 수 있다.
gh project item-list "$PM_PROJECT" --owner "$PM_OWNER" --limit 100 \ --query 'iteration:"Sprint 1" label:"kind:task"' --format json \ > pm-lab/sprint-1-end.json bash github-pm-starter-kit/sprint-report.sh \ pm-lab/sprint-1-start.json pm-lab/sprint-1-end.json "Sprint 1" \ > pm-lab/sprint-1-report.md cat pm-lab/sprint-1-report.md
# Sprint 1 종료 비교 | 구분 | 건수 | | - | - | | 시작 시 약속 | 3 | | 약속 중 완료 | 1 | | 약속 중 미완료(이월 후보) | 2 | | 약속 중 종료 기록에 없음 | 0 | | 중간 추가 | 0 | | 중간 추가 중 완료 | 0 | 요약 문장: 처음 약속한 3개 중 1개 완료, 중간 추가 0개 중 0개 완료, 미완료 2개. ## 약속 중 완료 - [[LP-01] 사전신청 정책을 확정한다](https://github.com/gildong/launchpad-pm-lab/issues/1) ## 이월 후보 (약속 중 미완료) - [[LP-02] 랜딩 페이지에서 서비스 안내와 신청 버튼을 제공한다](...) · Status In Progress - [[LP-03] 방문자가 이메일로 사전신청을 완료한다](...) · Status Todo ...
비교의 기준은 이슈 주소다. 제목이 바뀌어도 같은 업무로 센다. 범위 변경 요청 LP-07은 Sprint 1에 넣지 않았으므로 “중간 추가”에 들어가지 않는다. Sprint 1에 넣기로 결정했다면 “중간 추가 1”로 따로 세어지고, 처음 약속한 3개의 분모는 바뀌지 않는다. Status가 Done인 항목만 완료로 센다.
보고서 끝의 “기록할 것” 세 줄을 편집기로 채운 뒤 운영 이슈에 올린다.
gh issue comment "$PM_SPRINT_URL" --body-file pm-lab/sprint-1-report.md
스토리 포인트로 추정하는 팀은 시작 기록에 그 시점의 추정치를 함께 남긴다. 종료 때 추정치를 고치면 시작 시 약속한 양과 비교할 수 없다. 서로 다른 팀의 포인트는 추정 기준이 다르므로 생산성 순위를 매기는 데 쓰지 않는다.
4미완료 업무 이월
종료 기록을 저장한 뒤에 Iteration을 옮긴다. 순서를 바꾸면 업무가 Sprint 1 조회에서 빠져, 종료 기록에 “처음부터 없던 업무”처럼 남는다.
- 웹의 Project 표 화면에서 LP-02와 LP-03의 Iteration을 Sprint 2로 바꾼다.
- 출시 목표가 같으므로 마일스톤은 M1 그대로 둔다. 출시 범위까지 바뀌었다면 마일스톤 변경 이유도 운영 이슈에 기록한다.
- 이월한 업무마다 이유를 댓글로 남긴다.
for key in LP-02 LP-03; do
url=$(awk -F'\t' -v k="$key" '$1 == k {print $3}' pm-lab/backlog-map.tsv)
gh issue comment "$url" \
--body "Sprint 1 종료 시 미완료로 Sprint 2에 이월한다. 이유와 다음 조치는 Sprint 1 종료 기록에 있다: $PM_SPRINT_URL"
done
for key in LP-02 LP-03; do ... done은 같은 명령을 두 업무에 차례로 실행한다. -v k="$key"는 셸 변수를 awk에 넘긴다.
운영 이슈 마무리
gh issue unpin "$PM_SPRINT_URL" gh issue close "$PM_SPRINT_URL" --reason completed \ --comment "Sprint 1 종료 기록을 남겼다. 이월 업무는 Sprint 2 운영 이슈의 시작 범위에 포함한다."
- LP-07이 M2 공개 런칭, Priority P2로 등록되고 운영 이슈에 범위 변경 기록이 있다.
- PR이 병합되었고 LP-01이 CLOSED COMPLETED, Status Done이다.
- LP-03의
blocked라벨이 지워지고 막힘 해소 댓글이 있다. - 운영 이슈에 종료 비교 보고가 올라갔고, “처음 약속한 3개 중 1개 완료”가 보인다.
- LP-02와 LP-03이 Sprint 2로 옮겨졌고 각각 이월 댓글이 있다.
- 종료 기록을 이월보다 먼저 저장하는 이유를 말할 수 있다.
막히면
| 증상 | 확인과 해결 |
|---|---|
gh pr merge가 병합 방법을 물음 | --squash를 빠뜨렸다. 질문에서 Squash and merge를 고르거나 Ctrl+C 후 다시 실행한다. |
| 병합 후에도 LP-01이 OPEN | PR 본문에 Closes #1이 없거나 번호가 다르다. gh pr view "$PM_PR_URL" --json closingIssuesReferences로 확인하고, 이슈를 gh issue close로 직접 닫는다. |
jq가 필요하다 | 수업 전 준비 3단계의 jq 설치 명령을 실행한다. 설치할 수 없으면 두 JSON 파일을 웹 화면의 Sprint 1 보드와 대조해 표를 직접 채운다. |
| 종료 비교에서 “종료 기록에 없음”이 생김 | 종료 기록 저장 전에 Iteration을 옮겼다. Iteration을 Sprint 1로 되돌리고 종료 기록을 다시 저장한 뒤 이월한다. |
for 블록 실행 후 아무 출력이 없음 | gh issue comment는 성공하면 댓글 주소를 출력한다. 출력이 없으면 url이 비어 있는지 cat pm-lab/backlog-map.tsv로 확인한다. |
마치며...
Sprint 1 도중의 요청은 업무 LP-07과 범위 변경 기록으로 남았고, LP-01은 PR 병합과 검증 근거 댓글로 닫혔다. 시작 기록과 종료 기록을 이슈 주소로 비교해 “처음 약속한 3개 중 1개 완료, 2개 이월”이라는 종료 보고를 남긴 뒤 이월을 진행했다. 종합 실습에서는 지금까지의 기록 방법을 정책 변경과 추가 요청이 겹친 상황에 적용한다.