AI 생성 코드에 ESLint·Prettier·tsc를 커밋 전에 자동 적용하기

husky와 lint-staged 설정, 전체 타입 검사 연결, CI 이중 검증까지

핵심 요약

  1. 형식·린트 검사는 편집 직후, 커밋 직전, PR 세 단계에 나눠 둔다.
  2. lint-staged는 스테이징 파일만 검사하고 실패하면 원래 상태로 복구한다.
  3. tsc는 함수 형태로 지정해 파일 경로 없이 tsconfig 기준으로 검사한다.
  4. 커밋 훅은 건너뛸 수 있으므로 CI에서 같은 검사를 다시 실행한다.

AI 코딩 도구가 만든 코드는 팀의 포맷 규칙과 다른 따옴표·들여쓰기를 쓰거나, 사용하지 않는 import를 남기거나, 타입 오류를 숨긴 채 끝나는 경우가 있다. 리뷰어가 이런 기계적인 문제까지 지적하면 정작 로직 검토에 쓸 시간이 줄어든다. 커밋 전에 형식·린트·타입 검사를 자동으로 돌리면 기계가 잡을 수 있는 문제는 PR에 올라오기 전에 걸러진다. 이 글은 lint-staged 문서를 기준으로 구성 방법을 정리한다. 리뷰에서 사람이 볼 항목은 AI 생성 코드 리뷰 규칙 글에서 다뤘다.

검사를 어디에 둘지 먼저 정하기

단계도구속도역할
편집 직후AI 도구의 훅(예: Claude Code PostToolUse)즉시에이전트가 만든 파일을 바로 포맷
커밋 직전husky + lint-staged스테이징 파일만이라 빠름형식·린트 오류가 커밋에 들어가지 않게
PRCI(GitHub Actions 등)전체 검사라 느림훅을 건너뛴 커밋까지 최종 차단

커밋 훅은 로컬에서 --no-verify로 건너뛸 수 있으므로 CI 검사를 대체하지 못한다. 세 단계는 서로를 보완한다.

1단계: husky와 lint-staged 설치

npm install --save-dev husky lint-staged
npx husky init

# .husky/pre-commit 내용을 다음 한 줄로 바꾼다
npx lint-staged

npx 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 --noEmit

CI에서는 고치지 않고 검사만 한다(--check, --fix 없음). 커밋 훅과 CI가 같은 설정 파일을 쓰게 해야 "로컬에서는 통과, CI에서는 실패"가 생기지 않는다. AI 리뷰 봇과 함께 운영하는 방법은 GitHub Actions AI 코드 리뷰 봇 글을 참고한다.

자주 생기는 문제와 해결

증상원인해결
훅이 실행되지 않음prepare 스크립트 미실행npm install 다시 실행, .husky/pre-commit 실행 권한 확인
tsc가 엉뚱한 오류파일 경로를 넘겨 tsconfig 무시함수 형태로 tsc --noEmit 지정
커밋이 너무 느림전체 타입 검사·테스트를 훅에서 실행무거운 검사는 CI로 이동
경고가 쌓임ESLint 경고를 허용--max-warnings=0으로 경고도 실패 처리

기존 저장소에 처음 도입할 때의 순서

  1. 먼저 전체 코드에 npx prettier --write .를 한 번 실행해 형식만 바꾸는 커밋을 따로 만든다. 이후 PR의 diff에 형식 변경이 섞이지 않는다.
  2. 이 형식 변경 커밋의 해시를 .git-blame-ignore-revs에 넣어 blame 결과가 흐려지지 않게 한다.
  3. ESLint는 처음부터 오류로 막기보다 경고 개수를 측정하고, 규칙을 몇 개씩 오류로 올려 가며 정리한다.
  4. 정리가 끝난 뒤 lint-staged에 --max-warnings=0을 붙여 새 경고가 들어오지 않게 한다.
  5. 팀에 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는 패키지별 설정 파일을 지원한다. 문서의 모노레포 안내에 따라 각 패키지에 설정을 둔다.

참고 자료 · 검증 기준

위 자료와 내용을 대조한 날짜: . 도구·서비스 정책은 이후 바뀔 수 있으므로 적용 전 공식 문서를 다시 확인하세요.

이 글은 위 참고 자료를 바탕으로 정리했으며, 내용은 운영 과정에서 순차적으로 보완될 수 있습니다.