TrendShift 딥다이브 · 2026-07-01

Caveman 딥다이브
— "말 짧게 하면 왜 힘 세지냐" AI 에이전트 출력 압축 스킬

caveman은 Claude Code(및 Codex·Gemini·Cursor·Windsurf·Cline·Copilot 등 30여 개 에이전트)가 대답을 "똑똑한 원시인"처럼 짧게 하도록 만드는 스킬/플러그인이다. 줄이는 건 출력 토큰(mouth)뿐, 사고 토큰(brain)은 그대로 둔다 — 생각은 다 하되 말만 짧게 한다는 것. README 자체 표현으로 "why use many token when few do trick". MIT 라이선스, JavaScript 63% / Python 28% / PowerShell·Shell 각 5%. (저장소: JuliusBrussee/caveman · ★69.4k · MIT · 최신 v1.9.0)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

이 저장소가 대체 무엇인가.

핵심 메시지

"뇌는 그대로, 입만 줄인다" — AI가 하는 생각(추론)의 양은 안 건드리고, 사용자에게 내놓는 말(출력)만 압축한다.

caveman의 정체는 새 모델도, 새 알고리즘도 아니다. Claude Code의 SKILL.md 한 장짜리 규칙 파일과, 세션 시작·매 메시지마다 끼어드는 훅(hook) 몇 개가 전부다. 이 규칙 파일이 모델의 시스템 컨텍스트에 "관사·군더더기·인사치레 다 빼고, 기술 용어·코드·에러 메시지는 그대로 두고 짧게 답하라"는 지시를 주입하면, 모델은 여전히 문제를 깊이 생각하지만 최종 답변만 원시인처럼 짧게 내뱉는다.

비유하면: 모델의 사고 과정 = 요리사가 주방에서 재료를 다듬고 간을 보는 전 과정, 출력 = 손님상에 나가는 완성 요리. caveman은 요리사의 실력이나 조리 과정을 바꾸지 않는다. 다만 "설명 카드는 빼고 요리만 딱 내놓으라"고 서빙 방식만 바꾼 것이다.

README의 자기소개는 "A Claude Code skill/plugin (also Codex, Gemini, Cursor, Windsurf, Cline, Copilot, 30+ more) that makes agent talk like caveman — cuts ~75% of output tokens, keeps full technical accuracy. Brain still big. Mouth small." — "뇌는 여전히 크고, 입만 작다"는 표현이 이 프로젝트 전체를 한 줄로 요약한다.

실제로 뜯어보면 이 저장소는 하나의 프로그램이 아니라 7개의 스킬(caveman·caveman-commit·caveman-review·caveman-stats·caveman-compress·caveman-help·cavecrew) + 2개의 Claude Code 훅(SessionStart·UserPromptSubmit) + 1개의 MCP 미들웨어(caveman-shrink) + 1개의 크로스플랫폼 설치기(bin/install.js, 34개 에이전트 감지)로 이루어진 작은 생태계다. 리서치 결과, 겉보기엔 "농담 같은 스킬"이지만 심볼릭 링크 공격을 막는 심볼릭 링크 하드닝, 원자적 파일 쓰기, SHA-256 체크섬 검증 같은 보안 엔지니어링이 꽤 진지하게 들어가 있다 — 뒤에서 실제 코드로 확인한다.

용어
출력 토큰 vs 사고 토큰 (Output Tokens vs Thinking Tokens)
LLM이 답을 만들 때는 내부적으로 "생각"(추론 과정, 최신 모델은 이 과정 자체도 토큰으로 과금되는 경우가 있다)을 거친 뒤 "최종 답변"을 내놓는다. caveman은 이 최종 답변, 즉 사용자 눈에 보이는 텍스트의 길이만 줄인다. 문제를 얼마나 깊이 파고드는지, 얼마나 정확한 답을 찾는지에 관여하는 사고 과정은 전혀 건드리지 않는다는 것이 이 프로젝트의 핵심 차별점이다.

2왜 주목받는가

69.4k★ 트렌딩 이유 · 밈 요소 · 실제 벤치마크의 정직성.

2026년 상반기 AI 코딩 에이전트 생태계에서 토큰 비용은 실제 통증 포인트다. Claude Opus 같은 고급 모델은 출력 토큰 1백만 개당 $75가 들고(caveman-stats.js의 가격표에서 확인됨), 긴 세션일수록 청구서가 눈덩이처럼 불어난다. caveman은 이 문제를 "AI가 원시인처럼 말하게 하면 된다"는 우스꽝스러운 프레이밍으로 포장했지만, 실제로는 진지한 프롬프트 엔지니어링 + 토큰 회계 문제를 다룬다 — 이 "밈과 실용의 낙차"가 폭발적 트렌딩의 1차 요인이다.

정직함이라는 차별점 — 다른 "프롬프트 치트시트"와 무엇이 다른가

비교 대상흔한 "이렇게 말해라" 프롬프트 모음caveman의 접근
효과 검증스크린샷 몇 장, 벤치마크 없음evals/(3-arm 하니스) + benchmarks/(실제 Claude API 호출) 스크립트를 레포에 커밋
비교 기준선보통 비교 대상 자체가 없음baseline(시스템 프롬프트 없음) vs terse("Answer concisely." 만) vs skill(스킬 적용) — 3중 비교로 "스킬 자체의 순수 기여분"만 분리
과장 여부확인 불가자기 하니스가 "이전 버전은 baseline과 비교해 수치가 부풀려졌었다"고 스스로 지적하고 방법론을 고침(아래 상세)
지속성대화 몇 턴 지나면 흐지부지SessionStart 훅이 플래그 파일을 심어 세션 내내, 첫 메시지부터 유지
적용 범위Claude Code 전용인 경우多Claude Code·Codex·Gemini CLI·opencode·OpenClaw는 자동 적용, Cursor·Windsurf 등 30여 개는 npx skills add로 확산
가장 인상적인 정직성 — evals/의 자기비판
"베이스라인과 비교하면 그건 속임수다"

evals/README.md는 이렇게 적어 놓았다: "The honest delta for any skill is skill vs terse(__terse__) — 즉 스킬 자체가 '그냥 간결하게 답해'라는 지시 위에 추가로 얼마나 더 줄이는가다. 스킬을 시스템 프롬프트 없는 baseline과 비교하는 건 스킬의 효과를 일반적인 '간결하게' 요청과 뒤섞는 것이고, 이것이 이 하니스의 예전 버전이 부풀려진 수치를 낸 이유다." 자기 프로젝트의 과거 방법론을 스스로 "치팅"이라 부르고 고친 사례는 흔치 않다.

그럼에도 발견된 흠 — 정직하지만 낡은 스냅샷
evals/snapshots/results.json의 arm 이름이 현재 skills/ 폴더 구조와 안 맞는다

