AI가 작성한 코드의 리뷰 규칙: PR 템플릿, 자동 검사, CODEOWNERS로 팀 기준 고정하기

AI 사용 범위 표시와 변경 이유 작성을 PR 단계에서 확인하는 설정 파일과 검사 스크립트

AI 코드 리뷰 규칙 설정 썸네일

핵심 요약

  1. AI 사용 범위 표시와 변경 이유 작성은 PR 템플릿과 본문 검사로 PR 단계에서 확인한다.
  2. PR 본문은 신뢰할 수 없는 입력이므로 Actions에서 환경 변수로 넘겨 스크립트 인젝션을 막는다.
  3. CODEOWNERS와 브랜치 보호로 인증·결제·워크플로우 경로는 지정 리뷰어 승인을 필수로 만든다.
  4. Copilot 리뷰는 기본값으로 필수 승인에 포함되지 않으며, 승인 기능을 켜기 전에 범위를 정한다.

AI가 만든 코드는 빨리 나오지만, 리뷰 기준이 말로만 정해져 있으면 바쁜 날부터 무너진다. "AI를 썼으면 PR에 적자", "변경 이유는 직접 쓰자" 같은 규칙은 템플릿과 자동 검사로 PR 단계에 박아 둬야 유지된다. 이 글은 다섯 가지 리뷰 규칙을 정하고, 그중 기계적으로 확인할 수 있는 것은 GitHub 설정 파일과 검사 스크립트로 구현한다. 검사 스크립트는 2026-10-07에 아래 예시 PR 본문으로 실행해 결과를 함께 실었다.

리뷰 규칙 다섯 가지와 구현 위치

규칙왜 필요한가구현 위치
1. AI 사용 범위를 PR에 표시리뷰어가 생성 코드 특유의 위험 지점(예외 처리, 권한 확인)을 먼저 본다PR 템플릿 + 자동 검사
2. 변경 이유는 작성자가 직접설명할 수 없는 코드는 장애 때 고칠 수 없다자동 검사(AI 사용 시 필수)
3. 실행한 테스트와 재현 절차 기록AI 테스트는 낙관적인 경우만 다루기 쉽다PR 템플릿 체크박스
4. 민감 영역은 지정 리뷰어 승인인증·결제·배포 설정은 영향 범위가 크다CODEOWNERS + 브랜치 보호
5. 반복 지적은 규칙 파일로같은 지적을 다음 생성에서 줄인다AGENTS.md 등 규칙 파일

원칙은 AI 생성 여부와 관계없이 최종 책임이 PR 작성자에게 있다는 것이다. AI 사용 표시는 책임을 나누는 장치가 아니라 리뷰 범위를 알려 주는 메모다. 누가 어느 줄을 썼는지 추적하는 데까지 가면 리뷰가 코드 품질보다 도구 사용 감시로 흐른다.

설정 순서

  1. .github/pull_request_template.md를 만든다. 공식 문서 기준으로 템플릿은 기본 브랜치에 병합되어야 PR 작성 화면에 나타난다.
  2. PR 본문을 검사하는 스크립트를 저장소에 넣고 로컬에서 예시 본문으로 테스트한다.
  3. GitHub Actions 워크플로우로 PR이 열리거나 본문이 수정될 때마다 검사를 실행한다.
  4. .github/CODEOWNERS에 민감 경로의 담당자를 적고, 브랜치 보호에서 코드 소유자 리뷰를 필수로 켠다.
  5. 리뷰에서 두 번 이상 나온 지적은 규칙 파일에 추가한다.

PR 템플릿

## 변경 이유
<!-- 왜 바꿨는지 직접 적어 주세요. AI 요약을 그대로 붙이지 않습니다. -->

## AI 사용 범위
- [ ] 사용하지 않음
- [ ] 테스트 코드 초안
- [ ] 구현 코드 일부
- [ ] 구현 코드 대부분

## 확인한 것
- [ ] 로컬에서 테스트 통과 (실행한 명령: )
- [ ] 입력 검증·권한 확인·민감 정보 로그 점검
- [ ] 새로 추가한 의존성 확인

