바이브코딩
AI 리팩터링 | 체크포인트 한계와 Git 롤백
작업 종류 세 축, REFACTOR.md 중단 조건, worktree와 골든 파일
롤백 가능한 AI 리팩터링은 에이전트가 만든 구조 변경을 외부 동작은 유지한 채, Git 커밋과 워크트리, 특성화 테스트로 되돌릴 수 있는 상태로 자르는 작업 규약이다. 대상은 Cursor 체크포인트와 Claude Code /rewind를 이미 쓰는 코드베이스이며, 프롬프트 문장만으로 diff를 줄이는 기법이 아니다.
에이전트는 함수 하나를 고치라는 요청에도 파일 전체를 읽는다. 컨텍스트 안의 개선 지점은 모두 “지금 고칠 수 있는 것”이 되고, 세션이 끝나면 맥락이 사라진다는 전제가 범위를 키운다. 사람에게는 파일 열 개의 리뷰 비용이 몸값으로 남지만, 모델에게는 토큰이 조금 더 드는 일에 가깝다. 요청 범위를 지킨 짧은 diff보다 겸사겸사 정리까지 마친 긴 diff가 더 유능해 보이는 평가 편향도 같은 방향으로 작동한다.
프롬프트를 다듬으면 실패 확률은 조금 낮아지지만, 실패했을 때 치르는 비용은 거의 그대로다. 사고 복구의 좌표는 체크포인트가 아니라 Git이다. Claude Code 공식 문서는 체크포인트를 로컬 undo로, Git을 영구 히스토리로 구분한다. 범위 밖 파일, package.json, 마이그레이션이 한 요청에 섞이면 코드 git reset만으로는 로컬과 CI와 배포가 어긋난다. 되돌릴 해시가 없는 리팩터링은 이름이 무엇이든 실험이다.
범위가 커지는 이유
흔한 설명은 모델이 성실해서 눈에 보이는 것을 다 고친다는 것이다. 틀린 말은 아니지만, 그 진단의 대책은 “지시를 잘하자”에서 끝난다. 실제 메커니즘은 프롬프트 바깥에 세 개가 있다.
첫째, 컨텍스트에 파일 전체가 들어 있다. 사람은 “이건 다음에”라고 미루는데, 그 미룸은 내일도 같은 코드베이스에 있을 것이라는 전제에서 나온다. 에이전트에게 그 전제는 없다. 둘째, 편집에 비용 신호가 없다. diff 크기에 대한 페널티가 시스템 어디에도 없다. 셋째, “도움이 됐다”는 평가가 눈에 보이는 개선에 후하다. 3줄짜리 정확 준수보다 80줄짜리 겸사겸사 정리가 더 유능해 보인다.
대응도 셋이다. 컨텍스트를 파일과 경로로 좁히고, 파일 수와 순증감 라인에 상한을 두어 비용 신호를 인위적으로 만들고, 완료 조건을 “보기 좋음”이 아니라 기존 테스트 무수정, 골든 diff 없음처럼 검증 가능한 문장으로 쓴다.
핵심 포인트: “작게 해 달라”는 문장은 범위를 줄이지 않는다. 기준 커밋, 중단 조건, 테스트 쓰기 금지가 환경에 있어야 diff가 작아진다.
세 축
리팩터링과 기능 변경을 섞지 말라는 조언은 널리 알려져 있으나, 실무 축은 두 개가 아니라 세 개다.
| 종류 | 정의 | 검증 | 롤백 |
|---|---|---|---|
| 리팩터링 | 외부 동작이 바이트 단위로 동일 | 기존 테스트 전부 통과, 출력 diff 없음 | git reset |
| 동작 변경 | 출력이 의도적으로 달라짐 | 새 테스트 추가, 기존 테스트 수정 | git reset과 배포 롤백 |
| 의존성 변경 | 패키지, 설정, DB 마이그레이션 | 별도 절차 | 코드 롤백으로 안 됨 |
세 번째가 가장 자주 빠지고 가장 위험하다. git reset --hard를 해도 설치된 패키지와 실행된 마이그레이션은 남는다. “리팩터링 하다가 필요해져서 마이그레이션도 돌렸다”는 진행 보고가 아니라 사고 신호다.
경계선은 질문 하나다. 기존 테스트를 수정해야 하면, 그 작업은 이름이 무엇이든 리팩터링이 아니다. 사용자에게 보이는 문구, API 응답 구조, DB 스키마, 성능 목표 중 하나라도 건드리면 별도 커밋, 가능하면 별도 요청으로 쪼갠다. 테스트가 실패했을 때 원인이 구조인지 동작인지 즉시 갈라야 판단이 된다. 둘이 섞인 diff는 실패해도 어디부터 의심할지 알 수 없다.

