AI 생성 코드에 ESLint·Prettier·tsc를 커밋 전에 자동 적용하기
husky와 lint-staged 설정, 전체 타입 검사 연결, CI 이중 검증까지
핵심 요약
- 형식·린트 검사는 편집 직후, 커밋 직전, PR 세 단계에 나눠 둔다.
- lint-staged는 스테이징 파일만 검사하고 실패하면 원래 상태로 복구한다.
- tsc는 함수 형태로 지정해 파일 경로 없이 tsconfig 기준으로 검사한다.
- 커밋 훅은 건너뛸 수 있으므로 CI에서 같은 검사를 다시 실행한다.
AI 코딩 도구가 만든 코드는 팀의 포맷 규칙과 다른 따옴표·들여쓰기를 쓰거나, 사용하지 않는 import를 남기거나, 타입 오류를 숨긴 채 끝나는 경우가 있다. 리뷰어가 이런 기계적인 문제까지 지적하면 정작 로직 검토에 쓸 시간이 줄어든다. 커밋 전에 형식·린트·타입 검사를 자동으로 돌리면 기계가 잡을 수 있는 문제는 PR에 올라오기 전에 걸러진다. 이 글은 lint-staged 문서를 기준으로 구성 방법을 정리한다. 리뷰에서 사람이 볼 항목은 AI 생성 코드 리뷰 규칙 글에서 다뤘다.
검사를 어디에 둘지 먼저 정하기
| 단계 | 도구 | 속도 | 역할 |
|---|---|---|---|
| 편집 직후 | AI 도구의 훅(예: Claude Code PostToolUse) | 즉시 | 에이전트가 만든 파일을 바로 포맷 |
| 커밋 직전 | husky + lint-staged | 스테이징 파일만이라 빠름 | 형식·린트 오류가 커밋에 들어가지 않게 |
| PR | CI(GitHub Actions 등) | 전체 검사라 느림 | 훅을 건너뛴 커밋까지 최종 차단 |
커밋 훅은 로컬에서 --no-verify로 건너뛸 수 있으므로 CI 검사를 대체하지 못한다. 세 단계는 서로를 보완한다.
1단계: husky와 lint-staged 설치
npm install --save-dev husky lint-staged
npx husky init
# .husky/pre-commit 내용을 다음 한 줄로 바꾼다
npx lint-stagednpx husky init은 .husky/pre-commit 파일과 package.json의 prepare 스크립트를 만들어, 팀원이 의존성을 설치하면 훅이 자동으로 등록되게 한다.
2단계: 스테이징된 파일만 검사하도록 설정
// lint-staged.config.js
export default {
'*.{js,jsx,ts,tsx}': ['eslint --fix --max-warnings=0', 'prettier --write'],
'*.{json,md,css,yml}': ['prettier --write'],
'*.{ts,tsx}': [() => 'tsc --noEmit'],
}lint-staged는 기본적으로 스테이징된 파일만 대상으로 하고, 각 명령 뒤에 해당 파일 경로를 붙여 실행한다. eslint --fix와 prettier --write가 고친 내용은 커밋에 자동으로 포함된다. 문서는 작업 전에 원래 상태를 git stash로 백업하고, 실패하면 자동으로 복구한다고 설명한다.
3단계: tsc는 파일이 아니라 프로젝트 단위로
타입 검사는 파일 하나만 보면 의미가 없다. tsc에 파일 경로를 넘기면 tsconfig.json을 무시하고 기본 옵션으로 검사해 엉뚱한 오류가 난다. 문서가 안내하듯 함수 형태(() => 'tsc --noEmit')로 쓰면 lint-staged가 파일 경로를 붙이지 않아 프로젝트 설정 전체로 검사한다. 프로젝트가 커서 커밋마다 전체 타입 검사가 부담스럽다면 이 항목은 CI로 넘기고, 커밋 훅에는 ESLint와 Prettier만 남긴다.
4단계: AI 도구의 편집 단계에도 연결하기
Claude Code를 쓴다면 Hooks의 PostToolUse 이벤트로 파일 수정 직후 포맷터를 실행할 수 있다. 에이전트가 포맷이 맞춰진 파일을 다시 읽으므로 이후 수정에서 형식 차이로 인한 불필요한 diff가 줄어든다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{ "type": "command", "command": "npx prettier --write --ignore-unknown \"$CLAUDE_PROJECT_DIR\"/src", "timeout": 30 }]
}
]
}
}5단계: CI에서 같은 검사를 한 번 더
# .github/workflows/check.yml (일부)
- run: npm ci
- run: npx prettier --check .
- run: npx eslint . --max-warnings=0
- run: npx tsc --noEmitCI에서는 고치지 않고 검사만 한다(--check, --fix 없음). 커밋 훅과 CI가 같은 설정 파일을 쓰게 해야 "로컬에서는 통과, CI에서는 실패"가 생기지 않는다. AI 리뷰 봇과 함께 운영하는 방법은 GitHub Actions AI 코드 리뷰 봇 글을 참고한다.
자주 생기는 문제와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| 훅이 실행되지 않음 | prepare 스크립트 미실행 | npm install 다시 실행, .husky/pre-commit 실행 권한 확인 |
| tsc가 엉뚱한 오류 | 파일 경로를 넘겨 tsconfig 무시 | 함수 형태로 tsc --noEmit 지정 |
| 커밋이 너무 느림 | 전체 타입 검사·테스트를 훅에서 실행 | 무거운 검사는 CI로 이동 |
| 경고가 쌓임 | ESLint 경고를 허용 | --max-warnings=0으로 경고도 실패 처리 |
기존 저장소에 처음 도입할 때의 순서
- 먼저 전체 코드에
npx prettier --write .를 한 번 실행해 형식만 바꾸는 커밋을 따로 만든다. 이후 PR의 diff에 형식 변경이 섞이지 않는다. - 이 형식 변경 커밋의 해시를
.git-blame-ignore-revs에 넣어 blame 결과가 흐려지지 않게 한다. - ESLint는 처음부터 오류로 막기보다 경고 개수를 측정하고, 규칙을 몇 개씩 오류로 올려 가며 정리한다.
- 정리가 끝난 뒤 lint-staged에
--max-warnings=0을 붙여 새 경고가 들어오지 않게 한다. - 팀에
npm install을 다시 실행해 훅을 등록하라고 안내하고, CI 검사도 같은 날 켠다.
AI 도구에 작업을 맡길 때 "커밋 전에 npx lint-staged가 통과해야 완료"라는 완료 조건을 함께 주면, 에이전트가 스스로 형식과 린트 오류를 고친 뒤 작업을 끝낸다. 타입 검사 규칙 자체를 엄격하게 하는 방법은 TypeScript가 AI 코딩 품질에 도움이 되는 이유 글에서 다뤘다.
초보자가 자주 실수하는 포인트
- lint-staged 설정에 'tsc --noEmit'을 문자열로 넣어 파일 경로가 붙는 경우
- 커밋 훅만 두고 CI 검사를 생략하는 경우
- 전체 테스트까지 커밋 훅에 넣어 커밋이 지나치게 느려지는 경우
- 로컬과 CI가 서로 다른 ESLint·Prettier 설정을 쓰는 경우
체크리스트
- .husky/pre-commit이 npx lint-staged를 실행하는가
- ESLint와 Prettier가 스테이징 파일에만 실행되는가
- tsc가 함수 형태로 지정되었거나 CI로 옮겨졌는가
- CI가 같은 설정으로 검사만 수행하는가
- ESLint 경고도 실패로 처리하는가
자주 묻는 질문
커밋 훅을 건너뛰어야 할 때는 어떻게 하나요?
git commit --no-verify로 건너뛸 수 있다. 그래서 최종 차단은 CI가 맡아야 한다.
ESLint와 Prettier 규칙이 충돌하면 어떻게 하나요?
형식 관련 규칙은 Prettier에 맡기고 ESLint에서는 형식 규칙을 끄는 구성이 일반적이다. 두 도구의 공식 문서에서 권장 연동 방법을 확인한다.
모노레포에서도 쓸 수 있나요?
lint-staged는 패키지별 설정 파일을 지원한다. 문서의 모노레포 안내에 따라 각 패키지에 설정을 둔다.
참고 자료 · 검증 기준
위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.
이 글은 위 참고 자료를 바탕으로 정리했으며, 내용은 운영 과정에서 순차적으로 보완될 수 있습니다.