조성개발실록
테마 변경
태그
방명록
ABOUT ME

💰 코드가 저렴해졌으니 규칙도 코드로 쓰면 되겠다

DevelopmentProductivity

March 29, 2026



요즘 AI로 인해 코드 생산이 저렴해졌다는 이야기로 가득합니다.
저도 근래 코드를 직접 작성한 경우가 드물고, 주로 코드를 설계하거나 의사결정 및 검토하는 데 시간을 많이 보냅니다.
생산성은 올라간 것이 맞는 것 같긴 한데요, 다른 병목을 만나게 됩니다.

병목은 나다

코드가 빠르게 생산되는 만큼 설계 및 검토에 쓰는 시간이 많아졌습니다.
저는 솔직히 아직 "테스트/검증만 잘 되면 OK" 는 못하겠어요.
아직도 IDE를 열고 코드를 봐야만 직성이 풀립니다.
그러다 보면 열에 아홉은 마음에 안 드는 것들이 있습니다. 예를 들면:

  • 여기는 디자인 시스템을 왜 안썼지? @docs/design-system-guideline.md 읽고 다시
  • 이건 공통으로 쓸 훅이 아니고 여기서만 쓰니까 이 위치보단 로컬범위로 옮겨야겠는데
  • FooCard는 FooList에서만 쓰는거 아니야? 컴포넌트 폴더 패턴으로 묶으라고 @docs/architecture-guideline.md

처음에는 에이전트가 읽을 문서들(CLAUDE.md 또는 AGENTS.md와 연결된 참조 문서 등..)을 갈고 닦는 데 노력을 많이 해봤는데요
이런 식의 문서화 및 컨텍스트 전달 + 똑똑한 모델 + 큰 컨텍스트 윈도우 + ...
에도 불구하고, 여전히 AI는 실수합니다.

똑바로 해

사람이라고 생각해보면 근데 딱히 화는 안 나는데요
"반드시 지켜야 할 룰"이랍시고 문서 몇 천 줄 써놓고 지켜라! 해도 규칙이 지켜지진 않습니다.
다 읽었어도 까먹기도 하고, 이게 그 상황인지 모르기도 하고, 그러면서 으레 실수하니까요
LLM도 그런 경향이 있는데, 컨텍스트에 담고서도 까먹습니다.
최근에 이런 현상을 컨텍스트 롯(context rot)이라고 부르는 것 같네요

평소처럼 이 문서 읽고 다시해라 라고 화내고 있었는데
번뜩! 생각이 났습니다

번뜩

그냥 규칙을 위반하면 알려주는 코드를 만들어서 규칙이 지켜지는 시스템을 만들면 되잖아??
코드가 저렴해졌으니 규칙까지도 코드로 써버리면 되겠는데요~
이렇게 해야 함을 까먹고 작업하다가 규칙을 위반하면, 맞다, 이렇게 해야했지를 알 수 있게요.
이러면 저는 좀 더 검토 병목을 줄이고, 중요한 것에 시간을 쏟을 수 있습니다.

규칙을 설명하는 데 지쳤다면: 시스템으로 만들자

이전에는 마치 밥 먹고 "너 여기 뭐 흘렸다" 를 나중에 친구가 알려주고 나서야 알 수 있었는데
밥을 먹으면서 흘리자마자 바로 알 수 있는 체계를 만드는 셈입니다.

작업하는 주체가 인간이든 AI든, "지금 잘못된 방향으로 가고 있다"를 빠르게 아는 환경이 필요하다는 생각이 들었습니다.
이전에 FSD 린트 규칙으로 "규칙이 지켜지는 환경"을 만들어본 것처럼요

경고입니다

근데 실무에서 갑자기 시스템을 쌓아볼까 해도 크게 두 가지 이유로 인해 주저하곤 하는데요
(1) 이걸 어떻게 판단하지? 규칙을 판단하는 코드를 작성하기엔 휴리스틱한 것 같아서
(2) 코드로 작성하려고 해도, 작성하는 데 시간이 많이 들 것 같아서

