AI Tips

andrej-karpathy-skills 완전 분석: Karpathy의 관찰을 CLAUDE.md 한 파일로 증류하다

룰모아 2026. 4. 14. 13:50

GitHub: https://github.com/forrestchang/andrej-karpathy-skills
작성자: Forrest Chang (@forrestchang)
Stars: 26.6k/ Forks: 2.2k
라이선스: MIT
핵심 파일: CLAUDE.md + .claude/skills/karpathy-guidelines.md


목차

  1. 시작점: Karpathy의 트윗
  2. 이 프로젝트가 하는 일
  3. 4가지 원칙 심층 분석
  4. 원칙 간의 관계 구조
  5. 설치 및 적용 방법
  6. 실전 적용 예시
  7. CLAUDE.md 접근법의 의미
  8. 다른 CLAUDE.md 템플릿과의 차이
  9. 한계점과 트레이드오프
  10. 결론: 단순함의 힘

1. 시작점: Karpathy의 트윗

2025년 말, Andrej Karpathy(전 Tesla AI 디렉터, OpenAI 공동 창업자)가 X(트위터)에 긴 글을 올렸다. 그는 몇 주간 Claude로 집중 코딩을 하면서 경험한 것을 솔직하게 적었다.

핵심 메시지는 두 가지였다.

첫째, 워크플로우가 근본적으로 바뀌었다:

2025년 11월의 약 80% 수동 코딩 + 20% 에이전트 사용에서, 12월에는 정반대로 80% 에이전트 + 20% 수동 편집으로 전환됐다. "나는 이제 거의 영어로 프로그래밍하고 있다 — LLM에게 코드를 어떻게 작성해야 하는지 말로 설명하면서"라고 썼다.

둘째, 그러나 실수의 성격이 달라졌다:

모델들은 여전히 실수를 한다. 예전과 다른 점은 단순 문법 오류가 아니라는 것이다. 이제는 조금 부주의하고 성급한 주니어 개발자가 할 법한 미묘한 개념적 오류들이 문제다.

그가 구체적으로 지목한 세 가지 문제:

"가장 흔한 유형은 모델이 당신을 대신해 잘못된 가정을 하고, 확인도 없이 그냥 계속 진행하는 것이다. 혼란을 관리하지 않고, 명확한 설명을 구하지 않고, 불일치를 드러내지 않고, 트레이드오프를 제시하지 않고, 반박해야 할 때 반박하지 않으며, 아직도 조금 지나치게 아첨한다."

 

"코드와 API를 과도하게 복잡하게 만들고, 추상화를 과하게 부풀리고, 죽은 코드를 정리하지 않는다."

"과제와 무관한 부수 효과로서 충분히 이해하지 못한 주석과 코드를 변경하거나 제거하기도 한다."

이 관찰은 AI 코딩을 진지하게 쓰는 개발자라면 누구나 공감할 내용이었다. forrestchang/andrej-karpathy-skills는 이 관찰을 실제로 작동하는 규칙으로 증류한 프로젝트다.


2. 이 프로젝트가 하는 일

한 마디로: Karpathy의 관찰 세 가지를 네 가지 원칙으로 변환한 CLAUDE.md 파일 하나를 제공한다.

Karpathy의 관찰                    →   원칙
────────────────────────────────────────────────────────
잘못된 가정, 혼란 관리 안 함         →   Think Before Coding
과도한 복잡성, 추상화 과잉           →   Simplicity First
무관한 코드 변경                    →   Surgical Changes
명확한 성공 기준 없음               →   Goal-Driven Execution

이것이 이 프로젝트의 전부다. 화려한 도구도, 복잡한 파이프라인도 없다. 텍스트 파일 하나.

그 단순함이 오히려 이 접근법의 강점이다. Claude Code는 세션 시작 시 CLAUDE.md를 자동으로 읽는다. 개발자가 추가로 할 일이 없다.


3. 4가지 원칙 심층 분석

원칙 1: Think Before Coding — 가정하지 말 것

문제 상황:

개발자가 "검색 속도를 높여줘"라고 말하면, 모델은 묻지도 않고 자신이 해석한 방향으로 구현하기 시작한다. 그리고 수백 줄을 쓴 뒤에야 "아, 그게 아니었구나"가 드러난다.

원칙이 요구하는 것:

  • 가정을 명시적으로 드러낼 것 — 불확실한 부분은 추측하지 않고 물어보기
  • 여러 해석을 제시할 것 — 모호한 요청에서는 가능한 해석들을 나열하고 사용자가 선택하게 하기
  • 더 단순한 방법이 있으면 먼저 말할 것 — 구현 전에 "이렇게 하는 게 더 간단하지 않을까요?" 제안하기
  • 혼란스러우면 멈출 것 — 불명확한 상태로 진행하지 않기

지시가 모호할 때 멈추고 물어보는 것이, 모든 것을 구현한 뒤 다시 수정하는 것보다 훨씬 효율적이다.

코드 수준의 적용:

# ❌ 이 원칙 없이 — 모델이 한 방향을 조용히 선택
User: "검색을 더 빠르게 해줘"
Claude: [1000줄의 캐싱 + 인덱스 + 비동기 리팩토링 코드를 바로 작성]

# ✅ 이 원칙 적용 후 — 해석을 먼저 제시
User: "검색을 더 빠르게 해줘"
Claude: "'검색을 빠르게'는 여러 방향으로 이해될 수 있습니다:
  1. 응답 시간 단축 (<100ms): DB 인덱스 + 캐싱, 예상 2시간
  2. 동시 처리량 증가: 비동기 리팩토링 + 커넥션 풀링, 예상 4시간
  3. 체감 속도 개선 (UX): 프로그레시브 로딩, 예상 3시간
  어느 방향을 원하시나요?"

원칙 2: Simplicity First — 최소 코드 원칙

문제 상황:

기능 하나를 추가해달라고 하면 모델은 종종 다음을 함께 포함시킨다:

  • 요청하지 않은 설정 시스템
  • 미래에 쓸지도 모를 추상화 레이어
  • 절대 발생하지 않을 시나리오의 에러 핸들링
  • "유연성"이나 "확장성"을 위한 인터페이스들

결과는 요청한 것의 5~10배 크기의 코드다.

원칙이 요구하는 것:

  • 요청받은 기능만 구현할 것 — 추측성 기능(speculative features) 추가 금지
  • 단일 사용 코드에는 추상화 없음 — 한 곳에서만 쓰이면 추상화하지 않기
  • 요청받지 않은 "유연성", "설정 가능성" 없음
  • 불가능한 시나리오에 대한 에러 핸들링 없음
  • 200줄이 50줄로 될 수 있다면 다시 쓸 것

테스트 기준: 시니어 엔지니어가 이 코드를 보고 "과도하게 복잡하다"고 말할 것인가? 그렇다면 단순화해야 한다.

# ❌ Simplicity First 위반 예시
def get_user(user_id: str) -> Optional[User]:
    """
    다양한 소스에서 사용자를 조회하는 범용 인터페이스.
    향후 캐싱, 레플리케이션, 샤딩 지원을 위해 추상화됨.
    """
    source = self._get_source_strategy()  # 현재는 DB 하나뿐
    transformer = self._get_transformer() # 현재는 변환 없음
    validator = self._get_validator()     # 현재는 검증 없음
    return self._execute_with_retry(
        lambda: transformer.transform(
            validator.validate(source.get(user_id))
        )
    )

# ✅ Simplicity First 적용
def get_user(user_id: str) -> Optional[User]:
    return self.db.query(User).filter_by(id=user_id).first()

원칙 3: Surgical Changes — 요청받은 것만 건드릴 것

문제 상황:

"이 함수의 버그를 고쳐줘"라고 했는데 diff를 보면:

  • 요청한 버그 수정: 3줄
  • 요청하지 않은 변경: 포매팅 30줄, 주석 수정 10줄, 인접 함수 리팩토링 50줄