커밋된 결과 파일은 caveman-cn·caveman-es·compress라는 arm을 담고 있는데, 현재 skills/에는 그런 폴더가 없다(현재는 caveman-compress로 개명됨). CLAUDE.md 자신도 "compress → skills/caveman-compress 개명이 있었다"고 인정한다. 즉 "방법론은 정직"하지만 "그 방법론으로 만든 최신 수치"는 검증되지 않은 상태다 — 마케팅 사기는 아니지만, 안일한 관리의 흔적이다.

숫자 하나가 아니라 네 가지 버전의 "절감율"

흥미로운 지점은 "몇 % 줄어드는가"라는 질문에 레포 스스로도 하나의 답을 안 준다는 것이다. skills/caveman/SKILL.md frontmatter는 ~75%, README 벤치마크 표와 caveman-stats.js의 하드코딩 값은 65%(범위 22~87%), CLAUDE.md는 절충해서 ~65-75%, 그리고 방법론이 더 엄격한 evals/는 terse 컨트롤 대비 중앙값 +50%(진짜 baseline 대비가 아님)를 말한다. 넷 다 "거짓"은 아니다 — 무엇과 비교했느냐(진짜 아무 지시 없음 vs "간결하게 답해" vs "너는 친절한 조수다")에 따라 달라지는 서로 다른 숫자일 뿐이지만, "정확히 65%"라고 외우면 곤란하다.

이런 상황을 상상해봐

다이어트 보조제 광고가 "체중 30% 감소!"라고 할 때, "아무것도 안 한 사람 대비"인지 "이미 운동하는 사람이 추가로 먹었을 때"인지에 따라 완전히 다른 이야기가 된다. caveman은 이례적으로 이 두 가지를 모두 실험하고 레포에 코드로 남겨두었다 — 그 결과 "운동 자체(terse 지시)"보다 "보조제(스킬)"가 실제로 기여하는 몫이 얼마인지까지 스스로 드러낸 셈이다.

3기술 스택 전체 지도

JS 코어 + Python 압축엔진 + 셸/PowerShell 설치기 + Claude Code 훅/스킬/플러그인 + MCP 미들웨어.