일단 1번, 휴리스틱하다는 것은 보통 제 착각입니다. 제가 부족해서 이런 생각을 했을 것 같네요
잠깐 생각해보면 어지간해선 패턴을 찾을 수 있고, 생각이 안 나면 AI와 몇 번 대화해보면 타협안이라도 찾을 수 있습니다.
그리고 2번의 경우, 이제 코드가 너무 저렴해져서 더 이상 이런 핑계를 댈 수 없습니다.
"이런 경우들은 이렇게 경고받게 하고 싶어" 만 정하면 사실상 다 됩니다
테스트 코드가 내 의도를 다 담는지, 그 검사가 내가 원하는 때에 원하는 형태로 피드백을 주는지, 같은 것을 검토하면 됩니다.

하네스 엔지니어링과 비슷할 수도?

OpenAI 팀이 하네스 엔지니어링에 대해 쓴 문서에는 이런 언급이 있는데요

문서화만으로는 에이전트가 생성한 코드베이스를 완전히 일관되게 유지할 수 없습니다. 구현을 세세하게 관리하지 않고 불변 조건을 강제 적용함으로써 기초를 튼튼히 유지하며 에이전트가 빠르게 출시할 수 있도록 합니다.
...
실제로 맞춤형 린터와 구조적 테스트, 그리고 소수의 '취향 불변성'을 통해 이러한 규칙을 적용합니다. 예를 들어, 맞춤형 린트를 사용하여 구조화된 로깅, 스키마 및 유형의 명명 규칙, 파일 크기 제한, 플랫폼별 안정성 요구 사항을 정적으로 적용합니다. 린트가 맞춤형이므로 오류 메시지를 작성하여 에이전트 컨텍스트에 수정 지침을 주입합니다.
...
문서화가 부족한 경우 규칙을 코드로 승격합니다.

이 글에서 OpenAI팀도 문서화만으로는 코드베이스의 일관성 유지를 보장할 수 없음을 경험했고,
이에 따라 규칙을 코드로 승격하도록 했음을 설명합니다.

Andrej Karpathy, Boris Cherny같은 사람들이 "에이전트가 닫힌 피드백 루프를 유지할 수 있게 해라"라고 하는 것과 비슷한 맥락이네요
Claude Code 창시자 Boris Cherny가 Claude Code를 사용하는 방법에서도 이런 이야기가 나옵니다.

문서만 준 경우 vs 규칙 가드레일을 세운 경우

핵심은 결정적(Deterministic) 프로세스로 가드레일을 세워, 무언가 잘못하면 즉시 알려주고 즉시 수정할 수 있게 한다 입니다.
사람이 참여하는 코드 리뷰는: 피드백은 느리고, 개선 루프는 길어지며, 소모되는 리소스는 비쌉니다. 여기서 병목이 생깁니다.
프리커밋 훅이 커밋 직전에, 린트가 코드 작성 직후, 경고해준다면 - 빠르고, 짧게 개선할 수 있고, 저렴합니다.

그래서 이런 것을 만들어봅니다.

크게 두 가지를 만들고 싶은데요,

  1. PR마다 "얼마나 잘 지켰는지" 검사하고, 새로운 위반은 없는지 확인합니다. + 나아가 점수를 산출해본다면?
  2. 프리커밋, 또는 AI의 작업 직후(ex. 클로드 코드에서는 PostToolUse 훅)에 새로운 위반을 발견하거나 경고하고, 닫힌 피드백 루프를 유지시킵니다.

특히 첫 번째 항목에서 점수를 도입해볼까? 생각한 이유는
이미 돌아가는 코드베이스에선 레거시와 이상향이 공존하기 마련인데
그럼 이미 레거시가 위반으로 잡힌 채로 시작할거구요. 이것을 점수로 나타내서
"지금 이상향에 어느정도 도달해있다" 를 알게 하면 어떨까? 싶어서 생각해봤습니다

이렇게 점수를 나타내서 PR마다 한 번씩 보게 하면 언젠가 레거시 근처를 작업할 때 "리팩토링 해야지.."를 생각하게 할 수 있지 않을까,
게다가 실제로 리팩토링해서 코드베이스가 개선되었을 때 그 개선된 점수 변화를 보여주면 개선하는 맛이 있지 않을까,
해서 실험적으로 시도해보는 중입니다.

이 맛이야