이것이 "Surgical Changes" 원칙이 해결하려는 문제다. LLM이 기존 코드를 편집할 때 인접한 코드를 "개선"하고, 파일을 재포매팅하거나, 망가지지 않은 것을 리팩토링하는 경향이 있다.

원칙이 요구하는 것:

기존 코드 편집 시:

  • 인접한 코드, 주석, 포매팅을 "개선"하지 않기
  • 망가지지 않은 것은 리팩토링하지 않기
  • 다르게 짜고 싶어도 기존 스타일 유지하기
  • 무관한 죽은 코드를 발견하면 삭제하지 말고 언급만 하기

내 변경으로 생긴 부수물 정리:

  • 내 변경이 만든 미사용 import/변수/함수는 제거하기
  • 기존에 있던 죽은 코드는 요청받지 않는 한 건드리지 않기

테스트 기준: 변경된 모든 줄이 사용자의 요청으로 직접 추적될 수 있는가?

# ❌ Surgical Changes 위반 — 요청은 버그 수정 하나였지만
- def calculate_total(items):
-     total = 0
-     for item in items:          # 기존 스타일
-         total = total + item.price
-     return total
+ def calculate_total(items: List[Item]) -> Decimal:
+     """Calculate the total price of items."""  # 요청 안 한 docstring
+     return sum(                               # 요청 안 한 리팩토링
+         Decimal(str(item.price))
+         for item in items
+         if item.price is not None             # 요청 안 한 에러 처리
+     )

# ✅ Surgical Changes 적용 — 버그만 수정
  def calculate_total(items):
      total = 0
      for item in items:
-         total = total + item.price
+         total = total + (item.price or 0)   # 버그 수정: None 처리
      return total

원칙 4: Goal-Driven Execution — 명령이 아닌 성공 기준 제시

문제 상황:

"이 기능을 추가해줘"라고 하면 모델은 구현하고 끝낸다. 뭔가 빠졌거나 잘못됐어도 알아채기 어렵다. 명확한 완료 기준이 없으면 "완료"가 무엇인지 정의되지 않는다.

원칙의 핵심 인사이트:

LLM은 특정 목표를 달성할 때까지 반복하는 것에 탁월하다. 무엇을 할지 지시하는 대신, 성공 기준을 제시하고 실행하게 하라.

변환 패턴:

명령형 (약한 기준) 목표 지향형 (강한 기준)

"유효성 검사 추가해" "유효하지 않은 입력에 대한 테스트 작성, 그 테스트를 통과시켜"
"버그 수정해" "버그를 재현하는 테스트 작성, 그 테스트를 통과시켜"
"X를 리팩토링해" "리팩토링 전후 테스트가 모두 통과하도록 보장해"

다단계 작업에서의 계획 표현:

1. [단계 설명] → 검증: [확인 방법]
2. [단계 설명] → 검증: [확인 방법]
3. [단계 설명] → 검증: [확인 방법]

실제 예시:

# ❌ 약한 기준
"로그인 시스템에서 비밀번호 변경 버그 고쳐줘"

# ✅ 강한 기준 (Goal-Driven)
"특정 로그인 문제를 해결해야 합니다: 
비밀번호 변경 후 이전 세션이 무효화되지 않는 버그.

계획:
1. 테스트 작성: 비밀번호 변경 → 이전 세션 거부 확인
   검증: 테스트가 실패함 (버그 재현)
2. 구현: 비밀번호 변경 시 해당 사용자 세션 전부 클리어
   검증: 테스트 통과
3. 엣지 케이스: 다중 기기 로그인, 동시 비밀번호 변경
   검증: 새로 추가한 테스트 모두 통과
4. 회귀 확인: 기존 로그인 관련 테스트 전부 통과
   검증: pnpm test

현재 로그인 모듈 테스트 커버리지: [데이터]
어떤 로그인 문제를 만났는지 구체적으로 알려주세요."