PR 본문 검사 스크립트

AI 사용 범위 체크박스가 하나 이상 선택됐는지, AI를 썼다면 변경 이유가 20자 이상 직접 적혔는지 확인한다. HTML 주석(템플릿의 안내 문구)은 글자 수에서 뺀다.

// PR 본문의 "AI 사용 범위" 섹션이 채워졌는지 검사한다.
// GitHub Actions에서는 PR 본문을 환경변수로 넘겨 실행하고, 로컬에서는 파일로 테스트한다.
export function checkAiDisclosure(body = '') {
  const errors = [];
  const section = body.match(/## AI 사용 범위\s*\n([\s\S]*?)(?=\n## |\s*$)/);
  if (!section) return ['"## AI 사용 범위" 섹션이 없습니다. PR 템플릿을 지우지 마세요.'];

  const checked = section[1].match(/^- \[x\] .+$/gim) || [];
  if (checked.length === 0) errors.push('AI 사용 범위 체크박스를 하나 이상 선택하세요.');

  const usedAi = checked.some((line) => !/사용하지 않음/.test(line));
  const why = body.match(/## 변경 이유\s*\n([\s\S]*?)(?=\n## |\s*$)/);
  const whyText = (why ? why[1] : '').replace(/<!--[\s\S]*?-->/g, '').trim();
  if (usedAi && whyText.length < 20) {
    errors.push('AI가 작성한 코드가 있으면 "변경 이유"를 직접 20자 이상 적으세요.');
  }
  return errors;
}

// node check-ai-disclosure.mjs <본문 파일...>
if (process.argv[1].endsWith('check-ai-disclosure.mjs')) {
  const { readFileSync } = await import('node:fs');
  const files = process.argv.slice(2);
  let failed = 0;
  for (const f of files) {
    const errors = checkAiDisclosure(readFileSync(f, 'utf8'));
    console.log(`${errors.length ? 'FAIL' : 'PASS'} ${f}`);
    errors.forEach((e) => console.log(`  - ${e}`));
    if (errors.length) failed++;
  }
  process.exitCode = failed ? 1 : 0;
}

아래는 정상 작성, 변경 이유 미작성, 템플릿 삭제의 세 가지 예시 본문으로 실행한 결과다.

$ node check-ai-disclosure.mjs pr-ok.md pr-empty-reason.md pr-deleted.md
PASS pr-ok.md
FAIL pr-empty-reason.md
  - AI가 작성한 코드가 있으면 "변경 이유"를 직접 20자 이상 적으세요.
FAIL pr-deleted.md
  - "## AI 사용 범위" 섹션이 없습니다. PR 템플릿을 지우지 마세요.
exit=1

pr-empty-reason.md는 템플릿의 안내 주석만 남기고 "구현 코드 대부분"에 체크한 경우다. 주석을 글자 수에서 빼지 않으면 이 PR이 통과하므로, 검사 로직을 만들 때 이런 예시 본문을 함께 저장해 두고 테스트한다.

GitHub Actions 워크플로우

PR 제목과 본문은 외부 기여자가 마음대로 쓸 수 있는 값이다. GitHub의 스크립트 인젝션 문서는 이런 값을 run 명령 안에 ${{ }}로 직접 넣지 말라고 경고한다. 아래처럼 환경 변수로 넘기고 스크립트에서 읽는다.

# .github/workflows/pr-ai-disclosure.yml
name: PR AI disclosure
on:
  pull_request:
    types: [opened, edited, synchronize, reopened]
permissions:
  contents: read
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - name: Check PR body
        env:
          PR_BODY: ${{ github.event.pull_request.body }}
        run: |
          printf '%s' "$PR_BODY" > /tmp/pr-body.md
          node scripts/check-ai-disclosure.mjs /tmp/pr-body.md

edited 이벤트를 넣어야 작성자가 본문을 고친 뒤 검사가 다시 돈다. 이 검사를 브랜치 보호의 필수 상태 검사로 지정하면 본문을 채우기 전에는 병합할 수 없다.

CODEOWNERS로 민감 경로 지정

# .github/CODEOWNERS
/src/auth/      @org/security-reviewers
/src/payments/  @org/payments-owners
/.github/workflows/  @org/platform

공식 문서에 따르면 GitHub은 .github/, 루트, docs/ 순서로 CODEOWNERS 파일을 찾고 처음 찾은 파일만 쓴다. 브랜치 보호에서 "Require review from Code Owners"를 켜면 해당 경로가 바뀐 PR은 소유자 중 한 명의 승인이 있어야 병합된다. 소유자 전원이 아니라 한 명의 승인으로 충족된다는 점도 팀에 알려 둔다.

AI 리뷰 봇 승인은 어디까지 인정할까

Copilot code review 문서에 따르면 Copilot의 리뷰는 기본값으로 필수 승인 수에 포함되지 않는다. 저장소·조직·엔터프라이즈 설정에서 Copilot 승인을 켜면 팀원의 승인처럼 필수 승인 규칙을 충족시킬 수 있는데, 이 기능은 퍼블릭 프리뷰 단계이고 승인 뒤 새 커밋이 푸시되면 승인이 해제된다. CODEOWNERS로 지정한 민감 경로까지 봇 승인으로 병합되지 않도록, 이 설정을 켜기 전에 팀 규칙으로 범위를 정한다. 리뷰 봇을 Actions로 직접 붙이는 방법은 GitHub Actions AI 리뷰 봇 연동에서 다룬다.

반복 지적을 규칙 파일로 옮기는 루틴

리뷰에서 같은 지적이 두 번 나오면 그 내용을 AGENTS.md나 도구별 규칙 파일에 한 줄로 추가한다. 예를 들어 "API 핸들러에서 입력을 Zod로 검증하지 않음"이 반복되면 "모든 API 핸들러는 요청 본문을 Zod 스키마로 검증한다"를 규칙에 넣는다. 규칙 파일 구성은 Cursor Rules 작성법을, 커밋 전에 기계적으로 걸러낼 수 있는 항목은 커밋 전 ESLint·tsc 자동화를 참고한다.

초보자가 자주 실수하는 포인트

  • PR 템플릿을 기능 브랜치에만 추가해 작성 화면에 나타나지 않는다(기본 브랜치에 병합 필요).
  • 워크플로우에서 PR 본문을 ${{ }}로 run 명령에 직접 넣는다.
  • 검사 이벤트에 edited를 빼서 본문을 고쳐도 검사가 다시 돌지 않는다.
  • AI 사용 여부 추적을 줄 단위 감시로 확대해 리뷰가 도구 감시로 바뀐다.

체크리스트

  • .github/pull_request_template.md가 기본 브랜치에 있다.
  • PR 본문 검사 스크립트를 예시 본문 세 가지 이상으로 테스트했다.
  • 워크플로우가 PR 본문을 환경 변수로 받고 edited 이벤트에서도 실행된다.
  • CODEOWNERS에 민감 경로 담당자를 적고 코드 소유자 리뷰를 필수로 켰다.
  • AI 리뷰 봇 승인의 인정 범위를 팀 문서에 적었다.

자주 묻는 질문

AI 사용 여부를 꼭 표시해야 하나요?

목적은 감시가 아니라 리뷰 범위를 알려 주는 것입니다. 표시가 있으면 리뷰어가 예외 처리나 권한 확인처럼 생성 코드가 놓치기 쉬운 부분을 먼저 봅니다. 팀이 원하지 않으면 체크박스를 "테스트 범위" 표시로 바꿔도 같은 효과를 낼 수 있습니다.

외부 기여자 PR에도 이 검사가 돌아가나요?

pull_request 이벤트는 포크에서 온 PR에도 실행되지만 읽기 권한으로 제한됩니다. 이 검사는 본문만 읽으므로 쓰기 권한이 필요 없고, permissions를 contents: read로 고정해 두면 됩니다.

참고 자료 · 검증 기준

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

이 글은 실제 실무 환경 테스트 및 검증된 기술 자료를 바탕으로 작성되었으며, 환경 변화에 따라 지속적으로 갱신됩니다.