AI Human 7기 · Git·GitHub 통합 핸드북

Git 완전정복

Git 완전정복 팀 협업 가이드, GitHub 코드리뷰 운영 가이드, GitHub 생태계 연계 도구 가이드 — 세 권을 하나로 통합했습니다. 기본 개념과 push·pull, GitHub Flow부터 PR 10단계, 코드리뷰, 충돌 해결과 에러 카탈로그, 5인 팀 협업, 배포와 오픈소스 기여, 보안까지. 수강 중에는 과제와 팀 프로젝트의 기준으로, 수료 후에는 취업 포트폴리오의 안내서로 사용하세요.

12챕터 + 부록 로드맵
10PR을 만드는 10단계
30+에러 상황별 해결 카드
12주포트폴리오 로드맵
CH.1 · Git이란 무엇인가

AI 개발자에게 Git은 선택이 아니라 필수입니다

Git은 소스 코드의 변경 이력을 추적하고 관리하는 분산 버전 관리 시스템(DVCS)입니다. 모델 코드, 프롬프트, 파이프라인 구성이 계속 변하는 AI 프로젝트에서 버전 관리 없이는 팀 협업이 불가능합니다.

🎬 실제 상황
RAG 파이프라인을 수정하던 중 "어제까지는 됐는데…" 하는 상황. 어느 코드가 문제인지, 누가 언제 바꿨는지 알 수 없어 팀 전체가 멈춥니다. 카카오톡·이메일로 코드를 주고받으면 이 상황이 매주 반복됩니다.

1-1. 버전 관리가 해결하는 것

버전 관리 없이Git 사용 후
rag_v1.py / rag_v2_final.py 파일 혼란rag_pipeline.py 하나 — 이력은 Git이 관리
프롬프트 수정 후 이전 버전 복구 불가git checkout으로 언제든 이전 프롬프트로 복귀
팀원이 같은 파일 수정 → 서로 덮어쓰기브랜치로 격리 → PR로 안전하게 병합
"누가 이 코드 바꿨어?" 추적 불가git log / git blame으로 즉시 확인
"내 코드가 왜 안 되지?" → 강사가 일일이 확인저장소 링크 하나로 환경까지 재현 가능

1-2. Git ≠ GitHub

구분정체비유
Git내 PC에서 실행되는 버전 관리 도구카메라 (기록하는 도구)
GitHubGit 저장소(repository, 줄여서 repo)를 인터넷에 호스팅하는 서비스구글 포토 (올려서 공유하는 공간)

Git은 내 컴퓨터에서 실행되므로 인터넷이 없어도 파일을 기록할 수 있습니다. GitHub는 팀과 공유할 때 사용합니다. clone으로 내려받고, push로 올리고, pull·fetch로 새 내용을 받아옵니다.

1-3. 커밋은 "사진 한 장" — Git은 사진첩입니다

커밋(commit)은 Git에서 가장 중요한 단어입니다. 커밋 하나는 그 순간 프로젝트 폴더 전체를 찍은 사진 한 장입니다. 바뀐 부분만 오려 붙인 메모가 아니라, 폴더 전체의 모습이 통째로 남습니다.

시간 → 사진 ①첫 커밋 사진 ②표 추가 사진 ③오타 수정 main ▸ 최신
커밋할 때마다 사진첩에 사진이 한 장씩 쌓입니다. 어느 사진으로든 통째로 되돌아갈 수 있습니다.

이 사진첩 덕분에 두 가지가 가능해집니다. 하나, 언제든 이전 사진으로 통째로 되돌아갈 수 있습니다 — 파일을 일일이 고치는 게 아니라 사진 한 장을 다시 펼치는 일입니다. 둘, 사진마다 찍은 사람·시각·메모가 함께 남습니다 — 그래서 커밋할 때 메시지를 쓰고, git log로 "누가 언제 왜"를 추적할 수 있습니다.

30초 확인
Q. Git은 파일에서 "바뀐 부분만" 저장할까요?
답 보기

아니요. 커밋 한 번은 그 순간의 프로젝트 전체 사진(스냅샷)입니다. 그래서 "3일 전 상태로 되돌리기"가 무섭지 않습니다 — 사진첩에서 그날 사진을 펼치면 됩니다. 용량 걱정도 없습니다 — 안 바뀐 파일은 새로 저장하지 않고 같은 덩어리를 다시 가리키기만 해서, 사진 100장을 찍어도 저장 공간은 바뀐 만큼만 늘어납니다.

1-4. 4개의 공간 — 사진 한 장이 팀에 닿기까지 거치는 네 자리

Git에서는 같은 파일의 여러 버전이 각 공간에 따로 기록됩니다. 명령어는 한 공간의 내용을 다른 공간에 기록하는 방법입니다.

① 작업 폴더파일을 고치는 곳
git add
② 스테이지커밋 대기석
git commit
③ 로컬 저장소내 PC의 기록
git pushgit clone·pull
④ 원격 (GitHub)팀과 공유되는 기록
① 작업 폴더파일을 실제로 고치는 곳 — 지금 편집기로 보고 있는 그 폴더
② 스테이지커밋 대기석 — "이번 스냅샷에 담을 파일"을 고르는 곳
③ 로컬 저장소내 PC의 기록 창고(.git) — 커밋이 쌓이는 곳
④ 원격(GitHub)팀과 공유되는 기록 — push해야 비로소 올라감

핵심은 세 단계입니다. git add는 이번 변경을 고르고, git commit은 그 상태를 내 컴퓨터에 기록하고, git push는 기록을 GitHub에 올립니다. 반대로 git clonegit pull은 GitHub의 내용을 내 컴퓨터로 가져옵니다. 에러가 나면 먼저 "내 변경은 지금 어느 단계에 있지?"라고 확인하세요.

스테이지는 왜 있나
기능 작업과 버그 수정을 동시에 했다면, 이번 커밋에 넣을 파일만 먼저 고릅니다. 사진첩으로 말하면 스테이지 = 이번 사진에 담을 것 고르기, commit = 셔터 누르기, push = 사진첩을 팀과 공유하기입니다. commit까지는 내 컴퓨터 안에만 기록됩니다. 참고로 "add한 뒤 또 고쳤을 때" 헷갈리는 건 여러분 잘못이 아닙니다 — 경력 개발자 실험에서도 18%가 이 함정에서 틀렸습니다(MIT 연구, 2016).
30초 확인
Q. commit까지 했으면 팀원이 내 코드를 볼 수 있을까요?
답 보기

아직 못 봅니다. commit은 ③ 내 PC의 기록까지입니다. ④ GitHub에 올리는 것은 push — 그래서 "커밋했는데 GitHub에 안 보여요"의 답은 거의 항상 "push를 안 했어요"입니다.

1-5. 명령 지도 — 손에 익힐 7개 + 알아 둘 2개, 외울 것은 "방향"

Git 명령이 많아 보여도, 실전의 대부분은 아래 9개로 돌아갑니다. 명령마다 4공간 그림에서 무엇이 어느 쪽으로 움직이는지만 잡으면 외울 것이 거의 없습니다.

명령방향 (4공간)하는 일언제
git clone 주소④ → ③+① (폴더째 새로 생김)원격 저장소를 사진첩째 복사해 옵니다저장소마다 처음 한 번 (Ch.4)
git add 파일① → ②이번 사진에 담을 파일을 고릅니다커밋 직전마다 (Ch.3)
git diff① ↔ ② 비교아직 add하지 않은 수정을 보여줍니다 (--staged는 ② ↔ ③ 비교)add 직전 "내가 뭘 고쳤더라?" (Ch.3)
git commit -m "…"② → ③사진을 찍어 내 PC 사진첩에 기록합니다작은 작업 하나 끝날 때마다 (Ch.3)
git push③ → ④내 커밋들을 GitHub에 올립니다 — 이때부터 팀원이 봅니다커밋을 공유하고 싶을 때 (Ch.5)
git pull④ → ③ → ①원격의 새 커밋을 받아(③) 내 파일까지(①) 갱신합니다 — ②는 거치지 않습니다매일 아침 · 작업 시작 전 (Ch.5 ①)
git switch 브랜치갈래 이동책갈피를 옮겨 잡습니다 (-c = 새로 만들며 이동) — ① 작업 폴더도 그 갈래의 사진으로 갈아 끼워집니다작업 시작·전환 때 (Ch.4)
git fetch④ → ③원격의 소식만 받아옵니다 — 내 파일은 그대로"바뀐 게 있나?"만 볼 때 (Ch.5 ⑩)
git merge 브랜치갈래 합류그 갈래의 커밋을 지금 갈래로 합칩니다팀에서는 PR이 대신합니다 (Ch.4~5)

이 중 git diff읽기 짝꿍git status(지금 어디까지 갔나)·git log(무엇이 기록됐나)까지 읽기 3종은 아무것도 바꾸지 않는 안전한 명령이라 언제든, 몇 번이든 실행해도 됩니다.

1-6. push vs pull — 반대 방향의 쌍

구분git pushgit pull
방향 (4공간)③ 로컬 저장소 → ④ 원격④ 원격 → ③ 로컬 저장소 → ① 작업 폴더
하는 일내 커밋을 GitHub에 올립니다원격의 새 커밋을 받아 내 파일까지 갱신합니다
주고받는 단위언제나 커밋 — 커밋하지 않은 수정은 push해도 올라가지 않습니다
내 파일(①) 변화없음 — 원격만 바뀝니다있음 — 받은 커밋이 merge되며 작업 폴더까지 갱신
실행 시점커밋을 공유하고 싶을 때매일 아침 · 작업 시작 전 · push가 거부(fetch first)됐을 때
구성단일 동작fetch(받기) + merge(합치기) 두 동작의 묶음
push와 pull — 이렇게 기억하세요
push는 올리기(③→④), pull은 받기(④→③→①)입니다. pull은 받은 커밋을 내 갈래에 합치는(merge) 순간 ① 작업 폴더도 그 내용으로 갱신됩니다.
"그럼 pull이 내가 작업 중인 파일을 덮으면요?" — 덮을 상황이면 git이 pull 자체를 멈춥니다(Ch.7-C의 "pull이 거부됩니다" 카드). 그래서 결과적으로 내 미커밋 수정을 잃지 않습니다 — 이 에러도 안전장치입니다.
pull = fetch + merge: 소식을 받아와서(fetch), 내 갈래에 합치는(merge) 두 동작의 묶음입니다. 그래서 pull은 내 파일이 바뀌고, fetch만 하면 바뀌지 않습니다.
30초 확인
Q. 팀원이 "방금 main에 merge했어, 받아 가!"라고 합니다. git fetch만 하면 내 파일에 반영될까요?
답 보기

반영되지 않습니다. fetch는 소식을 ③까지만 받아오고 ① 내 작업 폴더는 그대로입니다. 받아서 반영까지 하려면 git pull — fetch에 merge까지 이어서 실행해 내 파일을 최신으로 만듭니다.

1-7. 브랜치는 "책갈피" — 복사본이 아닙니다

사진첩이 한 줄로만 쌓이면 팀 작업이 곤란해집니다. 내가 실험 중인 어중간한 사진과, 팀이 합의한 완성 사진이 한 줄에 섞이기 때문입니다. 그래서 Git은 사진첩에 갈래를 만들 수 있습니다. 이 갈래가 브랜치(branch)입니다.

오해가 가장 많은 부분: 브랜치는 폴더를 복사한 사본이 아닙니다. "이 갈래의 최신 사진"을 가리키는 책갈피일 뿐입니다. 그래서 브랜치를 아무리 만들어도 용량이 거의 늘지 않고, 만들고 지우는 데 1초가 안 걸립니다. 커밋을 하면 책갈피가 방금 찍은 사진으로 스스로 옮겨 갑니다.

main feat/intro-me main 갈래 — 항상 작동하는 완성본 내 작업 갈래 — 실험은 여기서
브랜치 = 특정 사진에 붙인 책갈피. 커밋하면 책갈피가 최신 사진으로 따라 움직입니다.

main 갈래는 "언제나 작동하는 완성본"으로 깨끗하게 지키고, 새 작업은 새 책갈피(feat/…)를 만들어 그 갈래 위에 커밋을 쌓습니다. 브랜치 명령과 네이밍 규칙은 Ch.4에서 다룹니다.

1-8. GitHub Flow — 팀이 오늘부터 쓸 약속

브랜치와 PR을 어떤 순서로 쓸지는 팀마다 약속이 필요합니다. 우리는 GitHub가 권장하고 실무에서 가장 널리 쓰이는 GitHub Flow를 그대로 씁니다. 규칙은 다섯 줄이 전부입니다.

1 · main 보호main은 항상 작동하는 상태로 지킵니다 — main에 직접 커밋하지 않습니다
2 · 브랜치 생성작업은 언제나 새 브랜치(feat/…)를 만들어 시작합니다
3 · 커밋·push작은 단위로 커밋을 쌓고 GitHub에 push합니다
4 · PR 열기PR(Pull Request) = "내 갈래를 main에 합쳐 주세요"라는 검토 요청서
5 · 리뷰 → merge팀원이 검토·승인하면 merge — 내 커밋들이 main 사진첩에 합류합니다

왜 이 수고를 할까요? 검토를 거친 변경만 main에 들어가므로 main이 깨질 일이 없고, 모든 변경에 "누가 만들고 누가 검토했는지" 기록이 남으며, 코드에 대한 대화가 PR 그 자리에 쌓입니다. 팀 프로젝트의 제출물이 바로 이 기록입니다.

1-9. 프로세스 한 바퀴 — 위 규칙을 명령으로 옮기면

다섯 규칙을 실제 명령 순서로 펼치면 아래와 같습니다 — 최초 1회의 걸음 0, 그리고 반복되는 여섯 걸음. 실전 프로젝트는 이 바퀴의 반복입니다. 지금 명령이 낯설어도 괜찮습니다 — 각 걸음을 해당 챕터에서 손으로 밟습니다.

0 · 저장소 가져오기git clone 주소 — 저장소마다 처음 한 번만 (Ch.4-5 · Ch.5 ⓪)
1 · 출발선 맞추기git switch maingit pull — 매일 아침, 팀의 최신에서 시작 (Ch.5 ① · 두 번째 바퀴부터)
2 · 내 갈래 만들기git switch -c feat/… — main을 두고 책갈피를 새로 (Ch.4)
3 · 작업 사이클수정 → git add 파일git commit -m "…"을 작게 여러 번 — 사진을 쌓습니다 (Ch.3)
4 · 올리기git push -u origin HEAD (HEAD = 지금 서 있는 내 브랜치) — 내 갈래를 GitHub에 (Ch.5 ③)
5 · PR → 리뷰GitHub 웹에서 PR 열기 → Reviewers 지정 → Approve 받기 (Ch.5 ④~⑧)
6 · 합류 → 다시 1로Merge 후 git switch main && git pull — 바퀴가 다시 돕니다 (Ch.5 ⑨~⑩)

막히는 지점은 대부분 걸음 사이의 이음새입니다 — 그때 Ch.7의 에러 카드가 현재 위치를 알려줍니다.

30초 확인
Q. 왜 main에 바로 커밋하면 안 될까요?
답 보기

main은 "항상 작동하는 완성본"이라는 팀 전체의 약속이기 때문입니다. 검토 없이 커밋이 섞이면 어느 순간 아무도 믿을 수 없는 갈래가 됩니다. 그래서 모든 변경은 브랜치 → PR → 리뷰를 거쳐 main에 합류합니다.

1-10. 이 과정에서 GitHub의 3가지 역할

역할구체적 사용 방법대상
과제 제출코드를 브랜치에 올리고 PR로 제출수강생
코드리뷰PR에서 서로 댓글로 피드백수강생 ↔ 수강생, 강사
포트폴리오README + 커밋 이력이 곧 포트폴리오수강생
✅ 핵심
GitHub은 코드 저장소가 아니라 "학습 과정의 기록장"입니다. 수강 종료 후 취업 포트폴리오의 핵심이 됩니다.

1-11. GitHub 활용 레벨 — 지금 나는 어디에 있는가

활용 수준실제 모습채용 담당자 반응
Lv.0 GitHub 없음코드를 카카오톡으로 공유"개발자인지 모르겠음"
Lv.1 코드 저장소레포는 있지만 README 없음, 커밋 메시지가 'fix'뿐"취미 수준"
Lv.2 기본 포트폴리오README 있음, 데모 스크린샷 있음, 커밋 컨벤션 준수"기본은 됨"
Lv.3 생태계 활용GitHub Pages 사이트, Actions CI, 배포 링크, 기술 문서"이 사람 뽑고 싶다"
Lv.4 오픈소스 기여오픈소스 PR merge 이력, 커뮤니티 활동 흔적"즉시 전력"

이 핸드북의 목표는 수강 기간 안에 Lv.2 → Lv.4로 올라가는 것입니다. Ch.2~7이 Lv.2를, Ch.8~10이 Lv.3의 협업 근육을, Ch.11이 Lv.3~4의 생태계를 만듭니다.

CH.2 · 설치 · 초기 설정 · SSH

처음 한 번만 제대로 설정하면 끝

2-1. OS별 설치

OS설치 방법
macOSbrew install git 또는 xcode-select --install
Windowsgit-scm.com/download/win에서 Git for Windows 설치(옵션은 기본값 그대로 Next) → 이후 모든 명령은 Git Bash에서 실행. 실행 방법: 시작 메뉴 → Git Bash. 폴더 우클릭 "터미널에서 열기"로 열리는 파란 창은 PowerShell이라 이 핸드북의 일부 명령이 동작하지 않습니다. Git Bash에서 붙여넣기는 마우스 우클릭 → Paste 또는 Shift+Insert (Ctrl+V가 안 되는 창이 있습니다)
Ubuntusudo apt update && sudo apt install git
# 설치 확인 — 버전 숫자가 나오면 통과
git --version
git version 2.43.0

2-2. 초기 설정 + 추천 alias

이름과 이메일은 GitHub 계정과 동일하게 입력하세요. alias는 자주 쓰는 명령을 줄여줍니다.

# ①② 이름·이메일 줄은 복사 버튼에서 제외됩니다 — 본인 값으로 바꿔 직접 입력하세요
git config --global user.name "본인이름"
git config --global user.email "github가입이메일"
git config --global init.defaultBranch main
# (선택) VS Code 사용자만 — VS Code가 없으면 이 줄을 건너뛰세요 (commit 때 편집기를 못 찾습니다)
#   git config --global core.editor "code --wait"
# pull은 merge 방식으로 — 안 하면 최신 Git에서 divergent 에러 (Ch.7-C)
git config --global pull.rebase false
# 줄바꿈 통일 — 본인 OS 줄 하나만 골라 직접 입력. 팀원마다 다르면 "한 줄 고쳤는데 diff가 파일 전체" (Ch.7-B)
#   Windows: git config --global core.autocrlf true
#   macOS:   git config --global core.autocrlf input
# 한글 파일명이 status에서 숫자 코드로 깨져 보이는 것 방지
git config --global core.quotepath false
# 추천 alias — st(상태), lg(그래프 로그), undo(직전 커밋 취소), today(오늘 작업)
git config --global alias.st status
git config --global alias.lg 'log --oneline --graph --all --decorate'
git config --global alias.undo 'reset HEAD~1 --mixed'
git config --global alias.today 'log --oneline --since=midnight'
# 설정 전체 확인 — 화면이 : 나 (END)에서 멈추면 고장이 아니라 스크롤 모드입니다, q 키로 빠져나옵니다
git config --list
# (git log·diff도 출력이 길면 같은 화면이 됩니다 — 언제나 q로 탈출)

