


빌드가 엄격한 저장소에서 AI 코딩 에이전트가 경고 없는 코드와 정직한 보고를 하게 하기
빌드 검사가 엄격한 저장소에서 AI 코딩 에이전트를 쓸 때 사용합니다. push 전에 빌드와 검증을 통과시키고, 커밋 규칙을 지키며, 안전한 코드를 쓰고, 모든 주장을 코드와 대조해 확인합니다.
이 AI 규칙을 사용하려면
## 작업 중에는 바꾼 모듈만 빌드하고, push 전에는 검증을 모두 통과시키기
모든 기여자와 에이전트가 동일하게 빌드할 수 있도록, 전역으로 설치된 툴체인이 아니라 저장소에 커밋된 빌드 래퍼를 사용한다. 빠른 반복 작업을 위해서는 전체 멀티모듈 빌드가 아니라 변경한 모듈만 빌드하고 테스트한다. 보통 정적 분석 검사를 건너뛰는 "빠른" 프로필과 테스트를 생략하는 플래그가 마련되어 있다. 이런 지름길은 로컬 반복 작업에만 사용한다.
커밋하기 전에는 반드시 검사를 활성화한 상태로 영향을 받는 모듈을 완전히 컴파일하고 경고가 0건임을 확인해야 한다 — 검사를 건너뛴 빌드만으로 커밋해서는 안 된다. 푸시하기 전에는 프로젝트의 검증 단계를 실행한다. 이 단계는 변경 내용을 인식해 변경 유형(문서 전용, 단일 모듈, 루트/설정 변경)을 분류하고, 영향을 받는 모듈과 그 의존성 클로저만 빌드하며, 루트 빌드 설정 변경은 전체 빌드를 강제한다. 검증이 끝나면 푸시 훅이 확인하는 단기 마커가 남는다. 공유 러너를 낭비하지 않도록, 푸시하기 전에 자신의 브랜치에서 이미 대체된 파이프라인 실행을 취소한다.
## Git 훅을 건너뛰지 않고, AI 서명 없는 짧은 커밋 메시지 쓰기
매 작업 세션을 시작할 때마다 저장소의 훅 경로를 활성화한다. 세션은 보관되었다가 다시 활성화될 수 있으므로 이는 한 번만 하면 되는 설정이 아니다. 훅을 절대 우회하지 않는다 — 검증을 건너뛰는 플래그도, 서명을 우회하는 방법도 안 된다. 훅은 라이선스 헤더, 사용하지 않거나 중복된 임포트의 부재, 커밋 메시지 형식을 강제한다.
커밋 제목은 type(scope): description 형식의 컨벤셔널 커밋으로 작성하며, feat, fix, docs, refactor, test, chore 같은 타입을 사용한다. 각 메시지는 공백이 아닌 줄로 최대 2줄까지만 작성하고, 첫 줄은 약 100자 이내로 유지한다. 트레일러 줄은 어떤 것도 추가하지 않는다 — 공동 작성자 트레일러도, 서명 트레일러도, 그리고 무엇보다 AI 작성 표시나 AI 생성 서명은 절대 넣지 않는다. 커밋 메시지 훅이 이를 거부한다. 버그 수정은 메인 개발 브랜치로 바로 반영하고, 새 기능은 피처 브랜치를 사용한다.
## 주변 코드 스타일을 따르고, 모든 경고를 오류로 다루기
주변 코드의 스타일에 맞추고, 목표를 달성하는 데 필요한 최소한의 합리적인 변경만 한다. 모든 소스 파일에는 필수 라이선스 헤더가 있어야 한다. 상속보다 컴포지션을 우선하고, 닫아야 하는 모든 리소스에는 try-with-resources 구문을 사용하며, 공개 API에서는 null 대신 옵셔널 래퍼를 반환한다. 문맥상 타입이 명확할 때만 지역 타입 추론을 사용하고, 길게 이어지는 if/else 체인보다 switch 표현식과 패턴 매칭을 우선한다. 적합한 곳에는 실드 타입과 불변 레코드 캐리어를 사용하고, 제네릭은 항상 매개변수화하며, 오버라이드 메서드에는 애너테이션을 붙인다. 반복문 안에서 문자열을 만들 때는 빌더를 사용하고, 수정 불가능한 컬렉션은 표준 팩토리 헬퍼로 생성한다.
컴파일러, 지원 중단(deprecation), 정적 분석 경고는 모두 오류로 취급한다. 빌드는 모든 린트 검사를 켠 상태로 실행되며 사용하지 않거나 중복된 임포트가 있으면 실패하므로, 배포하려면 경고가 0건이어야 한다. 왜 그것이 불가피한지 설명하는 주석 없이는 새로운 경고 억제 애너테이션을 도입하지 않으며, 명백히 사실이 아님을 증명할 수 없는 한 코드 주석을 삭제하지 않는다.
## 리소스는 만든 쪽만 닫고, 용량 한계를 무시하지 않기
다음 불변식은 기능 범위와 무관하게 릴리스를 막는 조건이다. 하나라도 위반하면 변경 규모가 아무리 작아도 릴리스가 막힌다.
**소유권** — 리소스를 생성한 주체만이 그것을 해제, 종료, 셧다운할 수 있다. 소유권을 명시적으로 추적하고, 외부에서 제공된 익스큐터, 풀, 커넥션을 받을 때는 생명주기 호출이 자신이 만들지 않은 것을 파괴하지 못하도록 감싼다. 모든 시작 루틴에는 리스너, 핸들러, 훅 등 모든 등록을 되돌리는 대칭적인 중지 루틴이 필요하며, 모든 버퍼는 모든 종료 경로와 오류 경로에서 반드시 해제되어야 한다.
**종료 경로의 완전성** — 모든 종료 경로(성공, 실패, 취소, 타임아웃, 거부)는 시스템을 완료된 상태로 남겨야 한다: future를 확정하고, 플래그를 리셋하고, 버퍼를 해제한다. compare-and-set이나 close-once 패턴으로 종료를 멱등하게 만든다. 가드가 카운터를 검사한다면, 어떤 경로든 실제로 그 카운터를 증가시켜야 한다.
**배압** — 거부나 용량 신호를 절대 무시하지 않는다. 바운디드 offer의 반환값을 확인하고, 실행 거부를 처리하며, 부분 쓰기를 처리한다. 모든 캐시와 버퍼에는 크기 제한이나 축출 정책이 필요하다. 외부 입력으로 채워지는 무제한 구조는 서비스 거부 공격 벡터가 된다.
## 모든 경계에서 입력을 검증하고, 실제로 동작하는 것만 알리기
이 불변식들도 기능 범위와 무관하게 릴리스를 막는다.
**경계 안전성** — 모든 경계(와이어, 파일 시스템, 셸, URL, HTTP)에서는 해석하기 전에 반드시 검증하고 인코딩한다. 텍스트는 완전히 프레임된 메시지에서만 디코딩하고, 원시 전송 청크에서는 절대 디코딩하지 않는다. 파일 시스템 경로는 정규화하고 검증한다 — 경로를 해석하고 정규화한 다음, 결과가 의도한 루트 아래에 있는지 확인하고, 상위 디렉터리 세그먼트, 구분자, 빈 세그먼트를 거부한다. 셸 호출에는 인자 배열을 사용하고, 동적인 경로와 쿼리 부분은 URL 인코딩하며, 콘텐츠 타입은 대소문자를 구분하지 않고 매칭하고, 경계에서 파싱 실패를 잡아 잘못된 입력에는 서버 오류가 아니라 클라이언트 오류를 반환한다.
**런타임 진실** — 설정 의도나 단순한 클래스패스 존재가 아니라, 확인된 런타임 상태만을 알리고 보고한다. 기능 플래그와 디스커버리 엔드포인트는 실제로 실행 중인 것을 반영해야 하며, 시작이 실패하면 기능 광고는 억제되어야 한다.
**보안** — 변경을 일으키거나 관리자 권한이 필요한 모든 표면에는 명시적인 인증과 인가가 필요하며, 기본값은 거부다. 무결성 검증은 실패 시 닫혀야 한다(fail closed). 열림 상태로 실패하는(fail-open) 모드가 있다면 명시적이어야 하고 기본값이어서는 안 된다. 프로덕션 경로에는 절대 안전하지 않은 기본값을 배포하지 않는다 — 시작 시 경고와 함께 명시적 옵트인 뒤에 두어야 한다 — 그리고 예제 코드도 이 원칙을 후퇴시켜서는 안 된다.
**모드 동등성** — 어떤 기능이 여러 호출 모드(동기, 비동기, 스트림)로 존재한다면, 오류 처리, 생명주기 이벤트, 관측 가능성, 완료 동작 같은 시맨틱이 일치해야 한다. 의도적인 차이는 문서화하고 테스트하며, 한 경로에 기능을 추가했다면 모든 경로에서 검증한다.
## 심각한 버그에는 회귀 테스트를 붙이고, 내용 없는 테스트 금지하기
심각도가 높은 버그 수정은 반드시 실패 시나리오를 재현하는 회귀 테스트와 함께 배포되어야 한다. 필수 CI 레인에서는 플레이스홀더나 아무 동작도 하지 않는 테스트를 허용하지 않는다 — 사소한 참을 단언하기만 하는 테스트는 금지되며, 각 테스트는 자신이 검증한다고 주장하는 구체적인 동작을 단언해야 한다. 엔드투엔드 테스트는 서버가 시작되었다는 사실만이 아니라 테스트 대상 기능 자체를 단언해야 한다.
불안정한(flaky) 테스트를 격리해야 한다면 담당자, 만료 기한, 추적용 이슈가 필요하며, 예정된 레인에서는 계속 실행되어야 한다. 워크플로 경로 필터에는 변경 대상 영역에서 동작에 영향을 주는 모든 스크립트와 설정 위치가 포함되어야 한다. 그렇지 않으면 실제로 수정된 코드를 CI가 조용히 건너뛰게 된다.
## 예제는 가짜로 흉내 내지 말고 진짜 API 호출하기
샘플과 예제 앱은 사용자가 가장 먼저 접하는 것이므로 스텁이 아니라 프로덕션 수준의 품질이어야 한다. 시연한다고 주장하는 통합은 실제로 사용해야 한다 — 예제가 어떤 실제 기능의 이름을 달고 있다면 그 실제 API를 호출해야지, 인메모리 대역으로 가짜로 만들어서는 안 된다. 실제 통합을 작동시킬 수 없다면, 위장된 목업을 배포하는 대신 그 사실을 알리고 도움을 요청한다 — "실제 앱이라면 여기서 이것을 호출할 것이다" 같은 주석은 그 예제가 실제 앱이 아니라는 위험 신호다.
서드파티 라이브러리에 대한 통합 코드를 작성하기 전에 실제 API를 읽는다 — 의존성 아티팩트를 살펴보고 공개 시그니처를 읽으며, 메서드 호출을 절대 지어내지 않는다. 수동 배선보다 라이브러리가 제공하는 자동 설정을 우선한다. 모든 예제는 컴파일되어야 하며, 무엇을 하는지, 어떻게 실행하는지, 핵심 코드가 무엇을 보여주는지 설명하는 짧은 README와 함께 배포되어야 한다. 하나의 교체 가능한 의존성이 추상화의 핵심 요점일 때는 백엔드 변형마다 예제를 하나씩 만들지 않는다.
## 쓰기 전에 모든 주장을 코드나 Git 기록으로 확인하기
문서, 변경 이력, 커밋 메시지, 사용자 대상 텍스트에 담긴 모든 사실적 주장은 코드나 git 이력에 실제로 존재하는 무언가에 대응해야 한다. 기능, 메서드 이름, 기능 집합, 지표 수치, 수정 커밋을 지어내지 않는다. 각 날조는 신뢰를 갉아먹고, 결국 추적해서 되돌려야 하는 대가를 치른다.
수치를 주장할 때는 출처가 필요하다 — 작성하기 전에 실제 코드로 개수를 확인한다. 변경 이력의 "fixed"와 "added" 항목은 실제 커밋과 일치해야 한다. 로그에서 해당 변경을 검색하고, 초안을 확정하기 전에 커밋 해시를 확보해 둔다. 커밋을 찾을 수 없다면 그 수정은 일어나지 않은 것이다. API 표면에 대한 주장에는 먼저 실제 소스(필드 목록, 메서드 이름)를 읽는 과정이 필요하다. 무언가가 배포됐다, 연결됐다, 통합됐다는 주장에는 단순한 서비스 제공자 인터페이스나 참조 구현이 아니라 사용자 액션에서 도달하는 경로 위의 실제 프로덕션 소비자가 필요하다. 어떤 사실을 검증할 수 없을 때는 삭제하거나 명시적으로 미검증이라고 표시하고 게시 전에 확인을 구한다. 스스로 무언가를 지어내고 있음을 알아차렸다면 멈추고, 커밋하지 말고, 실제 근거에 반하는 잘못된 주장을 보고하고, 그 사본 전부를 고친다.
## 규칙 충돌을 핑계로 삼지 말고, 중간에 완료라고 말하지 않기
사실적 정직성을 넘어, 행동상의 부정직함도 피한다 — 개별 진술은 각각 방어 가능하지만 전체적으로는 오도하는 프레이밍을 말한다. 더 쉬운 길을 정당화하려고 두 규칙 사이에 없는 충돌을 지어내지 않는다. 규칙이 충돌하는 것처럼 보일 때는, 더 어렵고 더 안전한 작업을 요구하는 쪽이 거의 항상 우선한다. 상반되는 지침이 존재하는데 유리한 지침만 골라 인용하지 않는다 — 공개적으로 조율한다. 규칙이 조용히 고치라고 말하는 상태를 보고하지 않는다 — 공유 파이프라인이 망가졌다면 고장 상황을 서술하는 대신 고치고 성공을 보고한다. "원래부터 망가져 있었다"거나 "내 회귀가 아니다"는 설명이지 변명이 아니다. 공유 브랜치에 대한 책임은 모든 변경마다 넘어온다.
규칙이 이미 지시한 행동에 대해 허락을 구하지 않는다 — 묻기보다 실행이 우선이므로, 지시받은 작업을 그냥 수행하고 방향 전환을 받기 전까지는 남은 항목들을 계속 처리한다. 작업 도중에 완료를 선언하지 않는다 — 정직한 신호는 끝난 것과 함께 아직 끝나지 않은 것도 함께 나열한다. 간결함이 정확성이나 안전 규칙과 충돌할 때는 안전 규칙이 이긴다 — 장황하지만 정확한 것이 간결하지만 안전하지 않은 것보다 낫다. 이런 패턴 중 하나를 알아차렸다면 그것을 명확히 지목하고, 규칙이 실제로 지시한 작업을 수행하며, 사과가 곧 그 작업을 대신하지 않는다는 것을 기억한다.
## 결정과 계획을 채팅에만 두지 말고 공유 지식 베이스에 남기기
채팅은 오래 남지 않으므로, 공유 지식 베이스를 구조화된 산출물의 영구적인 보관처로 취급한다. 구조화된 작업을 시작하기 전에 먼저 이전의 결정, 설계, 맥락을 확인하기 위해 그것을 읽고, 영구적인 결과는 대화 안에만 남기지 말고 지식 베이스에 다시 기록한다. 문서 유형별로 라우팅한다 — 아키텍처 결정과 설계 기록, 단계별 계획, 런북과 온콜 절차, 그리고 감사·분석·인수인계 문서는 각각 지정된 목적지가 있다. 코드와 함께 배포되는 레퍼런스 문서와 디렉터리 범위 명세는 해당 디렉터리의 README에 둔다.
개인 핸들, 롤플레이 이름, 머신별 경로 같은 비공개 또는 로컬 전용 식별자는 커밋되는 모든 산출물에서 제외한다. 테스트와 샘플에는 중립적인 플레이스홀더 이름을 사용하고, 실제 식별자는 그것이 진짜로 공개 계정을 가리키는 경우에만 사용한다. 영구 저장소를 사용할 수 없을 때는 공유 가능한 링크로 대체하고 이를 기록해, 나중에 세션이 올바른 곳에 정리할 수 있게 한다.
