#1에서 스캐폴더를 만들었다. 룰과 스킬을 깔아줬지만 에이전트가 안 지켜도 막을 수 없었다. 이번에 진짜 고삐를 만들었다. 에이전트가 파일을 수정하면 자동으로 검증하고, 에러가 나면 즉시 수정하게 만드는 시스템이다.
Hook이 뭔데
Claude Code, Gemini CLI 같은 AI 코딩 에이전트들은 파일을 수정할 때 Hook을 실행할 수 있다. git hook이랑 비슷한데, 에이전트의 도구 사용 전후에 끼어드는 것이다.
에이전트가 파일 수정 시도
→ PreToolUse (수정 전에 실행) — 차단 가능
→ 파일 수정
→ PostToolUse (수정 후에 실행) — 피드백 전달
에이전트마다 이름이 다르지만 개념은 같다.
| 에이전트 | 수정 전 | 수정 후 |
|---|---|---|
| Claude Code | PreToolUse | PostToolUse |
| Gemini CLI | BeforeTool | AfterTool |
| Codex CLI | PreToolUse | PostToolUse |
중요한 건 같은 bash 스크립트로 모든 에이전트를 커버한다는 점이다. 설정 파일 포맷만 다르고, 실행되는 hook 스크립트는 동일하다.
5개의 Hook
| Hook | 시점 | 한 줄 요약 |
|---|---|---|
| scope-guard | 파일 수정 전 | 허용 범위 밖 파일 수정 차단 |
| scaffold-guard | 파일 생성 전 | 파일 네이밍 규칙 검증 |
| post-write | 파일 수정 후 | lint + 타입체크 + 보안검사 |
| session-init | 세션 시작 | 프로젝트 컨텍스트 주입 |
| stop-review | 응답 종료 | 빌드 + 테스트 최종 검증 |
이 중에서 post-write가 핵심이다. 나머지는 보조.
post-write — 핵심 Hook
에이전트가 파일을 수정할 때마다 실행된다. 하는 일이 많다.
에이전트 Write → post-write.sh 실행
├── 0. Auto-format (biome/prettier로 자동 포맷)
├── 1. Lint 검사
├── 2. TypeScript type-check
├── 3. Import 위반 검사 (FSD/Clean 아키텍처 레이어 규칙)
├── 4. 블록체인 보안 검사 (.sol/.rs/.move)
└── 5. codingStandards 위반 검사
에러가 없으면 clean을 기록하고 끝. 에러가 있으면 재밌어진다.
Self-heal — 에러 나면 알아서 고쳐
PostToolUse hook의 출력은 에이전트 컨텍스트에 자동 주입된다. Claude Code 기준으로 additionalContext 필드를 쓰면 에이전트가 다음 행동에 이 정보를 참고한다.
{
"additionalContext": "⚠️ Type errors in src/ui/input.tsx:\nsrc/ui/input.tsx(3,1): error TS2322: ...\n\n즉시 수정하라."
}
에이전트가 파일을 쓰면 → hook이 에러를 발견하면 → “즉시 수정하라”를 에이전트에 전달하면 → 에이전트가 알아서 고친다. 이게 self-heal이다.
단, 보안 에러는 다르게 처리한다.
| 에러 종류 | 동작 |
|---|---|
| 단순 에러 (TS2322, lint 위반 등) | “즉시 수정하라” — 컨펌 없이 자동 수정 |
| 보안 에러 (SWC-115, reentrancy 등) | “원인을 설명하고 수정 계획을 제시한 후 사용자 확인을 받아라” |
tx.origin 사용이나 reentrancy 같은 보안 이슈는 에이전트가 맥락 없이 바꾸면 더 위험할 수 있어서, 사용자 확인을 거치게 했다.
scope-guard — 범위 밖 수정 차단
harness.config.json에 allowedScopes를 정의하면, 그 밖의 파일을 수정하려 할 때 exit 2로 차단한다.
{
"agent": {
"allowedScopes": ["src/**/*", "tests/**/*", "docs/**/*"]
}
}
에이전트가 package.json이나 .gitignore를 건드리려 하면 막힌다. 단, harness.config.json, package.json, tsconfig.json 같은 설정 파일은 항상 허용 목록에 넣어뒀다.
이걸 만들면서 내가 먼저 당했다. Confluence 문서를 docs/에 쓰려는데 scope-guard가 막아서, 결국 templates/에 넣었다. 하네스가 제대로 동작한다는 증거이긴 하다.
scaffold-guard — 파일 네이밍 검증
파일을 생성할 때 네이밍 규칙을 검증한다. 여기서 까다로웠던 건 언어별로 강제되는 네이밍이 다르다는 것이다.
| 언어 | 강제 네이밍 | 이유 |
|---|---|---|
| Go | snake_case | 언어 규약 |
| Python | snake_case | PEP 8 |
| Java | PascalCase | 클래스명 = 파일명 |
| Solidity | PascalCase | 컨트랙트명 = 파일명 |
| JS/TS | 사용자 선택 | 팀 컨벤션에 따라 다름 |
모노레포에서 FE는 camelCase, Go API는 snake_case, Solidity 컨트랙트는 PascalCase를 써야 하는데, 하나의 설정으로 전체를 강제하면 안 된다. 그래서 fileNaming을 앱별 맵으로 만들었다.
{
"rules": {
"fileNaming": {
"web": "camelCase",
"api": "snake_case",
"contracts": "PascalCase"
}
}
}
그리고 언어 규약으로 강제되는 파일명 패턴은 검증을 스킵한다. Go의 _test.go, Python의 test_*.py, Rust의 mod.rs 같은 건 네이밍 규칙과 무관하게 허용해야 한다. 이것도 실제 테스트 프로젝트에서 Go 테스트 파일이 inquiry-service-test.go로 생성되는 걸 보고 추가했다. Go 테스트는 반드시 _test.go여야 한다.
블록체인 보안 검사
post-write에서 .sol, .rs, .move 파일을 수정하면 보안 패턴을 자동으로 검사한다.
| 파일 | 검사 항목 |
|---|---|
.sol | tx.origin(SWC-115), selfdestruct(SWC-106), delegatecall(SWC-112), floating pragma(SWC-103), reentrancy |
.rs (Anchor) | unchecked arithmetic, unwrap() in production |
.move | public entry without assert! |
이건 linter가 아니라 grep 기반 패턴 매칭이다. 정밀도는 떨어지지만, slither나 mythril 같은 전문 도구를 설치하지 않아도 기본적인 위험 패턴은 잡을 수 있다.
Claude Code가 가장 잘 동작하는 이유
같은 hook 스크립트가 모든 에이전트에서 돌아가지만, Claude Code에서 가장 완전하게 동작한다. 이유는 PostToolUse의 additionalContext다.
Claude Code는 hook의 stdout을 파싱해서 additionalContext 필드를 에이전트 컨텍스트에 자동 주입한다. post-write가 “TS2322 에러가 있다, 즉시 수정하라”를 출력하면 에이전트가 다음 행동에서 이걸 보고 자동으로 수정한다.
다른 에이전트들은 hook 실행 + 메트릭 수집은 되지만, 에러 감지 후 “자동 수정 지시”를 에이전트 루프 안에서 전달하는 방식이 각각 다르다. Gemini CLI는 AfterTool에서 stdout을 읽지만, 에이전트 컨텍스트 주입이 Claude Code만큼 매끄럽지 않다.
다음
Hook이 에러를 잡아내는 건 됐다. 근데 같은 에러가 계속 반복되면? #3에서는 에이전트가 실수에서 학습하게 만든다 — learnings.json에 에러를 기록하고, 반복되면 규칙을 자동 추가하고, 메트릭으로 하네스의 효과를 측정하는 시스템을 다룬다.