강한 성공 기준은 LLM이 명확한 검증 루프를 돌며 독립적으로 실행할 수 있게 한다. 약한 기준("작동하게 해줘")은 계속 중간 개입이 필요하다.


4. 원칙 간의 관계 구조

네 가지 원칙은 독립적이지 않다. 각각이 서로 다른 실수 유형을 겨냥하면서도 구조적으로 연결되어 있다.

┌─────────────────────────────────────────────────────┐
│            LLM 코딩의 핵심 문제들                        │
│                                                     │
│  [잘못된 가정]  [과도한 복잡성]  [부수적 변경]  [불명확한 완료]  │ 
└──────┬──────────────┬────────────────┬────────────┬─┘
       ↓              ↓                ↓            ↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Think Before │ │  Simplicity  │ │   Surgical   │ │ Goal-Driven  │
│   Coding     │ │    First     │ │   Changes    │ │  Execution   │
│              │ │              │ │              │ │              │
│ 구현 전 단계    │ │ 구현 중 단계    │ │ 편집 중 단계    │ │ 완료 판단 단계│
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘

시간축으로 보면:

  1. Think Before Coding — 코딩 시작 : 가정 확인, 모호함 해소
  2. Simplicity First — 코딩 : 최소 구현 유지
  3. Surgical Changes — 편집 : 범위 이탈 방지
  4. Goal-Driven Execution — 완료 판단: 검증 루프로 "끝"을 정의

이 순서로 적용하면 각 단계에서 발생하는 실수 유형을 순서대로 방어한다.


5. 설치 및 적용 방법

옵션 A: CLAUDE.md (권장)

새 프로젝트:

curl -o CLAUDE.md https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md

기존 프로젝트에 추가:

echo "" >> CLAUDE.md
curl https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md >> CLAUDE.md

Claude Code는 세션 시작 시 현재 디렉터리의 CLAUDE.md를 자동으로 읽는다. 추가 설정이 전혀 필요 없다.

옵션 B: Claude Code 스킬 디렉터리

mkdir -p .claude/skills
curl -o .claude/skills/karpathy-guidelines.md \
  https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/.claude/skills/karpathy-guidelines.md

스킬 파일로 설치하면 모든 프로젝트에서 공통으로 사용 가능한 Claude Code 플러그인이 된다.

기존 CLAUDE.md와 병합

이 가이드라인은 프로젝트별 규칙과 함께 사용하도록 설계되어 있다:

# 기존 CLAUDE.md

## Project-Specific Guidelines
- TypeScript strict mode 사용
- 모든 API 엔드포인트에 테스트 필수
- src/utils/errors.ts의 기존 에러 처리 패턴 따르기

---

# (curl로 추가된 Karpathy 가이드라인)
## Think Before Coding
...

6. 실전 적용 예시

"이 작동이 제대로 되고 있는지" 확인하는 방법

README는 4가지 신호를 제시한다:

1. diff에 불필요한 변경이 없다

Surgical Changes가 작동 중이면 PR diff가 깔끔하다. 요청한 것만 바뀌어 있다.

# ✅ Surgical Changes 작동 중인 diff
  def process_payment(amount, currency):
-     if amount <= 0:
-         raise ValueError("amount must be positive")
+     if amount <= 0:
+         raise PaymentError("amount must be positive", code="INVALID_AMOUNT")
  
  # 나머지 100줄은 변경 없음

2. 과도한 설계로 인한 재작성이 줄었다

Simplicity First가 작동 중이면 "이거 너무 복잡하게 만든 것 같은데 다시 해줘"가 줄어든다.

3. 구현 전에 명확화 질문이 온다

Think Before Coding이 작동 중이면 코드가 아니라 질문이 먼저 온다.

4. PR이 깔끔하고 최소하다

드라이브바이 리팩토링이나 "개선"이 없다.

실전 시나리오별 적용

시나리오 1: 새 기능 추가

# 원칙 없이
"사용자 인증에 2FA를 추가해줘"
→ 모델이 TOTP, SMS, 이메일, 백업 코드, QR 코드 생성, 
  관리 UI까지 구현하기 시작함