2-3. HTTPS vs SSH — 어떤 방식으로 연결할까

구분HTTPSSSH
주소 형식https://github.com/user/repo.gitgit@github.com:user/repo.git
인증 방식매번 아이디 + 토큰 입력최초 1회 Key 등록 후 자동 인증
편의성번거로움 (push마다 인증)편리함 (자동 인증)
보안토큰 탈취 위험Key 파일 기반 (더 안전)
권장일시적 접근, 공용 PC개인 PC 개발 환경 (권장)

수업과 팀 실습은 HTTPS + 브라우저 인증이면 충분합니다 — Windows는 Git for Windows에 내장된 인증 관리자(GCM) 덕에 첫 push 때 브라우저 창 인증으로 끝납니다. SSH는 프로젝트 기간에 개인 PC에서 전환하는 선택지로 생각하세요(아래 2-4).

macOS에서 HTTPS를 쓸 때 — 인증 준비가 하나 더 필요합니다
"push 때 브라우저 창이 뜨는" 동작은 Git for Windows에 함께 설치되는 Git Credential Manager(GCM)의 기능입니다. macOS 기본 git에는 GCM이 없어, 첫 push에서 브라우저 창 대신 터미널에 Username/Password 입력란이 뜹니다 — 그런데 GitHub는 계정 비밀번호 입력을 받지 않습니다. brew install --cask git-credential-manager 한 줄이면 Windows와 동일하게 브라우저 인증 창이 뜹니다.
GCM이 안 될 때의 대안 두 가지

① GitHub CLI: brew install ghgh auth login(HTTPS 선택) → gh auth setup-git
② PAT(Personal Access Token): GitHub → Settings → Developer settings → Personal access tokens에서 발급 → push 때 Password 자리에 붙여넣기 (키체인에 저장되어 다음부터는 묻지 않습니다)

2-4. SSH Key 발급 — 4단계

1
키 생성
ssh-keygen -t ed25519 -C "github가입이메일"
# Enter 3회 (기본 경로 사용, 개인 PC는 passphrase 생략 가능)
# 키가 이미 있으면 Overwrite (y/n)? 가 뜹니다 — 덮어쓰려면 y
ls -la ~/.ssh/
# id_ed25519(개인키) + id_ed25519.pub(공개키) 두 파일 확인
2
공개키 복사
cat ~/.ssh/id_ed25519.pub
# 출력 전체를 복사. macOS는 pbcopy < ~/.ssh/id_ed25519.pub
# Windows Git Bash는 clip < ~/.ssh/id_ed25519.pub
3
GitHub 등록

GitHub → Settings → SSH and GPG keys → New SSH key → Title에 "내 노트북", Key 칸에 복사한 공개키 붙여넣기 → Add SSH key

4
연결 테스트
ssh -T git@github.com
# 처음 한 번은 Are you sure you want to continue connecting? 이 뜹니다 — yes 를 끝까지 입력하고 Enter (y만 치면 거부됩니다)
Hi username! You've successfully authenticated...

2-5. AI 프로젝트 .gitignore — 시작 시 반드시 설정

API 키·대용량 모델·가상환경은 처음부터 추적 대상에서 제외합니다. 프로젝트 루트에 .gitignore 파일을 만들고 아래 내용을 넣으세요. 만드는 법: Git Bash에서 touch .gitignore 후 VS Code 등 편집기로 열어 붙여넣기 — 메모장 "다른 이름으로 저장"은 .txt가 붙을 수 있어 피하세요.

# 환경변수 (절대 커밋 금지)
.env
.env.local
.env.*.local
.streamlit/secrets.toml
# 가상환경·캐시
.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/
# AI 모델 파일 (용량 초과 + 라이선스 문제)
*.gguf
*.bin
*.pt
*.safetensors
models/
# 벡터 DB (재생성 가능)
chroma_db/
faiss_index/
# 미디어·OS 파일
*.wav
*.mp3
.DS_Store
Thumbs.db

2-6. 명령 전에 확인할 두 가지

초보자가 자주 하는 실수는 명령을 잘못 입력하는 것이 아니라, 엉뚱한 폴더에서 명령을 실행하는 것입니다. 아래 두 줄을 모든 작업 전에 먼저 실행하세요.

# ① 나는 지금 어느 폴더에 있나
pwd
# ② git은 지금 어느 저장소를 보고 있나
git rev-parse --show-toplevel
결과 읽는 법
②에서 fatal: not a git repository가 나오면 — 에러가 아닙니다. "여긴 git 관리 폴더가 아니에요"라는 위치 정보입니다. 반대로 예상 밖의 상위 폴더가 나오면, 지금 add·push가 그 전체에 적용된다는 경고입니다.
CH.3 · Staging & Commit

커밋 메시지만 읽어도 무슨 작업인지 알 수 있어야 합니다

3-1. 기본 워크플로우

git status                    # 지금 상태 확인 — 모든 작업의 시작
git diff                      # Working Dir vs Staging 비교
git diff --staged             # Staging vs 마지막 커밋 비교
git add src/rag/embedding.py  # 파일 단위로 담기
# git add -p  ← 부분 선택 담기(권장)는 대화형이라 단독으로 실행하세요 — 블록 통째 붙여넣기에 섞이면 다음 줄을 답변으로 삼킵니다
git commit -m "feat: BGE-M3 임베딩 모델 교체 구현"
git log --oneline --graph --all

3-2. 커밋 타입 9종 — 형식: 타입: 무엇을 어떻게 했고 결과가 무엇인지

타입의미AI 프로젝트 사용 시점
feat새 기능 추가STT/TTS/RAG/Agent 기능을 처음 구현할 때
fix버그 수정오류, 예외, 잘못된 동작을 고칠 때
refactor기능 변경 없이 코드 구조 개선함수 분리, 모듈화, 변수명 정리
prompt프롬프트 수정시스템 프롬프트, few-shot 예시 변경
docs문서만 수정README, 주석, API 설명
test테스트 추가/수정단위 테스트, 통합 테스트
data데이터/DB 관련 변경ChromaDB 스키마, 데이터셋 경로
chore빌드·도구·설정 변경requirements.txt, Dockerfile
experiment실험적 변경모델 교체 A/B 테스트, 프롬프트 실험

3-3. 타입별 실전 예시

feat — 새 기능

git switch -c feature/stt-whisper-local
git add src/stt/whisper_stt.py src/stt/__init__.py
git commit -m "feat: Whisper large-v3 로컬 STT 파이프라인 구현"
# 본문까지 쓰려면 git commit (에디터가 열림):
#   feat: Whisper large-v3 로컬 STT 파이프라인 구현
#   - LocalWhisperSTT 클래스 구현
#   - 한국어 자동 감지 및 강제 지정 옵션
#   - STTResult 데이터클래스로 반환 형식 통일
#   Closes #7

fix — 원인을 함께 기록

git switch -c fix/stt-language-misdetect
git add src/stt/whisper_stt.py
git commit -m "fix: Whisper 짧은 한국어 발화 오인식 — language='ko' 강제 지정"
# 원인 기록: 2초 미만 발화에서 language=None이면 중국어로 감지. Fixes #15

refactor — 기능이 같고 구조만 바뀌면 feat이 아니라 refactor

git switch -c feature/rag-module-split
git add src/rag/embedding.py src/rag/vectorstore.py src/rag/retriever.py
git commit -m "refactor: RAG 파이프라인을 기능별 모듈로 분리"

prompt — 프롬프트 변경 전용 타입

git switch -c feature/prompt-korean-quality
git add src/llm/prompts/system_prompt.txt
git commit -m "prompt: 시스템 프롬프트 한국어 응답 품질 개선"
# 이전 프롬프트와 비교: git diff HEAD~1 src/llm/prompts/system_prompt.txt

chore + 절대 하지 말아야 할 예

git switch -c chore/langchain-1x-migration
git add requirements.txt src/llm/ollama_client.py
git commit -m "chore: LangChain 0.x → 1.x 마이그레이션"
# ─── 절대 이렇게 하지 말 것 ─────────────
git commit -m "수정"        # 무슨 수정?
git commit -m "update"      # 무엇을?
git commit -m "작업중"      # 미완성 커밋 금지
git commit -m "asdf"        # 이력이 무의미해짐
CH.4 · 브랜치 전략

하나의 기능 = 하나의 브랜치

브랜치는 작업 공간입니다. 원본 코드를 건드리지 않고 새 작업 공간에서 작업한 뒤, 완성되면 원본에 합칩니다. main이 최종 발표 자료라면, feature 브랜치는 초안 작업 파일입니다.

4-1. 브랜치 기본 명령

git branch -a                              # 모든 브랜치 보기
git switch -c feature/stt-whisper-groq     # 만들면서 이동
git switch main                            # main으로 이동
git branch -d feature/stt-whisper-groq     # 병합 완료된 브랜치 삭제
git push origin --delete feature/stt-whisper-groq  # 원격 브랜치도 삭제

4-2. 브랜치 네이밍 규칙

팀 프로젝트용