REFACTOR.md 각 절
롤백 기준을 코딩이 끝난 뒤에 정하면 “조금만 더 고쳐 보자”가 반복되고, 되돌릴 지점은 멀어진다. 순서를 뒤집는다. 첫 프롬프트를 보내기 전에 작업 폴더에 REFACTOR.md를 만들고, 프롬프트에는 이 파일 경로만 넘긴다. 계약을 본문에 길게 붙이면 세션이 길수록 조건이 희석된다.
작업 폴더의 REFACTOR.md 본문 예시는 다음 블록이다.
# 리팩터링 계약
## 범위
- 대상: src/payments/calculator.ts 단일 파일
- 금지: src/payments/ 밖의 모든 파일, package.json, 마이그레이션
- 종류: 리팩터링 (기존 테스트 수정 금지)
## 시작 상태
- 브랜치: refactor/calc-split
- 기준 커밋: a3f9c21
- 되돌리기: git reset --hard a3f9c21
## 완료 조건 (전부 만족해야 함)
- [ ] npm test 통과 (기존 테스트 무수정)
- [ ] npm run typecheck 통과
- [ ] 골든 파일 diff 없음 (npm run golden:check)
- [ ] 변경 파일 3개 이하, 순증감 200줄 이하
## 중단 조건 (하나라도 걸리면 즉시 정지, 사람 호출)
- 기존 테스트를 수정해야만 통과하는 상황
- 범위 밖 파일을 고쳐야 하는 상황
- 같은 실패가 3번 반복
- 패키지 설치나 DB 마이그레이션이 필요해짐
범위 절은 대상 파일과 금지 경로와 작업 종류를 한곳에 둔다. src/payments/calculator.ts만 열고 package.json을 건드리지 않는다는 문장이 여기 있다. 종류를 리팩터링으로 적으면 기존 테스트 수정이 곧 계약 위반이다.
시작 상태 절은 브랜치와 기준 해시와 되돌리기 명령을 적는다. a3f9c21는 예시 해시다. git rev-parse HEAD 출력을 이 칸에 그대로 붙인다. 해시가 없으면 아래 규칙이 전부 무의미해진다.
완료 조건은 전부 만족해야 한다. 테스트와 타입과 골든과 파일 수, 순증감 200줄이 한 묶음이다. 완료 조건만 있으면 에이전트는 어떻게든 완료하려고 범위를 넓힌다. “테스트가 실패해서 테스트를 수정했다”가 그렇게 나온다.
중단 조건은 하나라도 걸리면 즉시 정지하고 사람을 부른다. 멈춰야 할 지점을 명시하지 않으면 멈추지 않는다. 작업 브랜치의 첫 커밋 메시지에 요약을 남기면 git log에서 계약이 다시 보인다. 파일과 커밋 메시지 양쪽에 두는 편이 안전하다.
중단 네 줄은 각각 다른 사고를 가리킨다. 기존 테스트를 수정해야만 통과하면 작업 종류가 리팩터가 아니다. 범위 밖 파일을 고쳐야 하면 컨텍스트가 계약을 이긴 것이다. 같은 실패가 3번 반복되면 에이전트가 같은 diff를 돌리고 있는 것이다. 패키지 설치나 DB 마이그레이션이 필요해지면 세 축의 세 번째 행이다. git reset --hard가 설치 산출물과 원격 스키마를 되돌리지 않는다. 완료 조건의 파일 3개와 순증감 200줄은 SmartBear 200~400줄과 실무 상한을 계약 문장으로 옮긴 것이다. 골든 diff 없음은 expected.json을 고쳐서 통과하는 최단 경로를 막는다.
/rewind 한계
Cursor의 체크포인트나 Claude Code의 /rewind가 있으니 안심해도 된다는 생각은 위험하다. Claude Code 공식 문서에 명시된 제약은 네 가지다.
Bash로 만든 변경은 추적되지 않는다. 에이전트가 rm, mv, cp, 리다이렉트로 파일을 건드리면 체크포인트에 남지 않고 /rewind로 돌아오지 않는다. 문서에 예시까지 있는 항목이다.
보관 한도가 있다. 세션당 최근 100개 체크포인트의 스냅샷만 유지되고, 세션과 함께 30일 뒤 정리된다. cleanupPeriodDays로 조정할 수 있다.
심볼릭 링크와 하드 링크는 복원에서 건너뛴다. dotfile 관리자가 심링크한 설정 파일이나 pnpm이 하드링크한 파일이 여기 해당한다. 복원 후 Restored the code, but skipped N files 경고가 뜬다.
문서 스스로 대체재가 아니라고 말한다. 체크포인트는 로컬 undo, Git은 영구 히스토리라는 문장이 그대로 들어 있다. Cursor 체크포인트도 성격이 같다. restore를 눌렀는데 안 돌아온다는 사례는 파일 편집 도구 밖에서 일어난 변경에서 반복된다.
서브에이전트나 백그라운드 스킬이 만든 편집도 세션 체크포인트 복원 대상이 아닐 수 있다. Claude Code 문서는 그런 경우 Git으로 되돌리라고 안내한다. 체크포인트는 방금 받은 답변이 마음에 안 들 때 쓰는 undo이지, 사고 복구 수단이 아니다. 터미널 명령이 섞이는 작업이라면 롤백 기준은 커밋 단위로도 잡아야 한다.
worktree
“작게 나눠 달라”가 안 먹히는 이유는 앞 절의 세 원인이 전부 프롬프트 바깥에 있기 때문이다. 환경 쪽 장치가 더 확실하다.
시작 전 커밋은 빈 커밋이라도 만든다. 되돌아갈 좌표가 없으면 아래 규칙이 전부 무의미해진다. 기준 해시 생성 명령은 다음 블록이다.
git add -A && git commit -m "wip: before refactor" --allow-empty
git rev-parse HEAD
--allow-empty는 변경이 없어도 커밋을 만든다. 워킹 트리가 깨끗해도 해시가 필요하다. git rev-parse HEAD 출력을 REFACTOR.md의 기준 커밋과 같게 적는다.
단계마다 커밋을 강제하는 문구의 핵심은 실패를 에이전트가 스스로 고치지 못하게 하는 문장이다. 실패하면 고치라는 허용이 들어가는 순간부터 범위가 확장된다. 실패는 사람이 볼 신호이지 에이전트가 처리할 작업이 아니다. 여러 단계를 한 커밋에 몰아넣지 않는다는 규칙을 같은 요청에 둔다.
위험한 작업은 메인 작업 디렉터리를 건드리지 못하게 워크트리로 격리한다. 망하면 디렉터리째 제거한다. node_modules 재설치가 유일한 비용이고, 그 대가로 원래 작업 트리는 안전하다. 접근 방식을 두세 개 병렬로 시켜 보고 나은 쪽을 고르는 용도로도 쓴다. worktree 격리 명령은 다음 블록이다.
git worktree add ../repo-refactor refactor/calc-split
cd ../repo-refactor
첫 줄은 저장소 옆에 repo-refactor 디렉터리를 만들고 refactor/calc-split 브랜치를 체크아웃한다. 둘째 줄은 그 디렉터리로 들어간다. 제거는 git worktree remove --force repo-refactor다. Windows에서는 ..\\repo-refactor처럼 상대 경로로 옆에 두는 방식이 흔하다. 잠긴 파일이 있으면 에디터와 터미널을 닫은 뒤 다시 시도한다.
worktree 안의 node_modules는 메인 트리와 별이다. 망해서 디렉터리째 지우면 재설치가 유일한 비용이다. 접근 방식을 두세 개 병렬로 시키면 worktree가 두세 개가 된다. 나은 쪽만 남기고 나머지는 --force로 지운다. 메인 트리의 기준 커밋 a3f9c21는 그대로다. 체크포인트 /rewind는 메인 세션의 파일 편집만 되돌린다. 옆 디렉터리의 Bash rm은 Git 해시가 좌표다.
worktree는 같은 저장소의 두 번째 작업 디렉터리다. 메인 트리에서 에이전트가 rm을 실행해도, 격리를 쓰면 그 명령은 옆 디렉터리에서만 일어난다. 메인 트리의 기준 커밋은 그대로다. 접근을 두세 개 병렬로 시키면 worktree가 두세 개가 된다. 나은 쪽만 남기고 나머지는 --force로 지운다. 체크포인트 /rewind는 메인 세션의 파일 편집만 되돌린다. 옆 디렉터리의 Bash 변경은 Git 해시가 좌표다.