레이어기술 / 버전역할
런타임Node.js ≥18 (엔진 명시)훅·설치기·statusline·MCP 미들웨어 전부 Node로 통일
패키지 배포npm (caveman-installer, 0.1.0)bin: {"caveman": "./bin/install.js"} — 설치기만 배포, 스킬 본체는 별도 채널
스킬 정의Markdown + YAML frontmatter (SKILL.md)Claude Code Skill 표준 포맷. 7개 스킬 각각 규칙을 담은 한 장짜리 문서
Claude Code 훅Node.js 스크립트 (src/hooks/*.js)SessionStart·UserPromptSubmit 이벤트에 바인딩되는 실행 파일
statusline 뱃지Bash(.sh) + PowerShell(.ps1) 이중 구현OS 무관하게 [CAVEMAN] ⛏ 12.4k 뱃지 렌더링
압축 엔진 (파일용)Python 3 + Anthropic SDK (또는 claude --print CLI 폴백)/caveman-compress가 실제 Claude를 호출해 메모리 파일을 재작성
MCP 미들웨어Node.js 순수 정규식 (LLM 미사용, 별도 npm 패키지)caveman-shrink — MCP 서버 도구 설명(description)을 프록시 단에서 정적으로 압축
토큰 카운팅Claude Code 세션 JSONL 파싱caveman-stats.jsusage.output_tokens를 직접 집계
토크나이저(평가용)tiktoken o200k_base (OpenAI BPE, Claude 근사치)evals/measure.py가 오프라인으로 토큰 수 재계산
설치 채널별 배포Claude Code Plugin · Codex Plugin/Hook · Gemini CLI Extension · opencode 네이티브 플러그인 · OpenClaw · vercel-labs/skills34개 에이전트 프로필을 bin/install.jsPROVIDERS 배열 하나로 관리
CI/CDGitHub Actions (sync-skill.yml)SKILL.md 원본이 바뀌면 플러그인 사본을 cp로 재동기화 + caveman.skill ZIP 재빌드
테스트node --test (설치기) + Python 테스트 (압축기)tests/installer/*.test.mjs, tests/test_symlink_flag.js
용어
Claude Code Skill / SKILL.md
Claude Code가 인식하는 "스킬"은 name·description 같은 YAML frontmatter와 본문(실제 지시문)으로 이루어진 Markdown 파일 하나다. description은 "언제 이 스킬을 자동으로 켤지" 트리거 조건을 담고, 본문은 켜졌을 때 모델의 시스템 컨텍스트에 그대로 주입되는 규칙이다. caveman의 핵심 스킬(skills/caveman/SKILL.md)이 정확히 이 구조다 — 별도 코드 없이 "규칙 텍스트"만으로 동작을 바꾸는 것이 스킬 시스템의 본질이다.
설계 패턴
단일 진실원(Single Source of Truth) + 다중 배포 채널
caveman은 규칙을 skills/<name>/SKILL.md 딱 한 곳에만 두고, 각 에이전트(Claude Code 플러그인·Gemini·opencode 등)는 이 원본을 읽거나 복사해서 쓴다. 다만 조사 결과 이 "복사"가 100% 자동은 아니었다 — CI가 3개 스킬만 동기화하고, 나머지는 수동 복사거나 아예 누락되어 있었다(4장에서 상세). "이론적으로는 단일 진실원, 실제로는 부분적으로만 지켜지는 단일 진실원"이라는 현실적인 교훈을 준다.

4아키텍처 심화 분석

SKILL.md 주입 → 세션 훅 플래그파일 → 매 메시지 caveman 발화 → stats 토큰 집계 → statusline.

가장 핵심적인 질문은 "사용자가 /caveman을 안 쳐도 첫 메시지부터 원시인 말투가 나오는 이유"다. 답은 Claude Code의 SessionStart 훅이다. 아래는 실제 소스코드를 근거로 재구성한 전체 흐름이다.

┌─────────────────┐ 읽음 ┌──────────────────────┐ │ skills/caveman/ │ ────────▶│ src/hooks/ │ │ SKILL.md (원본) │ │ caveman-activate.js │──┐ └─────────────────┘ │ (SessionStart 훅) │ │ 쓴다 └──────────────────────┘ │ ▼ $CLAUDE_CONFIG_DIR/.caveman-active (내용물 = "full" 같은 모드 문자열, 평문) ▲ ┌──────────────────────┐ │ 읽는다 사용자가 매번 메시지 입력 ──▶│ caveman-mode-tracker │───┤ │ (UserPromptSubmit 훅) │ │ └──────────────────────┘ │ │ │ │ /caveman-stats 감지 시 │ ▼ │ ┌──────────────────────┐ │ │ caveman-stats.js │ │ │ (세션 JSONL 파싱) │ │ └──────────────────────┘ │ │ 결과를 decision:'block'으로 │ 반환 → 모델은 절대 안 거침 ▼ │ 사용자 화면 │ │ ┌──────────────────────┐ │ │ caveman-statusline.sh │◀───┘ 읽는다 │ [CAVEMAN] ⛏ 12.4k │ └──────────────────────┘

① SessionStart 훅 — 세션 시작과 동시에 규칙 주입 + 플래그 파일 기록

.claude-plugin/plugin.json이 두 이벤트를 정확히 이렇게 바인딩한다(검증된 실제 파일):

{
  "hooks": {
    "SessionStart": [{ "hooks": [{
      "type": "command",
      "command": "node \"${CLAUDE_PLUGIN_ROOT}/src/hooks/caveman-activate.js\"",
      "timeout": 5
    }]}],
    "UserPromptSubmit": [{ "hooks": [{
      "type": "command",
      "command": "node \"${CLAUDE_PLUGIN_ROOT}/src/hooks/caveman-mode-tracker.js\"",
      "timeout": 5
    }]}]
  }
}

caveman-activate.js는 매 세션 시작마다 (1) skills/caveman/SKILL.md파일에서 직접 읽어 — 하드코딩된 사본이 아니라 원본 그대로 — (2) 지금 활성화된 레벨에 해당하는 표 행·예시만 걸러내고 (3) 그 결과를 표준출력으로 내보낸다. Claude Code 훅 규약상 SessionStart의 stdout은 사용자에게 안 보이는 숨은 시스템 컨텍스트로 주입된다 — 모델은 보지만 사람은 못 보는 채널이다.

// src/hooks/caveman-activate.js (핵심 로직 요약, 실제 코드 근거)
const flagPath = path.join(claudeDir, '.caveman-active');
const mode = getDefaultMode();               // env > repo config > user config > 'full'
safeWriteFlag(flagPath, mode);                // 심볼릭 링크 안전 원자적 쓰기 (아래 상세)

let skillContent = fs.readFileSync(
  path.join(__dirname, '..', 'skills', 'caveman', 'SKILL.md'), 'utf8'
);
// 활성 레벨 행만 필터링 후 출력 — 매 세션 6개 레벨 예시를 전부 보여주지 않음
output = 'CAVEMAN MODE ACTIVE — level: ' + modeLabel + '\n\n' + filtered.join('\n');
process.stdout.write(output);   // SessionStart 훅 stdout = 숨은 시스템 컨텍스트

② UserPromptSubmit 훅 — 매 메시지 재확인 + 자연어 트리거 + /caveman-stats 가로채기

이 훅은 사용자가 메시지를 보낼 때마다 실행된다. 두 가지 일을 한다. 첫째, "talk like caveman" 같은 자연어를 정규식으로 감지해 자동으로 모드를 켠다. 둘째 — 이게 6장의 핵심인데 — /caveman-stats가 감지되면 모델에게 프롬프트를 아예 전달하지 않고 Node 스크립트를 직접 실행해 그 결과를 사용자에게 돌려준다.

// src/hooks/caveman-mode-tracker.js — /caveman-stats 가로채기 (실제 코드)
const statsMatch = /^\/caveman(?::caveman)?-stats(?:\s+(.*))?$/.exec(prompt);
if (statsMatch) {
  const out = execFileSync(process.execPath,
    [statsPath, '--session-file', data.transcript_path], { timeout: 5000 });
  process.stdout.write(JSON.stringify({ decision: 'block', reason: out.trim() }));
  return;
}
설계 패턴
Hook Interception (훅 가로채기)
decision: 'block'은 Claude Code 훅 프로토콜에서 "이 프롬프트를 모델에게 전달하지 말고, 여기 적힌 reason 텍스트를 대신 사용자에게 보여줘라"는 뜻이다. /caveman-stats는 이 메커니즘 덕분에 단 한 번도 LLM을 호출하지 않고 100% 결정론적인 Node 스크립트 실행 결과만 사용자에게 보여준다 — "AI 추정치"가 아니라 "그냥 프로그램이 계산한 숫자"라는 뜻이다.

③ 심볼릭 링크 방어 — "귀여운 스킬 레포"치고 이례적으로 진지한 보안 코드

플래그 파일(~/.claude/.caveman-active)은 경로가 예측 가능하다. 공격자가 이 경로를 ~/.ssh/id_rsa 같은 민감한 파일을 가리키는 심볼릭 링크로 미리 바꿔치기해 두면, statusline이나 훅이 그 내용을 읽어 터미널에 출력하거나 모델 컨텍스트에 흘려 넣을 위험이 있다. src/hooks/caveman-config.jssafeWriteFlag()는 이를 정면으로 방어한다.

// src/hooks/caveman-config.js — 심볼릭 링크 안전 원자적 쓰기 (실제 코드)
const O_NOFOLLOW = fs.constants.O_NOFOLLOW || 0;   // 심볼릭 링크면 open() 자체가 실패
const flags = fs.constants.O_WRONLY | fs.constants.O_CREAT
            | fs.constants.O_EXCL | O_NOFOLLOW;
const fd = fs.openSync(tempPath, flags, 0o600);     // 임시파일에 먼저 씀
fs.writeSync(fd, String(content));
fs.renameSync(tempPath, realFlagPath);              // rename은 원자적 — 중간 상태 없음

// 읽을 때도 최대 64바이트 캡 + [a-z0-9-] 화이트리스트만 통과
const MAX_FLAG_BYTES = 64;   // 가장 긴 정상값 "wenyan-ultra"=12바이트, 여유만 둠

이 방어 로직은 우연이 아니다 — tests/test_symlink_flag.js의 헤더 주석에 "Covers fix for issue #207: safeWriteFlag refuses flag writes when ~/.claude is a symlink"라고 적혀 있다. 실제 신고된 보안 이슈를 고치며 만들어진, 회귀 테스트까지 딸린 진짜 코드라는 뜻이다.

④ /caveman-stats — "실측"과 "고정 승수"가 섞인 절감량 계산

caveman-stats.js가 하는 일을 정확히 둘로 나눠야 한다. (A) 진짜 실측: Claude Code 세션의 JSONL 트랜스크립트($CLAUDE_CONFIG_DIR/projects/**/*.jsonl)를 직접 파싱해 usage.output_tokens를 그대로 합산한다 — 이건 100% 실제 Anthropic API 사용량 메타데이터다. (B) 추정: "caveman 없이 썼다면 몇 토큰이었을까"는 실측이 아니라, 커밋된 벤치마크 1회 실행에서 나온 65%라는 고정 상수를 역산에 사용한다.

// src/hooks/caveman-stats.js — 실제 코드, 주석까지 그대로
// Mean per-task savings from benchmarks/results/*.json (avg_savings: 65 across
// 10 tasks, sonnet-4-20250514). Only 'full' has measured data.
const COMPRESSION = { 'full': 0.65 };