브랜치역할예시
main항상 동작하는 최신 코드. 직접 커밋 금지(항상 존재)
feature/*기능 개발. 하나의 기능 = 하나의 브랜치feature/stt-whisper-groq
fix/*버그 수정fix/rag-encoding-error
hotfix/*배포 후 긴급 버그 수정hotfix/tts-crash-empty
experiment/*실험적 시도 (Ch.10 참고)experiment/qwen3-cot-prompt
release/*마일스톤 제출 준비release/m1
docs/*문서만 변경docs/api-setup-guide

일일 과제 제출용

상황규칙예시
챕터 일별 과제ch{번호}/day{일차}-{이름}ch02/day06-홍길동
미니 프로젝트project/{챕터}-{이름}project/ch02-홍길동
버그 수정fix/{설명}-{이름}fix/token-error-홍길동
최종 프로젝트feat/{기능명}feat/rag-pipeline
💡 브랜치 이름에 본인 이름을 넣는 이유
강사와 조교가 수십 개의 PR을 볼 때 누구 것인지 즉시 파악하기 위함입니다.

4-3. Merge 방향 원칙 — 가장 흔한 실수 지점

💡 원칙
git merge [합칠 브랜치]"현재 내가 있는 브랜치"에 내용을 가져옵니다. main에 병합하려면 반드시 git switch main으로 먼저 이동한 뒤 실행해야 합니다.
# STEP 1. 결과를 받을 브랜치(main)로 이동
git switch main
# STEP 2. 원격 main 최신화
git pull origin main
# STEP 3. feature 브랜치를 현재(main)에 병합
git merge feature/rag-chromadb
# STEP 4. push + 브랜치 삭제
git push origin main
git branch -d feature/rag-chromadb
git push origin --delete feature/rag-chromadb
# ─── 주의: 아래는 "main에 반영"이 목적이었다면 방향이 반대입니다 ───
git switch feature/rag-chromadb
git merge main        # 이 결과는 feature ← main — main에는 아무것도 반영되지 않습니다
# 단, "내 브랜치를 최신 main으로 갱신"하려는 목적이면 이 방향이 정상입니다 (PR 전 최신화, Ch.7-2)

4-4. GitHub Flow — 우리가 쓰는 6단계

단계작업명령
1main에서 feature 브랜치 생성git switch -c feature/xxx
2작업 + 자주 커밋git add 파일 && git commit -m '...'
3원격 pushgit push origin feature/xxx
4PR 생성 & 리뷰GitHub 웹브라우저
5main에 MergePR 화면의 Merge 버튼 (또는 git switch main → git merge)
6브랜치 삭제git branch -d feature/xxx
💡 develop 브랜치를 쓰지 않는 이유
develop은 Git Flow라는 복잡한 전략에서 사용합니다. 5인 이하 소규모 팀에서는 feature → main PR 방식(GitHub Flow)이 더 실용적입니다. develop이 생기면 PR 단계가 하나 더 늘어 불편합니다.

4-5. 원격 저장소 핵심 명령

git clone git@github.com:aihuman-7th/저장소이름.git   # 원격 저장소 복제
git remote add origin git@github.com:계정/저장소.git   # 기존 폴더에 원격 연결
git push -u origin main            # 첫 push (-u로 기본 연결 저장)
git push origin feature/rag-chromadb
git pull                           # fetch + merge
git pull --rebase                  # fetch + rebase (이력을 일자로)
CH.5 · Pull Request

PR 만들기 — 10단계로 따라 하기

Pull Request(PR)는 "내가 만든 변경을 main에 합쳐도 될까요?"라고 팀에 묻는 요청입니다. GitHub에서 변경 내용을 설명하고, 동료에게 리뷰를 받고, 문제가 없는지 확인한 뒤 합칩니다. PR은 코드가 아니라 "무엇을 왜 만들었는지"를 설명하는 문서이기도 합니다.

아래에서는 하나의 PR이 만들어지고 합쳐지는 과정을 10단계로 나눠 봅니다. 정상적인 방법을 먼저 보여주고, 바로 아래에 자주 만나는 실수와 해결 방법을 카드로 붙였습니다. 리뷰어가 수정을 요청하면 작업자가 추가 커밋을 push하는 피드백 루프(⑥→⑦→⑥)가 PR의 심장입니다.

전체 흐름
⓪ 저장소 준비(clone) → ① 브랜치 만들기 → ② 작업하고 기록하기 → ③ GitHub에 올리기 → ④ PR 열기 → ⑤ 리뷰어 지정 → ⑥ 리뷰 받기 → ⑦ 수정 올리기 → ⑧ 충돌 해결 → ⑨ main에 합치기 → ⑩ 정리하기
두 협업자가 변경 경로를 함께 검토한 뒤 하나의 초록색 기준선으로 합류시키는 모습
각자의 변경은 리뷰를 통과한 뒤 하나의 안정적인 main으로 합류합니다.

⓪ 팀 저장소 준비

0
팀 저장소를 내려받고 폴더로 이동합니다

팀 작업은 팀 저장소에서 시작합니다 — 특강에서는 gitday-*, 프로젝트 기간에는 proj1-A 같은 이름입니다. org 초대를 수락했다면 별도 설정 없이 HTTPS로 clone됩니다. 주소는 본인 팀 저장소 페이지의 초록 Code 버튼에서 복사하세요 — 예시 주소를 그대로 실행하면 에러 없이 다른 팀 저장소가 clone되어 한참 뒤에야 알아차리게 됩니다.

clone이 Repository not found로 거부되면 대부분 초대 미수락이거나 주소 오타입니다 — Ch.7-C의 'Repository not found' 카드를 펼치세요. 다른 팀 저장소는 읽을 수는 있지만 push가 거부됩니다(Ch.7-C).

mkdir -p ~/work && cd ~/work   # 확인 1) 작업 폴더 만들기 겸 이동 — OneDrive·바탕화면 밖, 공백·한글 없는 경로가 안전합니다
# Windows 계정 이름이 한글이면(~가 한글 경로면): mkdir -p /c/work && cd /c/work
git clone https://github.com/aihuman-7th/본인팀저장소.git   # 본인 팀 주소로 바꿔 직접 입력
cd 본인팀저장소   # ★ 확인 2) clone은 폴더만 만듭니다 — 이 cd를 빠뜨리면 다음 명령이 전부 어긋납니다
pwd && git rev-parse --show-toplevel   # 지금 팀 저장소 안인가 — 저장소 폴더가 나와야 통과
git remote -v                          # 확인 3) origin — 팀 저장소 주소가 이미 보여야. clone이 자동으로 넣어줍니다(손으로 add 금지)
⚠️ "not a git repository"가 나와도 git init 하지 마세요
이 에러의 답은 "없는 저장소를 새로 만드는 것"이 아니라 "있는 저장소로 들어가는 것(cd 저장소폴더)"입니다. git init으로 답하면 족보 없는 빈 저장소가 시작되어, 나중에 PR 화면에서 버튼이 안 뜨는 사고로 이어집니다(Ch.7-A 고아 브랜치 카드).
한 바퀴에서 빠뜨리기 쉬운 세 곳 — 순서대로 확인
막히는 사람의 대부분은 아래 셋 중 하나를 건너뛴 경우입니다. 각 단계 뒤의 확인 한 줄이 그 자리에서 잡아줍니다.
① 폴더 + 진입 (⓪) — clone 다음에 반드시 cd 저장소폴더. 확인: git rev-parse --show-toplevel가 저장소 폴더를 가리켜야.
② origin은 clone이 자동 설정 (⓪) — 손으로 git remote add origin …을 치고 있다면 이미 어긋난 것(대개 ①의 cd를 빠뜨리고 init한 경우). 확인: git remote -v에 팀 주소가 이미 있어야.
③ 작업은 새 브랜치에서 (①) — git switch -c feat/… 없이 커밋하면 main에 그대로 쌓입니다. 확인: git branch --show-currentfeat/…여야(main이면 멈춤), 커밋 줄이 [feat/… ]여야([main …]·(root-commit)이면 신호).
예제 과제 — README에 내 소개 추가하기
아래 10단계는 팀 저장소 README.md에 내 소개 한 줄을 추가하는 과제에 맞춰져 있습니다. 복사 버튼이 있는 명령은 그대로 사용해도 됩니다.
① 브랜치명만 feat/intro-본인GitHub아이디로 직접 입력합니다
README.md의 소개 표 맨 아래에 | 이름 | github-id | 한 줄 소개 | 형식으로 한 줄을 추가하고 저장합니다
③ 각 단계의 git diff·GitHub 화면을 확인한 뒤 다음으로 넘어갑니다

① 브랜치 만들기 — 최신 main에서 출발합니다

1
main을 최신으로 만든 뒤 내 작업 공간을 만듭니다

먼저 main을 최신 상태로 받고, 그 위에 내 브랜치를 만듭니다. 기능은 feat/작업내용, 버그 수정은 fix/내용처럼 이름을 붙입니다.

# 출발점을 최신으로 맞춤
git switch main
git pull origin main
git switch -c feat/intro-본인GitHub아이디   # 본인 아이디로 바꿔 직접 입력
git branch --show-current   # feat/intro-내아이디인지 확인
PR ①내 PR에 남의 커밋이 잔뜩 섞여 있습니다 Commits (9) — 내 커밋은 3개, 처음 보는 커밋 6개가 함께 옴 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

main이 아니라 다른 feature 브랜치 위에서 분기했습니다. 부모 브랜치의 커밋이 전부 내 PR에 따라 들어옵니다(부모 오염).

복구
# 커밋을 쌓기 전에 알아챘다면: 잘못 분기한 브랜치를 지우고 최신 main에서 다시 분기
git switch main
git branch -D feat/login   # 잘못 만든 브랜치 정리 — 안 지우면 'a branch named ... already exists' 에러가 납니다
git pull origin main
git switch -c feat/login

이미 커밋을 쌓았다면 혼자 수습하지 말고 상황 전체를 AI·조교에게 설명해 주세요(Ch.7 끝의 도움 요청법).

예방

브랜치 만들기 전 git branch로 현재 위치 확인 — "지금 나는 main인가?"를 의식처럼 확인합니다.

PR ①브랜치를 안 만들고 main에 직접 커밋했습니다 [main 1a2b3c] feat: 로그인 폼 구현 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

브랜치 생성을 건너뛰고 main에서 바로 작업했습니다. 커밋 메시지 앞의 [main …]이 신호입니다. 팀 규칙은 main 직접 push 금지입니다.

복구
# push 전이라면: 커밋을 새 브랜치로 옮기고 main을 되돌림
git status                       # ⓪ 미커밋 변경이 있으면 먼저
git stash push -u -m "main 복구 전 임시 보관"
git branch feat/login            # ① 지금 커밋을 새 브랜치로 보존
git fetch origin                 # ② 지금 origin/main을 최신으로 맞춘 뒤 되돌립니다
git reset --hard origin/main    # ③ main을 원격 상태로 되돌리기
git switch feat/login           # ④ 커밋된 작업이 보존된 브랜치로 이동
git stash pop                   # ⓪에서 "Saved working directory…"가 떴을 때만 실행 (No stash entries found = 보관한 게 없다는 뜻, 무해)

①~④를 순서대로 실행하면 안전합니다 — 지울 것을 먼저 브랜치와 stash에 보존해 두기 때문입니다. 이미 push했다면 revert 후 PR로 재작업합니다(팀 규칙). 상세 절차는 Ch.7 카탈로그 D를 참고하세요.

예방

작업 시작 의식 3줄(switch main → pull → switch -c)을 한 몸처럼 붙여서 실행합니다.

② 작업하고 기록하기 — 작게, 자주

2
한 가지 변경을 한 번에 기록합니다

파일을 고친 뒤 변경 내용을 확인하고 커밋합니다. 커밋 메시지는 docs:처럼 무엇을 바꿨는지 짧게 적습니다(타입 규칙은 Ch.3).

git diff README.md
git add README.md
git diff --staged
git commit -m "docs: README에 팀원 소개 행 추가"
PR ②wip 커밋만 쌓았습니다 a1b2c3d wip · e4f5a6b wip2 · 9c8d7e6 최종최종 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

커밋을 미루다 "wip"·"wip2" 같은 의미 없는 커밋만 쌓았고, 관련 없는 변경까지 뒤섞였습니다. 리뷰어는 무엇을 봐야 할지 알 수 없습니다.

복구
# push 전이라면: 최근 커밋들을 풀어서 다시 묶기
git reset HEAD~3
# 기본 모드(--mixed)는 세 커밋의 변경을 스테이지에서 내려 작업 폴더에 되돌려 둡니다 — 파일은 그대로
# 이제 파일 단위로 나눠 add → 의미 있는 커밋 2~3개로 재구성
git add login.py
git commit -m "feat: 로그인 폼 구현"
git add auth.py
git commit -m "feat: 인증 로직 추가"

이미 push한 커밋은 그대로 두고, 다음 커밋부터 바로잡으면 됩니다.

예방

기능 하나 완성 = 커밋 하나. "wip"이라고 적고 싶어지는 순간이 곧 커밋을 쪼갤 타이밍입니다.

③ GitHub에 올리기 — 첫 push

3
내 브랜치를 GitHub에 처음 올립니다

첫 push에서 -u를 붙이면 지금 브랜치와 GitHub의 브랜치가 연결됩니다. 그다음부터는 git push만 입력하면 됩니다.

git branch --show-current   # 먼저 확인 — feat/… 가 아니라 main이 나오면 멈추고 ①로 (main도 그대로 push되어 버립니다)
git push -u origin HEAD
# HEAD = 지금 서 있는 내 브랜치. 다음부터는 git push만으로 충분
PR ③두 번째 push부터 에러가 납니다 fatal: The current branch feat/login has no upstream branch. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

첫 push에서 -u를 생략해 로컬 브랜치가 원격의 어느 브랜치와 짝인지 Git이 모릅니다.

복구
git push -u origin feat/login
예방

새 브랜치의 첫 push는 항상 git push -u origin 브랜치명으로 굳히면 다시 만나지 않는 에러입니다.

PR ③GitHub에서 내 브랜치가 안 보입니다 Compare & pull request 배너가 나타나지 않음 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

로컬에서 커밋만 하고 push를 하지 않았습니다. 브랜치와 커밋은 아직 내 컴퓨터에만 존재합니다.

복구
git push -u origin feat/login
예방

PR을 만들러 가기 전 git status를 확인 — "Your branch is ahead of …"가 보이면 아직 push 전이라는 뜻입니다.

④ PR 열기 — 합칠 방향과 내용을 확인합니다

4
base: main ← compare: 내 브랜치

push 직후 GitHub 저장소에 뜨는 Compare & pull request 노란 배너를 클릭합니다. 배너가 없으면 Pull requests 탭 → New pull request로 들어갑니다. 화면 상단에서 방향을 확인하세요: base: main ← compare: feat/intro-내아이디 — 오른쪽 브랜치의 내용이 왼쪽으로 합쳐집니다. 제목은 docs: README에 팀원 소개 행 추가, 본문은 템플릿(아래 5-13)을 채웁니다.

push했는데 Pull requests 목록이 비어 있어요
정상입니다 — push는 브랜치를 올리는 것까지이고, PR은 아직 안 만들어진 상태입니다. 저장소에 브랜치는 보이는데 PR 목록이 비어 있다면 아직 아무도 [Create pull request]를 안 누른 것. push 뒤에 웹에서 배너 → Create pull request까지 눌러야 "제출"이 됩니다. "커밋 → push → PR 생성"은 서로 다른 세 동작입니다.
Create pull request 버튼은 브랜치 주인이 누릅니다
이 버튼을 누른 사람이 PR 작성자가 됩니다 — 남의 브랜치로 대신 눌러 주면 크레딧이 그 사람에게 넘어가고(스코어보드·포트폴리오 집계가 어긋남), 작성자가 되어버려 그 PR을 본인이 Approve할 수 없게 됩니다(Ch.5 ⑤). PR은 반드시 브랜치를 만든 사람이 자기 화면에서 직접 여세요.
30초 확인
Q. base와 compare, 어느 쪽에 내 브랜치가 와야 할까요?
답 보기

compare(오른쪽)입니다. base는 받는 곳(main), compare는 보내는 곳(내 브랜치) — 화살표는 항상 main을 향합니다. 반대로 열면 Files changed에 남의 변경만 가득해집니다(아래 카드).

PR ④base와 compare를 반대로 열었습니다 base: feat/login ← compare: main — diff에 남의 변경만 가득 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

방향이 반전되어 "main을 내 브랜치에 합쳐 달라"는 PR이 됐습니다. Files changed에 내 작업 대신 다른 사람들의 변경이 보이는 것이 증상입니다.

복구

해당 PR을 Close하고, 생성 화면에서 base: main ← compare: feat/login 방향을 확인한 뒤 다시 생성합니다. base만 잘못 골랐다면 PR 제목 옆 Edit에서 base를 바꿀 수도 있습니다.

예방

Create pull request를 누르기 전 "화살표는 항상 main을 향한다"를 소리 내어 확인합니다.

PR ④제목 "수정했습니다", 본문은 빈칸입니다 제목: 수정했습니다 · 본문: 템플릿 빈칸 그대로 제출 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

PR을 "업로드 버튼"으로만 생각했습니다. 리뷰어는 무엇을 왜 바꿨는지 모른 채 코드부터 읽어야 하고, 리뷰 품질이 함께 떨어집니다.

복구

PR 본문의 Edit을 눌러 템플릿을 채웁니다 — 변경 요약(무엇을 왜, 2~3줄), 리뷰 포인트(집중해서 봐줬으면 하는 지점), 체크리스트.

예방

제목은 feat: … 컨벤션으로 한 줄 요약, 본문은 "리뷰어가 3분 안에 맥락을 잡을 수 있는가"를 기준으로 작성합니다.

⑤ 리뷰 요청 — 읽어줄 팀원을 지정합니다

5
Reviewers에서 팀원을 선택합니다

PR 오른쪽의 Reviewers에서 리뷰어를 선택합니다. 지정한 사람에게 리뷰 요청 알림이 갑니다.

PR ⑤PR을 올렸는데 아무도 안 봅니다 Reviewers: No reviews — 며칠째 그대로 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

리뷰어를 지정하지 않아 아무에게도 알림이 가지 않았습니다. PR은 만든다고 저절로 읽히지 않습니다.

복구

Reviewers 톱니바퀴 → 팀원 지정. 급하면 팀 채널에 PR 링크를 공유하며 리뷰를 부탁합니다.

예방

"PR 생성 → Reviewers 지정"까지가 한 동작입니다. 지정 없는 PR은 제출이 끝나지 않은 상태로 간주하세요.

PR ⑤내 PR을 내가 Approve하려고 했습니다 Pull request authors can't approve their own pull request ▸ 원인을 추리한 뒤 펼쳐 확인
원인

GitHub은 PR 작성자 본인의 승인을 허용하지 않습니다. 셀프 승인은 리뷰가 아니기 때문입니다.

복구

팀원에게 리뷰를 요청합니다. 팀 merge 조건은 팀원 1명 이상의 Approve입니다.

예방

Approve는 언제나 동료의 몫 — 내가 할 일은 리뷰어가 읽기 좋은 PR을 만드는 것까지입니다.

⑥ 리뷰 받기 — 질문과 제안을 확인합니다

6
코드의 특정 줄에 의견을 남깁니다

Files changed에서 추가된 줄과 삭제된 줄을 확인합니다. 의견을 남길 줄의 + 버튼을 누르고, 마지막에 Review changes에서 Comment·Approve·Request changes 중 하나를 선택합니다. 리뷰 문장 쓰는 법은 Ch.6 코드리뷰에 정리돼 있습니다.

PR ⑥리뷰 코멘트는 달렸는데 Approve가 없습니다 Reviewers: 💬 Comment — Approvals 0 · merge 조건 미충족 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

Comment 제출은 승인이 아닙니다 — 의견만 남긴 상태라 merge 조건(팀원 1명 이상 Approve)이 채워지지 않았습니다. 코멘트가 달렸다는 이유로 작성자가 승인으로 착각하기 쉬운 지점입니다.

복구

Request changes를 받았다면: 반영 커밋을 push → 해당 코멘트에 무엇을 어떻게 반영했는지 답글 → 리뷰어에게 재리뷰(Approve) 요청 → 해결된 스레드는 Resolve conversation으로 정리합니다. Comment만 받았다면: 답글로 논의를 마친 뒤 Approve로 다시 제출해 달라고 요청하세요.

예방

Free 플랜에서는 Request changes 상태여도 Merge 버튼이 눌립니다 — 해소 전 merge 금지는 Approve 규칙과 같은 규율입니다. merge 전 사이드바에서 "Approve" 표시를 눈으로 확인하세요.

⑦ 수정 반영 — 같은 PR에 이어서 올립니다

7
같은 브랜치에 수정 커밋을 추가합니다

리뷰 의견을 반영할 때 새 PR을 만들지 않습니다. 같은 브랜치에서 고치고 commit·push하면 기존 PR이 자동으로 업데이트됩니다. 반영한 내용은 댓글로 알려주세요.

git switch feat/intro-본인GitHub아이디   # 원래 PR 브랜치로 직접 이동
git diff README.md
git add README.md
git commit -m "docs: 리뷰 반영 — 팀원 소개 수정"
git push
PR ⑦수정 반영하려고 새 PR을 또 만들었습니다 A pull request already exists for aihuman-7th:feat/login. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

가장 흔한 실수입니다. "수정본 = 새 제출"이라는 과제 습관 때문에 PR을 또 만들지만, PR은 브랜치에 연결된 살아 있는 문서라 같은 브랜치에 push하면 자동 갱신됩니다.

복구

실수로 만든 중복 PR(또는 중복 브랜치의 PR)은 Close하고, 원래 브랜치로 돌아가 추가 커밋 → push 합니다. 리뷰 이력은 원래 PR에 이어집니다.

예방

"하나의 기능 = 하나의 브랜치 = 하나의 PR". 머지되기 전까지 그 PR이 그 기능의 유일한 창구입니다.

PR ⑦"고쳤어요" 댓글을 달았는데 리뷰어 화면은 그대로입니다 PR Commits 탭에 새 커밋이 없음 — push 누락 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

로컬에서 커밋만 하고 push를 잊었습니다. 수정 내용이 내 컴퓨터에만 있어 PR에는 반영되지 않았습니다.

복구
git push

push 후 PR의 Commits 탭에 새 커밋이 나타나는지 확인합니다.

예방

"고쳤어요" 댓글은 push 후, PR 화면에서 내 커밋을 눈으로 확인한 다음에 답니다.

⑧ 충돌 해결 — 두 사람의 변경을 합칩니다

8
충돌난 줄을 정리하고 다시 커밋합니다

같은 줄을 서로 다르게 고치면 충돌이 생깁니다. Resolve conflicts를 누르고 두 변경 중 무엇을 남길지 팀원과 정한 뒤, 충돌 표시 세 줄을 지우고 Mark as resolved → Commit merge를 누릅니다. 원리는 Ch.7-1, 로컬 해결 절차는 Ch.7 방법 2에 있습니다.

PR ⑧충돌 마커를 지우지 않고 커밋했습니다 SyntaxError: invalid syntax — 파일에 <<<<<<< HEAD 잔존 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

충돌 해결 시 코드만 고르고 마커(<<<<<<< ======= >>>>>>>)를 남긴 채 커밋했습니다. 마커는 코드가 아니므로 실행이 깨집니다.

복구
# 마커 잔재 전수 검사 — 세 마커를 모두 검사합니다 (위에서부터 지우다 만 경우 ===·>>>만 남기도 합니다)
# ^(줄 시작) 앵커가 중요: README의 충돌 안내 문구(문장 중간 마커)는 건너뛰고 진짜 마커만 잡습니다
git grep -nE '^(<<<<<<<|=======$|>>>>>>>)'
# 마커 삭제 후
git add 파일
git commit -m "fix: 충돌 마커 제거"
git push
예방

충돌 해결 커밋 직전 git grep -nE '^(<<<<<<<|=======$|>>>>>>>)' 한 줄을 습관으로 — 출력이 없어야 통과입니다.

PR ⑧충돌 해결한다며 상대 코드를 통째로 지웠습니다 머지 후 팀원의 기능이 사라짐 — "제 코드 어디 갔어요?" ▸ 원인을 추리한 뒤 펼쳐 확인
원인

충돌을 "빨리 없애는 것"으로 이해해 내 쪽만 남겼습니다. 상대의 작업이 통째로 소실됩니다. 충돌 해결은 선택이 아니라 합의입니다.

복구

상대와 대화해 어느 쪽을(또는 둘 다) 살릴지 정합니다. 지워진 코드는 사라진 것이 아니라 이력에 남아 있으므로, 해당 PR의 Files changed나 커밋 이력에서 복원합니다.

예방

충돌 지점에 상대 이름이 보이면 지우기 전에 먼저 묻기. 매일 아침 pull과 작업 파일 분리로 충돌 자체를 줄입니다.

⑨ main에 합치기 — 리뷰가 끝난 뒤 진행합니다

9
Approve와 검사 결과를 확인한 뒤 합칩니다

팀원 1명 이상의 Approve가 확인되면 PR 작성자 본인이 Merge pull request → Confirm merge를 누릅니다. 방식은 기본값인 Create a merge commit을 사용합니다.

누르기 전에 한 가지 더 — 체크 영역에 Secret Scan CI가 표시된다면 ✓(초록)인지 확인하세요. ✕(빨강)은 비밀키가 이미 올라갔다는 신호입니다. merge를 멈추고 Ch.12 절차(키 재발급 → 추적 제거)를 먼저 실행합니다.

규칙이 하나 더 있습니다 — Approve 이후에 push한 커밋이 있다면, merge 전에 리뷰어에게 변경분 확인 답글(또는 재Approve)을 받습니다. Approve는 그 시점까지의 코드에 대한 서명입니다. GitHub은 이후 커밋이 쌓여도 Approve 표시를 그대로 유지합니다 — 표시가 남아 있다고 새 커밋까지 승인된 것이 아닙니다.

PR ⑨Approve 없이 머지해 버렸습니다 Merged · Reviewers: No reviews ▸ 원인을 추리한 뒤 펼쳐 확인
원인

Free 플랜 org는 버튼이 강제로 막히지 않습니다 — merge 조건(팀원 1명 이상 Approve)은 규율로 운영되고, 주간 리뷰 감사 리포트로 관측됩니다. 눌러진다고 되는 것이 아닙니다.

복구

머지된 PR 화면의 Revert 버튼으로 되돌리는 PR을 만들고, 원래 변경은 리뷰를 받은 뒤 다시 머지합니다.

예방

Merge 버튼을 누르기 전 사이드바에서 Approve 여부를 육안으로 확인 — "초록 체크 없으면 머지 없다".

머지 방식 3종 — 버튼의 드롭다운에는 세 가지가 있습니다. 팀 기본값은 merge commit입니다.

방식이력에 남는 것우리 팀
Create a merge commit브랜치의 커밋이 전부 보존되고 병합 커밋 1개가 추가됨기본값 — 각자의 커밋이 본인 계정 크레딧으로 남아 포트폴리오 증빙이 됩니다
Squash and merge커밋 여러 개가 1개로 합쳐짐사용 안 함 — 개별 커밋 크레딧이 사라집니다
Rebase and merge이력이 새 해시로 다시 만들어짐사용 안 함 — 이력 비교가 어려워져 지금 단계에서는 피합니다

⑩ 마무리 — 다음 작업을 준비합니다

10
끝난 브랜치를 지우고 main을 최신으로 만듭니다

GitHub에서 Delete branch를 눌러 끝난 브랜치를 정리합니다. 내 컴퓨터에서도 main을 최신으로 받은 뒤 다음 작업을 시작합니다.

git switch main
git pull origin main
git fetch --prune   # 원격에서 지워진 브랜치 목록 정리 — fetch는 원격 정보만 받아옵니다
# (pull = fetch + merge. fetch만 하면 내 브랜치의 파일은 바뀌지 않습니다)
git branch -d feat/intro-본인GitHub아이디   # 머지 끝난 내 브랜치 이름을 직접 입력해 삭제 (팁: @{-1} = "직전 브랜치"의 축약형)
PR ⑩로컬 main을 동기화하지 않고 다음 브랜치를 만들었습니다 This branch has conflicts that must be resolved ▸ 원인을 추리한 뒤 펼쳐 확인
원인

내 로컬 main은 pull 하기 전까지 과거에 머물러 있습니다. 구버전 main에서 분기하면 이미 머지된 팀원들의 변경과 어긋난 채 시작 — 다음 PR의 충돌이 예약됩니다.

복구
git switch main
git pull origin main
# 작업 전이면: 브랜치를 지우고 최신 main에서 다시 분기
# 이미 작업했다면: git switch feat/next && git merge main
예방

머지 직후 의식: git switch main && git pull origin main. 매일 아침에도 같은 두 줄로 하루를 시작합니다.

PR ⑩머지가 끝난 브랜치에 계속 커밋하고 있습니다 머지 완료된 PR 타임라인에 새 커밋이 계속 쌓임 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

브랜치 하나를 만능 작업장처럼 재사용했습니다. 머지된 PR은 닫힌 문서라 새 커밋이 리뷰로 이어지지 않고, 기능 단위 이력도 뒤섞입니다.

복구

최신 main에서 새 브랜치를 만들고, 이어 하던 변경을 그 브랜치로 옮겨 새 PR을 엽니다.

예방

머지 = 브랜치 수명 종료. Delete branch를 누르는 순간 그 이름과는 작별하고, 새 작업은 새 브랜치에서 시작합니다.

팀의 PR 규칙 한 장

aihuman-7th 팀 규칙 요약
· 브랜치는 feat/작업내용·fix/내용, 작업은 반드시 브랜치에서 — main 직접 push 금지(위반 시 revert 후 PR로 재작업)
· PR 본문은 템플릿 3칸: 변경 요약 · 리뷰 포인트 · 체크리스트
· merge 조건: 팀원 1명 이상 Approve — "LGTM 한 줄"은 리뷰로 인정하지 않습니다
· 본인 작업은 본인 계정으로 커밋(포트폴리오 증빙), 공동 작업은 Co-authored-by:
· 강사는 PR마다 개입하지 않고 마일스톤 시점에 일괄 리뷰합니다
· 제출물 = 마감 시점의 main + 강사가 생성하는 태그 — 마감 후 커밋은 평가 대상이 아닙니다
· 머지되지 않은 열린 PR도 제출물(main)에 포함되지 않습니다 — 마감 최소 반나절 전에 리뷰·머지를 끝내세요

이 10단계는 한 번 외우고 끝나는 절차가 아니라 프로젝트 기간 내내 반복합니다. 어느 단계에서 막혔는지 확인한 뒤, 이 챕터의 해당 카드를 다시 찾아보세요.

5-11. PR 제목 규칙

[Ch02-Day06] Anthropic API 스트리밍 응답 구현
[Ch03-Mini] PDF 기반 RAG Q&A 챗봇 완성
[Fix] 토큰 초과 시 재시도 로직 추가
feat: STT Groq API 마이그레이션 (8초 → 0.7초)   # 성능 변화를 제목에 담으면 최고

5-12. PR 본문 템플릿 — AI 활용 내역까지

## 구현 내용
- Anthropic API의 스트리밍 응답을 구현했습니다
- 토큰 사용량을 실시간으로 출력하는 기능을 추가했습니다

## AI 활용 내역
- Claude에게 스트리밍 구현 방법을 질문하고 코드를 생성받았습니다
- 생성된 코드에서 에러 처리 부분이 누락된 것을 발견하고 직접 추가했습니다

## 어려웠던 점 / 해결 방법
- 비동기 스트리밍에서 yield 사용법이 헷갈렸습니다
- Python 공식 문서와 Claude의 도움으로 해결했습니다

## 리뷰 요청 포인트
- 에러 처리 방식이 올바른지 확인해주세요
- 더 효율적인 스트리밍 방법이 있는지 피드백 부탁드립니다

## 셀프 체크리스트
- [x] 코드가 실행됩니다
- [x] 린트 에러가 없습니다
- [x] README를 업데이트했습니다
- [ ] 테스트를 작성했습니다 (다음 PR에서 추가 예정)
💡 AI 활용 내역을 쓰는 이유
AI가 짜준 코드를 그대로 올리면 "과정을 모르는" 상태가 됩니다. 무엇을 질문했고, 생성된 코드에서 무엇을 발견해 고쳤는지 기록하는 것 자체가 학습이고, 리뷰어에게는 어디를 집중해서 봐야 하는지 알려주는 지도가 됩니다.

5-13. 팀 공용 PR 템플릿 파일

저장소에 아래 파일을 만들어두면 PR을 열 때마다 자동으로 양식이 채워집니다.

<!-- 파일 위치: .github/PULL_REQUEST_TEMPLATE.md — 이 HTML 주석은 PR 화면에 표시되지 않습니다 -->
## 변경 내용
- (무엇을 구현/수정했는지)

## 성능 / 결과 변화
- Before: (기존 상태)
- After: (변경 후 상태)

## 테스트 방법
```bash
python tests/test_stt.py
```

## 체크리스트
- [ ] .env 파일 미포함 확인
- [ ] 모델 파일(.gguf) 미포함 확인
- [ ] requirements.txt 업데이트

Closes #
CH.6 · 코드리뷰

받는 것보다 "주는 것"이 더 많이 배웁니다

코드리뷰는 동료의 코드를 읽고 개선점을 댓글로 제안하는 활동입니다.

코드리뷰를 받는 사람코드리뷰를 주는 사람
내 코드의 문제점을 발견남의 코드를 읽으며 패턴 학습
다른 사람의 시각으로 개선설명하면서 개념이 더 깊어짐
취업 후 실무와 동일한 경험실수를 찾는 능력이 길러짐

6-1. GitHub에서 코드리뷰 하는 방법 — 4 STEP

1
리뷰할 PR 접속

GitHub → 상대방 저장소 → Pull requests 탭 → 내가 리뷰어로 배정된 PR 클릭

2
Files changed 탭 클릭

초록색 = 추가된 코드, 빨간색 = 삭제된 코드

3
줄 단위 댓글 작성

댓글 달고 싶은 줄 위에 마우스 올리기 → 왼쪽 파란 + 버튼 클릭 → 댓글 입력 → Add single comment

4
전체 리뷰 제출

Review changes 버튼 → Comment(의견만) / Approve(승인) / Request changes(수정 요청) 선택 → Submit review

6-2. 단계별 코드리뷰 체크리스트

기초 단계 — Spec과 코드 기본기

리뷰 항목확인 질문
Spec 완결성5개 섹션(Why/Goal/What/How/AC)이 모두 있는가?
변수명변수 이름이 의미를 전달하는가? (a, b, x는 ❌)
함수 크기함수 하나가 하나의 일만 하는가?
주석복잡한 로직에 설명 주석이 있는가?
에러 처리예외 상황을 처리하고 있는가?

LLM + RAG 단계

리뷰 항목확인 질문
프롬프트 명확성System 프롬프트에 역할이 명확히 정의되어 있는가?
토큰 관리불필요하게 긴 프롬프트는 없는가?
에러 처리API 오류 시 처리 로직이 있는가?
청킹 전략RAG에서 청크 크기 선택 이유가 있는가?
AI 활용 명시AI 사용 여부와 방법이 PR에 명시되어 있는가?

에이전트 + 배포 단계

리뷰 항목확인 질문
Spec 대응코드가 Spec의 모든 AC를 충족하는가?
에이전트 이탈Spec에 없는 기능이 추가되지 않았는가?
Harness 존재관찰/제어/검증/복구 레이어가 있는가?
보안API 키가 코드에 하드코딩되어 있지 않은가?
README 일치배포 URL과 README가 일치하는가?

6-3. 좋은 댓글 vs 나쁜 댓글

❌ 나쁜 댓글
"이거 틀렸어요." / "왜 이렇게 했어요?" / "LGTM" (Looks Good To Me — 아무 생각 없는 승인)
✅ 좋은 댓글
"이 부분에서 네트워크 오류가 나면 빈 리스트가 반환될 수 있어요. try-except로 처리하면 어떨까요?" — 문제 + 해결 방향을 함께 제시합니다.
댓글 유형예시언제 쓰는가
질문형"이 변수를 list 대신 set으로 쓴 이유가 있나요?"의도를 이해하고 싶을 때
제안형"defaultdict를 쓰면 KeyError 없이 처리 가능해요"더 나은 방법이 있을 때
칭찬형"에러 처리를 꼼꼼하게 했네요! 배웠어요"잘 한 부분을 인정할 때
필수수정"API 키가 코드에 노출되어 있어요. .env로 이동 필수"보안 등 반드시 고쳐야 할 때
⚠️ 중요
"LGTM" 한 줄 댓글은 코드리뷰로 인정하지 않습니다. 최소한 하나의 구체적인 피드백(질문, 제안, 칭찬)이 있어야 합니다.

6-4. AI 코드리뷰 — 일반 리뷰와 무엇이 다른가

AI 프로젝트의 코드 리뷰는 일반 소프트웨어 리뷰와 다릅니다. 프롬프트 변경, 모델 교체, 임베딩 모델 교체 등 AI 특화 항목을 리뷰어가 체계적으로 확인해야 합니다.

리뷰 항목일반 소프트웨어AI 개발 추가 항목
기능 검증입력/출력 정확성프롬프트 변경 → LLM 응답 품질 변화
성능실행 시간, 메모리LLM 호출 비용, 토큰 수, 응답 지연
보안XSS, SQL InjectionAPI Key 노출, 모델 파일 커밋
유지보수성코드 가독성프롬프트 버전 추적 가능성
테스트단위/통합 테스트LLM 출력 비결정성 → 평가 지표로 검증

프롬프트 변경 PR 체크리스트

체크 항목확인 방법통과 기준
변경된 프롬프트 내용git diff로 before/after 비교목적에 맞는 변경인지 논리 검토
실험 결과 첨부 여부PR 설명에 LangSmith URL 또는 테스트 결과수치 기반 근거 있어야 병합 승인
한국어 응답 품질로컬에서 직접 테스트응답이 자연스러운 한국어인지
할루시네이션 증가 여부LangSmith traces에서 샘플 확인기존 대비 사실 오류 증가 없어야 함
System prompt 보안민감 정보 포함 여부API Key, 개인정보 절대 포함 금지
# 프롬프트 변경 PR에서 리뷰어가 확인하는 방법
git diff main feature/prompt-v2 -- src/llm/prompts/   # 1. 변경 내용 확인
git switch feature/prompt-v2                          # 2. 로컬에서 직접 테스트
python scripts/test_prompt.py --samples 10
# 3. LangSmith에서 이전/이후 run 비교 — run_name으로 커밋 hash 검색 (Ch.10)

모델 전환 PR 체크리스트 (예: Ollama qwen → Groq llama, 로컬 → 클라우드 API)

체크 항목확인 방법통과 기준
base_url 전환 패턴코드에서 하드코딩 없는지 확인환경변수(LLM_PROVIDER)로 전환 여부
API Key 환경변수 처리os.environ.get() 사용 여부코드에 API Key 직접 입력 절대 금지
.env.example 업데이트신규 환경변수 예시 추가 여부팀원이 바로 설정 가능해야 함
비용 추정토큰 사용량 × 단가 계산기존 대비 비용 증가 팀 합의 필요
응답 품질 비교동일 입력으로 A/B 테스트기존 모델 대비 품질 저하 없어야 함
fallback 처리API 오류 시 로컬 모델로 전환 로직운영 중 API 장애 대응 방안

임베딩 모델 교체 PR 체크리스트 (예: all-MiniLM → bge-m3)

체크 항목확인 방법통과 기준
기존 벡터 DB 호환성새 모델의 차원(dimension) 확인차원 변경 시 DB 재색인 필요, 문서화
한국어 검색 품질동일 쿼리 셋으로 Hit Rate 비교기존 대비 Recall@5 저하 없어야 함
처리 속도임베딩 생성 시간 측정SLA 요구사항 충족 여부
비용 변화API 모델 교체 시 단가 비교무료 → 유료 전환이면 팀 합의 필요
ChromaDB 재색인 여부collection 삭제 후 재생성 로직마이그레이션 스크립트 포함 여부

6-5. 건설적 AI 코드리뷰 문화

리뷰어 입장에서

  • 사람이 아닌 코드를 비판하세요: "이 프롬프트가 할루시네이션을 유발할 것 같아요" (당신이 나쁘다는 뜻이 아님)
  • 질문 형태로 제안: "이 청크 크기(512)를 256으로 줄이면 한국어 검색 품질이 나아질 것 같은데 어떻게 생각하세요?"
  • 실험 근거 요청: "이 프롬프트 변경의 효과를 수치로 보여주실 수 있나요?"
  • Nitpick 구분: 변수명 스타일(선호도)과 API Key 노출(보안 이슈)을 다르게 취급

PR 작성자 입장에서

  • 모든 리뷰 댓글에 응답하기 (반영 / 미반영 이유 모두 명시)
  • 실험 결과를 PR 설명에 먼저 첨부해서 리뷰어 부담 줄이기
  • 대규모 프롬프트 변경은 작은 PR 여러 개로 분리
  • '실험 중인 코드'는 experiment/* 브랜치에서, '확정된 코드'만 main으로 PR
리뷰 댓글 예시 (나쁨)리뷰 댓글 예시 (좋음)
이 프롬프트 왜 이렇게 썼어요?이 프롬프트의 이 부분이 모호할 것 같아요. '한국어로 답변하세요'를 더 구체적으로 '~하는 방식으로 한국어 답변을 작성하세요'로 수정하면 어떨까요?
모델 바꾸지 마세요Groq로 전환하면 비용이 발생하는데 월 예상 비용을 계산해 주실 수 있을까요? 팀 예산 확인 후 진행하면 좋겠습니다.
임베딩 교체하면 안 됩니다bge-m3로 교체 시 ChromaDB의 기존 데이터는 어떻게 되나요? 재색인 스크립트가 PR에 포함되어 있으면 리뷰하기 쉬울 것 같습니다.
참고 — 평가 반영 기준 예시 (기수별 공지가 우선)
항목배점기준
과제 구현40%기능 동작 + 에러 처리 + 코드 품질
코드리뷰 제공30%주당 최소 2개, 댓글 2문장 이상
AI 활용 로그20%PR에 AI 사용 여부와 방법이 명시되었는가
주간 회고10%3가지 질문에 성실히 답변

이 표는 코드리뷰를 성적에 반영하는 운영 방식의 예시입니다. 실제 반영 비율과 기준은 해당 기수의 공지를 따릅니다.

CH.7 · 충돌 해결과 에러 찾기

충돌은 사고가 아니라 협업의 일상입니다

충돌(conflict)은 두 사람이 같은 곳을 다르게 고쳤다는 알림입니다. Git이 임의로 한쪽을 버리지 않고 사람에게 판단을 넘기는 것 — 오히려 안전장치입니다.

7-1. 충돌 마커 해부

<<<<<<< feat/intro-me      ← 내 브랜치가 쓴 내용
| 나 | my-id | 안녕하세요 |
=======
| 팀원 | their-id | 반갑습니다 |
>>>>>>> main               ← 이미 머지된 내용

마커 세 줄(<<<·===·>>>)은 git이 그어준 경계선입니다. 해결 = 경계선을 지우고, 남길 내용을 사람이 결정하는 것. 위 소개 표처럼 둘 다 의미가 있으면 두 행 모두 살리면 됩니다.

30초 확인
Q. 충돌 해결에서 "지우는 것"은 무엇일까요 — 마커 세 줄? 상대방의 내용?
답 보기

마커 세 줄만 지웁니다. 내용은 지우는 게 아니라 남길 것을 고르는 것이고, 소개 표처럼 둘 다 의미가 있으면 둘 다 살립니다. 상대 내용을 함부로 지우는 순간이 두 번째 사고의 시작입니다.

7-2. 해결 방법 두 가지

방법 1 — GitHub 웹 에디터 (처음에는 이것부터)

1
PR 화면에서 Resolve conflicts 클릭
2
마커 줄만 지우고, 남길 내용을 정리

절대 상대 코드를 통째로 지우지 마세요 — 어느 쪽을 살릴지는 작성자 둘이 대화로 결정합니다. 합친 뒤 행 순서가 추가한 순서와 달라질 수 있습니다 — 모두 살아 있으면 순서는 무관, 정상입니다.

3
Mark as resolved → Commit merge → Merge

해결 후 PR에서 "This branch has conflicts" 문구가 사라지고 Merge 버튼이 초록색이면 통과입니다.

방법 2 — 로컬에서 해결 (웹 에디터가 안 될 때)
git switch feat/내브랜치
git pull --no-rebase origin main   # main의 최신을 내 브랜치로 가져옴 → 충돌 발생
CONFLICT (content): Merge conflict in README.md
# 해결이 꼬였으면 병합 자체를 취소하고 pull 전 상태로: git merge --abort
# 편집기에서 마커 정리 후
git add README.md
git commit -m "merge: main 충돌 해결"
git push
git grep -nE '^(<<<<<<<|=======$|>>>>>>>)'   # 마커 3종 잔재 검사 — 아무것도 안 나와야 완료 (^앵커: README 안내 문구는 제외)

로컬 머지에서는 내 브랜치 쪽이 <<<<<<< HEAD로 표시되고, 아래쪽(>>>>>>>) 라벨은 브랜치명 대신 커밋 해시(영숫자 40자)로 보일 수 있습니다 — 위 "충돌 마커 해부"의 라벨과 달라 보여도 같은 구조입니다.

충돌 상태에서 pull을 다시 치면 error: Pulling is not possible because you have unmerged files.가 뜹니다 — 고장이 아니라 "먼저 이 충돌부터 해결하라"는 뜻입니다. 마커 정리 → git addgit commit이 답이고, 병합 자체를 무르려면 git merge --abort입니다.

--no-rebase를 붙이는 이유: 초기 설정(Ch.2-2의 pull.rebase false)을 건너뛴 PC에서 git pull만 치면 충돌을 만나기도 전에 fatal: Need to specify how to reconcile divergent branches로 멈춥니다. --no-rebase는 어느 PC에서든 merge 방식으로 동작하게 하는 안전벨트입니다 (초기 설정을 한 PC에서도 무해).

어느 쪽을 살릴지 판단 기준: ① 둘 다 의미 있으면 둘 다(소개 표) ② 같은 로직의 두 버전이면 작성자 둘이 대화로 결정 — 혼자 정하고 지우는 것이 두 번째 사고의 시작입니다.

노트북(.ipynb) 충돌은 다르게 다룹니다
Jupyter 노트북은 겉보기와 달리 내부가 JSON이라, 충돌이 나면 마커가 JSON 구조 한가운데에 들어가 손으로 정리하는 것이 사실상 불가능하고 위의 웹 에디터 3단계도 통하지 않습니다.
예방 — 노트북은 1인 1파일로 분리하고, 커밋 전 출력 셀을 비웁니다(Jupyter 메뉴 Kernel → Restart & Clear Output) — 출력까지 저장되면 실행할 때마다 diff가 생겨 충돌 확률이 뜁니다.
충돌 시 — 마커 수동 정리 금지. 한쪽 버전을 통째로 선택하고(git checkout --ours 노트북.ipynb 내 쪽 / --theirs 상대 쪽, 선택 후 git add — 웹에서는 한쪽만 남기기), 밀려난 쪽의 변경은 그 노트북을 열어 다시 실행해서 합칩니다.
③ 심화 — 커밋 때 출력 셀을 자동으로 비워 주는 nbstripout 도구가 있습니다. 필요해지면 AI에게 설정법을 물어보세요.
충돌 예방 습관
① 작업 시작 전 git switch main && git pull ② PR은 작게 자주 ③ 같은 파일을 오래 들고 있지 않기 — 충돌의 크기는 "동시에 고친 시간"에 비례합니다.

7-3. 고급 기능 — Stash · Rebase · Tag

git stash — 작업 중 급한 일이 생겼을 때

git stash push -m "RAG 청크 크기 튜닝 작업 중"   # 작업 임시 보관
# ... 긴급 대응 후 복귀 ...
git switch feature/rag-chunk-tuning
git stash pop                                    # 보관한 작업 복원
git stash list                                   # 보관 목록 확인 — stash는 쌓입니다. pop 후에도 남은 항목이 없는지 여기서 확인

git rebase -i — push 전 커밋 정리

git rebase -i HEAD~3
# pick   a1b2c3  feat: Whisper STT 기본 구조
# squash d4e5f6  fix: 오타 수정
# squash g7h8i9  fix: 또 오타 수정
# → 커밋 3개가 1개로 합쳐짐. push 전에만 사용. 이미 push된 브랜치는 금지.

git tag — 마일스톤 버전 표시

git tag -a m1-submit -m "M1 마일스톤 제출: STT+TTS+LLM 완성"
git push origin --tags
git checkout m1-submit   # 제출 버전으로 이동
git switch -             # 원래 브랜치로 복귀

7-4. AI 프로젝트 특화 실수 TOP 5

실수시나리오해결책
.env 커밋OPENAI_API_KEY를 실수로 push즉시 API Key 재발급 + git filter-repo로 이력 제거 (Ch.12)
모델 파일 커밋qwen3.5.gguf(4GB)를 push 시도git rm --cached models/*.gguf + .gitignore 추가
main 직접 커밋브랜치 보호 설정 전 직접 pushgit revert + main 브랜치 보호 규칙 즉시 설정
타 팀원 모듈 무단 수정src/stt/를 팀원 C가 임의로 수정PR에 변경사항 명시 + 담당 팀원에게 리뷰 요청
커밋 없이 브랜치 이동git switch 시 작업 내용 날아감switch 전 git stash 습관화

7-5. 자주 쓰는 복구 명령

git commit --amend -m "올바른 메시지"   # 직전 커밋 메시지 수정 (push 전)
git reset --soft  HEAD~1               # 커밋만 취소 (staged 유지)
git reset --mixed HEAD~1               # 커밋 취소 (modified 상태)
git reset --hard  HEAD~1               # 커밋 + 파일 모두 되돌림 (주의!)
git restore 파일명.py                   # 수정 취소
git restore --staged 파일명.py          # Staging 취소

7-6. 에러 카탈로그 — 상황별 해결 카드

에러 메시지는 "망했다"는 뜻이 아니라 현재 상태를 알려주는 안내입니다. 수업과 프로젝트에서 자주 만나는 상황을 증상별로 모아 두었습니다. 카드 읽는 순서: ① 터미널의 에러 문구와 카드의 회색 문장을 비교하고(에러는 처음부터 끝까지 읽기) ② 펼치기 전에 원인을 한 번 추측한 뒤 ③ 복구 명령을 실행하고 예방 습관을 하나 기억합니다.

엉킨 오류 경로를 돋보기로 추적하고 세 단계의 초록색 복구 경로로 정리하는 모습
증상을 확대해 읽고, 원인을 찾고, 복구한 뒤 예방 규칙으로 남깁니다.

카테고리 바로가기A 위치·시작 · B 스테이징·커밋 · C 원격·push·인증 · D 브랜치·병합 · E PR · F 보안 · G 되돌리기 SOS

— 페이지 내 검색(Ctrl+F)으로 복구 명령까지 찾고 싶을 때 먼저 누르세요 (Ch.5의 케이스 카드도 함께 열립니다)

A. 위치·시작

A. 위치·시작여기는 git 저장소가 아니라고 합니다 fatal: not a git repository (or any of the parent directories): .git ▸ 원인을 추리한 뒤 펼쳐 확인
원인

지금 서 있는 폴더가 git 저장소가 아닙니다 — 프로젝트 폴더 밖에서 git 명령을 실행했습니다(대개 clone 뒤 cd를 빠뜨림).

복구
pwd                       # 지금 위치 확인
cd ~/work/저장소이름       # 있는 저장소로 '들어가기' — 이게 답입니다
git status

❌ 이 에러에 git init으로 답하지 마세요. init은 "없는 저장소를 새로 만드는" 명령이라, 여기서 실행하면 족보 없는 빈 저장소가 시작되어 나중에 PR 버튼이 안 뜨는 사고로 번집니다(아래 고아 브랜치 카드). 답은 언제나 cd로 들어가는 것입니다.

예방

git 명령 전 확인 의식(Ch.2-6) + ls를 몸에 붙입니다.

A. 위치·시작clone은 됐는데 git 명령이 안 먹힙니다 fatal: not a git repository (or any of the parent directories): .git ▸ 원인을 추리한 뒤 펼쳐 확인
원인

위 케이스와 같은 에러의 다른 원인 — git clone은 새 하위 폴더를 만들 뿐, 그 안으로 이동시켜 주지는 않습니다.

복구
ls               # clone이 만든 폴더 이름 확인
cd 저장소이름     # 그 폴더 안으로 이동
git status
예방

clone 직후에는 반드시 cd 저장소이름이 한 세트입니다.

A. 위치·시작cd 하려는 폴더가 없다고 합니다 bash: cd: git-practice: No such file or directory ▸ 원인을 추리한 뒤 펼쳐 확인
원인

이동하려는 폴더가 아직 없습니다 — clone을 건너뛴 채 cd부터 실행했거나, ~/work 같은 작업 폴더를 아직 만들지 않은 경우입니다.

복구
ls               # 폴더가 정말 없는지 확인
# 작업 폴더 자체가 없다면: mkdir -p ~/work && cd ~/work
# clone을 건너뛴 것이라면: 본인 저장소 주소로 clone부터
git clone https://github.com/내아이디/내저장소.git
cd 내저장소
예방

"clone 먼저, cd는 그다음" 순서를 지킵니다.

A. 위치·시작git status에 프로젝트가 아니라 바탕화면 폴더가 보입니다 Untracked files: Desktop/ Documents/ Downloads/ ▸ 원인을 추리한 뒤 펼쳐 확인
원인

홈 폴더 같은 잘못된 위치에서 git init을 실행해 그 폴더 전체가 저장소가 됐습니다.

복구
pwd
git rev-parse --show-toplevel   # 저장소 루트가 정말 홈 폴더인지 확인
mv .git .git-home-mistake-backup   # 즉시 삭제하지 않고 되돌릴 수 있게 격리
git status   # "not a git repository"가 나오면 격리 성공
cd 프로젝트폴더

반드시 출력된 저장소 루트가 홈 폴더와 정확히 같을 때만 격리하세요. 백업 삭제는 프로젝트가 정상임을 확인한 뒤 진행합니다.

예방

git init·clone 전에 pwd로 위치부터 확인합니다.

A. 위치·시작● push는 됐는데 PR 버튼이 안 뜹니다 — 커밋에 (root-commit)이 보였습니다 첫 커밋: [feature/xxx (root-commit) c2e63f3] … push 때: fatal: 'origin' does not appear to be a git repository PR 화면: There isn't anything to compare. …entirely different commit histories ▸ 원인을 추리한 뒤 펼쳐 확인 — 오늘 가장 많이 나오는 사고
원인

한 줄기 연쇄입니다: clone 뒤 cd를 빠뜨림 → "not a git repository"가 뜸 → 그걸 git init으로 처방 → 팀 저장소와 족보가 없는 별개의 빈 저장소가 시작됨. 그래서 ① 첫 커밋에 (root-commit)이 붙고 ② origin이 없어 push가 막히며(손으로 remote add하면 브랜치는 올라가지만) ③ main과 공통 조상이 없어 PR 화면이 entirely different commit histories비교 자체를 거부합니다. 세 증상이 전부 같은 원인(cd 누락 → init)을 가리킵니다.

복구
# 1) 사고로 생긴 저장소를 지우지 말고 격리 (작업한 파일은 폴더에 그대로 남습니다)
git log --oneline          # 첫 줄에 (root-commit)이 보이면 확진
mv .git .git-accident-backup
# 2) 팀 저장소를 '제대로' 복제하고 그 안으로 들어가기
git clone https://github.com/aihuman-7th/본인팀저장소.git
cd 본인팀저장소
# 3) 새 브랜치를 만들고, 아까 만든 파일을 이 저장소로 옮겨 담기
git switch -c feat/intro-본인아이디
mv ../내파일 .
git add . && git commit -m "feat: 파일 추가"
git push -u origin HEAD    # 이제 origin은 clone이 넣어둔 것 — 손으로 add할 필요 없음

이미 고아 브랜치를 원격에 올렸다면 git push origin --delete feature/xxx로 지워 혼선을 없앱니다.

예방

clone과 cd는 한 세트 · "not a git repository"에 git init으로 답하지 않기 · 커밋 줄 맨 앞에 (root-commit)이 보이면 "clone이 아니라 init에서 출발했구나"로 바로 확진.

B. 스테이징·커밋

B. 스테이징·커밋커밋할 게 없다고 합니다 nothing to commit, working tree clean ▸ 원인을 추리한 뒤 펼쳐 확인
원인

에러가 아니라 "모든 변경이 이미 커밋된 깨끗한 상태"라는 보고입니다 — 대부분 정상입니다.

복구
git log --oneline   # 방금 커밋이 기록됐는지 확인
git status          # 고친 게 있는데도 이 메시지면: 편집기 저장(Ctrl+S, macOS는 Cmd+S) 여부와 pwd 확인
예방

addstatus 확인 → commit 순서를 습관으로 만듭니다.

B. 스테이징·커밋add 하려는 파일을 못 찾습니다 fatal: pathspec 'comment.js' did not match any files ▸ 원인을 추리한 뒤 펼쳐 확인
원인

현재 폴더에 그 이름의 파일이 없습니다 — 오타이거나, 다른 폴더에 서 있습니다.

복구
pwd              # ① 지금 어디인가
ls               # ② 파일이 여기 있는가
cd 프로젝트폴더    # ③ 올바른 폴더로 이동
git add comment.js
예방

파일명은 Tab 자동완성으로 입력하면 오타가 사라집니다.

B. 스테이징·커밋누구인지 알려달라며 커밋을 거부합니다 Author identity unknown *** Please tell me who you are. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

이 PC의 git에 이름·이메일이 등록되지 않아 "이 커밋을 누가 했는지" 기록할 수 없는 상태입니다. Windows는 이 에러와 함께 커밋이 거부되지만, macOS에서는 에러 없이 컴퓨터 계정 이름으로 서명된 커밋이 만들어지는 경우가 많습니다 — GitHub 잔디·커밋 크레딧에 연결되지 않으므로 에러가 안 떠도 반드시 설정하세요.

복구
# 아래 두 줄은 본인 이름·GitHub에 연결된 이메일로 바꿔 입력하세요 (그대로 치면 전원이 홍길동이 됩니다)
git config --global user.name  "본인이름"
git config --global user.email "github연결이메일"
git commit -m "feat: 첫 커밋"   # 다시 커밋
예방

새 PC에서는 초기 설정(Ch.2-2)을 가장 먼저 실행합니다 — 한 번이면 끝입니다.

B. 스테이징·커밋커밋했더니 이상한 화면(Vim)에 갇혔습니다 # Please enter the commit message for your changes. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

-m 없이 git commit만 치면 메시지를 받으려고 기본 편집기(Vim)가 열립니다 — 고장이 아닙니다.

복구
# Vim이 열렸다면: Esc 키 → :q! 를 입력 → Enter (저장 없이 탈출)
# Ch.2-2에서 code --wait를 설정했다면 Vim 대신 VS Code 탭이 열립니다 — 저장하지 않고 탭을 닫으면 커밋 취소
# "Aborting commit due to empty commit message."가 뜨면: 편집기를 못 찾았거나 메시지가 빈 것 — 아래처럼 -m으로
git commit -m "feat: 커밋 메시지"  # -m과 함께 다시
예방

커밋은 항상 -m "메시지"와 함께 실행합니다.

B. 스테이징·커밋index.lock이 있다며 커밋이 안 됩니다 fatal: Unable to create '.git/index.lock': File exists. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

index는 ②스테이지(Ch.1-4)의 git 내부 파일명입니다. 이전 git 작업이 비정상 종료되면서(Vim 강제 종료·Ctrl+C·터미널 여러 개) 스테이지 잠금 파일이 남은 것입니다.

복구
# 먼저 확인: 에디터(VS Code 등)·다른 터미널에서 git 작업이 진행 중이지 않은지
# — 작업이 끝났는데 남아 있는 파일일 때만 삭제합니다
rm .git/index.lock
git commit -m "feat: 커밋 메시지"
예방

-m 옵션을 쓰고, git 명령 도중 강제 종료하지 않습니다.

B. 스테이징·커밋-m 뒤에 파일명을 적었더니 파일명이 메시지가 됐습니다 [main a1b2c3d] comment.js ▸ 원인을 추리한 뒤 펼쳐 확인
원인

-m 뒤는 파일명이 아니라 "이 커밋의 설명 메모"입니다 — 파일 선택은 add의 일입니다.

복구
git commit --amend -m "feat: 댓글 기능 추가"   # push 전이면 메시지만 교체
예방

"파일은 add에서, 메시지는 commit에서" — 역할 분담을 기억합니다.

B. 스테이징·커밋add 했더니 LF will be replaced by CRLF 경고가 뜹니다 warning: in the working copy of 'hello.txt', LF will be replaced by CRLF ▸ 원인을 추리한 뒤 펼쳐 확인
원인

에러가 아니라 줄바꿈 변환 예고입니다. Windows(CRLF)와 macOS(LF)는 줄바꿈 문자가 달라서, Git for Windows가 기본 설정(core.autocrlf=true)대로 "저장할 때 LF로, 꺼낼 때 CRLF로 자동 변환하겠다"고 알려주는 정상 동작입니다.

복구

없습니다 — 무시하고 그대로 진행하면 됩니다.

예방

팀 전체가 같은 설정을 유지하면 됩니다: Windows는 git config --global core.autocrlf true 유지, macOS는 git config --global core.autocrlf input 권장. 이 설정이 팀원마다 다르면 "한 줄만 고쳤는데 diff가 파일 전체로 뜨는" 현상이 생길 수 있습니다 — 그래서 Ch.2-2 초기 설정에 올려 두었습니다. 팀 전원이 프로젝트 첫날 함께 맞추세요.

B. 스테이징·커밋한글 파일명이 숫자 코드로 깨져 보입니다 Untracked files: "\354\236\220\352\270\260\354\206\214\352\260\234.md" ▸ 원인을 추리한 뒤 펼쳐 확인
원인

git이 한글 같은 비ASCII 파일명을 8진수 코드로 표시하는 기본 동작입니다 — 파일이 깨진 것이 아니라 표시만 그렇습니다.

복구
git config --global core.quotepath false
git status   # 한글 파일명이 제대로 보입니다
예방

초기 설정(Ch.2-2)과 함께 실행해 두면 다시 만나지 않습니다.

B. 스테이징·커밋커밋했는데 잔디가 안 자라요 — 아바타도 회색입니다 push는 성공 — GitHub에서 내 커밋의 아바타가 회색 원, 프로필 잔디에도 반영 안 됨 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

에러가 나지 않는 사고입니다 — git의 user.email이 GitHub 계정에 연결된 이메일과 달라서, push는 되지만 커밋이 내 계정과 연결되지 않습니다. 잔디·커밋 크레딧은 이메일로 매칭됩니다.

복구
git log -1 --format='%an %ae'   # 최근 커밋에 기록된 이름·이메일 확인
# GitHub → Settings → Emails 의 등록 이메일과 대조한 뒤
git config --global user.email "GitHub에-연결된-이메일-또는-noreply-주소"
# 이미 쌓인 과거 커밋: 커밋에 적힌 그 이메일을 GitHub 계정(Settings → Emails)에 추가하면 소급 연결됩니다
# 주의: private 저장소 커밋은 여기까지 해도 잔디에 안 보입니다 — 아래 예방의 Private contributions 체크까지
예방

초기 설정(Ch.2-2)의 이메일을 GitHub에 연결된 이메일 또는 Settings → Emails의 noreply 주소로 입력합니다. 단 팀 저장소는 private이라 이메일을 맞춰도 기본 설정에서는 잔디에 표시되지 않습니다 — 프로필 잔디 그래프 우측 상단의 Contribution settings → Private contributions를 체크해야 private 기여가 잔디에 보입니다(기여 개수만 표시되고 저장소 이름은 계속 비공개).

C. 원격·push·인증

C. 원격·PUSHpush가 거부됩니다 (fetch first) ! [rejected] main -> main (fetch first) ▸ 원인을 추리한 뒤 펼쳐 확인
원인

원격에 내 로컬에 없는 커밋이 있습니다 — 팀원이 나보다 먼저 push했습니다.

복구
git pull    # 원격의 새 커밋을 먼저 합치기
git push    # 충돌이 나면 7-2 절차로 해결 후 push

만약 fatal: Need to specify how to reconcile divergent branches가 뜨면 git pull --no-rebase로 실행하세요 (Ch.2-2의 초기 설정을 하면 안 뜹니다).

예방

작업 시작 전·push 전 git pull 습관이 이 에러의 백신입니다.

C. 원격·PUSHpull이 거부됩니다 (내 수정이 덮어써진다고 합니다) error: Your local changes to the following files would be overwritten by merge: README.md ▸ 원인을 추리한 뒤 펼쳐 확인
원인

커밋하지 않은 수정이 있는 채로 git pull을 실행했습니다. 원격의 새 커밋을 합치면 그 수정이 덮여 사라질 수 있어 git이 작업 내용을 지키려고 막은 것입니다 — D. 브랜치 이동 거부와 같은 원리의 pull 버전입니다.

복구
# 길 A. 작업이 완성됐다면 — 커밋 후 pull
git add README.md
git commit -m "feat: 소개 행 수정"
git pull
# 길 B. 아직 어중간하다면 — stash로 치워 두고 pull
git stash push -u -m "pull 전 임시 보관"
git pull
git stash pop    # 치워 둔 수정 복원 — 충돌이 나면 7-2 절차로 해결
예방

pull 전 git status — "매일 아침 git pull" 전에 어제의 미커밋 변경이 남아 있지 않은지 확인합니다.

C. 원격·PUSH명령을 쳤더니 usage: 로 항의합니다 usage: git remote add [<options>] <name> <url> ▸ 원인을 추리한 뒤 펼쳐 확인
원인

명령 문장에서 단어가 빠졌습니다 — git 명령은 "git + 동사 + 대상"의 3단어 문장이고, 빠진 자리가 있으면 사용법을 출력하며 멈춥니다.

복구
# usage: 줄의 < > 괄호 자리가 곧 빠뜨린 단어입니다
git remote add origin https://github.com/내아이디/git-practice.git
예방

Enter 전에 "동사와 대상이 다 있나" 한 번 훑어봅니다.

C. 원격·PUSHPermission denied (publickey) git@github.com: Permission denied (publickey). ▸ 원인을 추리한 뒤 펼쳐 확인
원인

SSH 주소를 쓰는데 이 PC의 SSH 키가 GitHub에 등록돼 있지 않습니다.

복구
# HTTPS로 시작했다면 — 주소가 git@…라면 HTTPS로 전환
git remote set-url origin https://github.com/내아이디/git-practice.git
# SSH를 쓰려면: 공개키 등록(Ch.2-4) 후 테스트
cat ~/.ssh/id_ed25519.pub   # 복사 → GitHub Settings → SSH and GPG keys
ssh -T git@github.com
예방

HTTPS(브라우저 인증)로 시작하고, SSH는 Ch.2-4 절차대로 등록한 뒤에 씁니다.

C. 원격·PUSHpush 목적지가 없다고 합니다 fatal: No configured push destination. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

원격 저장소(origin)가 연결되지 않았습니다 — fatal: 'origin' does not appear to be a git repository도 같은 뿌리입니다.

복구
git remote -v      # 아무것도 안 나오면 연결이 없는 것
git remote add origin https://github.com/내아이디/git-practice.git
# 주소가 이상하게 등록돼 있다면(예: git@github.com:.git — 자리표시자를 안 채운 채 실행한 흔적):
#   git remote set-url origin 올바른주소  로 교체
git push -u origin main   # -u: 다음부터는 git push만으로 OK
예방

새 프로젝트는 clone으로 시작하면 origin이 자동 연결됩니다.

C. 원격·PUSH100MB 초과 파일이라 push가 거부됩니다 remote: error: GH001: Large files detected. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

모델 파일(.gguf 등) 같은 대용량 파일이 커밋에 들어갔습니다 — GitHub은 파일당 100MB까지만 받습니다.

복구
git rm --cached models/qwen3.5.gguf   # 추적만 제거 (파일은 유지)
echo "*.gguf" >> .gitignore           # >는 새로 쓰기, >>는 파일 끝에 덧붙이기
git add .gitignore
git commit --amend --no-edit          # 방금 커밋에서 대용량 파일 제외
git push
# 여러 커밋 전에 들어갔다면: 이력 정리가 필요하니 상황 전체를 AI에게 설명하세요
예방

모델·오디오·DB 파일은 프로젝트 첫날 .gitignore(Ch.2-5)에 등록합니다.

C. 원격·PUSH브라우저 인증에 실패했습니다 fatal: Authentication failed for 'https://github.com/…' ▸ 원인을 추리한 뒤 펼쳐 확인
원인

인증 창을 닫았거나, 계정 비밀번호를 입력했습니다 — GitHub은 비밀번호 push를 받지 않고 브라우저 로그인(또는 토큰)으로 인증합니다.

복구
git push   # 다시 시도 → 뜨는 브라우저 창에서 GitHub 로그인
# macOS에서 브라우저 창이 원래 안 뜨는 경우: 고장이 아닙니다 —
#  기본 git에는 GCM이 없어서입니다. Ch.2-3의 macOS 인증 안내
#  (GCM 설치 / gh auth login / PAT) 중 하나로 해결
# 창이 안 뜨면(Windows): 저장된 옛 자격 증명을 지우고 재시도
#  Windows: 자격 증명 관리자 → git:https://github.com 제거
#  macOS:   키체인 접근 → github.com 항목 제거
예방

작업 전 브라우저에서 GitHub 로그인(macOS는 Ch.2-3의 인증 준비까지)을 미리 끝내 둡니다.

C. 원격·PUSHclone하려는데 저장소를 찾을 수 없다고 합니다 remote: Repository not found. fatal: repository 'https://github.com/aihuman-7th/proj1-A.git/' not found ▸ 원인을 추리한 뒤 펼쳐 확인
원인

"주소 오타"로만 보이지만 원인은 세 갈래입니다 — GitHub은 권한이 없는 private 저장소도 "없다"고 답하기 때문입니다(저장소 존재 여부 자체를 숨기는 동작). ① URL 오타 — 예시의 내아이디를 그대로 복사한 경우 포함 ② org 초대 미수락 상태로 private 팀 저장소에 접근 ③ 등록 폼 누락 등으로 아직 org 멤버 등록 자체가 안 된 상태.

복구
# ① 주소 재확인 — 손으로 치지 말고 저장소 페이지의 초록 Code 버튼에서 다시 복사
# ② 브라우저에서 github.com/aihuman-7th 접속 → 상단 초대 배너가 보이면 수락
# ③ 그래도 안 되면: 등록 폼 제출 여부를 확인하고 강사에게 등록 확인을 요청
git clone https://github.com/aihuman-7th/본인팀repo.git
예방

clone 주소는 항상 저장소 페이지 Code 버튼에서 복사하고, 등록 폼 제출·org 초대 수락을 미리 마칩니다.

C. 원격·PUSHpush가 403 권한 거부로 막힙니다 remote: Permission to aihuman-7th/proj1-A.git denied to 내아이디. (403) ▸ 원인을 추리한 뒤 펼쳐 확인
원인

이 저장소에 쓰기(write) 권한이 없습니다 — 다른 팀의 저장소를 clone해서 push했거나, 본인 팀 저장소라도 collaborator 등록·초대 수락이 아직 안 된 상태입니다. 우리 org는 서로의 저장소를 읽을 수는 있지만 push는 본인 팀 저장소에만 됩니다 — 그래서 clone은 되는데 push만 거부됩니다.

복구
git remote -v   # URL의 팀 이름이 내 팀이 맞는지 확인
# 다른 팀 저장소였다면: 본인 팀 저장소를 다시 clone해서 작업
# 본인 팀 저장소가 맞다면: 초대 수락 여부 확인 후, 강사에게
#  collaborator 등록 확인을 요청
예방

clone 주소는 항상 본인 팀 저장소 페이지에서 복사합니다.

D. 브랜치·병합

D. 브랜치·병합브랜치 이동이 거부됩니다 error: Your local changes to the following files would be overwritten by checkout: ▸ 원인을 추리한 뒤 펼쳐 확인
원인

커밋하지 않은 변경이 있는 채로 이동하면 그 변경이 덮여 사라질 수 있어 git이 막아 준 것입니다. (에러 속 checkout은 switch의 옛 이름입니다.)

복구
git branch --show-current   # 원래 브랜치 이름을 먼저 기록
git stash push -u -m "브랜치 이동 전 임시 보관"
git switch main            # 필요한 확인·동기화 작업
git switch 원래브랜치       # 변경을 만든 브랜치로 돌아온 뒤
git stash pop              # 다시 꺼내기 — 충돌 가능

stash pop을 main에서 실행하면 feature 변경이 main에 풀립니다. 반드시 원래 브랜치로 돌아온 뒤 꺼내세요.

예방

브랜치 이동 전 git status로 미커밋 변경이 없는지 확인합니다. 같은 원리로 pull이 거부되는 쌍둥이 케이스는 C. 원격의 "pull이 거부됩니다" 카드입니다.

D. 브랜치·병합detached HEAD 상태라고 합니다 You are in 'detached HEAD' state. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

브랜치가 아니라 과거 커밋(해시·태그)을 직접 checkout해서(checkout은 switch의 옛 이름), HEAD(지금 서 있는 커밋을 가리키는 이름표)가 브랜치에서 떨어져 나온 상태입니다. 지금 커밋하면 어느 브랜치에도 속하지 않게 됩니다.

복구
# 구경만 했고 보존할 변경·커밋이 없다면
git switch main

# detached 상태에서 커밋했다면 main으로 이동하지 말고 즉시
git switch -c fix/rescue-work   # 현재 커밋을 새 브랜치에 보존
예방

과거 커밋은 구경만 하고, 보고 나면 바로 git switch main으로 돌아옵니다.

D. 브랜치·병합main에 직접 커밋해버렸습니다 [main 1a2b3c] feat: 대시보드 추가 — push해도 막히지 않고 그냥 올라감 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

브랜치를 만들지 않고 main에서 커밋했습니다. 커밋 메시지 앞의 [main …]이 신호이고, Free 플랜 org는 원격이 막아주지 않습니다 — 에러 없이 그냥 올라가버린 것이 사고입니다. main 직접 push 금지 규칙 위반이며, 주간 리포트에서 드러납니다.

복구
# A. 아직 push 전이라면 — 커밋을 브랜치로 옮기고 main 되돌리기
git status                       # ⓪ 미커밋 변경이 있으면 먼저
git stash push -u -m "main 복구 전 임시 보관"
git branch feat/작업             # ① 지금 커밋을 새 브랜치로 복사
git fetch origin                 # ② 지금 origin/main을 최신으로 맞춘 뒤 되돌립니다
git reset --hard origin/main    # ③ main을 원격 상태로 (커밋은 ①에 안전)
git switch feat/작업             # ④ 브랜치에서 push → PR로 진행
git stash pop                   # ⓪에서 보관한 것이 있을 때만 복원
# reset --hard는 커밋을 브랜치에 보존하고 미커밋 변경을 stash한 뒤에만 쓰는 예외입니다

# B. 이미 push했다면 — 이력을 지우지 않고 취소 커밋으로
git revert 해시                  # 취소 커밋 생성 → push 후 PR로 재작업
예방

작업 첫 명령을 git switch -c feat/…로 고정합니다 — main은 받기만 하는 곳.

D. 브랜치·병합merge 했는데 main에는 아무것도 없습니다 Merge made by the 'ort' strategy. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

merge는 "지금 서 있는 브랜치로 가져오는" 명령입니다 — feature에서 실행하면 feature가 main을 흡수할 뿐(해롭진 않지만) main에는 반영되지 않습니다. (증상 줄의 'ort'는 git 2.34 이상의 기본 병합 전략 이름입니다.)

복구

개인 연습 저장소 기준입니다 — 팀 저장소라면 merge 대신 push 후 PR로 합류하세요.

git switch main        # ① 결과를 '받을' 브랜치로 먼저 이동
git pull               # ② 원격 main 최신화
git merge feat/login   # ③ feature를 main에 병합
git push               # ⚠️ 개인 연습 저장소에서만 — 팀 저장소는 이 줄 대신 브랜치 push 후 PR로 (main 직접 push 금지)
예방

병합 전 git branch*가 어디 있는지 확인 — "받을 곳으로 이동해서 merge". Merge 방향 원칙은 Ch.4-3.

D. 브랜치·병합팀원의 브랜치를 이어받아야 합니다 팀원 부재로 feat/login 작업을 내가 이어받아야 함 — 내 로컬 git branch 목록에는 그 브랜치가 없음 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

그 브랜치는 원격(origin)에는 있지만 아직 내 로컬로 가져오지 않은 상태입니다. 파일만 복사해 새 브랜치를 만들면 팀원의 커밋 이력과 크레딧이 소실되므로, 원격 브랜치를 그대로 가져와 이어서 작업합니다.

복구
git fetch origin        # 원격 브랜치 목록 최신화
git switch feat/login   # 원격에 같은 이름이 있으면 자동으로 연결해 가져옵니다
# 이어서 작업 → 커밋 → git push — 같은 브랜치이므로 열려 있던 PR이 자동 갱신됩니다 (Ch.5 ⑦)
예방

인수인계·페어 작업은 파일 복사가 아니라 브랜치 인계로 — 팀원의 커밋 이력과 리뷰 이력이 그대로 보존됩니다.

E. PR

Ch.5에서 더 보기
PR 단계별 문제와 해결 방법은 Ch.5 PR 10단계에 정리돼 있습니다. 여기서는 가장 자주 만나는 세 가지만 다시 보여줍니다.
E. PRPR을 열었는데 비교할 변경이 없다고 합니다 There isn't anything to compare. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

base와 compare를 반대로 지정했습니다 — 화살표는 compare(내 브랜치)의 변경을 base(main)로 보내는 방향입니다.

복구
base: main  ←  compare: feat/login   # 이 방향으로 다시 선택
# 이미 반대로 생성했다면 그 PR은 Close 후 올바른 방향으로 재생성
예방

Create 누르기 전 "base: main"인지 확인 — 자세한 흐름은 Ch.5 ④단계.

E. PR리뷰 수정을 반영하려고 PR을 또 만들었습니다 A pull request already exists for aihuman-7th:feat/login. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

PR은 브랜치를 따라다닙니다 — 같은 브랜치에 커밋을 추가해 push하면 기존 PR이 자동 갱신되므로 새 PR이 필요 없습니다.

복구
# 실수로 만든 중복 PR은 Close 하고, 리뷰받던 브랜치에서:
git switch feat/login
git add login.py && git commit -m "fix: 리뷰 반영 — 예외 처리 추가"
git push        # 기존 PR에 커밋이 자동으로 추가됩니다
예방

"리뷰 반영 = 같은 브랜치에 추가 커밋 push" — Ch.5 ⑦단계의 핵심 문장입니다.

E. PR로컬 main이 옛날 버전입니다 Your branch is behind 'origin/main' by 3 commits, and can be fast-forwarded. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

머지 후 로컬 main을 pull하지 않고 계속 썼습니다 — 구버전 main에서 새 브랜치를 만들면 다음 PR에 충돌이 예약됩니다.

복구
git switch main
git pull
git switch -c feat/next-work   # 최신 main에서 분기
예방

머지 직후 루틴: 원격 브랜치 삭제 → git switch main && git pullCh.5 ⑩단계.

F. 보안

F. 보안.env가 GitHub에 올라갔습니다 Secret Scan CI ✕ 실패 (gitleaks: leaks found) — 또는 저장소 파일 목록에 .env가 보임 ▸ 원인을 추리한 뒤 펼쳐 확인
원인

.gitignore에 .env를 등록하기 전에 git add .을 해서 API 키가 인터넷에 노출됐습니다.

복구
# 0) 키부터 즉시 재발급 — 파일을 지워도 이력에 남으므로 재발급이 진짜 해결
echo ".env" >> .gitignore
git rm --cached .env        # 추적에서 제거 (로컬 파일은 유지)
git add .gitignore
git commit -m "fix: .env 추적 제거"
git push
예방

저장소를 만들면 .gitignore부터 첫 커밋에 포함합니다(원리는 Ch.12).

F. 보안.gitignore에 적었는데 계속 추적됩니다 Changes not staged for commit: modified: .env ▸ 원인을 추리한 뒤 펼쳐 확인
원인

.gitignore는 "아직 추적하지 않는" 파일만 거릅니다 — 이미 add·commit된 파일은 계속 추적됩니다.

복구
git rm --cached .env   # 추적 목록에서만 제거
git commit -m "chore: .env 추적 중단"
git push
예방

무시할 파일은 "처음부터" .gitignore에 — 추적 시작 후에는 rm --cached가 필요합니다.

F. 보안.gitignore가 있는데 웹 업로드로 올라갔습니다 Add files via upload ▸ 원인을 추리한 뒤 펼쳐 확인
원인

.gitignore는 로컬 git add만 거릅니다 — GitHub 웹의 "Upload files"는 검사 없이 그대로 커밋합니다(증상 줄은 그때 자동으로 붙는 커밋 메시지).

복구
# 민감 파일이면 키 재발급부터. 그다음 로컬에서:
git pull                    # 웹 커밋을 로컬로 가져오고
git rm --cached .env        # 추적 제거 후
git commit -m "fix: 웹 업로드된 .env 제거"
git push
예방

프로젝트 파일은 항상 로컬 add → commit → push 경로로만 올립니다.

G. 되돌리기 SOS

G. 되돌리기파일을 지운 커밋을 이미 push했습니다 delete mode 100644 src/app.py ▸ 원인을 추리한 뒤 펼쳐 확인
원인

삭제도 하나의 변경으로 커밋됐을 뿐입니다 — 이전 커밋에는 파일이 그대로 살아 있습니다.

복구
git log --oneline               # 삭제가 들어간 커밋 해시 확인
git revert a1b2c3d              # 그 커밋을 '취소하는 새 커밋' 생성 (push 후에도 안전)
git push
# 파일 하나만 살릴 때: git restore --source=b2c3d4e src/app.py  (b2c3d4e = 삭제 전 커밋)
예방

push된 이력은 지우지 말고 revert로 "취소 커밋"을 쌓는 게 원칙입니다.

G. 되돌리기커밋 메시지를 잘못 적었습니다 a1b2c3d asdf ▸ 원인을 추리한 뒤 펼쳐 확인
원인

push 전이면 마지막 커밋 메시지는 자유롭게 고칠 수 있지만, push 후에는 팀과 이력을 공유한 상태입니다.

복구
git commit --amend -m "feat: 로그인 기능 추가"   # push 전에만
# 이미 push했다면: 그대로 두는 게 안전 — force push는 팀 이력을 깨뜨립니다(강사에게 문의)
예방

커밋 전 메시지를 한 번 소리 내 읽고, 타입 prefix(feat:·fix:)를 붙이는 습관을 들입니다.

G. 되돌리기머지를 되돌리고 싶은데 revert가 거부됩니다 error: commit a1b2c3d is a merge but no -m option was given. ▸ 원인을 추리한 뒤 펼쳐 확인
원인

머지 커밋은 부모가 둘이라, 어느 쪽 기준으로 되돌릴지 -m으로 지정해야 합니다.

복구
git log --oneline        # 머지 커밋 해시 확인
git revert -m 1 a1b2c3d  # -m 1 = main 쪽 부모 기준으로 취소
git push
예방

머지 전 Approve·CI 확인 습관을 지키면 되돌릴 일 자체가 줄어듭니다 — 그래도 복구는 가능하니 당황하지 않기.

7-7. 여기 없는 에러를 만났을 때

AI에게 도움 요청하는 법
막히면 아래 세 가지를 한 번에 보내세요: ① 에러 메시지 전체pwd 결과하려던 일을 설명하는 한 문장. 이 정보가 있어야 AI가 지금 위치와 상황을 제대로 판단할 수 있습니다. AI가 준 명령도 바로 실행하지 말고, 무엇을 바꾸는 명령인지 먼저 확인하세요. 특히 reset --hardpush --force가 보이면 멈추고 강사에게 다시 확인합니다.
그래도 해결되지 않으면 Issue 남기기
AI로도 해결되지 않으면 팀 저장소의 Issues에 질문을 남기세요.
① 팀 저장소 상단 Issues 탭 → New issue 클릭
② 제목은 증상 요약 한 줄(예: "push 403 — 초대 수락 후에도 거부"), 본문에는 에러 메시지 전체·pwd 결과·하려던 일을 그대로 붙여넣기
③ 본문에 @아이디를 적으면(멘션) 그 사람에게 알림이 갑니다 — 팀원에게 먼저 물을 일이면 팀원 아이디를 멘션
Submit new issue 클릭 → 답변으로 해결되면 확인 댓글 후 Close — 코드 수정으로 해결되는 이슈라면 커밋 메시지의 Closes #번호(Ch.9-4)로 merge 시 자동으로 닫을 수도 있습니다

7-8. 자주 묻는 질문 (FAQ)

Q1. main 브랜치에 직접 커밋해버렸어요
git stash push -u -m "복구 전 임시 보관"   # ⓪ 미커밋 변경부터 보존 — 이게 없으면 아래 reset이 작업을 지웁니다
git branch rescue/my-work                 # ① 커밋을 새 브랜치로 보존
git fetch origin
git reset --hard origin/main              # ② main을 원격 상태로 — 커밋이 몇 개든 안전 (①에 보존됨)
git switch rescue/my-work                 # ③ 보존한 브랜치에서 push → PR로 진행
git stash pop                             # ⓪에서 "Saved…"가 떴을 때만

같은 상황의 상세 카드: Ch.7-6 D "main에 직접 커밋해버렸습니다" — 동일한 절차입니다.

Q2. 커밋 메시지를 잘못 입력했어요
git commit --amend -m "수정된 커밋 메시지"   # push 전이라면 수정 가능
# 이미 push했다면 강사에게 문의 (force push는 주의 필요)
Q3. 실수로 파일을 삭제한 커밋을 push했어요
git log --oneline                          # 삭제 전 커밋 확인
git checkout 커밋해시 -- 파일경로            # 특정 커밋의 파일 복구
# 예: git checkout abc1234 -- ch02/day06/code.py
Q4. PR을 머지하고 나서 브랜치는 어떻게 하나요?
# GitHub에서 PR 머지 후 "Delete branch" 클릭 (권장)
git checkout main
git branch -d ch02/day06-홍길동   # 로컬에서도 삭제
git fetch --prune                # 원격 브랜치 목록 정리
Q5. 커밋 전 작업이 사라질까 봐 브랜치 이동이 무서워요

git switch 전에 git stash를 습관화하세요. 보관 → 이동 → 복귀 → git stash pop 순서면 작업을 잃지 않습니다 (7-3 참고).

CH.8 · 5인 팀 협업 완전 가이드

역할 분담 → 브랜치 격리 → 일일 루틴 — 이 세 가지가 전부입니다

8-1. 팀 구성과 역할 분담 (AI 음성 에이전트 프로젝트 예시)

👑 팀장 — PM + Agent Architect

저장소 초기 세팅, 브랜치 보호 규칙, interfaces.py 설계. Sprint 계획, Issues/Milestone 관리, PR 최종 병합. LangGraph 전체 흐름 설계, 마일스톤 태깅, 발표 총괄.

🎤 팀원 A — Voice 엔지니어 (STT+TTS) src/stt/ · src/tts/

Whisper STT (로컬 → Groq), Kokoro/Qwen3-TTS. M1 마일스톤 핵심.

🧠 팀원 B — LLM + Prompt 엔지니어 src/llm/ · src/agent/

Ollama → API 전환 클라이언트, 프롬프트 설계, Tool Calling. M2 마일스톤 핵심.

📚 팀원 C — RAG 엔지니어 src/rag/

임베딩 비교 실험, ChromaDB, Cohere Rerank, RAG 품질 평가. M3 마일스톤 핵심.

🖥️ 팀원 D — 프론트엔드 + 배포 src/ui/ · 배포 설정

Streamlit UI, FastAPI, Docker Compose, Railway/Render 배포.

8-2. 브랜치 전략 (GitHub Flow)

main
├─ feature/stt-whisper-groq        (팀원 A)
├─ feature/llm-tool-calling        (팀원 B)
├─ feature/rag-bge-m3-embedding    (팀원 C)
├─ feature/ui-chat-interface       (팀원 D)
└─ release/m1                      (팀장, 마일스톤 직전 생성)
💡 feature 브랜치는 기능 단위로 작게
하나의 PR은 500줄 이하를 권장합니다. 크게 잡으면 리뷰가 어렵고 충돌이 늘어납니다.

8-3. 일일 협업 루틴

시점활동명령
매일 아침main 최신화git switch main && git pull origin main
작업 시작feature 브랜치git switch -c feature/xxx
작업 중 (1~2시간)자주 커밋git add -p && git commit -m '...'
작업 완료PR 생성git push + GitHub PR 생성

8-4. 역할별 실전 시나리오 4가지

시나리오 1 — 팀원 A: Groq Whisper 전환 (기본 흐름)
git switch main && git pull origin main
git switch -c feature/stt-groq-migration
git add src/stt/whisper_stt.py
git commit -m "feat: Groq Whisper API 클라이언트 추가"
git add .env.example
git commit -m "docs: GROQ_API_KEY 환경변수 예시 추가"
git push origin feature/stt-groq-migration
# PR 제목: 'feat: STT Groq API 마이그레이션 (8초 → 0.7초)'
시나리오 2 — 팀원 C: rebase로 충돌 해결
git switch feature/rag-bge-m3-embedding
git fetch origin && git rebase origin/main
# 충돌 해결 후
git add src/rag/retriever.py && git rebase --continue
git push --force-with-lease origin feature/rag-bge-m3-embedding
# --force-with-lease: 남의 커밋을 덮어쓰지 않는 안전한 force push
# 전제: 이 브랜치에 나 혼자만 커밋할 때 — 둘 이상이 함께 쓰는 브랜치에는 금지 (Ch.7-3의 rebase 규칙과 동일)
시나리오 3 — 팀원 D: 긴급 hotfix (stash 활용)
git stash push -m "UI 다크모드 작업 중"     # 하던 일 보관
git switch main && git pull origin main
git switch -c hotfix/streamlit-session-crash
git add src/ui/streamlit_app.py
git commit -m "fix: Streamlit session_state 초기화 누락 수정"
# ⚠️ 아래 직접 merge·push는 개인 연습 저장소 기준 — 팀 저장소에서는 hotfix도 push 후 PR로 (main 직접 push 금지)
git switch main && git merge --no-ff hotfix/streamlit-session-crash
git push origin main
git branch -d hotfix/streamlit-session-crash
git switch feature/ui-dark-mode && git stash pop   # 하던 일 복귀
시나리오 4 — 팀장: M1 마일스톤 릴리즈 (release + tag)
git switch main && git pull origin main
git switch -c release/m1
echo '1.0.0-m1' > VERSION
git add VERSION && git commit -m "chore: M1 마일스톤 버전 설정"
# 팀원 전체 최종 테스트 후
# ⚠️ 아래 직접 merge·push는 팀장이 팀 합의 하에 릴리즈 시점에만 쓰는 예외 — 평상시에는 PR 경유 (main 직접 push 금지)
git switch main
git merge --no-ff release/m1 -m "release: M1 마일스톤"
git tag -a m1-submit -m "M1 마일스톤 제출"
git push origin main && git push origin m1-submit

8-5. 충돌 없는 협업 5가지 규칙

규칙설명
1. 매일 아침 pullgit pull origin main 필수 — 팀원 작업과 격차 최소화
2. 파일 소유권 존중자기 담당 src/ 폴더만 수정 — 같은 파일 동시 수정 방지
3. PR은 작게하나의 PR = 하나의 기능 (500줄 이하 권장)
4. 인터페이스 먼저 합의interfaces.py에 함수 스펙 먼저 정의
5. 데일리 체크인매일 15분: 한 것 / 할 것 / 막힌 것

8-6. 모듈 간 인터페이스 합의 — interfaces.py

팀장이 최초 작성하고, 팀원들은 이 스펙에 맞게 구현합니다. 실제 구현 전에도 Mock으로 연동 테스트가 가능해집니다.

# src/interfaces.py — 팀장이 최초 작성
from dataclasses import dataclass, field

@dataclass
class STTResult:
    text: str
    language: str
    confidence: float
    segments: list[dict] = field(default_factory=list)

@dataclass
class RAGResult:
    answer: str
    sources: list[str]
    retrieval_scores: list[float]

# 실제 구현 전에도 Mock으로 연동 테스트 가능
def mock_transcribe(audio_path: str) -> STTResult:
    return STTResult(text='테스트', language='ko', confidence=0.95)

8-7. 과정 전체의 저장소 구조

조직(Organization): aihuman-7th
├── 강사 저장소: curriculum            ← 교안, 강의 자료, 과제 출제
├── 공용 저장소: project-template      ← 과제 템플릿
├── 수강생 저장소: {이름}-assignments  ← 개인 과제 제출
│     ├── ch01_python/day01/          ← README.md + spec.md + code.py
│     ├── ch02_llm/  ch03_rag/  ch04_agentic_ai/  ch05_productization/
│     └── README.md                   ← 본인 소개 + 포트폴리오 요약
└── 팀 저장소: team-{번호}-project     ← 팀 최종 프로젝트
💡 TIP
개인 저장소의 README.md는 나중에 취업할 때 가장 먼저 보이는 파일입니다. 수강 초반부터 꾸준히 업데이트하는 습관을 들이세요.

8-8. 주간 워크플로우 예시

요일활동
강사: 이번 주 과제 Issue 등록 + 강의 / 수강생: 과제 확인 + 오늘 분량 작업 + 커밋
화~목강의 + 실습 + 매일 커밋
목 오후PR 제출 마감 → 리뷰어 자동 배정 알림 (Ch.9-5)
금 오전상호 코드리뷰 작성 (최소 2개)
금 오후강사 피드백 세션 + 우수 PR 공유 + 주간 회고 제출
CH.9 · 저장소 운영 — .github/ 폴더와 자동화

팀 협업의 질은 저장소 첫날 설정이 결정합니다

.github/ 폴더는 팀장이 저장소를 초기화할 때 가장 먼저 설정해야 할 공간입니다. 브랜치 보호 규칙, 자동 CI 테스트, 이슈 템플릿이 여기에 삽니다.

9-1. 브랜치 보호 규칙 (Branch Protection Rules)

main 브랜치를 실수로 직접 push하거나 테스트를 건너뛰고 병합하는 사고를 막는 설정입니다. 설정 위치: 저장소 → Settings → Branches → Add branch protection rule → main

⚠️ Free 플랜 + private 저장소는 이 설정이 지원되지 않습니다
우리 org처럼 무료 플랜의 private 저장소에서는 브랜치 보호를 켤 수 없습니다 — 그 경우 이 표의 내용은 팀 규율로 운영하고(Ch.5 팀 규칙: Approve 없으면 merge 없음), public 저장소나 유료 플랜에서는 설정으로 강제하세요.
설정 항목권장값효과
Require a pull request before mergingON (Required approvals: 1)PR 없이 main 직접 push 차단
Require status checks to passON (CI 테스트 선택)테스트 실패 시 merge 차단
Require branches to be up to dateON최신 main 기준으로만 merge 허용
Do not allow bypassing the above settingsON팀장 포함 모두 적용
Include administratorsON팀장도 예외 없음
✅ 브랜치 보호는 팀 구성 첫날 설정하세요
프로젝트 중반에 설정하면 이미 쌓인 나쁜 습관(main 직접 커밋)을 바꾸기 어렵습니다. 저장소 생성 직후 바로 설정하세요.

9-2. GitHub Actions CI 파이프라인

PR을 생성하면 자동으로 테스트와 코드 검사가 실행됩니다. 사람이 리뷰하기 전에 기본 품질을 기계가 확인합니다.

용어의미비유
Workflow자동화 작업 전체 흐름 (.yml 파일)자동화 레시피 전체
Trigger (on)워크플로우를 실행시키는 조건"언제 시작할까?"
Job워크플로우 안의 작업 단위레시피 안의 조리 단계
StepJob 안의 개별 명령조리 단계 안의 세부 동작
Runner작업을 실행하는 가상 서버조리를 실제로 하는 주방
# .github/workflows/ci.yml
name: CI — pytest + lint
on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Python 3.11 설정
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: 의존성 설치
        run: pip install -r requirements.txt
      - name: 단위 테스트
        run: pytest tests/ -v --tb=short
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
      - name: 코드 스타일 검사
        run: |
          pip install flake8
          flake8 src/ --max-line-length=100 --exclude=__pycache__
      - name: .env 파일 미포함 확인
        run: |
          if find . -name '.env' -not -path './.git/*' | grep -q '.'; then
            echo 'ERROR: .env 파일이 커밋에 포함됐습니다!'
            exit 1
          fi
💡 GitHub Secrets 설정
Actions에서 API Key를 쓰려면 저장소 → Settings → Secrets and variables → Actions → New repository secret에 OPENAI_API_KEY 등을 등록하세요. 코드에 직접 넣으면 절대 안 됩니다. 브랜치 보호 규칙(9-1)에서 이 CI를 status check로 지정하면 테스트가 통과해야만 merge됩니다.

9-3. Issue 템플릿

# .github/ISSUE_TEMPLATE/bug_report.md
---
name: 버그 리포트
about: 버그를 발견했을 때 사용
labels: bug
---
## 버그 설명
(무엇이 잘못됐는지)
## 재현 방법
1. '...' 실행
2. '...' 입력
3. 오류 발생
## 예상 동작
(원래 어떻게 되어야 했는지)
## 실제 동작 / 오류 메시지
```
(오류 로그 붙여넣기)
```
## 환경
- OS: (예: Windows 11, macOS 14, Ubuntu 22.04)
- Python: (예: 3.11.5)
- LLM Provider: (예: Ollama / Groq)

9-4. Issue → 브랜치 → PR — 이슈 중심 작업 흐름

단계GitHub 동작명령 / 위치
이슈 생성할 작업을 이슈로 등록저장소 → Issues → New issue
이슈 → 브랜치이슈에서 직접 브랜치 생성이슈 페이지 우측 'Create a branch'
커밋 연결커밋 메시지에 #이슈번호 포함feat: RAG 구현 Closes #7
PR → 이슈 자동 닫기PR merge 시 이슈 자동 닫힘PR 본문에 Closes #7 입력
Projects 보드진행 상황 칸반으로 시각화저장소 → Projects → New project

Milestone으로 마일스톤 관리

GitHub → Issues → Milestones → New milestone으로 M1, M2, M3를 생성하고 이슈를 연결합니다.

MilestoneDue date포함 이슈 예시
M1 — STT+TTS+LLM2주차 금요일#1 Whisper STT 구현, #2 Kokoro TTS 구현, #3 LLM 통합
M2 — Tool Calling Agent4주차 금요일#8 날씨 도구 구현, #9 RAG 도구 연동
M3 — RAG + 개인화6주차 금요일#15 BGE-M3 임베딩, #16 Rerank, #17 UI 연동

9-5. n8n으로 운영 자동화 — PR 알림·리뷰어 배정·리마인더

수강생 수십 명의 PR을 수동으로 관리하면 운영이 불가능합니다. n8n(노코드 자동화 도구)으로 반복 작업을 자동화하는 4가지 워크플로우입니다. 운영자용 내용이지만, "GitHub Webhook → 처리 → 알림" 구조는 여러분이 앞으로 만들 모든 자동화의 원형입니다.

자동화 대상수동 시 부담자동화 후
PR 생성 알림강사가 매번 확인Discord 자동 알림
리뷰어 배정강사가 직접 지정랜덤 자동 배정 (작성자 제외 2명)
마감 리마인더강사가 수동 공지목요일 자동 DM
주간 리포트강사가 수집·정리자동 집계 → 강사 DM ("PR 제출률 90%, 평균 리뷰 3.2개")
워크플로우 구조와 세팅 순서 (펼쳐보기)
# 워크플로우 1 — PR 생성 → Discord 알림
[트리거] GitHub Trigger (Event: Pull Request opened)
   ↓
[처리] Set 노드 — "{{PR 작성자}} 님이 PR을 올렸습니다!" + 제목·URL 추출
   ↓
[출력] Discord 노드 — #코드리뷰 채널로 전송
// 워크플로우 2 — 리뷰어 자동 배정 (n8n Code 노드)
const students = ["hong-gildong", "kim-cheolsu", "lee-younghee" /* ... */];
const author = $input.item.json.pull_request.user.login;
const candidates = students.filter(s => s !== author);   // 작성자 제외
const shuffled = candidates.sort(() => Math.random() - 0.5);
const reviewers = shuffled.slice(0, 2);                  // 무작위 2명
return [{ json: { reviewers } }];