# 원칙 적용 후
"사용자 인증에 2FA를 추가해줘"
→ Claude: "2FA는 여러 방식으로 구현 가능합니다:
  1. TOTP (Google Authenticator 등): 가장 일반적, 
     서버 인프라 추가 불필요
  2. SMS OTP: UX 쉬움, SMS 프로바이더 비용/의존성 발생
  3. 이메일 OTP: 추가 인프라 없음, 보안 상대적으로 낮음
  어느 방식을 원하시나요? 또한 처음부터 전부 구현할지, 
  TOTP만 먼저 MVP로 할지 결정해주세요."

시나리오 2: 버그 수정

# 원칙 없이
"결제 모듈에서 가끔 중복 청구가 발생해"
→ 모델이 전체 결제 모듈을 분석하고 여러 부분 "개선"

# 원칙 적용 후
"결제 모듈에서 가끔 중복 청구가 발생해"
→ Claude: 
  "1. 중복 청구를 재현하는 테스트 작성
     검증: 테스트 실패 (버그 재현 확인)
  2. 원인 파악: 가장 가능성 있는 원인은?
     - 멱등성 키 없음?
     - 네트워크 재시도 시 중복 요청?
     - 레이스 컨디션?
  어느 케이스에서 발생하는지 로그나 재현 조건을 알려주시면 
  좁혀서 수정하겠습니다."

7. CLAUDE.md 접근법의 의미

이 프로젝트가 흥미로운 이유 중 하나는 CLAUDE.md 자체에 대한 시각이다.

많은 사람이 CLAUDE.md를 "프로젝트 문서"나 "환경 설정"으로만 본다. 하지만 이 프로젝트는 다른 관점을 제시한다: CLAUDE.md는 모델의 행동 패턴을 바꾸는 인터페이스다.

LLM의 기본 행동 경향(가정 먼저 실행, 복잡성 선호, 범위 확장)은 훈련 과정에서 형성된다. 이 경향을 바꾸려면 모델에게 "이 프로젝트에서는 이렇게 동작하라"는 명시적인 지시가 필요하다. CLAUDE.md가 그 역할을 한다.

이 접근법의 실질적인 의미는 LLM 코딩이 실용적 임계점을 넘어섰다는 것이다. 모델은 더 많은 실행을 담당할 수 있을 만큼 유용하지만, 감독 없이 신뢰할 만큼 신뢰할 수는 없다. 이것이 새로운 워크플로우를 만든다 — 모델이 실행을 더 많이 하는 동안, 개발자는 범위, 명확성, 검증, 판단을 담당한다.

CLAUDE.md에 이 가이드라인을 넣는 것은 바로 이 "판단과 범위"를 사전에 문서화하는 행위다.


8. 다른 CLAUDE.md 템플릿과의 차이

일반적인 CLAUDE.md 템플릿들은 주로 "이 프로젝트에서 사용하는 기술 스택", "코딩 컨벤션", "테스트 방법"을 문서화한다. 이것들은 무엇을에 대한 설명이다.

Karpathy 가이드라인은 어떻게에 대한 원칙이다.

일반 CLAUDE.md:  "이 프로젝트에서 TypeScript strict mode를 사용한다"  (무엇을)
Karpathy:        "코드를 작성하기 전에 가정을 먼저 명시하라"          (어떻게)

이 차이가 중요한 이유는 실제 품질 문제의 상당 부분이 "무엇을" 쪽에 있지 않기 때문이다. Claude는 이미 TypeScript를 잘 안다. 문제는 요청을 받았을 때 어떻게 행동하는가 — 가정을 조용히 하고 달려나가는가, 먼저 물어보는가 — 에 있다.

특정 기술 스택과 무관하게 어떤 프로젝트에서든 적용 가능하기 때문에, 기존 CLAUDE.md에 추가하는 방식으로 설계되어 있다. 프로젝트 고유의 규칙(무엇을)과 행동 원칙(어떻게)이 함께 있을 때 가장 효과가 크다.


