AI에게 리팩토링을 맡길 때 망하지 않는 법 — Codex CLI의 의존성 분석·범위 제한·단계별 커밋
리팩토링은 늘 두렵다. 특히 3년 이상 쌓인 코드베이스에서 express를 fastify로 바꾼다거나, lodash 전체를 걷어낸다고 생각하면 어디서부터 건드려야 할지조차 막막하다. 처음엔 "그냥 파일 열고 하나씩 바꾸면 되지"라고 생각했는데, 두세 개 바꾸고 나면 어딘가 조용히 터져 있었다. 정적 분석 도구로 잡히지 않는 라우터 미들웨어의 실행 순서, 커스텀 데코레이터로 감싸둔 요청 컨텍스트, 테스트에서만 쓰이는 모킹 헬퍼 — 이런 것들이 뒤늦게 CI에서 빨간불로 돌아온다.
한 번은 스무 개 남짓 되는 모듈을 사흘에 걸쳐 바꾸다가, 마지막 배포 직전에 로깅 미들웨어 하나가 프로덕션에서 무한 재귀를 도는 걸 발견했다. 커밋이 하나로 뭉쳐 있어서 어느 변경이 원인인지 특정하는 데만 반나절이 갔다. 그때 배운 건 "리팩토링의 위험은 변경 자체가 아니라, 변경의 경계가 흐릿한 데서 온다"는 것이었다.
AI에게 리팩토링을 맡기면 이 경계는 더 흐릿해진다. 파일 여러 개를 한꺼번에 바꾸도록 두면 오히려 더 큰 사고가 나지 않을까 하는 걱정이 자연스럽다. 그런데 의존성 그래프 분석 → AGENTS.md 범위 선언 → 단계별 커밋 이 세 가지를 조합하면 경계가 뚜렷해진다. AI가 어디까지 건드릴 수 있는지를 처음부터 선언적으로 안내하고, 각 단계마다 타입 체크·테스트를 통과한 뒤에만 커밋하도록 유도할 수 있다.
이 글에서는 왜 이 조합이 효과적인지, 어떻게 세팅하는지, 그리고 어디서 조심해야 하는지를 함께 살펴본다.
왜 한 번에 다 맡기면 안 되는가
먼저 어떤 실패가 발생하는지부터 짚고 넘어가야 한다. 프레임워크 마이그레이션을 프롬프트 한 방으로 지시하면 대개 이런 일이 벌어진다.
- 은닉된 참조 누락:
req.body를 직접 참조하는 미들웨어는 잡지만, 팀이 만든 커스텀 헬퍼 안에서 우회 접근하는 코드는 놓친다. 정적 분석의 한계다. - 테스트 픽스처 방치: 프로덕션 코드는 바꾸는데, 테스트 헬퍼가 옛 API를 그대로 쓰고 있어서 CI는 통과하지만 실제 동작은 다르다.
- 커밋 뭉침: 30개 파일을 한 커밋으로 밀어넣으면 리뷰가 불가능하고, 롤백 단위가 사라진다.
- 범위 초과: "겸사겸사" 관련 없는 파일 스타일까지 바꿔놓아 diff가 부풀어 오른다.
이 네 가지 실패 유형은 서로 얽혀 있다. 범위가 흐릿해서 커밋이 뭉치고, 커밋이 뭉치니 어디서 참조가 누락됐는지 못 찾는다. 그래서 리팩토링을 위임할 때는 "무엇을 바꿀지"보다 "어디까지만 바꿀지"를 먼저 정하는 게 순서다.
세 가지 축이 하는 일
Codex CLI는 2025년 4월 OpenAI가 공개한 오픈소스 터미널 코딩 에이전트다. 터미널에서 자연어로 지시하면 실제 코드를 읽고, 수정하고, 명령어를 실행한다. 멀티 파일 리팩토링 맥락에서 살펴볼 축은 세 가지다.
의존성 그래프 분석 — 영향 범위를 먼저 나열한다
리팩토링에서 가장 무서운 건 "이 파일만 바꾸면 되겠지" 하고 바꿨다가 엉뚱한 곳이 터지는 상황이다. Codex CLI에게 수정 대상 파일의 import/require 참조를 역추적하도록 지시하면, 영향받는 모든 모듈을 먼저 나열하게 만들 수 있다. 최신 마이그레이션 문서가 필요한 경우에는 웹 검색을 활성화한 상태에서 요청하면 되는데, 세부 플래그와 사용 형태는 로컬에 설치된 Codex CLI 버전에서 codex --help로 확인하는 편이 안전하다. 릴리스마다 CLI 인터페이스가 바뀌는 시기라 문서 기준으로 그대로 옮겨 쓰기보다는, 자기 환경에서 지원 여부를 먼저 검증하는 게 낫다.
AGENTS.md — 에이전트가 볼 수 있는 세계를 선언한다
AGENTS.md는 프로젝트 루트와 현재 작업 디렉터리 사이의 계층 구조에서 자동으로 로드되는 Markdown 파일이다. 에이전트가 접근·수정할 수 있는 경로, 금지 명령어, 테스트 게이트를 선언해 작업 범위를 안내한다.
주의할 점은 AGENTS.md가 안내(guidance) 라는 것이다. 하드 강제가 아니라, 모델이 참고해야 하는 규약에 가깝다. 사용자 프롬프트가 명시적으로 "그냥 전부 다 바꿔"라고 지시하면 무시될 수도 있다. 그래서 민감한 제약은 뒤에서 다룰 OS 샌드박스 정책과 병행해야 한다.
단계별 커밋 — 롤백 포인트를 명확히 남긴다
대형 리팩토링을 논리적 단위로 잘라, 각 단계마다 타입 체크·린트·테스트를 통과한 뒤에만 커밋한다. 중간에 뭔가 깨지면 거기서 멈추도록 규칙을 걸어두면 "어디서 망가졌지?"를 git log만 보고 특정할 수 있다.
AGENTS.md 범위 선언부터 시작한다
AGENTS.md 없이 "전체 코드베이스에서 express를 fastify로 바꿔줘"라고 지시하면, 에이전트가 어디까지 건드릴지 예측하기 어렵다. 범위 선언이 먼저다.
기본 AGENTS.md 구조
# 리팩토링 범위 안내
## 수정 가능 경로
- src/routes/**
- src/middleware/**
- src/controllers/**
## 절대 건드리지 않을 경로
- src/config/database.ts
- migrations/
- .env*
## 금지 명령어
- rm -rf
- DROP TABLE
- git push --force
## 테스트 게이트
각 커밋 전에 다음을 통과해야 한다:
1. npx tsc --noEmit
2. npx eslint src/ --max-warnings 0
3. npm test -- --passWithNoTests
## 커밋 메시지 권장 형식
- 논리 단위 하나당 커밋 하나
- 형식: refactor(scope): 변경 내용 요약여기서 "커밋 메시지 권장 형식"은 Codex가 자동으로 강제하는 규약이 아니라 모델에게 전달되는 지시일 뿐이다. 모델이 이 형식을 항상 지킨다는 보장은 없으므로, 커밋 훅(예: commitlint)을 함께 걸어두는 편이 안정적이다.
AGENTS.md의 실효성 한계
- guidance 수준의 규칙: 프롬프트로 우회될 수 있다. 프로덕션 DB 스키마 파일 보호처럼 절대 뚫리면 안 되는 제약은 OS 샌드박스(macOS
sandbox-exec, Linuxbwrap, Codex의 파일시스템 정책 설정 등)로 파일시스템 쓰기 자체를 막아야 한다. Codex CLI가 어떤 샌드박스 옵션을 노출하는지는 사용 중인 버전의 문서와codex --help에서 확인하는 게 정확하다. - 문서 길이: AGENTS.md가 지나치게 길어지면 긴 세션에서 규칙 일부가 흐려질 수 있다. 정확한 임계값은 모델·버전에 따라 다르므로 수치를 특정하기보다는 "핵심 규칙만 짧게, 부가 규칙은 하위 디렉터리의 AGENTS.md로 분리"라는 원칙을 따르는 편이 실용적이다.
- 경로 계층: 상위 디렉터리의 AGENTS.md와 하위 AGENTS.md가 병합되는 방식은 CLI 버전에 따라 다르다. 실제로 어떤 규칙이 병합되어 적용됐는지는 세션 시작 시 Codex가 로드한 지시문을 확인하는 명령(현재 버전이 제공한다면)이나, 프롬프트에서 "현재 로드된 AGENTS.md 내용을 요약해줘"라고 직접 물어보는 방식으로 검증할 수 있다.
실제 흐름: Express → Fastify 마이그레이션
말로만 하면 감이 안 오니 흐름을 따라가보자. 아래 명령은 개념적 예시이며, 실제 플래그 이름·형태는 사용 중인 Codex CLI 버전에서 확인이 필요하다.
1단계: 영향 범위 분석 (수정 없음)
먼저 수정 없이 영향 범위만 파악하도록 지시한다. Codex CLI가 별도의 "plan" 슬래시 커맨드를 제공하는지는 버전마다 다르므로, 여기서는 프롬프트 자체에 "수정 금지"를 명시하는 방식으로 대체한다.
codex "이번 세션에서는 아무 파일도 수정하지 마.
전체 코드베이스에서 express 임포트를 모두 찾고,
Fastify 최신 마이그레이션 가이드를 참조해서
영향받는 파일 목록과 예상 수정 범위를 계층별로 나열해줘."출력 예시(개념적):
영향 파일 목록:
- src/app.ts (express() → fastify() 인스턴스 변경)
- src/routes/users.ts (Router() 제거, 라우트 등록 방식 변경)
- src/routes/products.ts (동일)
- src/middleware/auth.ts (req.body 접근 방식 변경)
- src/middleware/errorHandler.ts (에러 핸들러 시그니처 변경)
수정 불필요:
- src/config/database.ts (express 의존성 없음)
- src/utils/logger.ts (express 의존성 없음)2단계: git worktree로 격리 환경 준비
메인 브랜치를 오염시키지 않으려면 git worktree가 유용하다. 브랜치별로 독립된 파일시스템을 제공하기 때문에, 리팩토링이 실패해도 원래 작업 환경엔 영향이 없다.
git worktree add ../project-fastify-migration refactor/express-to-fastify
cd ../project-fastify-migration3단계: AGENTS.md 배치 후 레이어별 실행
# 1차: 라우트 레이어
codex "src/routes/ 하위 파일들의 express Router를 Fastify 라우팅으로 변환해.
변환 후 npx tsc --noEmit 과 npm test 를 실행하고,
통과하면 refactor(routes): express Router → Fastify route plugins 로 커밋해."
# 2차: 미들웨어 레이어
codex "src/middleware/ 하위 파일들의 express 미들웨어를 Fastify 훅으로 변환해.
변환 후 타입 체크·테스트 통과 확인 후 커밋해."
# 3차: 앱 엔트리포인트
codex "src/app.ts 의 express() 인스턴스를 fastify() 로 교체하고,
플러그인 등록 방식을 Fastify 스타일로 변경해."모노레포 병렬 처리: 터미널 탭 분리부터
모노레포에서 패키지가 열 개 넘으면 하나씩 처리하다 지친다. 가장 단순한 병렬화는 패키지별 worktree를 만들고 터미널 탭을 나눠 각각 Codex 세션을 띄우는 것이다.
git worktree add ../refactor-pkg-a refactor/pkg-a
git worktree add ../refactor-pkg-b refactor/pkg-b
# 탭 1
cd ../refactor-pkg-a && codex "packages/pkg-a 의 lodash 의존성을 네이티브 JS로 교체해..."
# 탭 2
cd ../refactor-pkg-b && codex "packages/pkg-b 의 lodash 의존성을 네이티브 JS로 교체해..."이 방식은 "서브에이전트 오케스트레이션"이라기보다 사람이 지휘하는 병렬 실행에 가깝다. 대신 각 패키지의 PR은 작은 diff로 유지되고, 실패한 패키지만 개별적으로 되돌릴 수 있다. Agents SDK류를 이용해 여러 Codex 인스턴스를 프로그램적으로 조율하는 접근도 가능하지만, 그 통합 방식은 SDK 버전과 API에 강하게 묶여 있으므로 이 글의 범위를 넘는다.
리팩토링 후 테스트 게이트 걸기
TDD 그 자체는 아니지만 — 실패하는 테스트를 먼저 쓰는 게 아니라, 리팩토링 후 기존 테스트가 여전히 통과하는지를 강제하는 안전망에 가깝다 — AGENTS.md에 게이트를 정의해두면 각 커밋 직전에 검증이 자동으로 실행되도록 유도할 수 있다.
#!/usr/bin/env bash
set -euo pipefail
npx tsc --noEmit
npx eslint src/ --max-warnings 0
npm test
STAGED=$(git diff --cached --name-only -- '*.ts' || true)
if [ -n "$STAGED" ]; then
if git diff --cached -- '*.ts' | grep -Eq 'TODO|FIXME|HACK'; then
echo "미완성 마커 발견: 커밋 중단"
exit 1
fi
fi원래 초안에서는 이걸 한 줄로 grep && echo && exit 1 체인으로 썼는데, 매치가 없을 때 grep이 exit 1을 반환하면서 && 체인이 끊어져 스크립트가 조용히 성공한다. staged 파일이 하나도 없어도 마찬가지다. 스크립트로 분리하고 -E와 파일 목록 체크를 넣어두는 편이 의도대로 동작한다.
트레이드오프
다이어그램은 "어떤 조건에서 문제가 생기는가"를, 표는 "그럼 어떻게 대응하는가"를 담았다.
| 항목 | 대응 방법 |
|---|---|
| AGENTS.md는 안내에 그침 | 민감한 제약은 OS 샌드박스·파일 권한과 병행 |
| AGENTS.md 비대화 | 핵심 규칙만 남기고 하위 디렉터리의 AGENTS.md로 분리 |
| 동적 임포트 추적 불가 | require(variable), 리플렉션 기반 참조는 수동 그레핑 또는 MCP 계열 그래프 도구 병행 |
| 샌드박스 네트워크 차단 | 외부 API 호출 테스트는 별도 스테이지로 분리 |
| 프롬프트 실수 | 첫 단계는 항상 수정 금지 프롬프트로 범위 분석만 |
| 커밋 메시지 규약 미준수 | commitlint 등 로컬 훅으로 형식 검증 |
정적 분석을 보완할 도구
Codex 자체의 정적 분석만으로 부족하다면, MCP(Model Context Protocol) 연동 도구가 도움이 된다.
- Codegraph: 로컬에서 코드베이스의 의존성 맵을 구축하는 오픈소스 프로젝트.
- code-review-graph: MCP 기반 코드 인텔리전스 그래프.
이런 도구들은 "이 함수를 바꾸면 어디까지 영향이 가는가"를 그래프 형태로 유지해두기 때문에, 매 세션마다 전체 코드베이스를 재스캔하지 않아도 된다. 다만 링크된 프로젝트들의 상세 기능·성숙도는 각자 저장소에서 직접 확인하는 걸 권한다.
마무리
멀티 파일 리팩토링을 AI에게 맡기는 게 무서운 이유는 "어디까지 건드릴지 모른다"는 불확실성 때문이다. 이 불확실성을 줄이려면 세 가지가 있어야 한다. AGENTS.md로 범위를 선언하고, 첫 단계는 수정 없이 영향 범위만 파악하고, git worktree로 격리한 뒤 단계별 게이트를 통과할 때만 커밋한다. 그러면 AI가 실수해도 피해 반경이 명확하고, 롤백 지점이 git log에 남는다.
동적 임포트나 리플렉션 기반 참조는 여전히 사람이 확인해야 한다. 정적 분석이 잡지 못하는 영역이 있다는 걸 인정하고 시작하는 편이 안전하다.
지금 당장 취할 수 있는 가장 낮은 진입점은 이것이다: 현재 브랜치에서 수정 없이 영향 범위만 나열하도록 Codex에 프롬프트를 던져보라. 파일이 하나도 바뀌지 않으니 리스크 없이 도구의 행동을 관찰할 수 있고, 여기서 나온 파일 목록이 그대로 AGENTS.md의 "수정 가능 경로" 초안이 된다.