세팅 순서: ① n8n Credentials에 GitHub Personal Access Token 등록 → ② Discord 채널 웹훅 URL 등록 → ③ GitHub Trigger → Set/Code → Discord 노드 순으로 연결 → ④ GitHub 조직 Settings → Webhooks에 n8n webhook URL 등록 (Content type: application/json) → ⑤ 테스트 PR로 확인. 워크플로우 3(목요일 마감 리마인더)과 4(금요일 주간 리포트)는 Schedule Trigger로 시작하는 변형입니다.

Webhook이 작동하지 않으면: ① 워크플로우 Active 상태 확인 ② Payload URL 일치 확인 ③ GitHub → Webhook → Recent Deliveries에서 전송 상태 확인 ④ 로컬 n8n이면 ngrok http 5678로 외부 URL 생성.

CH.10 · AI 실험 이력 관리 — LangSmith + Git

"어떤 코드에서 어떤 프롬프트로 어떤 결과였는지" — 완전한 이력

일반 소프트웨어는 코드만 버전 관리하면 됩니다. AI 개발은 코드 외에도 프롬프트, 모델, 파라미터(temperature, chunk_size 등)가 성능을 결정합니다. Git과 LangSmith를 연계하면 실험의 전체 이력을 추적할 수 있습니다.