골든 파일
레거시 코드에는 테스트가 없는 경우가 흔하다. “먼저 테스트를 만들라”는 맞는 말이지만 막연하다. 필요한 것은 올바른 동작을 검증하는 테스트가 아니라, 지금 나오는 결과를 그대로 박제하는 특성화 테스트(characterization test)다. 현재 코드에 버그가 있어도 상관없다. 리팩터링은 버그까지 보존해야 하는 작업이다. 개념의 출처는 Michael Feathers, Working Effectively with Legacy Code다.
가장 빠른 구현은 골든 파일이다. Vitest 골든 비교의 뼈대는 다음 블록이다.
import { calculate } from '../../src/payments/calculator';
import cases from './cases.json';
import expected from './expected.json';
test.each(cases.map((c, i) => [i, c] as const))('case %i', (i, input) => {
expect(calculate(input)).toEqual(expected[i]);
});
cases.json은 입력, expected.json은 현재 출력이다. test.each는 케이스마다 한 단언을 만든다. toEqual은 구조 전체 비교다. expected.json은 손으로 쓰지 않는다. 리팩터링 전에 현재 코드를 돌려서 생성한다. 기록 명령은 다음 블록이다.
npx tsx scripts/record-golden.ts
git add tests/golden && git commit -m "test: golden snapshot before refactor"
입력 케이스는 커버리지를 보면서 늘린다. 경계값(0, 음수, null, 빈 배열, 최대치)과 실제 프로덕션 로그에서 뽑은 샘플이 효율이 좋다. 완벽할 필요는 없다. 없는 것보다 스무 케이스가 낫다.
골든 생성 자체를 에이전트에게 시켜도 된다. 다만 순서를 끊는다. 입출력 케이스 작성과 현재 출력 기록 뒤에 멈추고, 사람이 expected.json을 확인한 뒤에만 리팩터링을 시작한다. 이 게이트가 없으면 에이전트가 리팩터링하다가 골든 파일을 “고쳐서” 통과시킨다. 드문 사고가 아니라 기본 실패 모드다. 완료 조건이 주어져 있고 기댓값 파일이 쓰기 가능하면, 그게 최단 경로이기 때문이다.
프롬프트로 막는 것보다 확실한 방법은 expected.json을 읽기 전용으로 만들거나 CI에 보호 한 줄을 넣는 것이다. CI 보호 한 줄은 다음 블록이다.
git diff --exit-code tests/golden/expected.json || (echo "골든 파일이 변경됨" && exit 1)
--exit-code는 diff가 있으면 0이 아닌 종료다. 골든 생성과 리팩터링을 한 프롬프트에 넣지 않는다. 사람이 expected.json을 확인하기 전에 리팩터가 시작되면 기댓값 자체가 오염된다.
특성화 테스트는 올바른 답을 적는 일이 아니다. 리팩터 전 출력을 사진 찍는 일이다. 버그가 사진에 들어가 있어도, 리팩터가 그 버그를 바꾸면 diff가 난다. 그 diff가 「구조만 바꿨다」는 주장을 깨뜨린다. 그래서 사람 확인이 리팩터보다 앞선다. 에이전트가 사진과 리팩터를 한 요청에서 하면, 사진을 새 출력에 맞춰 다시 찍는 것이 최단이다. 읽기 전용과 CI --exit-code가 그 최단을 막는다.