사실은 이게 오히려 100점이 아닌 점수가 깨진 유리창 효과 처럼 오히려 무뎌지게 만들진 않을까? 라는 걱정을 했는데
그래도 어디 돈 받고 파는 서비스도 아니고, 제가 쓰려고 만드는거라 일단 해보는 중입니다.
지금까진 꽤 괜찮은 것 같아요? 실제로 개선을 맛보고 나면 기분이 좋습니다

지금부터는 어떤 것을 구현했는지 대충 알아볼텐데,
구체적인 코드보다는 스니펫 중심으로 "이런 식으로 해봤다" 정도만 쓰려고 합니다.

1. 통합 러너로 "검사"를 플러그인처럼 등록하는 구조 만들기

개별 규칙들은 독립된 check-*.js 스크립트로 만들고,
통합 러너는 이들을 순회하며 실행하는 구조가 됩니다.

// index.js - 통합 러너
const checks = [
  require("./check-hook-scope"),
  require("./check-raw-div"),
  require("./check-api-url")
  // 새 체크 추가: check-*.js 파일 만들고 여기 등록
];

for (const check of checks) {
  const result = check.run();
  // result: { id, name, description, violations: [] }
  results.push(result);
}

각 체크(check-*.js)는 run() 함수를 export하고, 동일한 형태의 결과를 반환합니다.
새로운 규칙을 추가할 때는 해당 규칙을 검사하는 스크립트 파일을 구현하고 배열에 등록하면 됩니다.

테스트 코드는 여기서는 별로 할게 없었습니다. 여기서 구현하게 되면 통합 테스트인 셈이 되는데,
어차피 각 검사별로 단위 테스트를 검증하게 되기 때문에 단위 테스트만 잘 작성해줬습니다.

이 통합 러너는 아래와 같은 출력으로 점수를 보여주는데요,
전체 점수는 가장 간단하게 각 검사별 점수의 균등 가중 평균으로 계산했습니다.

🏗️  Architecture Guard — 92.5 / 100

  📦 Mantine & Design System (100.0)
    ✅ Raw div 사용 검사        100.0  (0 violations / 436 files)
    ...

  📦 Architecture Conventions (82.4)
    ⚠️  컴포넌트 폴더 패턴 검사   80.0  (26 violations / 130 files)
    ...

  📦 API & Data Fetching (100.0)
    ✅ API URL 중앙화 검사       100.0  (0 violations / 2290 files)
    ...

--compare플래그로 다른 실행 결과와 비교하여 변화를 보여줄 수도 있습니다.
예를 들어 dev 브랜치와 피쳐 브랜치 간의 점수 변화를 보여주는 등이요.

2. .guardignore

어떤 파일은 의도적으로 예외를 인정하고 싶을 수 있습니다.

수긍 인정

예를 들어, "어떤 컴포넌트가 지금은 한 군데서만 쓰이지만 장차 재사용 컴포넌트가 될 재목"인 케이스같은 경우요.
이런 경우는 스캔에서 제외할 수 있어야 노이즈를 줄이고 깨진 유리창 효과를 최소화해볼 수 있습니다.
따라서 .guardignore같은 파일 규칙을 만들어서, .eslintignore처럼 "무시할 경로"를 지정하게 했습니다.