10-1. 왜 함께 써야 하는가

상황Git만 쓸 때Git + LangSmith 연계 시
프롬프트 성능 비교prompt_v2.txt로 바꿨는데 왜 좋아졌는지 모름commit hash → LangSmith run 추적 가능
모델 교체 효과 측정gpt-4o-mini로 바꿨는데 지표 비교 불가commit 전/후 run 비교로 정확한 수치 확인
오류 원인 추적로그가 없어 어떤 입력에서 망가졌는지 모름LangSmith traces에서 입력/출력 전체 재현
팀원 실험 공유"내 로컬에서는 됐어…" 반복LangSmith 프로젝트 공유로 같은 환경 확인

10-2. 커밋 hash를 LangSmith run에 기록하기

# src/llm/prompt_manager.py
import os
from langchain_core.prompts import ChatPromptTemplate

GIT_COMMIT = os.environ.get('GIT_COMMIT', 'unknown')

def get_system_prompt() -> str:
    """현재 Git 커밋과 연결된 프롬프트 반환"""
    with open('src/llm/prompts/system_prompt.txt') as f:
        return f.read().strip()

def run_with_tracking(user_input: str, llm) -> dict:
    """LangSmith에 Git 커밋 hash를 메타데이터로 기록"""
    prompt = ChatPromptTemplate.from_messages([
        ('system', get_system_prompt()),
        ('human', '{input}'),
    ])
    chain = prompt | llm
    # run_name에 커밋 hash 포함 → LangSmith에서 검색 가능
    return chain.invoke(
        {'input': user_input},
        config={
            'run_name': f'run-{GIT_COMMIT[:7]}',
            'metadata': {
                'git_commit': GIT_COMMIT,
                'prompt_version': 'v2.1',
            }
        }
    )