function deriveSavings({ outputTokens, mode }) {
  const ratio = COMPRESSION[mode];
  if (ratio == null) return { estSavedTokens: 0, estSavedUsd: 0 };  // lite/ultra/wenyan은 추정 안 함
  const estNormal = Math.round(outputTokens / (1 - ratio));   // 실측값을 0.35로 나눠 역산
  return { estSavedTokens: estNormal - outputTokens, ... };
}

중요한 건 이 도구가 스스로 정직하다는 점이다. 출력 문구에 항상 "Est."(추정)라는 접두어를 붙이고, full 외 모드에서는 "No savings estimate for '<mode>' mode — only 'full' has benchmark data."라고 명시하며, 하단에는 항상 "Savings est. from benchmarks/ (mean per-task). Actual varies by task."라는 면책 문구가 붙는다. 이번 세션에서 실제로 A/B 테스트를 돌린 게 아니라 "과거 벤치마크 평균을 지금 세션 실측 토큰 수에 적용한 값"이라는 걸 도구 스스로 밝히는 셈이다.

⑤ caveman-compress — 메모리 파일을 압축해 입력 토큰까지 절감하는 루프

4번까지가 "출력 토큰"을 줄이는 얘기였다면, /caveman-compress는 CLAUDE.md 같은 메모리 파일 자체를 압축해 매 세션 로드될 때 드는 입력 토큰까지 줄인다. 이건 정규식 치환이 아니라 진짜 Claude 모델 호출이다.

CLAUDE.md (원본, 706 토큰) │ ├─ ① YAML frontmatter 외과적 분리 (LLM은 이 부분 절대 안 봄) │ ├─ ② 본문만 Claude에 전달 — "코드블록/URL/경로/헤딩 그대로, 산문만 압축" │ call_claude(prompt) → Anthropic SDK 또는 `claude --print` CLI 폴백 │ ├─ ③ 결과 검증 (validate.py) — 코드블록·URL·인라인코드·헤딩 바이트 단위 비교 │ 실패 시 → "정확히 이 에러만 고쳐" 타겟 재시도 (최대 2회, 재압축 아님) │ ├─ ④ 그래도 실패 → 원본 파일 그대로 복원, 압축본 폐기 │ └─ ⑤ 성공 → frontmatter 재부착 + 백업은 소스 트리 밖 별도 캐시 디렉토리에 저장 (CLAUDE.md 압축본, 285 토큰 · 59.6% 절감, 실측 가능한 수치)
// skills/caveman-compress/scripts/compress.py — 실제 LLM 호출 코드
def call_claude(prompt: str) -> str:
    api_key = os.environ.get("ANTHROPIC_API_KEY")
    if api_key:
        client = anthropic.Anthropic(api_key=api_key)
        msg = client.messages.create(
            model=os.environ.get("CAVEMAN_MODEL", "claude-sonnet-4-5"),
            max_tokens=8192, messages=[{"role": "user", "content": prompt}])
        return strip_llm_wrapper(msg.content[0].text.strip())
    # API 키 없으면 로컬 Claude Desktop/CLI 인증을 그대로 재사용
    result = subprocess.run([shutil.which("claude") or "claude", "--print"],
                             input=prompt, text=True, capture_output=True, check=True)
    return strip_llm_wrapper(result.stdout.strip())

검증 단계는 단순 "비교"가 아니라 정확히 무엇이 깨졌는지까지 짚어낸다 — 코드블록 불일치, URL 유실/추가, 인라인 코드 개수 불일치(Counter 기반이라 "3개 중 2개만 남음" 같은 부분 손실도 잡는다)를 개별 항목으로 보고하고, 그 에러 리스트만 다시 Claude에게 보내 "이 항목만 고쳐, 재압축 금지"라고 지시한다. 2회 재시도 후에도 실패하면 원본 파일을 그대로 복원한다 — LLM 출력이 파일을 훼손할 가능성에 대한 방어가 촘촘하다.

⑥ caveman-shrink — MCP 미들웨어는 완전히 다른 구현 (LLM 없음, 순수 정규식)

이름은 비슷하지만 caveman-compress(Python, LLM 호출)와 caveman-shrink(Node, 순수 정규식)는 아키텍처가 근본적으로 다르다. caveman-shrink는 MCP(Model Context Protocol) 클라이언트와 업스트림 MCP 서버 사이에 끼어드는 stdio JSON-RPC 프록시다.