# 글로벌 — 모든 체크에 적용
src/hooks/api/dictionary/** # 인라인 주석으로 사유를 설명할 수도 있습니다.

# 체크별 — 해당 체크에만 적용
[raw-div] src/app/.../MagazineContentRenderer.tsx
[swr-location] src/app/.../page.tsx

# 복수 체크
[raw-div,swr-location] src/some/file.tsx

이런 식으로 glob 패턴으로 작성해서, 단순 경로만 넣어두면 글로벌로 모든 체크를 스킵하거나
또는 [swr-location]처럼 특정 체크만 무시되게 할 수도 있습니다.
# 주석과 같이 줄 전체 또는 인라인 주석을 허용하여, 이유를 써둘 수도 있습니다.

이제 check-*.js 검사 스크립트 모두에서 쓸 .guardignore 처리 유틸 이 있어야 하므로
.guardignore의 각 요구사항에 따른 테스트 및 인터페이스를 먼저 작성하고 이에 부합하는 구현을 맡길 수 있습니다.

describe("loadIgnoreList", () => {
  it("글로벌 항목(브래킷 없음)을 global.exact에 추가한다", () => {});
  it("글로벌 /** 항목을 global.prefixes에 추가한다", () => {});
  it("[check-id] 브래킷 항목을 perCheck에 추가한다", () => {});
  it("[a,b] 복수 체크 브래킷 항목을 각 perCheck에 추가한다", () => {});
  it("인라인 주석(#)을 처리한다", () => {});
  ...
})

3. 단일 파일 검사기 (경량 피드백 버전)

위 통합 러너는 전체 프로젝트를 스캔하므로 ms단위로 끝나는 작업은 아닙니다.
어떤 규칙 검사는 또 많은 코드들을 스캔해야 하여 시간이 조금 필요할거구요
이런 규칙은 실시간 피드백 (PostToolUse 훅, 프리커밋 등)에서 완전히 검사하기에는 느립니다.

빨리해! 그냥해!

따라서 단일 파일 검사기check-file.js같은 것을 만들고,
작업한 파일만 경량으로 검사할 수 있게 합니다.

어떤 규칙(ex. "SWR은 응답 스키마 검증을 포함하는 fetcher 쓰자" 등.)은 이미 빠르게 수행할 수 있어 기존 검사 로직을 재사용할 수도 있고,
"커스텀 훅이 재사용되는지"는 전체 코드에서 import되는 구조를 스캔해야 하므로 다른 방법을 써야 할 수도 있습니다.

check-file.js <파일경로> --json

예를 들어:
┌──────────────────┬──────────────────────────────┬──────┐
│      체크         │             동작              │  속도 │
├──────────────────┼──────────────────────────────┼──────┤
│ api-url          │ /api/v\d 하드코딩 감지          │ ~1ms │
│ swr-location     │ 훅이 아닌 곳에서 SWR import      │ ~1ms │
│ component-folder │ 컴포넌트 폴더 패턴 미적용 감지      │ ~5ms │
│ scope-hint       │ 경로 기반 스코프 리마인더          │  0ms │
└──────────────────┴──────────────────────────────┴──────┘

특히 "재사용/로컬 스코프 규칙"처럼 경량으로 검사할 방법이 여의치 않거나,
실시간 피드백에서 엄격하게 잡기에는 효과적이지 않은 경우도 있습니다.
이럴 땐 타협안으로, "우리 여기서는 이런 규칙을 따르는데, 이에 맞는지 한번 확인합시다" 처럼
위반 피드백이 아닌 힌트를 한번 리마인드 하는 것으로 대체하기도 합니다.

4. PostToolUse 훅으로 등록

클로드는 프로젝트 단위로 .claude/ 폴더를 두고 해당 프로젝트 내에서 작업하는 누구든 그 설정을 공유하게 할 수 있습니다. .claude/settings.json에:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .claude/hooks/arch-guard-check.py",
            "timeout": 10,
            "statusMessage": "Checking architecture rules..."
          }
        ]
      },

이 훅은 .claude/hooks/foo.py와 같이 파이썬 스크립트로 넣어주면 되고,

def main():
  ...
  print(
    json.dumps(
      {
        "additionalContext": violation_messages
      }
    )
  )

if __name__ == "__main__":
    main()

대충 이런식이면 됩니다.
사실 그냥 무슨 스크립트를 어떤 타이밍에 실행하여 결과를 피드백으로 줄 수 있게 훅 등록해줘 처럼 시키면 해줍니다.
참고로 클로드 코드에게 추가 컨텍스트를 주입하려면 additionalContext같은 것을 사용할 수 있습니다.

5. 피드백 타이밍은 이렇게 되네요.

[코드 작성]
    │
    ├─ PostToolUse Hook (AI 실시간)
    │   └─ check-file.js --json <수정된 파일>
    │
    ├─ pre-commit Hook (커밋 직전)
    │   └─ git diff --cached --name-only | xargs check-file.js
    │
    ├─ npm run arch-guard (로컬 전수조사)
    │   └─ index.js → 모든 check-*.js 실행
    │
    └─ CI / GitHub Action (PR 코멘트)
        └─ index.js --compare → dev 대비 점수 비교

요약하면, 코드 작성 타이밍에서 가까울수록 필요한 파일만, 빠르게 검사하고,
작업이 마무리 단계일수록 전체에서 코드 품질 저하가 없는지 검사합니다.

트러블슈팅(1): CI에서 점수 변화가 항상 0으로 나와요

분명히 점수가 상승했어야 하는 작업을 했는데, 점수 변화가 없다고 나왔습니다.
이건 그냥 제 바보같은 실수 때문이었는데요

제 의도는 dev 코드 기준 검사 결과 ↔ PR 브랜치 코드 기준 검사 결과를 비교하는 것이었는데

# Before — dev의 "스크립트"만 가져와서 PR 코드를 검사
#   → baseline도 PR 코드, current도 PR 코드 → delta 항상 0
git checkout origin/dev -- scripts/arch-guard/
node scripts/arch-guard/index.js --json  # baseline
git checkout HEAD -- scripts/arch-guard/
node scripts/arch-guard/index.js --json  # current

이렇게 스크립트만 바꿔 끼워 동일한 코드를 검사하니 항상 변화량이 0인 문제가 있었습니다.

# After — git worktree로 dev 코드를 통째로 체크아웃
git worktree add /tmp/dev-worktree origin/dev
pushd /tmp/dev-worktree
node scripts/arch-guard/index.js --json   # dev 코드 기준 점수
popd && git worktree remove /tmp/dev-worktree

node scripts/arch-guard/index.js --json   # PR 코드 기준 점수
# → 실제 delta 계산 가능

대신 이렇게 dev 코드 자체를 검사 및 비교 기준으로 해줘야 합니다.

트러블슈팅(2): PostToolUse 훅의 additionalContext 표면화 문제

가장 골치였던 문제인데요,
실제로 규칙 위반때문에 additionalContext를 출력하는 상황에서
실행중인 에이전트에게 전달(표면화)되지 않는 문제가 있었습니다.

뭐라는거야?

훅에 의해 스크립트는 정상적으로 돌았지만 에이전트에게 피드백이 전달되지 않았으므로,
무언가 잘못되었다는 사실을 모른 채로 작업을 진행하고 완료해버리는 상황이 벌어졌습니다.

원인 및 시도

여러 PostToolUse + additionalContext 중복인 것으로 확인했습니다.

prettier-format.py      → 출력 없음 (파일만 수정)
eslint-check.py         → additionalContext 출력
arch-guard-check.py     → additionalContext 출력 (위반 리포트)
OMC post-tool-verifier  → additionalContext 출력 ("Code modified...")

저는 Oh-My-Claudecode(OMC)라는 클로드 코드 플러그인을 잘 사용중인데
여기서도 PostToolUse훅에서 additionalContext를 뱉고 있습니다.
이에 더해 또 다른 클로드 코드 설정(프로젝트/유저 스코프 등)에서도 같은 타이밍에 중복으로 additionalContext를 뱉습니다.
이런 식으로 additionalContext가 중복되면 마지막 컨텍스트만 남아 이전 출력들은 모두 드랍되는 문제가 있더라구요

이것이 의도한 동작인지 아닌지는 모르겠으나 이를 해결할 방법을 찾아야 했고 OMC를 조정하거나, additionalContext를 사용하지 않거나 등, 몇 가지를 고민했습니다

  1. OMC 조정: OMC_SKIP_HOOKS=post-tool-use
    • 단점: OMC가 이 hook에서 행하는 다른 기능 단절(세션 통계 등)
  2. 파일에 주석 삽입: 위반 시 소스코드에 주석으로 // @violation: 어쩌구 규칙을 위반했습니다... 와 같이 삽입
    • Prettier 등 클로드가 아닌 무언가에 의해 코드가 수정된 경우, 클로드가 "파일이 수정되었으니 확인해야 함" 과 같이 시스템 리마인더를 받는 동작에서 착안한 다소 Hacky한 방법
    • 단점: 혹시 주석이 남을 염려 + 시스템 리마인더와 함께 diff는 일정량만 주입되므로 에이전트가 알지 못한 채 넘어갈 염려
  3. 파일 기반: .last-edit-violations 와 같은 별도 파일에 적어두고, CLAUDE.md"반드시 코드 수정 후 이거 확인" 을 지시
    • 단점: 무조건 확인한다는 보장 X(문서로는 부족해서 시작했는데, 다시 문서에 의존)

일단 저는 OMC는 건드리지 않고 그대로 사용하고 싶었습니다.
또한 피드백은 여전히 비결정적이지 않고 결정적 시스템 의해 보장되기를 원했어요.
2번이 사실상 가장 가까운 해결책인 것 같긴 했는데.. 다른 방법을 찾았습니다.

해결: decision: "block" + reason 채널

클로드 코드 훅에서 피드백을 넣는 방법은 additionalContext 말고도
decision:"block" + reason 채널이 있습니다.
이는 차단 레벨로 에이전트에게 피드백을 주는 방식인데요
PostToolUse는 이미 툴을 사용한 후라서 뭔가 동작을 실제로 차단하진 않습니다
reason을 사용하여 피드백 메시지를 전달할 수도 있고요

# Before — 가려짐
print(json.dumps({"additionalContext": "위반 내용"}))

# After — 표면화됨
print(json.dumps({"decision": "block", "reason": "위반 내용"}))
# 실험 결과: 3개 채널 모두 동시에 표면화
PostToolUse:Edit hook blocking error: [arch-guard] 위반 발견...
PostToolUse:Edit hook blocking error: [eslint] 위반 발견...
PostToolUse:Edit hook additional context: Code modified. (OMC)

그래서 additionalContext -> decision:"block"으로 채널을 전환하고 실험해보니
여러 훅에 의한 decision:"block" + reason은 모두 독립적으로 표면화되는 것을 확인했습니다
이렇게 해서 문제를 일단락했고, 정상적으로 위반 감지 및 피드백 시스템을 사용중입니다.

결과 및 동작 예시

PostToolUse 피드백 1 PostToolUse 피드백 2

클로드 코드가 작업하다보면 이런 식으로 피드백을 받곤 합니다.
린트 룰 위반과 더불어 이번에 작업한 규칙 시스템에 의한 위반 리포트까지 피드백이 들어가는 모습을 볼 수 있습니다.
실제로 이것을 만나면 클로드가 "아, 이렇게 수정합니다:" 를 시작하고 에이전트 루프는 지속적으로 유지됩니다.

이번에는 PR 코멘트에 풀스캔 CI 결과 점수가 잘 나오는지 볼까요??

점수 상승 PR 코멘트

리팩토링으로 이전보다 점수가 상승했다면 축하를 해줍니다.
은근 기분이 좋아요. 언젠가 100점을 유지하는 그 날까지 ..

점수 하락 PR 코멘트

점수가 하락하면 점수 변화: 📉 -0.8 (93 -> 92.2)와 함께 위와 같은 "어떤 새로운 위반 항목이 있는지"를 코멘트로 알려줍니다.
코드리뷰가 시작되기 전에라도 알게 되어 고칠 기회가 남아있습니다.

마치며

규칙 위반 피드백 시스템은 계속 필요에 따라 수정되고 있는데요
"이건 계속 지적하게 되네" 싶거나, "이런 경우는 잡지 못헀네"가 있으면 바로바로 규칙을 추가 또는 수정하고 있습니다.

더욱 위험한 환경


요즘은 이런식으로, 폭발적으로 생산되는 코드의 가치와 품질을 어떻게 효과적으로 관리하고 제어할지 고민하는 중입니다.
자동화 테스트와 TDD, 문서화, 검증 스크립트 등 여러 방면으로 시도하며 노력하고 있고
이 글에서 소개한 규칙을 코드로 승격시켜 시스템을 만들기는 그 중 하나입니다.

최근에 이펙티브 엔지니어, 소프트웨어 장인 이런 책들을 읽으면서 느끼는게 있는데요
이미 옛날부터 고수들은 똑같은 고민을 하며 해법을 찾아왔고, 이미 짧은 피드백 루프, TDD같은 비법들을 발굴해뒀다는 점입니다.
이러한 비법들의 가치는 이전에도 높았지만 AI 시대에 와서도 더 중요해지고 있다고 느낍니다.
이제 행동 비용이 저렴해진 덕분에 이것들을 시도하고 실행하며 배우는 중입니다.

이만 마칩니다