10-3. 커밋 메시지에 실험 결과 URL 기록

프롬프트를 변경할 때마다 커밋에 LangSmith 실험 결과를 기록하면, 나중에 "그 프롬프트가 왜 좋았는지"를 즉시 추적할 수 있습니다.

git switch -c feature/prompt-v2-korean
git add src/llm/prompts/system_prompt.txt
git commit -m "prompt: 시스템 프롬프트 v2 — 한국어 응답 품질 개선

변경 내용:
- 출력 형식을 '마크다운 → 평문'으로 변경
- Few-shot 예시 2개 추가 (AI 관련 Q&A)

실험 결과 (LangSmith):
- 이전: https://smith.langchain.com/runs/abc123
- 이후: https://smith.langchain.com/runs/def456
- 품질 점수: 3.2 → 4.1 (5점 척도)

Closes #22"

10-4. 실험 브랜치 전략 — experiment/*

프롬프트·모델 파라미터 실험은 experiment/* 브랜치에서 수행합니다. 결과가 좋으면 PR로 main에 반영하고, 아니면 브랜치째 삭제합니다.

브랜치 유형예시처리 방법
experiment/prompt-*experiment/prompt-cot-few-shot결과 좋으면 → PR로 main 반영
experiment/model-*experiment/model-qwen3-vs-llamaA/B 비교 후 채택 모델 결정
experiment/rag-*experiment/rag-chunk-size-512최적 파라미터 찾으면 PR 반영
git switch -c experiment/prompt-cot-few-shot
# 실험 중 자주 커밋 — 버전별 점수를 메시지에 남기기
git commit -m "experiment: CoT 프롬프트 v1 — LangSmith 결과 확인 필요"
git commit -m "experiment: CoT 프롬프트 v2 — few-shot 2개 추가"
git commit -m "experiment: CoT 프롬프트 v3 — 최종 (점수 4.3/5.0)"
# 결과가 좋으면 main에 PR
git push origin experiment/prompt-cot-few-shot
# 결과가 나쁘면 미련 없이 삭제
git switch main
git branch -D experiment/prompt-cot-few-shot
CH.11 · 포트폴리오와 GitHub 생태계

잘 관리된 GitHub 하나가, 자기소개서 10장보다 강합니다

AI 도입 이후 주니어 채용 시장은 빠르게 좁아지고 있습니다 — 채용 담당자들 사이에서 "GitHub 없으면 면접 없다"는 말이 나올 정도입니다. 그래도 기회는 있습니다. AI-native 스킬 + 실제 동작하는 프로젝트 포트폴리오를 가진 주니어는 오히려 수요가 늘고 있고, GitHub 생태계를 제대로 활용하는 것이 가장 확실한 차별화 전략입니다.

💡 생태계 전체 지도
레이어 1 — GitHub 내부 기능(무료): Pages(정적 사이트) · Actions(자동 테스트·배포) · Projects(칸반 보드) · Codespaces(브라우저 VS Code)
레이어 2 — 외부 연동 도구(무료/저가): Vercel(Next.js 배포) · Streamlit Cloud(Streamlit 앱) · Render(FastAPI 백엔드) · GitBook·MkDocs(기술 문서)

11-1. README 작성법 — 채용 담당자를 3분 안에 설득하기

채용 담당자는 GitHub에서 3분을 봅니다. README 첫 화면에서 무엇을·왜·어떻게가 보이면 클릭을 이어가고, 없으면 닫습니다.

보는 순서확인 항목판단 기준
1초레포 이름 + 설명(About)의미 있는 이름인가? 'project1'은 탈락
5초README 첫 단락"이게 뭔지" 바로 이해되는가?
20초데모 GIF 또는 스크린샷실제로 동작하는 프로젝트인가?
1분기술 스택 배지내가 찾는 기술을 쓰는가?
2분설치 방법내가 실행해볼 수 있는가?
3분커밋 이력일관되게 작업한 흔적이 있는가?

README 7단계 구조 — 이 순서를 지키세요

1
프로젝트 한 줄 소개 + 배지
# 📄 법률 문서 RAG 챗봇
계약서와 법률 문서에 대한 질문을 5초 안에 답변하는 AI 챗봇

![Python](https://img.shields.io/badge/Python-3.11-blue)
![FastAPI](https://img.shields.io/badge/FastAPI-0.110-green)
![LangChain](https://img.shields.io/badge/LangChain-0.2-orange)
2
데모 GIF 또는 배포 링크 — README 상단에

없으면 "실행 안 되는 프로젝트"로 인식됩니다. GIF는 Mac이면 QuickTime 녹화 → ffmpeg 변환, Windows면 ShareX. 10~30초면 충분합니다.

## 🎬 데모
[![Demo](docs/demo.gif)](https://your-app.streamlit.app)
> 라이브 데모: https://your-app.streamlit.app
3
핵심 기능 3~5개 — 기능 나열이 아니라 "사용자가 얻는 이점" 중심
## ✨ 핵심 기능
- 📄 **PDF/Word 문서 업로드** → 자동 벡터 인덱싱 (ChromaDB)
- 💬 **자연어 질문** → 출처 인용과 함께 정확한 답변 생성
- 🔄 **대화 기록 유지** — 이전 질문 맥락을 이어서 답변
4
기술 스택 표 — 레이어별로 구분 (무작위 나열은 감점)
## 🛠 기술 스택
| 레이어 | 기술 |
|--------|------|
| Frontend | Streamlit |
| Backend | FastAPI, Python 3.11 |
| AI/LLM | LangChain, GPT-4o-mini |
| Vector DB | ChromaDB |
| Deploy | Streamlit Cloud |
5
아키텍처 다이어그램 1장 — Mermaid면 충분
## 🏗 아키텍처
```mermaid
graph LR
A[사용자] -->|질문| B[Streamlit UI]
B -->|API 호출| C[FastAPI 서버]
C -->|임베딩 검색| D[(ChromaDB)]
C -->|LLM 호출| E[GPT-4o-mini]
```
6
설치·실행 방법 — 복붙하면 바로 실행되게
## 🚀 시작하기
git clone https://github.com/your-id/legal-rag-chatbot.git
cd legal-rag-chatbot
python -m venv .venv && source .venv/bin/activate
# Windows(Git Bash)는: source .venv/Scripts/activate
pip install -r requirements.txt
cp .env.example .env   # API 키 입력
streamlit run app.py
7
향후 개선 계획 (Roadmap) — "발전시킬 의지"의 신호
## 🗺 Roadmap
- [x] PDF 문서 RAG 구현
- [x] Streamlit Cloud 배포
- [ ] 한국어 임베딩 모델(KoSimCSE) 적용
- [ ] Docker 컨테이너화

README 감점 요소 5가지

나쁜 예문제점대안
레포 이름: project1, test, final무엇인지 알 수 없음legal-rag-chatbot 등 기능 명시
README: 'This is my project'정보가 없음위 7단계 구조로 작성
스크린샷 없음실행 여부 불분명최소 1장의 UI 스크린샷 필수
커밋 메시지: fix, test, asdf작업 내용 파악 불가feat: BGE-M3 임베딩 모델 교체 형식
.env 파일이 레포에 있음API 키 유출 + 보안 사고Ch.12 보안 가이드 참고

11-2. GitHub Pages + MkDocs — 무료 포트폴리오 사이트

구분GitHub PagesNotion 공유Tistory/Velog
도메인username.github.io (내 이름)notion.site/... (타사)velog.io/@username
커밋 연결레포와 직접 연결 — 커밋 이력이 곧 블로그연결 없음연결 없음
채용 인식"개발자""메모 공유""블로그"
커스텀 도메인가능 (CNAME)유료 플랜 필요불가
15분 실습 — username.github.io 만들기 (5 STEP)
1
사이트 레포 생성

New repository → 이름을 정확히 본인아이디.github.io로 (예: 아이디가 hong-gildong이면 레포명은 hong-gildong.github.io) → Public + Add a README 체크. 이 이름이어야 https://아이디.github.io 주소로 서비스됩니다 — 다른 이름이면 …github.io/레포명 하위 경로가 됩니다. (프로필 소개 README용 아이디 레포와는 별개의 저장소입니다 — 부록 체크리스트 ⭐ 항목)

2
클론 + MkDocs 설치
git clone git@github.com:아이디/아이디.github.io.git && cd 아이디.github.io
pip install mkdocs mkdocs-material
mkdocs new .   # docs/index.md + mkdocs.yml 생성
3
mkdocs.yml 설정
site_name: 홍길동 | AI Engineer
site_url: https://hong-gildong.github.io
theme:
  name: material
  features: [navigation.tabs, search.suggest]
nav:
  - Home: index.md
  - Projects:
      - RAG Chatbot: projects/rag-chatbot.md
4
Actions 자동 배포 설정
# .github/workflows/deploy.yml
name: Deploy MkDocs
on:
  push:
    branches: [main]
permissions:
  contents: write   # gh-pages 브랜치 push 권한 — 없으면 신규 계정에서 403
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.11' }
      - run: pip install mkdocs mkdocs-material
      - run: mkdocs gh-deploy --force
5
Pages 활성화 + 확인

저장소 → Settings → Pages → Source: gh-pages 브랜치 선택. 로컬 미리보기는 mkdocs serve. push 후 3~5분 뒤 https://아이디.github.io 접속. Actions 탭의 초록 체크(✓)가 배포 성공 신호입니다.

11-3. 배포 연동 — Live Demo 링크가 채용 담당자 경험을 가릅니다

도구적합한 서비스무료 한도빌드 시간슬립 이슈
Streamlit CloudStreamlit 앱 (RAG 챗봇, 시각화)무제한 공개 앱2~3분장기 미사용 시 슬립
VercelNext.js, React, 정적 사이트무제한 공개30~90초없음
Render (Free)FastAPI, Flask 백엔드750시간/월3~5분15분 후 슬립
HuggingFace SpacesGradio, Streamlit AI 데모무제한 공개3~10분없음
프로젝트 유형프론트엔드백엔드 API벡터 DB
RAG 챗봇 (간단)Streamlit Cloud없음 (Streamlit 단독)ChromaDB in-memory
RAG 챗봇 (분리형)Streamlit CloudRender (FastAPI)ChromaDB
AI 에이전트 (웹앱)Vercel (Next.js)Render (FastAPI)Supabase pgvector
배포 실습 1 — Streamlit Cloud (5분 완성)
rag-chatbot/
├── app.py               ← 메인 파일 (루트에 있어야 함)
├── requirements.txt     ← pip freeze > requirements.txt 권장 (버전 고정)
├── .streamlit/secrets.toml  ← 로컬 API 키 (.gitignore 필수!)
└── src/

순서: share.streamlit.io 접속 → GitHub 로그인 → Create app → 레포·브랜치·app.py 지정 → Secrets 탭에 secrets.toml 내용을 직접 붙여넣기(GitHub에 올리지 않음) → Deploy → 2~3분 대기. 이후 main에 push하면 자동 재배포됩니다.

# app.py — Cloud에선 st.secrets, 로컬에선 .env로 fallback
import streamlit as st, os
def get_secret(key: str) -> str:
    try:
        return st.secrets['api_keys'][key]
    except (KeyError, FileNotFoundError):
        return os.getenv(key, '')
오류원인해결
ModuleNotFoundErrorrequirements.txt에 패키지 누락pip freeze > requirements.txt 후 재push
No secrets foundsecrets가 Cloud에 미등록앱 Settings → Secrets 탭에 입력
Memory limit exceededChromaDB 데이터 과다인덱싱 데이터 축소 또는 외부 DB
App is sleeping장기 미접속 슬립첫 접속 후 10~15초 대기
배포 실습 2 — Vercel (Next.js 프론트, PR마다 Preview URL)

순서: vercel.com → Add New Project → GitHub 레포 선택 → Next.js 자동 감지 → Environment Variables에 NEXT_PUBLIC_API_URL·API 키 입력 → Deploy (30~90초).

가장 강력한 기능: feature 브랜치를 push하면 PR마다 별도 Preview URL이 자동 생성되고 PR 코멘트에 달립니다. 리뷰어가 merge 전에 변경사항을 직접 체험할 수 있습니다.

# FastAPI 백엔드(Render) 연결 시 CORS 설정 필수
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        'https://내앱.vercel.app',   # Vercel 도메인
        'http://localhost:3000',       # 로컬 개발
    ],
    allow_methods=['*'], allow_headers=['*'],
)
배포 실습 3 — Render (FastAPI 백엔드) + 슬립 해결법
# render.yaml — 루트에 두면 대시보드 클릭 없이 자동 설정
services:
  - type: web
    name: rag-api
    runtime: python
    plan: free            # 무료 (슬립 있음) / starter = $7/월
    region: singapore     # 한국에서 가장 가까운 리전
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn src.api.main:app --host 0.0.0.0 --port $PORT
    healthCheckPath: /health
    envVars:
      - key: OPENAI_API_KEY
        sync: false       # 대시보드에서 직접 입력
# /health 엔드포인트 — 상태 확인용
@app.get('/health')
def health_check():
    return {'status': 'ok', 'version': '1.0.0'}
# 배포 확인: https://rag-api.onrender.com/health → {status: ok}
# Swagger UI: https://rag-api.onrender.com/docs
⚠️ 무료 플랜 슬립
15분 비활성 시 슬립 → 첫 요청에 30~60초 지연. 데모 당일에는 반드시 미리 요청을 보내 워밍업하세요. 상시 해결책: ① Render Starter $7/월 ② UptimeRobot 무료 모니터링(5분마다 /health 핑) ③ GitHub Actions cron으로 10분마다 curl .../health.

11-4. 기술 문서화 — GitBook vs MkDocs

기준MkDocs MaterialGitBook
GitHub 연동레포의 .md → Actions로 자동 배포GitHub 레포와 동기화 (Git Sync)
설정 방법YAML 설정 파일 (mkdocs.yml)웹 대시보드 (코드 없음)
무료 범위완전 무료 (자체 호스팅)무료 플랜은 공개 문서만
난이도⭐⭐ (YAML + 터미널 기본)⭐ (웹 UI로 클릭만)
추천개인 포트폴리오·기술 블로그팀 협업 문서·비개발자 협업

GitBook 연동 요약: Space 생성 → Integrations → GitHub → 레포·브랜치·Root path(docs/) 지정 → SUMMARY.md로 목차 구성 → push하면 사이트 자동 갱신 → Publish로 공개 URL 생성.

11-5. 오픈소스 기여 — 첫 PR을 날리기까지

기여 방법채용 담당자 인식난이도
개인 프로젝트만 있음"스스로 공부했구나"
좋은 README + 배포"완성도가 있구나"⭐⭐
오픈소스 PR 이력 있음"실제 팀 협업 경험 있구나"⭐⭐⭐
오픈소스 PR merge됨"검증된 코드를 쓸 수 있구나"⭐⭐⭐⭐
1
good first issue 찾기

기여할 레포 → Issues 탭 → Labels → good first issue 필터. 또는 검색창에 label:"good first issue" is:open language:python. 첫 기여로는 문서 오타 수정 → 예제 코드 추가 → 테스트 케이스 추가 순서가 쉽습니다.

2
Fork + 로컬 세팅 + upstream 등록
# 1. 기여할 레포 우측 상단 'Fork' 클릭 → 내 계정에 복사본 생성
git clone git@github.com:내아이디/langchain.git && cd langchain
git remote add upstream git@github.com:langchain-ai/langchain.git
git fetch upstream && git checkout main && git merge upstream/main
3
기여 브랜치 생성 + 작업
git checkout -b fix/docs-typo-issue-1234   # 이슈 번호를 브랜치 이름에
git add docs/typo-fix.md
git commit -m 'docs: Fix typo in RAG tutorial (closes #1234)'
git push origin fix/docs-typo-issue-1234   # 내 Fork에 push
4
원본 레포에 PR — 간결하게, 변경 이유를 명확히
# PR 제목: docs: Fix typo 'retreival' → 'retrieval' in RAG guide
## 변경 사항
RAG 튜토리얼 문서에서 오타를 수정했습니다. `retreival` → `retrieval` (3곳)
## 관련 이슈
Closes #1234
## 체크리스트
- [x] 기존 테스트 통과 확인
- [x] 문서 변경만 포함 (코드 변경 없음)
5
리뷰 반영 → merge → 정리

수정 요청이 오면 같은 브랜치에 커밋 추가 후 push. merge되면 GitHub 프로필에 contributor 흔적이 남고, 이력서에 "기여 레포: github.com/langchain-ai/langchain"을 적을 수 있습니다.

AI 개발자에게 추천하는 기여 레포

레포기여 유형첫 기여 난이도
langchain-ai/langchain문서 오타, 예제 코드, 번역⭐ (문서) ⭐⭐⭐ (코드)
langchain-ai/langgraph예제 노트북 추가, 문서 개선⭐⭐
chroma-core/chroma번역, 사용 예제 추가⭐⭐
huggingface/transformers영문 문서 오타, 예제 스크립트⭐⭐
NirDiamant/RAG_Techniques새 RAG 기법 노트북 추가⭐⭐⭐
CH.12 · 보안 관리 — .env · Secrets · 공개 전략

한 번 GitHub에 올라간 키는 이미 유출된 것입니다

🚨 실제 사고 시나리오
OPENAI_API_KEY를 코드에 직접 입력하거나 .env 파일을 커밋 → GitHub push → GitHub Secret Scanning 봇이 수 초 안에 감지 → OpenAI가 해당 키를 자동으로 비활성화 → 프로젝트 전체 중단. 한 번 올라간 키는 git history에 영원히 남습니다.

12-1. 올바른 환경변수 관리 — 3단계 체계

① .gitignore(Ch.2-5의 목록)로 추적 차단 → ② .env.example로 팀원에게 키 이름만 공유 → ③ 코드에서 환경변수로 읽기.

.env.example — 레포에 커밋 OK, 값은 비워둠

# .env.example
# LLM API Keys
OPENAI_API_KEY=your-openai-api-key-here
GROQ_API_KEY=your-groq-api-key-here
ANTHROPIC_API_KEY=your-anthropic-api-key-here
# LangSmith (실험 추적)
LANGCHAIN_API_KEY=your-langsmith-api-key-here
LANGCHAIN_PROJECT=ai-human-7th
LANGCHAIN_TRACING_V2=true
# 앱 설정
DEBUG=false
MAX_TOKENS=2000

# 팀원이 처음 세팅할 때: cp .env.example .env → 실제 키 입력

코드에서 읽는 올바른 패턴

# ❌ 절대 이렇게 하지 말 것
openai_key = 'sk-proj-abc123...'   # 하드코딩

# ✅ 방법 1 — python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
openai_key = os.getenv('OPENAI_API_KEY')
if not openai_key:
    raise ValueError('OPENAI_API_KEY가 설정되지 않았습니다')

# ✅ 방법 2 — pydantic-settings (권장)
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
    openai_api_key: str
    groq_api_key: str = ''    # 선택적
    debug: bool = False
    class Config:
        env_file = '.env'
settings = Settings()

12-2. 저장소 공개/비공개 전략

레포 종류공개 권장?이유
포트폴리오 프로젝트✅ Public채용 담당자가 볼 수 있어야 함
수업 과제 레포✅ Public수강 중 코드리뷰 + 수료 후 포트폴리오
실험/스터디 레포✅ Public학습 과정 자체가 포트폴리오
회사 업무 코드❌ Private계약상 기밀, 절대 공개 금지
API 키가 포함된 코드❌ Private (키 제거 후 Public 전환)유출 즉시 사고
.env 파일❌ 절대 커밋 금지Public/Private 무관하게 커밋 금지

12-3. 이미 .env를 올렸다면 — 긴급 복구 3 STEP

1
API 키 즉시 재발급 — 이게 1순위

기존 키는 이미 노출된 것으로 간주합니다. OpenAI: platform.openai.com → API Keys → 삭제 후 재생성. Anthropic: console.anthropic.com. Groq: console.groq.com. 이력을 지워도 이미 노출된 키는 되살릴 수 없습니다 — 재발급이 먼저입니다.

2
Git 추적에서 제거
echo '.env' >> .gitignore
git rm --cached .env         # 추적만 제거, 로컬 파일은 유지
git add .gitignore
git commit -m 'fix: .env 파일 Git 추적 제거 및 .gitignore 추가'
git push
3
이력에서 완전 제거 (선택)
pip install git-filter-repo
git filter-repo --path .env --invert-paths --force
git push origin --force --all   # 팀원 전체 동기화 필요 — 팀과 합의 후 실행
# 완료 확인: GitHub 레포에서 파일 검색 → 이력에서 안 나오면 완료
# 이후 팀원은 레포를 새로 clone해야 합니다
부록 · 주차별 로드맵 + 최종 체크리스트

Lv.2에서 Lv.4까지 — 이 순서만 따라가세요

주차별 도입 로드맵 (12주)

주차이번 주 목표완료 기준
1주차README 7단계 구조 작성 (Ch.11-1)README에 데모 스크린샷 + 기술 스택 표 포함
2주차GitHub Pages 포트폴리오 오픈 (Ch.11-2)username.github.io 접속 가능
3주차GitHub Actions CI 설정 (Ch.9-2)PR 올리면 자동 pytest 실행
4주차Streamlit Cloud 첫 배포 (Ch.11-3)Live Demo 링크 README에 추가
5주차GitHub Projects 이슈 관리 시작 (Ch.9-4)이번 주 작업을 이슈로 등록 후 진행
6주차Vercel로 Next.js 프론트 배포 (Ch.11-3)프론트엔드 배포 URL 생성
7주차Render로 FastAPI 백엔드 배포 (Ch.11-3)API 외부 접속 + Swagger 확인
8주차MkDocs로 기술 문서 작성 (Ch.11-4)프로젝트 아키텍처 문서 1개 완성
9주차GitBook 팀 문서 작성 (Ch.11-4)팀 프로젝트 기술 문서 공개 URL 생성
10주차첫 오픈소스 기여 PR 제출 (Ch.11-5)good first issue 찾아 PR 제출
11주차보안 체크리스트 점검 (Ch.12)전체 레포 .env 미포함 + Secrets 설정 확인
12주차포트폴리오 최종 정리아래 체크리스트 10개 항목 전체 완료

최종 체크리스트 — 수료 전 점검 10항목

"GitHub은 코드 저장소가 아니라
여러분의 학습 과정과 성장을 보여주는 공간입니다.
잘 관리된 GitHub 하나가, 자기소개서 10장보다 강합니다."

첫 주 세팅 체크리스트 — 수업 시작 전

이 핸드북은 팀 프로젝트와 포트폴리오를 완성하는 여정의 안내서입니다. 막힐 때마다 다시 펼쳐 보세요.