CI가 강제할 수 없는 코딩 에이전트용 규칙 작성을 중단한 이유
요약
코딩 에이전트를 위한 단순한 문서 기반 지침의 한계를 지적하며, 규칙을 '설명'이 아닌 실행 가능한 '강제(enforcement)' 도구로 전환해야 함을 강조합니다. 반복적이고 기계적 탐지가 가능한 실패 사례를 중심으로 작은 단위의 CLI 게이트를 구축하는 전략을 제안합니다.
핵심 포인트
- 텍스트 기반 지침은 인간과 에이전트 모두에게 망각될 위험이 있음
- 규칙은 설명(Description)이 아닌 강제(Enforcement) 수단이어야 함
- 반복적이고 기계적 탐지가 가능한 사례를 게이트 후보로 선정
- Agents Playbook을 통해 실행 가능한 작은 단위의 CLI 게이트 활용 가능
저는 예전에 저장소 지침(repository instructions)을 최종 결과물로 취급하곤 했습니다.
신중하게 AGENTS.md를 작성합니다. 아키텍처를 설명합니다. any 사용을 금지합니다. 명명된 내보내기(named exports)를 요구합니다. 코딩 에이전트에게 파일을 작게 유지하고, 계약(contract)이 변경될 때 ADR(Architecture Decision Record)을 업데이트하라고 지시합니다.
문서 자체는 훌륭할 수 있지만, 다음 풀 리퀘스트(pull request)가 여전히 이를 위반할 수 있습니다.
그것이 항상 에이전트의 잘못은 아닙니다. 산문(prose)으로만 존재하는 규칙은 인간이든 자동화된 도구든 모든 기여자에게 정확히 적절한 순간에 그 규칙을 기억할 것을 요구합니다. 리뷰(Review) 단계는 저장소가 해당 규칙이 무시되었다는 사실을 처음으로 발견하게 되는 장소가 됩니다.
저는 이제 지침을 '설명'으로, 게이트(gate)를 '강제(enforcement)'로 취급하기 시작했습니다.
거버넌스 배포가 아닌, 하나의 실패부터 시작하라
유혹적인 대응은 모든 것을 자동화하는 것입니다. 하지만 이는 다른 문제를 야기합니다. 아무도 이해하지 못하고 모두가 우회하는 법을 배우게 되는 소음 가득한 체크(checks)의 벽을 만들게 됩니다.
저는 이제 이미 발생한 실패부터 시작합니다.
에이전트가 TypeScript 변경 사항을 빠르게 통과시키기 위해 any를 사용한다고 가정해 봅시다. 저장소 규칙은 unknown을 사용하고, 값을 좁히고(narrow), 런타임(runtime)에 외부 입력을 검증하라고 명시합니다. 리뷰어가 이 지름길을 포착하여 수정을 요청하지만, 두 번의 풀 리퀘스트가 지난 후 동일한 실수가 다시 나타납니다.
이것은 다음과 같은 이유로 좋은 게이트 후보가 됩니다:
- 반복적임;
- 기계적으로 탐지 가능함;
- 확인 비용이 저렴함;
- 실패 시 조치 가능함.
“적절한 추상화를 선택하라”는 좋은 게이트 후보가 아닙니다. 그것은 여전히 엔지니어링적 판단(engineering judgment)을 필요로 하기 때문입니다.
가장 작고 유용한 버전을 재현하라
저는 Agents Playbook에서 가져온 실행 가능한 게이트들을 의존성 없는(zero-dependency) CLI로 공개했습니다. 플레이북의 나머지 부분을 채택하지 않고도 하나의 게이트를 테스트할 수 있습니다.
작은 피스처(fixture)를 생성합니다:
mkdir -p /tmp/playbook-gate-demo/src
printf 'export const answer: number = 42\n' \
> /tmp/playbook-gate-demo/src/example.ts
no-any 게이트만 실행합니다:
npx --yes @agentskit/playbook@0.1.0 \
run no-any \
--cwd /tmp/playbook-gate-demo
유효한 파일은 통과합니다:
no-any: OK. 0 escape-hatched.
이제 이를 단축 명령어로 교체해 보겠습니다:
printf 'export const unsafe: any = 42\n' \
> /tmp/playbook-gate-demo/src/example.ts
...
명령어가 0이 아닌 종료 코드(non-zero)로 종료되며 위반 사항을 지적합니다:
src/example.ts:1:22 — 타입 위치에 `any`가 있습니다.
`unknown` + 런타임 스키마 파싱(runtime schema parse)을 사용하거나, 특정 타입을 사용하세요.
여기서 유용한 점은 스크립트가 any를 검색할 수 있다는 사실이 아닙니다. 성숙한 TypeScript 저장소라면 보통 AST(Abstract Syntax Tree) 기반의 @typescript-eslint/no-explicit-any 규칙을 사용하고 있을 것입니다. 진짜 유용한 점은 피드백의 형태입니다:
- 저장소가 기여자(contributor)가 이해할 수 있는 언어로 규칙을 명시합니다.
- 빠른 검사(fast check)가 기계적인 부분을 잡아냅니다.
- 에러 메시지가 권장되는 대체 방법을 설명합니다.
- 동일한 명령어가 로컬과 CI(지속적 통합) 환경 모두에서 실행됩니다.
정책(policy)과 게이트(gate)는 서로 다른 역할을 수행하지만, 같은 방향을 가리킵니다.
탈출구(Escape hatches)에는 예산이 필요합니다
절대적인 규칙은 종종 부정직해지기 쉽습니다.
레거시 의존성(legacy dependency), 생성된 타입(generated type), 또는 마이그레이션(migration)으로 인해 정말로 일시적인 예외가 필요한 경계선들이 존재합니다. 만약 가능한 대응책이 "의존성을 다시 작성하라" 또는 "검사를 비활성화하라"뿐이라면, 사람들은 검사를 비활성화할 것입니다.
참조용 게이트는 동일 라인에 명시적인 예외를 허용합니다:
export const legacyValue: any = input // allow-any: upstream SDK has no types
이 주석은 타입을 안전하게 만드는 마법의 문구가 아닙니다. 결정을 가시화(visible)하는 것입니다. 게이트는 탈출구(escape hatches)의 개수를 세며, 저장소는 그 수가 조용히 늘어나지 않도록 기준선(baseline)을 유지할 수 있습니다.
이것이 예외(exception)와 보이지 않는 우회(invisible bypass)의 차이점입니다:
- 이유가 코드 옆에 명시됩니다.
- 리뷰어가 이에 대해 이의를 제기할 수 있습니다.
- CI가 예외의 증가를 방지할 수 있습니다.
- 팀이 나중에 기술 부채(debt)를 검색할 수 있습니다.
정당한 예외를 표현할 수 없는 게이트는 결국 게이트 외부에서 우회되는 방법을 찾게 될 것입니다.
비용이 많이 드는 피드백 루프(feedback loop) 이전에 검사를 배치하세요
저는 풀 리퀘스트(pull request)를 보내기 전에 가장 짧고 유용한 루프를 사용할 수 있기를 바랍니다.
Playbook CLI를 사용하는 저장소(repository)의 경우, 패키지 스크립트(package scripts)를 작게 유지할 수 있습니다:
{
"scripts": {
"check:agent-rules": "agents-playbook run no-any named-exports",
...
CI는 동일한 명령어를 실행해야 합니다. 프리커밋 훅(pre-commit hook)은 편의를 위한 도구일 뿐, 신뢰 경계(trust boundary)가 아닙니다. 훅은 건너뛸 수 있지만, 필수적인 CI 검사는 공유 브랜치(shared branch)를 보호합니다.
특히 코딩 에이전트(coding agents)에게는 빠른 로컬 경로가 중요합니다. 에이전트가 편집 직후에 집중된 게이트(gate)를 즉시 실행할 수 있다면, 관련 컨텍스트(context)가 여전히 활성화되어 있는 동안 위반 사항을 수정할 수 있습니다. 거대한 저장소 파이프라인(pipeline)을 기다리는 것은 5초면 끝날 수정을 또 다른 리뷰 사이클(review cycle)로 만들어 버립니다.
통과된 게이트(green gate)와 좋은 변경(good change)을 혼동하지 마세요
no-any 게이트는 자신이 인식하는 패턴을 찾지 못했다는 것을 증명할 수 있습니다. 하지만 교체된 타입(type)이 올바르다는 것은 증명할 수 없습니다.
이름 지정 내보내기(named-export) 게이트는 API가 잘 설계되었음을 증명할 수 없습니다. 파일 크기(file-size) 게이트는 파일을 분할한 것이 응집도(cohesion)를 개선했는지 증명할 수 없습니다. 비밀번호 스캐너(secret scanner)는 권한 부여(authorization)가 올바른지 증명할 수 없습니다.
에이전트가 개입할 때 이 경계는 매우 중요합니다. 기계적으로 확인 가능한 규칙(Machine-checkable rules)은 깔끔한 통과(pass) 또는 실패(fail)를 생성하기 때문에 매력적이지만, 엔지니어링 품질은 세기 쉬운 것들의 집합보다 훨씬 더 거대한 개념입니다.
저는 세 가지 카테고리를 사용합니다:
| 규칙 | 최적의 강제 방식 |
|---|---|
| 결정론적 구문(Deterministic syntax) 또는 저장소 불변성(repository invariant) | 자동화된 게이트 |
| ... |
만약 설계 규칙(design rule)이 의도(intent)를 추측하지 않고서는 확인될 수 없다면, 저는 이를 리뷰 레이어(review layer)에 남겨둡니다. 약한 대리 지표(weak proxy)를 자동화하는 것은 인간의 결정이 필요함을 인정하는 것보다 더 나쁠 수 있습니다.
성과를 거둔 도입 순서
현재 Playbook에는 비밀번호(secrets), 파일 크기(file size), 이름 지정 내보내기(named exports), ADR/RFC 요구 사항, 국제화(internationalization), 디자인 토큰(design tokens), 네이티브 HTML(native HTML), 그리고 PR 의도(PR intent)와 같은 사항들을 다루는 13개의 의존성 없는(zero-dependency) 참조 게이트가 포함되어 있습니다.
저는 첫날부터 13개를 모두 활성화하지는 않을 것입니다.
제가 사용하는 순서는 다음과 같습니다:
- 실제 리뷰에서 반복되는 실패 사례를 하나 수집합니다.
- 저장소 규칙(repository rule)과 그 이면에 있는 이유를 작성합니다.
- 기계적인 위반(mechanical violation)을 잡아낼 수 있는 가장 작은 게이트(gate)를 추가합니다.
- 실패 메시지가 기여자(contributor)에게 다음에 무엇을 해야 할지 알려주도록 만듭니다.
- 로컬과 필요한 CI에서 실행합니다.
- 다른 게이트를 추가하기 전에 오탐(false positives)을 측정합니다.
이렇게 하면 거버넌스(governance)가 증거에 기반하여 유지됩니다. 모든 체크는 다음과 같은 질문에 답할 수 있어야 합니다: “당신은 어떤 실패를 방지하기 위해 여기에 있습니까?”
저는 Agents Playbook을 만들고 유지 관리하고 있으므로, 이것은 중립적인 도구 리뷰가 아닙니다. CLI와 13개의 모든 참조 게이트는 오픈 소스이며, 위의 좁은 범위의 실험은 API 키나 모델 호출 없이도 의도적으로 재현하기 쉽게 만들어졌습니다.
준비 과정 공개: 저는 이 초안을 정리하고 비판하는 데 AI 도구를 사용했습니다. 저는 게시된 패키지를 대상으로 명령어를 실행했고, 게이트 소스 코드와 테스트를 검사했으며, 공개 저장소와 대조하여 주장을 확인했습니다. 저는 최종 텍스트에 대해 책임을 집니다.
만약 여러분이 이미 CI에서 코딩 에이전트 규칙을 강제하고 있다면, 저는 가장 거대한 정책 파일이 아니라, 가장 유용한 실패 메시지를 생성하는 규칙에 관심이 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기