단 한 번의 명령으로 AI 에이전트가 사용하기 안전한 코드베이스 만들기
요약
AI 코딩 에이전트가 발생시키는 반복적인 실수와 위험한 명령을 방지하기 위한 'agent-starter' 도구를 소개합니다. 이 도구는 Claude Code 환경에서 가드레일을 설정하여 에이전트가 안전하게 코드베이스를 수정할 수 있도록 돕습니다.
핵심 포인트
- 에이전트의 비동기 호출 누락, 타입 오류, 위험한 Git 명령 등의 반복적 실패 해결
- agent-starter를 통해 단 한 번의 명령으로 5개의 강제 실행 훅과 8개의 슬래시 명령 설치
- git reset --hard 등 되돌릴 수 없는 파괴적인 Bash 명령 실행을 사전에 차단
- 에이전트의 자율 실행 중 발생할 수 있는 실수를 방지하는 가드레일 시스템 구축
문제는 에이전트가 나쁘다는 것이 아닙니다. 매번 똑같은 방식으로 실패한다는 점입니다.
AI 코딩 에이전트는 빠릅니다. 하지만 에이전트가 작성한 diff(차이점)를 충분히 검토해 보았다면, 다음과 같은 몇 가지 실패 사례가 반복해서 나타나는 것을 보았을 것입니다:
await를 누락하여 비동기 호출(async call)을 프로덕션 환경에서만 발견되는 레이스 컨디션(race condition)으로 만드는 경우.- 타입(types)이 일치하지 않는 곳마다
as any를 뿌려두는 경우. package.json에 없는 패키지로부터 import를 시도하는 경우.- 실패하는 코드를 감싸서 빨간색 테스트를 초록색으로 만들기 위해
try/except: pass를 사용하는 경우. - 가끔씩, 커밋되지 않은 작업 내용을 잡아먹는
git reset --hard를 실행하는 경우.
리뷰를 통해 한 번에 하나의 diff씩 영원히 이를 잡아낼 수도 있습니다. 아니면 에이전트가 코드를 작성하는 즉시 코드베이스 자체가 이를 거부하도록 만들 수도 있습니다. 두 번째 옵션이 바로 agent-starter가 설정해 주는 방식이며, 그 대부분은 단 한 번의 명령으로 이루어집니다.
단 한 번의 명령
/plugin marketplace add sneg55/agent-starter
/plugin install agent-starter@agent-starter
이 명령은 5개의 강제 실행 훅(enforcement hooks)을 연결하고 8개의 슬래시 명령(slash commands)을 로드합니다. 그런 다음 코드베이스를 지정하면 됩니다: 새로운 레포지토리를 위한 /new-project, 기존 레포지토리를 개조하기 위한 /adopt-project(먼저 감사(audit)를 수행하며, 모든 것은 선택 사항(opt-in)이고 아무것도 덮어쓰지 않습니다).
이 포스트의 나머지 내용은 해당 명령이 실제로 무엇을 하는지, 그리고 왜 각 요소가 그곳에 있는지에 대한 설명입니다.
에이전트가 작업하는 동안 작동하는 가드레일 (Guardrails)
Claude Code 훅은 에이전트의 루프 내 정의된 시점, 즉 Bash 명령 실행 전, 파일 쓰기 후, 세션 시작 시점에 셸 스크립트(shell scripts)를 실행합니다. agent-starter는 이를 의존성이 적고 가벼운 작은 bash 스크립트로 제공합니다. 그중 제 역할을 다하는 것들은 다음과 같습니다:
되돌릴 수 없는 명령 차단하기
block-dangerous-commands.sh는 어떤 Bash 명령이 실행되기 _전(before)_에 실행됩니다. 이는 되돌릴 수 없는 작업물을 파괴하는 몇 가지 항목들을 차단합니다:
git push --force(--force-with-lease사용을 권장합니다)git reset --hard/--mergegit clean -f,git checkout -- .,git restore ./,~, 또는$HOME에 대한 재귀적rm명령chmod -R 777 /
이 명령이 실행될 때, 에이전트는 stderr(표준 에러)에서 다음 내용을 확인하고 중단합니다:
Blocked dangerous command:
git push --force origin main
...
실제로 의도한 드문 경우를 위해 탈출구(CLAUDE_ALLOW_DANGEROUS=1)가 마련되어 있습니다. 목표는 에이전트를 일일이 감시하는 것이 아닙니다. 파괴적인 경로를 선택할 때, 긴 자율 실행(autonomous run) 도중 실수로 입력된 토큰이 아니라 의도적인 오버라이드(override)를 거치도록 만드는 것입니다.
침묵하는 에러 처리(silent error handling) 거부하기
check-silent-errors.sh는 모든 쓰기 작업 _이후(after)_에 실행됩니다. LLM은 실패하는 테스트를 통과시키기 위해 try/except를 사용하곤 하지만, 이 경우 코드는 여전히 깨져 있으며, 단지 나중에 아무도 알아차리지 못할 정도로 조용히 깨질 뿐입니다. 이 훅(hook)은 에러를 삼켜버리는 패턴들을 차단합니다:
- Python에서의 단순한
except:,except: pass,except: ... - JS/TS에서의 빈
catch {} - 본문이
console.log뿐인catch블록 (info 수준으로 에러를 로깅하는 것도 결국 에러를 삼키는 것입니다)
모든 핸들러는 실질적인 작업을 수행해야 합니다: 에러를 다시 발생(re-raise)시키거나, 센티널(sentinel)을 반환하거나, console.error를 통해 컨텍스트와 함께 로깅해야 합니다. 일회성 예외는 명시적이어야 합니다. // silent-ok (또는 # silent-ok) 주석을 남기면 훅은 해당 지점을 건드리지 않습니다. 예외 사항이 diff(차이점)에 남기 때문에, 숨겨지는 대신 리뷰 과정에서 드러나게 됩니다.
게이트가 아닌 피드백 채널로서의 린트(Lint)
이 저장소에서 다른 것은 다 가져가지 않더라도, 이것만큼은 꼭 가져갈 만한 아이디어입니다.
대부분의 프로젝트는 린트(lint)를 CI 게이트로 취급합니다. 에이전트가 전체 기능을 작성하고 PR을 올리면 CI가 실패(red)하고, 그때쯤이면 에이전트는 이미 다른 작업으로 넘어가 버린 상태입니다. 이 경우 수정 비용이 많이 들고 컨텍스트를 놓치게 됩니다.
agent-starter는 모든 개별 수정 사항에 대해 린트(lint)를 실행합니다. lint-on-edit.sh는 에이전트가 방금 수정한 파일에 대해서만 biome check --write를 실행한 다음 eslint --fix를 실행하며, 남아있는 모든 오류를 stderr(표준 에러)로 다시 전달합니다. 에이전트는 다음 턴에 이 오류들을 읽고 컨텍스트가 아직 유효할 때 이를 수정합니다. Python의 경우 ruff check를 실행한 후 ruff format을 실행하여 동일한 처리를 거칩니다.
이 규칙 세트는 취향이 아니라 에이전트가 실제로 저지르는 실수에 맞춰 조정되었습니다:
- 비동기 정확성(Async correctness)은 1순위(Tier 1) 에러 레벨입니다:
no-floating-promises,no-misused-promises,await-thenable. 이는 작성 시점에 잡아내는await누락 클래스입니다. - 임포트 해석(Import resolution)은 환각(hallucinations)을 잡아냅니다:
import/no-unresolved는 존재하지 않는 패키지로부터의 임포트를 표시하며,import/no-extraneous-dependencies는 선언되지 않은 임포트를 표시합니다. no-explicit-any와no-unsafe-*계열 규칙은as any라는 탈출구를 차단하여, 에이전트가 타입을 우회하는 대신 타입 구조(type shape)를 직접 수정하도록 만듭니다.- 규칙을 억제하려면 서술된 이유가 필요합니다 (
require-description). 따라서eslint-disable이 저항이 가장 적은 쉬운 길로 조용히 선택될 수 없게 합니다.
그리고 의도적인 생략도 있습니다: max-lines는 없습니다. 줄 수 제한은 일관성 있는 긴 코드(리듀서(reducer), 파서(parser), JSX가 많은 컴포넌트 등)에 불이익을 주고, 에이전트가 논리를 함께 읽어야만 의미가 있는 일회용 헬퍼 함수들로 잘게 쪼개도록 유도합니다. 대신 15로 제한된 인지 복잡도(Cognitive complexity)를 통해 실제로 중요하게 생각하는 요소를 측정합니다.
Biome은 포맷팅과 빠른 구문 규칙(ESLint보다 10~100배 빠름)을 담당하며, ESLint는 컴파일러가 필요한 타입 인식 규칙(type-aware rules)을 담당합니다. 이를 통해 모든 키 입력 시의 속도와 중요한 지점에서의 깊이를 모두 얻을 수 있습니다.
더 조용한 두 가지
check-file-size.sh는 파일이 설정된 크기 목표를 초과할 때 경고를 보냅니다. 파일 크기가 너무 커지면 에이전트가 맥락을 놓치기 때문입니다. check-codebase-health.sh는 세션 시작 시 실행되어, 에이전트가 작업 중간에 문제를 발견하는 대신 문맥(context)을 파악한 상태로 시작할 수 있게 합니다. 또한, 이전 설정들을 위한 선택적 '편집 전 읽기(read-before-edit)' 가드(./install.sh --with-read-guard)도 제공됩니다. 최신 Claude Code는 '편집 전 읽기'를 기본적으로 강제하므로, 기본 설치에서는 의도적으로 제외되었습니다.
지속 가능한 문맥(Durable context): CLAUDE.md와 메모리 분류 체계
가드레일(Guardrails)이 잘못된 쓰기를 방지한다면, 문맥(Context)은 잘못된 쓰기를 예방합니다. CLAUDE.md 템플릿은 4가지 유형의 메모리 시스템을 제공하여, 에이전트가 프로젝트에 대해 알고 있는 정보가 단순한 메모장이 아닌 구조화된 형태를 갖추도록 합니다.
| 유형 | 포함 내용 |
|---|---|
| user | 당신이 누구인지: 역할, 선호도, 숙련도 |
| ... |
두 가지 규칙이 이 시스템의 신뢰성을 유지합니다. 코드나 git에서 유도할 수 있는 정보는 절대 저장하지 마세요. 그런 정보는 복사본이 오래되어 실제와 달라지기 시작하기 때문입니다. 또한, 상대적인 날짜는 절대적인 날짜로 변환하세요. "지난주"라는 표현은 세션이 세 번 지나고 나면 의미가 없어집니다. feedback 메모리는 '이유(Why)'와 '적용 방법(How to apply)'을 함께 담고 있어, 한 번 준 수정 사항이 계속해서 스스로 적용되도록 합니다.
실제로 새로운 부분: 코드베이스가 스스로 학습함
위의 모든 내용은 잘 구축된 고정된 설정입니다. 제가 단순히 설정 파일(dotfiles) 더미를 복사하는 대신 이것을 만든 이유는 그 위에 구축된 루프(loop) 때문입니다.
대부분의 시작 도구들은 스냅샷(snapshot) 형태입니다. 모든 프로젝트는 동일한 규칙에서 시작하며, 이 코드베이스가 실제로 어떻게 사용되는지에 대해 점점 더 똑똑해지지 않습니다. agent-starter는 그 신호(signal)를 포착하여 더 나은 규칙으로 전환합니다. 다음의 4단계로 이루어집니다:
① 신호(signal) → ② 저장(store) → ③ 승격(promote) → ④ 측정(measure) → (①로 복귀)
▲ │
└──────────────────────────────────────────────┘
- Signal (신호). 훅(hook)이 차단하거나 경고를 보낼 때마다(파일이 너무 큼,
await누락, 오류 삼킴, 위험한 명령 등),.harness/ledger.jsonl파일에 JSON 한 줄을 추가합니다. 로깅은 최선(best-effort)을 다할 뿐이며 항상 종료 코드 0을 반환하므로, 이를 호출한 훅을 절대 중단시키지 않습니다. 사용자의 명시적인 수정 사항은 이미feedback메모리로 캡처되며, 이는 모든 신호 중 가장 가치 있는 신호입니다. - Store (저장). 추가 전용(append-only) 원장(ledger)은 구조화된 이벤트를 보유하고, 메모리 파일은 산문(prose) 형태의 기록을 보유합니다. 원시 원장(raw ledger)은
.gitignore에 등록되어 제외됩니다(로컬용이며 노이즈가 많음). 정제된 학습 내용(distilled learnings)만이 커밋됩니다. - Promote (승격).
/reflect스킬은 원장과 사용자의 피드백 메모리를 읽고, 반복되는 실수를 클러스터링(clustering)하여 구체적인 변경 사항을 _제안(proposes)_합니다. 예를 들어 새로운 프로젝트 규칙, 훅 임계값(hook-threshold) 조정, 린트(lint) 규칙, 또는 ADR(Architecture Decision Record) 등이 될 수 있습니다. 어떤 것도 자동으로 적용되지 않습니다. 사용자가 모든 변경 사항을 승인해야 합니다. - Measure (측정). 통계 스크립트가
recurring_events지표를 계산하며, 각 성찰(reflection)은 이를.harness/reflections/에 스냅샷으로 저장합니다. 다음 성찰 단계에서 지난번에 추가한 규칙이 목표로 했던 실수를 실제로 줄였는지 확인할 수 있으므로, 아무도 검증하지 않은 선의의 규칙들이 쌓이기만 하는 대신 루프가 완성됩니다.
그 밑바탕에 깔린 원칙은 다음과 같습니다: 신호는 비공개이며(gitignored 된 원장), 지혜는 공유됩니다(커밋된 성찰과 그 결과로 생성된 규칙들). /new-project로 스캐폴딩(scaffolded)된 프로젝트는 이러한 구조가 내장된 상태로 탄생합니다.
패턴의 출처
기본 패턴들은 발명된 것이 아닙니다. 파일 크기 목표, 디렉토리당 도구 배치, 린트 계층화 등 많은 부분이 Claude Code CLI의 자체 소스에서 역공학(reverse-engineered)되었습니다. 해당 요소들은 리포지토리 내에 _"Anthropic의 Claude Code 소스에서 유도됨(derived from Anthropic's Claude Code source)"_이라고 표시되어 있습니다. 자기 개선 루프(self-improvement loop)와 추가적인 툴링(tooling)은 그 위에 추가된 것입니다.
시도해보기
원하는 목적에 맞는 진입점을 선택하세요:
- 모든 것을 한 번에 (Everything, one step): 이 포스트 상단에 있는 플러그인 설치를 이용하세요.
- 기술만 전역적으로 적용 (Just the skills, globally):
npx skills add sneg55/agent-starter -a claude-code -g명령어를 실행하세요. ~/.claude에 훅(hooks)만 추가: 리포지토리(repo)를 클론(clone)한 뒤./install.sh를 실행하세요. 설정 연결을 멱등성(idempotently) 있게 병합하므로, 다시 실행해도 항목이 중복되지 않습니다.- 설치 없이 사용: 에이전트(agent)가 해당 리포지토리를 가리키게 하세요. URL을 전달하며 "이 리포지토리를 읽고 내 프로젝트를 설정해줘"라고 말하면 됩니다. 에이전트가 엔트리 파일(entry file)을 읽고 대화형으로 설정을 진행합니다.
리포지토리(Repo): github.com/sneg55/agent-starter, MIT 라이선스.
만약 당신이 이미 매일 AI 에이전트(AI agent)와 함께 작업하고 있다면, 가장 레버리지가 높은 요소는 lint-on-edit입니다. 린터(linter)를 CI에서 분리하여 에이전트의 쓰기 루프(write loop) 안으로 옮겨보세요. 애초에 리뷰 단계까지 도달하지 않는 코드가 얼마나 많아지는지 확인하게 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기