Claude (MCP 클라이언트) │ JSON-RPC over stdio ▼ ┌─────────────────────┐ spawn() ┌──────────────────────────┐ │ caveman-shrink │ ──────────▶│ 업스트림 MCP 서버 │ │ (프록시, index.js) │◀────────── │ (예: filesystem 서버) │ └─────────────────────┘ tools/list │ │ │ 응답 가로채서 │ 응답 │ │ description 필드만 압축 └──────────────────────────┘ ▼ Claude에게 압축된 tools/list 응답 전달
// src/mcp-servers/caveman-shrink/compress.js — 정규식으로 보호 구간 지정
const PROTECTED_PATTERNS = [
  /```[\s\S]*?```/g,          // 코드블록
  /`[^`\n]+`/g,               // 인라인 코드
  /\bhttps?:\/\/\S+/gi,       // URL
  /\b[\w.-]*[\/\\][\w.\/\\\-]+/g,  // 경로
];
// 보호 구간을 숫자 sentinel로 치환 → 나머지 산문만 압축 → 되돌려 붙임

README가 "compresses the same boundaries as the caveman skill"이라고 표현한 대로, 코드/URL/경로는 보호하되 나머지 산문(관사·인사치레·헤지 표현)만 정규식으로 걷어낸다는 점에서 caveman/SKILL.md의 규칙과 철학은 같지만 구현은 별개다 — LLM 호출 비용/지연 없이 MCP 툴 설명을 실시간으로 압축해야 하니 의도적으로 순수 기계적 구현을 택했다.

⑦ 4(6)단계 레벨 — lite / full / ultra / wenyan(3단계)

SKILL.md의 실제 표는 4단계가 아니라 6줄이다 — wenyan이 lite/full/ultra 세 하위 단계로 다시 나뉜다. CLI 명령에서 "wenyan"이라고만 쓰면 내부적으로 wenyan-full의 별칭으로 처리된다.

레벨실제 규칙 (SKILL.md 원문 요약)
lite군더더기·헤지 표현만 제거. 관사와 완전한 문장은 유지 — 전문적이지만 간결한 정도
full (기본값)관사 제거, 문장 파편 허용, 짧은 동의어 사용. 전형적 caveman체. 도구 호출 내레이션·장식용 표·이모지·긴 원본 에러로그 덤프 금지
ultra산문 단어만 약어화(DB/auth/config/req/res) — 코드 심볼·함수명은 절대 약어화 금지. 접속사 생략, 인과관계는 화살표(X → Y)로
wenyan-lite반(半)고전체. 필러/헤지는 빼지만 문법 구조와 고전적 어투는 유지
wenyan-full최대 고전 압축. 문언문(文言文) 80~90% 글자 수 감소. 동사가 목적어 앞, 주어 생략, 之/乃/為/其 같은 고전 조사 사용
wenyan-ultra고전 중국어 느낌은 유지하며 극단적 축약
실제 예시 (SKILL.md 원문에서 발췌)

"왜 React 컴포넌트가 리렌더링되나요?"에 대해 — lite: "매 렌더마다 새 객체 참조를 만들기 때문입니다. useMemo로 감싸세요." → full: "매 렌더 새 객체 참조. 인라인 객체 prop = 새 참조 = 리렌더. useMemo로 감싸." → ultra: "인라인 obj prop → 새 참조 → 리렌더. useMemo." → wenyan-full: "每繪新生對象參照,故重繪;以 useMemo 包之則免。"

⑧ Auto-Clarity — 압축이 오히려 위험할 때는 스스로 끈다

SKILL.md는 압축이 안전과 충돌하는 지점을 명시적으로 정의한다: 보안 경고, 되돌릴 수 없는 작업의 확인, 순서가 헷갈릴 수 있는 다단계 작업(예: "migrate table drop column backup first" — 관사·접속사 없이는 순서가 불명확)에서는 caveman체를 자동으로 끄고 평범한 영어로 전환한 뒤, 위험 구간이 끝나면 다시 caveman체로 돌아온다. 이건 "무조건 짧게"가 아니라 "안전을 해치지 않는 선에서 짧게"라는 설계 원칙을 보여준다.

5디렉토리 구조 해부

7개 스킬 + 훅 + evals/benchmarks 이중 검증 + 멀티에이전트 배포.

caveman/ ├── skills/ # 7개 스킬 — 각각 SKILL.md 한 장이 진짜 "코드" │ ├── caveman/SKILL.md # 핵심 규칙 — lite/full/ultra/wenyan(3) 6단계 │ ├── caveman-commit/SKILL.md # Conventional Commits ≤50자, AI 서명 금지 │ ├── caveman-review/SKILL.md # 한 줄 PR 리뷰, L<line>: 형식 │ ├── caveman-stats/SKILL.md # 10줄짜리 스텁 — 진짜 로직은 훅에 있음 │ ├── caveman-compress/ # 유일하게 Python 스크립트를 동반하는 스킬 │ │ ├── SKILL.md │ │ ├── scripts/{compress,validate,detect,cli,benchmark}.py │ │ └── SECURITY.md # Snyk High-Risk 오탐 해명 문서 │ ├── caveman-help/SKILL.md # 빠른 참조 카드, 1회성 출력 │ └── cavecrew/SKILL.md # "언제 서브에이전트에 위임할지" 판단 가이드 │ ├── agents/ # cavecrew 서브에이전트 3종 (표준 Markdown+frontmatter) │ ├── cavecrew-investigator.md # model: haiku 고정, 읽기 전용, file:line 표만 반환 │ ├── cavecrew-builder.md # Bash 툴 자체가 없음 — 1~2파일 수정만, 3개+ 거부 │ └── cavecrew-reviewer.md # model: haiku 고정, 한 줄 심각도 태그 리뷰 │ ├── src/ │ ├── hooks/ # 이 레포의 진짜 "엔진" │ │ ├── caveman-activate.js # SessionStart — SKILL.md 읽어 주입 + 플래그 파일 씀 │ │ ├── caveman-mode-tracker.js # UserPromptSubmit — /caveman-stats 가로채기 │ │ ├── caveman-config.js # safeWriteFlag/readFlag — 심볼릭 링크 방어 공유 모듈 │ │ ├── caveman-stats.js # JSONL 파싱 + 65% 고정승수 + USD 환산 │ │ ├── caveman-statusline.{sh,ps1} # [CAVEMAN] 뱃지, OS별 이중 구현 │ │ └── checksums.sha256 # curl|bash 설치 시 훅 파일 무결성 검증용 │ ├── mcp-servers/caveman-shrink/ # MCP 프록시 — 별도 npm 패키지 (LLM 미사용) │ ├── plugins/opencode/ # opencode 전용 네이티브 플러그인 (가장 기능 완전한 미러) │ ├── rules/caveman-activate.md # --with-init이 레포별 규칙파일 생성 시 쓰는 원본 │ └── tools/caveman-init.js # .cursor/rules, .windsurf/rules 등 생성 스크립트 │ ├── plugins/caveman/ # Claude Code 플러그인 배포판 (CI가 skills/ 일부만 cp로 동기화) │ ├── .claude-plugin/plugin.json # 훅 바인딩 선언 (SessionStart/UserPromptSubmit) │ ├── skills/{cavecrew,caveman,caveman-compress,caveman-stats}/ # 7개 중 4개만, stats는 수동복사 │ ├── agents/cavecrew-*.md │ └── .codex-plugin/plugin.json # Codex 전용 리치 매니페스트 (별도 채널) │ ├── .codex/{config.toml,hooks.json} # Codex용 — 훨씬 단순, 하드코딩 echo 한 줄 (Node 스크립트 아님) ├── commands/*.toml # Codex/Gemini TOML 커맨드 스텁 (caveman/-commit/-review/-init) ├── evals/ # "정직한" 3-arm 하니스 — baseline/terse/skill │ ├── llm_run.py # claude CLI로 실제 호출, skills/ 자동 탐색해 arm 생성 │ ├── measure.py # tiktoken으로 오프라인 재계산, median/mean/stdev 전부 표시 │ └── snapshots/results.json # 커밋된 실측 스냅샷 (단, arm 이름이 낡음 — 4장 참고) ├── benchmarks/ # 실제 Anthropic API 직접 호출 (Claude Code CLI 아님) │ ├── run.py # baseline="You are a helpful assistant." — evals보다 약한 대조군 │ ├── prompts.json # 10개 실전 프롬프트 (react-rerender, postgres-pool 등) │ └── results/.gitkeep # 결과 JSON은 .gitignore 처리 — README 표만 남고 원본 미검증 가능 ├── bin/install.js # 1400줄 — 34개 에이전트 감지·설치·제거 로직 전부 ├── tests/{installer,caveman-compress}/ # node --test + Python 테스트 ├── dist/caveman.skill # zip -r로 만든 배포용 ZIP (caveman 스킬 1개만 포함, 3파일) ├── CLAUDE.md # 유지보수자용 — 훅 아키텍처·파일 소유권·CI 동기화 규칙 문서화 ├── GEMINI.md / AGENTS.md # 각 4줄, @./skills/... import — 가장 깔끔한 배포 방식 └── gemini-extension.json / skills-lock.json
파일/폴더역할
skills/caveman/SKILL.md유일하게 편집해야 할 "행동 규칙" 원본. CLAUDE.md가 "Only file to edit for behavior changes"라고 못박음
src/hooks/caveman-config.js모든 플래그 파일 읽기/쓰기가 반드시 거쳐야 하는 심볼릭 링크 안전 공유 모듈
.github/workflows/sync-skill.ymlskills/**/SKILL.md 변경 시 plugins/caveman/ 사본 재동기화 + ZIP 재빌드. 단, 7개 중 3개(caveman·caveman-compress·cavecrew)만 대상
evals/README.md"skill vs baseline 비교는 치팅"이라고 스스로 못박은 방법론 문서
skills-lock.jsonvercel-labs/skills CLI가 남긴 부산물 — cavecrew 스킬 1개만 기록, 이 레포의 어떤 스크립트도 안 읽음
주의 — 배포 채널마다 실제로 받는 것이 다르다
"Claude Code 플러그인 설치 = 전체 7개 스킬"이 아니다

plugins/caveman/skills/에는 caveman-commit·caveman-review·caveman-help 폴더가 아예 없다. CLAUDE.md가 명시적으로 "이 3개는 CI가 플러그인에 동기화하지 않는다, standalone 훅 설치 경로나 다른 에이전트의 npx skills add로만 도달한다"고 인정한다. 즉 Claude Code 플러그인 마켓플레이스로 설치한 사용자는 기본적으로 4개 스킬(caveman·caveman-compress·caveman-stats·cavecrew)만 받는다.

6학습 포인트 (기술별)

이 저장소 하나로 배울 수 있는 것 + 실습 아이디어.

6-1. Claude Code 스킬 만들기 — "한 장의 Markdown이 코드"라는 감각

skills/caveman/SKILL.md는 프로그래밍 언어 코드를 단 한 줄도 쓰지 않고 모델의 행동을 완전히 바꾼다. frontmatter의 description이 트리거 조건("사용자가 'caveman mode', 'talk like caveman' 이라고 하면")을 정의하고, 본문이 실제 규칙이다. 좋은 스킬 작성의 핵심은 "무엇을 하지 마라"를 예시와 함께 명시하는 것 — SKILL.md는 "Not: '기꺼이 도와드리겠습니다...' / Yes: '인증 미들웨어 버그. 토큰 만료 체크가 < 아니라 <=. 수정:'"처럼 나쁜 예/좋은 예를 나란히 보여준다.

실습 아이디어

자신만의 미니 스킬을 만들어보자. 예를 들어 "항상 답변 끝에 '다음 할 일 1가지'를 붙이는 스킬"을 SKILL.md 형식(frontmatter + 본문 + 예시)으로 작성하고, 로컬 Claude Code에 ~/.claude/skills/로 넣어 실제로 트리거되는지 확인해보자.

6-2. Claude Code 훅 — SessionStart/UserPromptSubmit으로 "항상 켜짐" 만들기

스킬만으로는 "매번 사용자가 트리거 문구를 말해야" 켜진다. caveman이 첫 메시지부터 자동으로 동작하는 비밀은 SessionStart 훅이 stdout으로 숨은 컨텍스트를 주입한다는 데 있다. 훅의 계약은 단순하다 — stdin으로 JSON을 받고, stdout으로 텍스트나 {"decision": ..., "reason": ...} 형태 JSON을 내보내면 된다.

// 최소 SessionStart 훅 예시 (caveman-activate.js 구조를 단순화)
#!/usr/bin/env node
process.stdout.write("항상 존댓말로 답하고 이모지는 절대 쓰지 마라.");
process.exit(0);
// .claude/settings.json 에 등록:
// "hooks": { "SessionStart": [{ "hooks": [{ "type": "command", "command": "node ./my-hook.js" }] }] }
실습 아이디어

UserPromptSubmit 훅을 하나 만들어, 사용자가 "요약해줘"라고 치면 decision: 'block'으로 모델을 거치지 않고 바로 고정 문자열을 돌려주는 실험을 해보자. /caveman-stats가 왜 "AI 계산이 아니라 프로그램 계산"인지 몸으로 이해할 수 있다.

6-3. 예측 가능한 경로에 파일 쓰기 — 심볼릭 링크 공격과 원자적 쓰기

~/.claude/.caveman-active처럼 경로가 고정된 파일에 반복적으로 쓰는 도구는 전부 이 문제를 마주한다: 로컬 공격자가 그 경로를 미리 심볼릭 링크로 바꿔치기하면? caveman은 O_NOFOLLOW 플래그(심볼릭 링크면 open 자체가 실패)로 열고, 임시 파일에 쓴 뒤 rename()으로 원자적으로 교체하는 패턴을 쓴다. 읽을 때도 크기 캡(64바이트)과 화이트리스트 정규식으로 방어한다.

실습 아이디어

Node.js로 "고정 경로에 안전하게 쓰기" 함수를 직접 구현해보자. 먼저 순진하게 fs.writeFileSync(고정경로, 내용)으로 짜고, 그 경로를 심볼릭 링크로 바꿔치기했을 때 무슨 일이 일어나는지 확인한 다음, caveman-config.jssafeWriteFlag() 패턴(O_NOFOLLOW + 임시파일 + rename)으로 고쳐 같은 공격이 막히는지 검증해보자.

6-4. MCP 미들웨어 패턴 — 프로토콜 프록시로 기존 서버를 감싸기

caveman-shrink는 MCP 클라이언트-서버 사이에 끼어 JSON-RPC 메시지를 가로채고 특정 필드만 수정한 뒤 그대로 전달한다. 이 패턴은 MCP뿐 아니라 어떤 stdio/JSON-RPC 기반 프로토콜에도 적용 가능한 범용 기법이다 — 원본 서버 코드를 전혀 건드리지 않고 동작을 확장한다는 게 핵심.

// caveman-shrink/index.js 구조 요약 — 이 패턴 자체가 학습 포인트
const upstream = spawn(args[0], args.slice(1));  // 원본 MCP 서버를 자식 프로세스로 띄움
upstream.stdout.on('data', chunk => {
  const msg = JSON.parse(line);
  if (msg.result?.tools) msg.result.tools.forEach(t => t.description = compress(t.description));
  process.stdout.write(JSON.stringify(msg) + '\n');  // 변형해서 클라이언트로 전달
});
실습 아이디어

아무 MCP 서버(예: @modelcontextprotocol/server-filesystem)와 클라이언트 사이에 자기만의 프록시를 만들어, tools/list 응답의 description 앞에 이모지를 붙이는 것부터 시작해보자. JSON-RPC 라인 버퍼링(\n 단위로 잘라 파싱)까지 직접 구현하면 프로토콜 프록시의 핵심을 체득할 수 있다.

6-5. 토큰 회계 — 실측과 추정을 명확히 구분해서 보여주기

caveman-stats.js는 좋은 선례를 보여준다: "이건 실제로 측정한 값"(JSONL에서 뽑은 output_tokens)과 "이건 과거 벤치마크 기반 추정치"(65% 고정 승수)를 코드 구조로도, 출력 문구로도 명확히 분리한다. 실무에서 비용/성능 대시보드를 만들 때 이 구분을 흐리면 사용자가 추정치를 실측으로 오해하기 쉽다.

실습 아이디어

본인이 만든 어떤 스크립트든 "실행 시간을 측정"하는 부분과 "예상 절감 효과를 추정"하는 부분을 분리하고, 추정치 출력에는 반드시 "Est."/"약" 같은 접두어와 근거(무엇 대비 몇 %를 어디서 가져왔는지)를 함께 표시하는 습관을 만들어보자.

6-6. 정직한 eval 설계 — baseline/terse/skill 3-arm 하니스

evals/가 주는 가장 중요한 교훈은 "무엇과 비교하느냐"가 숫자의 의미를 완전히 바꾼다는 것이다. "간결하게 답해"라는 지시만으로도 어느 정도 절감되므로, 스킬의 진짜 기여를 알려면 스킬 vs (스킬 없이 그냥 간결하게)를 비교해야 한다 — 스킬 vs 아무 지시 없음을 비교하면 "일반적인 간결함 요청"의 효과까지 스킬 덕분으로 착각하게 된다.

// evals/llm_run.py 구조 요약 — 3-arm 정의
arms = {
  "__baseline__": None,                                    # 시스템 프롬프트 없음
  "__terse__": "Answer concisely.",                        # 대조군 — 일반적 "간결히"
  "caveman": "Answer concisely.\n\n" + skill_md_text,       # 진짜 비교 대상
}
# 정직한 지표 = arms["caveman"] vs arms["__terse__"], baseline과 비교하면 "치팅"
실습 아이디어

자신이 만든 프롬프트/스킬이 있다면 동일한 3-arm 구조(baseline / 일반 지시 / 내 스킬)로 최소 5개 프롬프트에 대해 tiktoken 등으로 토큰 수를 재고 median/mean/stdev를 계산해보자. "내 스킬이 실제로 얼마나 추가 기여하는지"를 숫자로 확인하는 습관은 어떤 프롬프트 엔지니어링에도 적용된다.

6-7. 프롬프트로 출력 스타일을 강제하기 — 규칙 + 안전 예외 + 페르소나 자기지시 금지

SKILL.md의 스타일 강제 규칙에서 배울 점이 세 가지 있다. (1) "무엇을 뺄지" 구체적 목록(관사, filler 단어, 인사치레)을 나열한다. (2) 안전 예외(보안 경고·되돌릴 수 없는 작업 시 자동으로 정상 모드 복귀)를 명시한다. (3) "self-reference 금지" — "저는 지금 caveman 모드입니다" 같은 페르소나 자기설명을 하지 말라고 명시한다. 이 세 원칙은 "AI에게 특정 톤을 강제하는" 어떤 프롬프트에도 그대로 적용 가능하다.

실습 아이디어

"항상 5줄 이내로 답하되, 코드는 예외로 하고, 스스로 '5줄 규칙'을 언급하지 않는다"는 나만의 스타일 스킬을 작성해 실제로 몇 번 테스트해보고, 모델이 규칙을 어기는 경계 상황(코드+설명이 섞인 답)을 찾아 규칙을 다듬어보자.

7시스템 요구사항

Node ≥18 하나면 끝 — 진짜 다양성은 "지원 에이전트" 쪽에 있다.

항목요구사항
런타임Node.js 18 이상 (package.json engines 필드 + install.sh/ps1의 버전 체크로 이중 강제)
OSmacOS · Linux · Windows — 훅은 .sh/.ps1 이중 구현, 설치기는 하나의 Node 스크립트로 통일(과거 bash+PowerShell 병행 관리 중 드리프트 문제로 issue #249 발생 후 통합)
설치 한 줄 (curl)curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash
설치 한 줄 (PowerShell)irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.ps1 | iex
caveman-compress 전용Python 3 + (선택) ANTHROPIC_API_KEY 환경변수, 없으면 로컬 claude CLI 인증을 그대로 재사용
caveman-shrink 전용Node ≥18만 있으면 됨 (LLM 미사용, 순수 정규식)
하드웨어일반 개발 노트북으로 충분 — 실제 추론은 클라우드 LLM(Claude/Codex 등)에서 일어나 로컬 GPU 불필요

지원 에이전트 매트릭스 — "자동 실행"과 "수동 트리거"는 다르다

README는 "30+ agents"라 하고 CLAUDE.md는 "40+"라 한다(양쪽 다 대략적 수치, 정확히는 bin/install.jsPROVIDERS 배열에 하드 프로필 31개 + soft 프로필 3개 = 34개 정의). 중요한 건 숫자보다 "자동으로 켜지는가, 매번 사용자가 켜야 하는가"다.

에이전트배포 방식자동 활성화?
Claude Code플러그인(훅+스킬) 또는 standalone 훅 설치예 — SessionStart 훅이 규칙 주입
Codexplugins/caveman/ 플러그인 + 레포 .codex/hooks.json예(macOS/Linux) — 단, 하드코딩 echo 한 줄로 훨씬 단순한 버전
Gemini CLIGEMINI.md 컨텍스트 파일(4개 스킬 @-import)예 — 세션마다 로드
opencode네이티브 ESM 플러그인, session.created예 — 가장 기능이 완전한 비-Claude 미러(단 statusline 없음)
OpenClaw워크스페이스 스킬 + SOUL.md 마커 부트스트랩예 — 매 턴 자동 주입(단, 12K/60K 용량 캡 적용)
Cursor · Windsurf · Clinenpx skills add + --with-init으로 레포별 규칙파일--with-init 별도 실행해야 완전 자동
Copilotnpx skills add -a github-copilot (soft probe)탐지 자체가 수동 지정 필요
나머지 20여 개(Junie·Trae·Warp 등)npx skills add JuliusBrussee/caveman -a <profile>아니오 — 매 세션 /caveman 수동 입력 필요
정리하면

설치 장벽은 거의 없다(Node 하나). 하지만 "30여 개 에이전트 지원"이라는 문구를 볼 때, 실제로는 5개 에이전트(Claude Code·Codex·Gemini CLI·opencode·OpenClaw)만 진짜 "제로 클릭 항상 켜짐"이고, 나머지 대부분은 업스트림 vercel-labs/skills 도구에 위임한 "설치는 되지만 세션마다 직접 켜야 하는" 상태라는 걸 CLAUDE.md 표가 스스로 밝히고 있다.

8직접 해볼 수 있는 실습 과제

난이도별 5단계. 설치 → 레벨 실험 → 메모리 압축 → 나만의 훅/스킬 → MCP·평가 재현.

실습 1

설치하고 4(6)단계 레벨을 직접 비교해보기 난이도 ★☆☆ 입문

curl -fsSL .../install.sh | bash로 설치한 뒤(또는 로컬 클론에서 node bin/install.js), Claude Code에서 같은 질문("왜 이 코드가 메모리 누수를 일으키나요?")을 /caveman lite, /caveman full, /caveman ultra, /caveman wenyan 네 번 반복해서 물어보고 답변 길이·스타일 차이를 직접 비교한다. 목표: "출력만 줄고 정확도는 유지된다"는 핵심 주장을 스스로 검증.

실습 2

/caveman-compress로 나만의 CLAUDE.md 압축해보기 난이도 ★★☆ 중급

본인이 쓰는 CLAUDE.md나 메모 파일 하나를 골라 /caveman-compress <파일경로>를 실행하고, 압축 전/후 파일을 나란히 놓고 코드블록·URL·경로가 정말 한 글자도 안 바뀌었는지 직접 diff로 확인한다. 백업이 소스 트리 밖(플랫폼별 캐시 디렉토리)에 저장된다는 점도 실제로 찾아본다. 목표: LLM 기반 압축이 "안전하게" 동작하려면 어떤 검증 단계가 필요한지 체감.

실습 3

나만의 미니 SessionStart 훅 + 스킬 작성 난이도 ★★☆ 중급

src/hooks/caveman-activate.js의 구조(플래그 파일 쓰기 + SKILL.md 읽어 주입)를 참고해, "세션 시작 시 오늘 날짜와 함께 인사말을 숨은 컨텍스트로 주입하는" 나만의 SessionStart 훅을 만들고 ~/.claude/settings.json에 등록해본다. 이어서 트리거 조건이 있는 나만의 SKILL.md도 하나 작성해 실제로 자동 발동하는지 확인. 목표: 스킬(트리거형)과 훅(항상 실행형)의 역할 차이를 코드로 체득.

실습 4

caveman-shrink로 MCP 툴 설명 압축해보기 난이도 ★★★ 고급

npx caveman-shrink npx @modelcontextprotocol/server-filesystem /some/dir로 아무 MCP 서버나 감싸 실행하고, CAVEMAN_SHRINK_DEBUG=1 환경변수로 압축 전/후 description 필드의 실제 델타를 stderr로 확인한다. 이어서 compress.jsPROTECTED_PATTERNS 정규식 배열에 새로운 보호 패턴(예: 이메일 주소)을 추가해보고 정말 보호되는지 검증. 목표: MCP 프로토콜 프록시가 실시간으로 JSON-RPC 메시지를 가로채 변형하는 과정을 직접 관찰.

실습 5

evals/ 3-arm 하니스를 직접 재현하고 최신화해보기 난이도 ★★★ 고급

evals/llm_run.py를 로컬에서 실제로 실행해(claude CLI 필요) 현재 skills/ 폴더 구조 기준으로 새 스냅샷을 생성하고, 커밋된 evals/snapshots/results.json의 낡은 arm 이름(caveman-cn·compress 등, 4장에서 지적한 discrepancy)과 비교해본다. evals/measure.py로 median/mean/stdev를 계산하고, "baseline 대비"와 "terse 대비" 두 가지 절감률이 왜 다른지 직접 숫자로 확인. 목표: "정직한 벤치마크 설계"가 실제로 데이터에 어떤 차이를 만드는지 재현.

9관련 기술 심화 학습 로드맵

이 저장소를 출발점으로, 5주 코스.

주차주제학습 자료
1주차프롬프트 엔지니어링 기초 — 스타일 강제와 페르소나skills/caveman/SKILL.md 전문 정독 · Anthropic 공식 프롬프트 엔지니어링 가이드 · 실습 1
2주차Claude Code 확장 — Skill/Hook/Plugin 3종 세트Claude Code 공식 문서(Skills, Hooks, Plugins) · src/hooks/caveman-activate.js+caveman-mode-tracker.js 정독 · 실습 3
3주차파일시스템 보안 — 심볼릭 링크·원자적 쓰기·경쟁 상태caveman-config.jssafeWriteFlag 정독 · Node.js fs.constants 문서 · TOCTOU(Time-of-check to time-of-use) 공격 개념 학습
4주차MCP(Model Context Protocol) 심화modelcontextprotocol.io 공식 스펙 · caveman-shrink/index.js 정독 · 실습 4 · 자신만의 MCP 서버 하나 만들어보기
5주차벤치마크·eval 설계 방법론evals/README.md+llm_run.py+measure.py 정독 · "대조군 선택이 결과를 어떻게 왜곡하는가" 통계 기초 · 실습 5

10핵심 키워드 사전

본문에 나온 용어 빠른 참조.

용어의미
출력 토큰 (Output Tokens)모델이 사용자에게 보여주는 최종 답변의 토큰 수. caveman이 줄이는 대상.
사고 토큰 (Thinking Tokens)모델이 답을 만들기 위해 내부적으로 추론하는 과정의 토큰. caveman은 이걸 건드리지 않는다.
SKILL.mdClaude Code Skill의 표준 정의 파일 — YAML frontmatter(트리거 조건) + 본문(실제 규칙)으로 구성된 Markdown.
Hook (훅)Claude Code 세션의 특정 이벤트(SessionStart, UserPromptSubmit 등)에 바인딩되어 자동 실행되는 스크립트.
decision: 'block'훅이 반환할 수 있는 특수 응답 — 프롬프트를 모델에 전달하지 않고 reason 텍스트를 즉시 사용자에게 보여줌.
statuslineClaude Code 터미널 하단에 상시 표시되는 커스텀 상태 표시줄. caveman은 여기에 절감 토큰 뱃지를 그린다.
MCP (Model Context Protocol)LLM 클라이언트와 외부 도구/서버가 통신하는 표준 프로토콜(JSON-RPC 기반).
MCP 미들웨어MCP 클라이언트-서버 사이에 끼어들어 메시지를 가로채고 변형하는 프록시. caveman-shrink가 이 패턴.
심볼릭 링크 클로버링 (Symlink Clobbering)예측 가능한 경로의 파일을 심볼릭 링크로 바꿔치기해 프로그램이 엉뚱한(민감한) 파일을 읽거나 쓰게 만드는 공격.
원자적 쓰기 (Atomic Write)임시 파일에 먼저 쓰고 rename()으로 교체해, 쓰기 도중 상태가 노출되지 않도록 하는 안전한 파일 쓰기 기법.
Conventional Commits<type>(<scope>): <summary> 형식의 커밋 메시지 표준. caveman-commit이 이 형식을 강제.
3-Arm 하니스 (Three-Arm Harness)baseline/대조군/실험군 셋을 동시에 비교해 "무엇 대비 얼마나"를 명확히 하는 실험 설계. evals/의 baseline·terse·skill 구조.
멱등 설치 (Idempotent Install)여러 번 실행해도 결과가 같은(중복 등록이 안 되는) 설치 방식. caveman은 마커 문자열 존재 여부로 이를 보장한다.
tiktokenOpenAI가 공개한 BPE 토크나이저 라이브러리. caveman evals에서 Claude 토큰 수의 근사치 계산에 사용(정확치 않음, 근사).

11참고 링크