SmartBear diff
작은 diff는 줄 수가 적다는 뜻만은 아니다. 리뷰어가 변경 의도를 한 문장으로 설명할 수 있으면 작은 diff다. 감으로 판단하면 매번 흔들리니 숫자가 필요하다.
가장 널리 인용되는 기준은 SmartBear가 Cisco 개발팀의 코드 리뷰 약 2,500건을 분석한 연구에서 나온다. 한 번에 200~400줄 이하를 리뷰할 것, 시간당 500줄을 넘기면 결함 발견율이 뚜렷하게 떨어질 것, 한 세션이 60분을 넘으면 집중력이 무너질 것. 2006년 연구이고 AI 이전 시대라는 점은 감안해야 한다.
다만 AI 시대에는 같은 숫자를 더 빡빡하게 잡을 이유가 있다. 사람이 쓴 400줄은 쓰는 동안 이미 한 번 검토된 400줄이다. AI가 30초 만에 낸 400줄은 아무도 안 본 400줄이다. 같은 숫자가 아니다.
| 기준 | 상한 | 넘으면 |
|---|---|---|
| 변경 파일 수 | 3개 | 단계를 쪼개서 다시 요청 |
| 순증감 라인 | 200줄 | 단계를 쪼개서 다시 요청 |
| 변경 이유 | 1개 | 이유별로 커밋 분리 |
| public API, URL, DB 필드, 환경변수 | 포함 시 | 별도 리뷰로 승격 |
| 포맷팅과 로직 혼재 | 혼재 시 | 포맷팅만 먼저 커밋 |
| 리뷰 소요 | 20분 | 이미 큰 것 |
생성 코드와 기계적 변경(포맷팅, 임포트 정렬)은 별도 커밋으로 빼고 카운트에서 제외한다. 안 그러면 숫자가 의미를 잃는다. 확인은 git diff --stat a3f9c21 한 줄이다. 상한을 넘긴 diff가 오면 이어서 다듬지 않고 그 자리에서 되돌린다. “이미 만들었으니 정리해서 쓰자”가 가장 위험한 선택이다.
리뷰어가 한 문장으로 의도를 말할 수 없으면 파일 수가 3 이하여도 큰 diff다. 변경 이유 1개 상한이 그 문장을 강제한다. 포맷팅과 로직이 한 커밋이면 리뷰 20분이 포맷 잡음에 쓰인다. public API와 URL, DB 필드, 환경변수는 외부 계약이라 별도 리뷰로 승격한다. AI 400줄은 사람이 쓰는 동안 검토된 400줄이 아니다. SmartBear 200~400줄을 더 빡빡하게 잡는 이유다.
2025년 DORA 보고서는 AI 도입이 이제 처리량은 개선하지만 배포 불안정성은 여전히 높인다고 보고한다. 속도가 붙은 만큼 하류에서 터진다는 뜻이고, 그 하류가 대체로 리뷰와 롤백이다. 같은 보고서는 작은 배치로 일할 때 AI의 긍정적 효과가 증폭된다고도 말한다. diff 크기 통제는 이 지점에 직접 개입한다.
각 단계가 끝날 때 볼 것은 셋이다. 범위 이탈, 테스트 파일 변경, 요약과 diff의 불일치. 범위 확인 명령은 다음 블록이다.
git diff --name-only HEAD~1 | grep -v '^src/payments/' && echo "범위 이탈"
테스트 경로가 바뀌었는지는 git diff --name-only HEAD~1 | grep -E '(test|spec|golden)'로 본다. 리팩터링이라면 테스트는 바뀌지 않아야 한다. 바뀌었다면 작업 종류가 바뀐 것이고, 세 축 표로 돌아간다. 에이전트 요약을 먼저 읽으면 요약이 프레임을 만들어서, diff의 이상한 부분이 눈에 들어오지 않는다. 인지 편향이다. diff를 먼저 읽는다.
셋 중 하나라도 걸리면 다음 단계로 넘어가지 않고 그 자리에서 되돌린다. 세 단계가 쌓인 뒤에 되돌리면 멀쩡했던 두 단계까지 함께 날아간다. 터미널 실행은 코드 롤백으로 돌아오지 않는다. 성공한 단계를 커밋으로 안 지키면 전부 잃는다. 3단계가 망했을 때 1단계와 2단계가 커밋되어 있으면 3단계만 버리면 된다.
일곱 단계 중 프롬프트는 계약 전달 하나뿐이다. 나머지 여섯은 환경이다. 기준 커밋, worktree, 골든 스냅샷, REFACTOR.md, 단계 커밋, 단계 검증, 실패 시 git reset --hard 또는 worktree 제거. 테스트 조작을 권한과 CI로 끊는 순서는 테스트 우선 AI 코딩 루프와 짝이다.
의존성 변경이 git reset 밖인 이유
git reset --hard가 되돌리는 것은 작업 트리와 인덱스와 HEAD가 가리키는 커밋이다. 이미 설치된 node_modules, 전역 패키지, 원격에 적용된 DB 마이그레이션, 클라우드에 만든 버킷은 그 명령의 대상이 아니다. 리팩터링 계약의 종류가 리팩터인데 마이그레이션이 필요해졌다면, 그건 진행이 아니라 중단 조건이다. 설치된 패키지를 코드와 같이 되돌려야 하면 lockfile과 설치 산출물이 어긋난다. CI는 lockfile을 보고, 로컬은 node_modules를 본다. 세 축 표의 세 번째 행이 빠지면 그 어긋남이 「리팩터 실패」로 안 보이고 「환경이 이상하다」로 보인다.
package.json을 고치면 의존성 변경이다. 스키마 파일을 고치고 마이그레이션을 돌리면 의존성 변경이다. 둘 다 외부 동작이 바이트 단위로 같다고 단정할 수 없다. 기존 테스트를 수정해야 하면 이름이 무엇이든 리팩터링이 아니다. 사용자에게 보이는 문구, API 응답 구조, DB 스키마, 성능 목표 중 하나라도 건드리면 별도 커밋, 가능하면 별도 요청으로 쪼갠다. 테스트가 실패했을 때 원인이 구조인지 동작인지 즉시 갈라야 판단이 된다.
포맷팅과 로직을 한 커밋에 넣지 않는 이유
생성 코드와 기계적 변경(포맷팅, 임포트 정렬)은 별도 커밋으로 빼고 카운트에서 제외한다. 안 그러면 파일 3개, 순증감 200줄 상한이 의미를 잃는다. 포맷터 한 번이 200줄을 채우면 로직 한 줄이 같은 숫자 안에 숨는다. 리뷰어가 변경 의도를 한 문장으로 설명할 수 없으면 작은 diff가 아니다. public API, URL, DB 필드, 환경변수가 포함되면 별도 리뷰로 승격한다. 리뷰가 20분이면 이미 큰 것이다.
SmartBear Cisco 사례는 약 2,500건이다. 한 번에 200~400줄 이하, 시간당 500줄을 넘기면 결함 발견율이 떨어진다. 한 세션 60분을 넘으면 집중력이 무너진다. 2006년 연구이고 AI 이전이다. 사람이 쓴 400줄은 쓰는 동안 이미 한 번 검토된 400줄이다. AI가 30초 만에 낸 400줄은 아무도 안 본 400줄이다. 같은 숫자를 더 빡빡하게 잡는 이유가 여기 있다. git diff --stat a3f9c21이 그 확인이다. 상한을 넘긴 diff는 그 자리에서 되돌린다. 「이미 만들었으니 정리해서 쓰자」가 가장 위험한 선택이다.
2025년 DORA 보고서는 AI 도입이 처리량은 개선하지만 배포 불안정성은 여전히 높인다고 보고한다. 작은 배치로 일할 때 AI의 긍정적 효과가 증폭된다고도 말한다. diff 크기 통제는 이 지점에 직접 개입한다.
단계 커밋이 실패 신호를 사람 쪽에 두는 방식
단계마다 커밋을 강제하는 문구의 핵심은 실패를 에이전트가 스스로 고치지 못하게 하는 문장이다. 실패하면 고치라는 허용이 들어가는 순간부터 범위가 확장된다. 실패는 사람이 볼 신호이지 에이전트가 처리할 작업이 아니다. 여러 단계를 한 커밋에 몰아넣지 않는다는 규칙을 같은 요청에 둔다. 3단계가 망했을 때 1단계와 2단계가 커밋되어 있으면 3단계만 버리면 된다. 성공한 단계를 커밋으로 안 지키면 전부 잃는다.
에이전트 요약을 먼저 읽으면 요약이 프레임을 만들어서, diff의 이상한 부분이 눈에 들어오지 않는다. 인지 편향이다. diff를 먼저 읽는다. 범위 확인은 git diff --name-only HEAD~1 | grep -v '^src/payments/'가 출력을 내면 이탈이다. 테스트 경로는 git diff --name-only HEAD~1 | grep -E '(test|spec|golden)'로 본다. 리팩터링이라면 테스트는 바뀌지 않아야 한다. 바뀌었다면 작업 종류가 바뀐 것이고 세 축 표로 돌아간다.
worktree는 메인 작업 디렉터리를 건드리지 못하게 옆에 격리한다. git worktree add ../repo-refactor refactor/calc-split이 저장소 옆에 디렉터리를 만든다. 망하면 git worktree remove --force repo-refactor다. node_modules 재설치가 유일한 비용이다. 접근 방식을 두세 개 병렬로 시켜 보고 나은 쪽을 고르는 용도로도 쓴다. Windows에서는 ..\\repo-refactor처럼 상대 경로로 옆에 두는 방식이 흔하다. 잠긴 파일이 있으면 에디터와 터미널을 닫은 뒤 다시 시도한다.
일곱 단계 중 프롬프트는 계약 전달 하나뿐이다. 나머지 여섯은 환경이다. 기준 커밋, worktree, 골든 스냅샷, REFACTOR.md, 단계 커밋, 단계 검증, 실패 시 git reset --hard 또는 worktree 제거. 테스트 조작을 권한과 CI로 끊는 순서는 테스트 우선 AI 코딩 루프와 짝이다.
특성화 테스트가 버그를 보존하는 이유
레거시 코드에는 테스트가 없는 경우가 흔하다. 필요한 것은 올바른 동작을 검증하는 테스트가 아니라, 지금 나오는 결과를 그대로 박제하는 특성화 테스트다. 현재 코드에 버그가 있어도 상관없다. 리팩터링은 버그까지 보존해야 하는 작업이다. 개념의 출처는 Michael Feathers, Working Effectively with Legacy Code다.
cases.json은 입력, expected.json은 현재 출력이다. expected.json은 손으로 쓰지 않는다. 리팩터링 전에 현재 코드를 돌려서 생성한다. npx tsx scripts/record-golden.ts가 그 기록이다. 입력 케이스는 경계값(0, 음수, null, 빈 배열, 최대치)과 프로덕션 로그 샘플이 효율이 좋다. 없는 것보다 스무 케이스가 낫다.
골든 생성 자체를 에이전트에게 시켜도 된다. 입출력 케이스 작성과 현재 출력 기록 뒤에 멈추고, 사람이 expected.json을 확인한 뒤에만 리팩터링을 시작한다. 이 게이트가 없으면 에이전트가 리팩터링하다가 골든 파일을 고쳐서 통과시킨다. 완료 조건이 주어져 있고 기댓값 파일이 쓰기 가능하면, 그게 최단 경로다. git diff --exit-code tests/golden/expected.json이 CI에서 그 파일을 잠근다. 골든 생성과 리팩터링을 한 프롬프트에 넣지 않는다.
체크포인트 100개와 30일
Claude Code 공식 문서는 세션당 최근 100개 체크포인트의 스냅샷만 유지되고, 세션과 함께 30일 뒤 정리된다고 적는다. cleanupPeriodDays로 조정할 수 있다. Bash로 만든 rm, mv, cp, 리다이렉트는 추적되지 않는다. 심볼릭 링크와 하드 링크는 복원에서 건너뛴다. Restored the code, but skipped N files 경고가 그 표시다. 서브에이전트나 백그라운드 스킬이 만든 편집도 세션 체크포인트 복원 대상이 아닐 수 있다. 문서는 그런 경우 Git으로 되돌리라고 안내한다.
체크포인트는 방금 받은 답변이 마음에 안 들 때 쓰는 로컬 undo다. Git은 영구 히스토리다. Cursor 체크포인트도 성격이 같다. restore를 눌렀는데 안 돌아온다는 사례는 파일 편집 도구 밖에서 일어난 변경에서 반복된다. 터미널 명령이 섞이는 작업이라면 롤백 기준은 커밋 단위로도 잡아야 한다. 시작 전 커밋은 빈 커밋이라도 만든다. --allow-empty는 워킹 트리가 깨끗해도 해시를 만든다. git rev-parse HEAD 출력을 REFACTOR.md의 기준 커밋과 같게 적는다.
단계 검증과 사고 유형
각 단계가 끝날 때 볼 것은 셋이다. 범위 이탈, 테스트 파일 변경, 요약과 diff의 불일치. 범위 확인 명령은 다음 블록이다.
git diff --name-only HEAD~1 | grep -v '^src/payments/' && echo "범위 이탈"
HEAD~1은 바로 전 커밋이다. grep -v '^src/payments/'는 그 경로가 아닌 이름을 남긴다. 출력이 있으면 계약의 대상 밖이다. 테스트 경로가 바뀌었는지는 다음 한 줄로 본다.
git diff --name-only HEAD~1 | grep -E '(test|spec|golden)'
리팩터링이라면 테스트는 바뀌지 않아야 한다. 바뀌었다면 작업 종류가 바뀐 것이고 세 축 표로 돌아간다. 에이전트 요약을 먼저 읽으면 요약이 프레임을 만들어서, diff의 이상한 부분이 눈에 들어오지 않는다. 인지 편향이다. diff를 먼저 읽는다.
셋 중 하나라도 걸리면 다음 단계로 넘어가지 않고 그 자리에서 되돌린다. 세 단계가 쌓인 뒤에 되돌리면 멀쩡했던 두 단계까지 함께 날아간다. 터미널 실행은 코드 롤백으로 돌아오지 않는다. 에이전트가 마이그레이션을 돌리거나 패키지를 설치하면 git reset --hard로 원상복구되지 않는다. 계약서의 종류가 리팩터링인데 마이그레이션이 필요해졌다면 그건 진행이 아니라 중단 조건이다.
성공한 단계를 커밋으로 안 지키면 전부 잃는다. 3단계가 망했을 때 1단계와 2단계가 커밋되어 있으면 3단계만 버리면 된다. 「거의 다 됐는데」에서 범위를 조금만 넓히면 지금까지 세운 계약이 전부 무효가 된다. 남은 것을 다음 단계로 미루는 쪽이 거의 항상 맞다. 일곱 단계 중 프롬프트는 계약 전달 하나뿐이다. 나머지 여섯은 환경이다. 기준 커밋, worktree, 골든 스냅샷, REFACTOR.md, 단계 커밋, 단계 검증, 실패 시 git reset --hard 또는 worktree 제거.
마무리
앞에서 다룬 롤백 가능한 AI 리팩터링의 핵심만 짧게 정리한다.
- 범위가 커지는 이유는 성실함이 아니라 컨텍스트와 비용 신호와 평가 편향이다.
- 작업 종류는 리팩터, 동작 변경, 의존성 변경 세 축이다. 의존성은 코드 롤백으로 안 돌아온다.
REFACTOR.md의 중단 조건이 완료 조건보다 중요하다.- Claude Code 체크포인트는 bash와 심링크와 100개, 30일 한도가 있고 Git 대체재가 아니다.
- 시작 전 커밋, 단계 커밋, worktree가 프롬프트보다 확실하다.
- SmartBear 200~400줄과 실무 파일 3개, 순증감 200줄 상한을 AI diff에 더 빡빡하게 적용한다.
- 골든 파일은 사람 승인 전에 리팩터와 한 요청에 넣지 않는다.
「되돌릴 커밋이 없으면 리팩터링이 아니다」 실패 비용은 프롬프트 문장이 아니라 환경이 정한다. 기준 해시와 중단 조건과 골든 게이트가 있는 요청만 에이전트에 넘긴다.
출처와 링크
- SmartBear / Cisco 코드 리뷰 사례 연구: 약 2,500건. 200~400줄, 시간당 500줄, 60분 기준
- Claude Code Checkpointing 문서: bash 미추적, 100개 스냅샷, 30일 정리, 심링크와 하드링크 건너뜀, Git 대체 불가
- DORA 2025 State of AI-assisted Software Development: 처리량과 배포 불안정성, 작은 배치
- Git worktree 문서: 병렬 작업 디렉터리 격리
- 테스트 우선 AI 코딩 루프: RED-GREEN-REFACTOR-REPORT와 테스트 조작 차단
조사 기준: 2026년 8월. 에이전트 도구의 체크포인트 동작과 권한 설정은 버전마다 바뀌므로, 적용 전에 사용 중인 버전 문서를 다시 확인한다. Michael Feathers의 특성화 테스트 개념은 도서 원문을 1차 출처로 둔다.
FAQ
자주 묻는 질문
리팩터링 계약을 요청마다 처음부터 다시 써야 하는가?
템플릿 파일 이름과 기준 커밋 해시만 바꿔 재사용하는 편이 보통이다. 첫 작성은 수 분이 걸리지만, 범위 밖 수정이 한 번이라도 쌓이면 그 비용이 반나절로 늘어난다. 팀 공통 폴더에 두면 개인마다 문장을 다시 만들지 않아도 된다.
파일 하나, 함수 하나의 짧은 수정도 계약이 필요한가?
대략 스무 줄 안쪽이고 테스트가 이미 있으면 diff만 보고 끝내는 경우가 많다. 계약이 필요해지는 지점은 여러 파일에 걸치거나, 테스트가 없거나, 이미 프로덕션에 나가 있는 코드다. 셋 중 둘이 겹치면 REFACTOR.md를 둔다.
에이전트가 중단 조건을 무시하고 계속 고치면 무엇이 남는가?
프롬프트만으로는 거의 막히지 않는다. 시작 전 커밋, 단계 커밋, 워크트리 격리가 먼저다. Cursor나 Claude Code에 권한 설정이 있으면 범위 밖 쓰기와 마이그레이션, 패키지 설치를 거부 목록에 넣는 편이 더 확실하다.
시간이나 난수에 의존해 골든 파일을 못 만드는 코드는 어떻게 하는가?
부수 효과가 경계 안에 있으면 특성화 테스트가 흔들린다. 그런 코드는 리팩터링 전에 부수 효과를 경계 밖으로 밀어내는 작업이 먼저다. 그 작업 자체가 별도 리팩터이므로 그것부터 계약을 쓴다.
체크포인트와 Git 커밋의 역할은 어떻게 갈리는가?
방금 받은 답이 마음에 안 들 때, 커밋할 가치도 없는 시도를 되돌릴 때는 세션 undo로 충분하다. bash와 외부 편집, 심링크 경로는 복원이 안 되거나 건너뛸 수 있다. 사고 복구의 좌표는 커밋 해시와 worktree다.
리팩터 중에 package.json만 살짝 올려도 되는가?
패키지 변경은 의존성 축이라 코드 git reset만으로는 로컬과 CI와 배포가 어긋날 수 있다. 필요하면 리팩터 계약을 중단하고 의존성 전용 요청과 커밋으로 분리한다. lockfile diff가 보이면 이미 종류가 바뀐 신호다.
Windows에서 git worktree 경로는 어떻게 잡는가?
상대 경로로 저장소 옆에 두는 방식이 흔하다. 에이전트는 그 폴더를 워크스페이스로 연 뒤에만 돌린다. 제거는 git worktree remove --force 이고, 잠긴 파일이 있으면 에디터와 터미널을 닫은 뒤 다시 시도한다.
용어
관련 용어
외부에서 관찰 가능한 동작(기능, API, 사용자 경험)을 변경하지 않으면서 코드의 내부 구조를 개선하는 체계적 작업이다. Martin Fowler의 1999년 저서 『Refactoring: Improving the Design of Existing Code』에서 체계화된 개념으로, 가독성 향상, 중복 제거, 복잡도 감소, 성능 개선, 테스트 용이성 향상 등을 목적으로 한다. 바이브 코딩에서 리팩토링은 특별한 의미를 가진다. AI가 초기에 생성한 코드는 기능적으로 동작하더라도 구조적으로 최적이 아닌 경우가 많으므로, AI와 협업하여 리팩토링을 수행하는 것이 일반적인 워크플로이다. 예를 들어, Claude Code에게 '이 컴포넌트를 더 작은 컴포넌트로 분리하고, 공통 로직을 커스텀 훅으로 추출해줘'와 같은 리팩토링 지시를 내릴 수 있다. 2026년 기준으로 AI 도구는 단일 서비스 내 리팩토링(파일 분할, 함수 추출, 타입 개선 등)은 잘 수행하지만, 마이크로서비스 간 크로스 시스템 리팩토링(서비스 경계 재정의, 데이터 모델 마이그레이션 등)은 아직 인간의 아키텍처 판단이 필수적인 영역이다.
커뮤니티·문화 SWE 벤치AI 코딩 에이전트의 실제 소프트웨어 엔지니어링 능력을 측정하는 벤치마크로, Princeton NLP 그룹이 2023년 10월에 발표했다. 단순한 코드 생성 능력이 아니라, 실제 오픈소스 GitHub 리포지토리에서 보고된 실제 이슈(버그 리포트, 기능 요청)를 해결하는 능력을 평가한다는 점에서 기존 벤치마크(HumanEval, MBPP 등)와 차별화된다. 테스트 과정: 에이전트에게 GitHub 이슈 설명과 관련 코드베이스가 주어지면, 에이전트가 코드를 수정하고 이 수정이 기존 테스트를 통과하는지 확인한다. 이는 실제 소프트웨어 개발 환경과 가장 유사한 평가 방식이다. 2026년 3월 기준 주요 성적: Claude Code 80.8%, Codex CLI 약 70%대(변형에 따라 상이). SWE-bench는 AI 코딩 도구 성능 비교의 사실상 표준(de facto standard)이 되었으며, 새로운 도구나 모델이 출시될 때 SWE-bench 성적이 가장 먼저 언급된다. SWE-bench Verified(검증된 하위 집합), SWE-bench Lite(경량 버전), Terminal Bench(터미널 에이전트 특화) 등의 변형도 존재한다.
에이전틱 엔지니어링 에이전트 피드백 루프AI 에이전트가 실행 결과를 보고 스스로 다음 수정을 시도하도록 만드는 반복 구조이다. 사람이 결과만 읽고 다시 지시하는 방식과 달리, 피드백 루프에서는 테스트 실패, 린트 오류, 빌드 로그, 브라우저 콘솔 오류, 사용자 스모크 결과가 에이전트에게 다시 입력된다. 에이전트는 이 증거를 바탕으로 원인을 좁히고 작은 수정으로 다시 검증한다. 좋은 피드백 루프는 무한 반복이 아니라 중단 조건을 가진다. 예를 들어 같은 테스트가 세 번 실패하면 사람에게 넘기고, 배포 후 핵심 페이지가 깨지면 롤백 기준을 적용한다. 바이브 코딩에서는 이 루프가 있어야 AI가 만든 코드가 우연히 한 번 동작하는 수준을 넘어 안정적인 기능으로 다듬어진다.
링크
관련 링크
쉬운 보안을 지향하는 한국어 보안 계정으로, AI·VIBE 코딩 흐름에서 놓치기 쉬운 보안 감각을 되짚는 데 유용합니다.
VIBE 코딩 레퍼런스 웹사이트 해부도 · Website Anatomy MapAI와 웹사이트를 함께 만들 때 ‘그 부분’이 아니라 정확한 UI·웹 용어로 지시할 수 있게 돕는 영-한 시각 사전입니다.
VIBE 코딩 제품 리서치 Killed by Google · Google GraveyardGoogle이 종료한 서비스와 제품을 한눈에 모아, 플랫폼 의존성과 제품 지속성 리스크를 판단하게 해 주는 ‘Google 묘지’ 아카이브입니다.
관련 글
관련 글
Recommended
테스트 우선 AI 코딩 | RED GREEN REFACTOR REPORT
테스트 우선 AI 코딩 루프는 구현 diff를 받기 전에 실패 테스트를 먼저 고정하고, 통과 뒤에만 구조를 정리하며, PR에 실패 로그와 남은 위험을 남기는 작업 규약이다. 러너 예시는 Vitest이며, 권한 예시는 Claude Code의 Edit(...) deny와 훅 문서다. 순서는 RED, GREEN, REFACTOR, REPORT 네 단계다.
모델이 30초 만에 깔끔한 diff를 내면 “일단 된 것 같다”는 판단이 생긴다. 테스트가 없으면 그 코드는 동작하는 코드가 아니라 동작한다고 믿고 싶은 코드다. 실패 테스트가 있어야 에이전트가 어디까지 고쳐야 하는지 알고, 사람이 결과를 감이 아니라 증거로 판단한다.
다만 테스트를 먼저 써도, 그 테스트가 조작 가능하면 아무 소용이 없다. 에이전트가 기대값을 바꿔서 통과시키는 일은 흔한…
구글 애드센스 고시 SEO 점검 개선 후 승인 | fire-your-seo-agency 스킬
사이트를 만들어 두고 Google 애드센스에 신청했다가, 몇 달째 통과하지 못한 적이 있다. 거절 메일을 받을 때마다 무엇을 고쳐야 하는지가 한 줄로 떨어지지 않았고, AI에게 「SEO 점검하고 개선해 줘」라고만 말해 보기도 했다. 그때마다 뭔가 바뀌기는 했지만, 기준이 없어서 다음에 같은 점검을 다시 시킬 수가 없었다.
이거, 메일 받아 보신 분? 많으실 겁니다. 애드센스를 하다 보면 마주치는 그 팔짱 낀 아저씨 쪽에, 이런 사유가 적혀 있었다.
그림. 애드센스 안내에서 자주 보이는 팔짱 낀 아저씨