9. 한계점과 트레이드오프

속도 vs 안전의 트레이드오프

README가 솔직하게 인정하는 부분이다:

"이 가이드라인은 빠른 속도보다 신중함 쪽으로 편향되어 있다. 사소한 작업(간단한 오타 수정, 명백한 한 줄 변경)에는 판단을 사용하라 — 모든 변경이 이 모든 엄격함을 필요로 하진 않는다."

"검색을 더 빠르게 해줘" 같은 단순한 요청에도 모델이 먼저 세 가지 해석을 제시하며 물어본다면, 때로는 답을 이미 알고 있는 개발자에게 불필요한 마찰이 될 수 있다. 원칙을 적용하되, 상황에 따른 판단도 병행해야 한다.

모든 모델에서 동일하게 작동하지 않는다

이 가이드라인은 Claude Code를 기준으로 최적화되어 있다. 다른 AI 코딩 어시스턴트(GitHub Copilot, Cursor, Gemini CLI 등)도 CLAUDE.md나 유사한 지시 파일을 읽지만, 각 모델이 지시를 따르는 정도와 방식은 다를 수 있다.

지시의 한계

CLAUDE.md에 아무리 잘 작성해도, 모델이 항상 완벽하게 따르지는 않는다. 특히:

  • 컨텍스트 윈도우가 매우 길어진 세션 후반부
  • 원칙 여러 개가 동시에 적용되어야 하는 복잡한 작업
  • 원칙 간 충돌이 있는 상황 (예: "빠르게 해줘"는 Think Before Coding과 Goal-Driven Execution이 긴장 관계에 있음)

프로젝트 규모

현재 이 프로젝트는 단 7개 커밋의 소규모 프로젝트다. 251 Stars를 받은 것은 내용의 가치 때문이지, 코드 복잡성이나 지속적 유지보수 때문이 아니다. 문서 파일이 핵심이므로, 장기적으로 내용이 업데이트되고 발전할지는 지켜봐야 한다.


10. 결론: 단순함의 힘

andrej-karpathy-skills의 가장 인상적인 점은 도구의 단순함이 메시지와 일치한다는 것이다.

"AI가 너무 복잡하게 만든다"는 문제를 해결하기 위해 복잡한 시스템을 만들지 않았다. 텍스트 파일 하나로 해결했다. Simplicity First 원칙 자체가 프로젝트 구조에 반영되어 있다.

이 프로젝트가 담고 있는 핵심 인사이트를 정리하면:

1. LLM의 실수는 문법 오류에서 판단 오류로 바뀌었다

모델들은 이제 미묘한 개념적 오류를 만든다 — 마치 약간 부주의하고 성급한 주니어 개발자가 할 법한 실수들이다. 이 종류의 실수는 실행 전에 원칙으로 방어하는 것이 더 효과적이다.

2. 좋은 지시는 "무엇을"이 아니라 "어떻게"를 정의한다

기술 스택이나 프로젝트 컨벤션(무엇을)은 이미 많은 팀이 CLAUDE.md에 문서화하고 있다. 그런데 실제 품질 문제의 상당 부분은 행동 패턴(어떻게)에서 온다.

3. 성공 기준이 명확하면 LLM은 더 잘 동작한다

LLM은 특정 목표를 달성할 때까지 반복하는 것에 탁월하다. 이것을 뒤집으면: 목표가 모호하면 LLM은 그 모호함 안에서 제멋대로 판단한다.

4. 감독 대신 원칙으로

모든 diff를 꼼꼼히 리뷰하는 것도 방법이지만, 사전에 행동 원칙을 제시해 실수 자체를 줄이는 것이 더 근본적인 접근이다.


Claude Code를 진지하게 사용하는 개발자라면, CLAUDE.md에 프로젝트 컨벤션만 담지 말고 Karpathy 가이드라인도 함께 넣어볼 것을 추천한다. 설치는 curl 한 줄이면 끝나고, 효과는 첫 번째 깔끔한 diff에서 바로 확인할 수 있다.


참고 자료