hooks/·skills/·scripts/goals.py·packs/ 실제 소스를 해부해, "어떤 검증을, 어떤 메커니즘으로 강제하는가"에 초점을 맞춘다.이 레포가 무엇을 하는 물건인가.
fablize는 똑똑한 새 모델이 아니다. Claude Code 위에 얹는 하네스(harness) — 즉 모델을 감싸 행동을 통제하는 얇은 운영 규칙 집합이다. 핵심 슬로건은 "a harness cannot raise a model's ceiling; it makes the model reach its own ceiling." 즉 모델의 천장(능력)을 높이지는 못하지만, 모델이 자기 천장까지 닿게 만든다는 것이다. 그 수단이 검증·완료·조사를 "절차"로 강제하는 4개의 메커니즘(스킬·후크·라우터·골 엔진)이다.
제작자가 발견한 핵심은 이렇다. Fable 5와 Opus 4.8을 비교했더니, 닫힌(정답이 있는) 작업에서는 두 모델이 사실상 동등했다. 차이는 열린(open-ended) 작업에서만 났고, 그 본질은 "암시 하나를 한 발 더 따라가는 깊이"였다. 그런데 이 깊이는 모델의 능력이라 지시로 옮길 수 없었다(주입 실험에서 반박됨). 반면 "실제로 돌려본다 · 끝까지 본다 · 체계적으로 조사한다" 같은 좋은 작업의 절차는 전이됐다. fablize는 바로 이 전이 가능한 절차만 골라 담은 것이다.
왜 지금 TrendShift에 오르는가.
"AI 코딩 에이전트가 작업을 다 했다고 말해 놓고 실제로는 안 했다"는 문제는 모든 사용자가 겪는 고질병이다. fablize가 주목받는 이유는, 이 문제를 정신론(더 잘하라는 프롬프트)이 아니라 결정론적 메커니즘(후크·검증 게이트)으로 막으려 하기 때문이다. 게다가 그 설계가 실측 비교 실험에 근거한다는 점이 차별점이다.
대부분의 "프롬프트 모음/룰셋"은 "이렇게 하면 좋다더라"의 모음이다. fablize는 19회 A/B + 26회 실세션으로 효과가 확인된 절차(✅ shipped)와 확인 안 된 아이디어(❌ not shipped)를 표로 구분하고, 후자는 "개인 개발에 남겨둔다"며 일부러 빼버린다. "능력은 못 올린다"고 README에 못 박는 하네스는 드물다.
"이제 X를 구현하겠습니다"로 끝나고 실제론 안 하는 조기 종료(early stop)를, LLM에게 부탁하는 게 아니라 정규식 기반 Stop 후크(finish-the-work.sh)로 잡아낸다. "약속만 하고 끝낸" 문장을 감지하면 턴을 block하고 "지금 도구로 실제 하라"고 다시 밀어 넣는다. 규율을 코드로 강제한다.
모든 작업에 모든 규칙을 들이붓지 않는다. router.sh(UserPromptSubmit 후크)가 프롬프트를 보고 디버깅이면 조사 프로토콜만, HTML/SVG면 렌더 검증 팩만 주입한다. "가장 작게 매칭되는 규율만, 진짜 다중 범주일 때만 중첩" — 컨텍스트 낭비를 줄이는 정밀 주입이다.
① 능력은 못 올린다. 열린 창작 품질·자발적 깊이는 모델 선택의 문제지 하네스의 몫이 아니다. ② 측정의 범위. 효과 수치는 단일 패밀리(Claude) 자기측정(19회)이라 "방향은 견고하나 소수점은 주장하지 않는다"고 명시한다. ③ 후크 오작동. 조기종료 후크는 "원하시면 보고서를 쓰겠습니다" 같은 선언형 제안에 잘못 발동할 수 있어, 제안을 질문형으로 쓰라고 안내한다.
의외로 단순하다 — Bash + Python(stdlib) + Markdown + Claude Code 플러그인 규약.
fablize에는 무거운 런타임이 없다. 외부 의존성 0이 설계 철학이다. 모든 게 Bash 후크, 표준 라이브러리만 쓰는 Python 한 개, Markdown 규약 문서로 이뤄진다. "하네스는 가벼워야 한다"는 원칙의 구현이다.
| 구성 | 역할 | 실제 파일 / 기술 |
|---|---|---|
| 플러그인 매니페스트 | Claude Code가 인식하는 진입점 | .claude-plugin/plugin.json, marketplace.json |
| 스킬(Skill) | 작업 신호 시 불려오는 운영 지침서 | skills/fablize/SKILL.md (YAML frontmatter + 본문) |
| 후크(Hooks) | 이벤트 시점 자동 개입 | hooks/router.sh(UserPromptSubmit), finish-the-work.sh(Stop), hooks.json |
| 골 엔진 | 다중 스토리 분해 + 검증 게이트 | scripts/goals.py (Python 3 stdlib only) |
| 규율 팩(Packs) | 주입되는 절차 텍스트 | packs/investigation-protocol.txt, verification-grounding-pack.txt |
| 슬래시 커맨드 | 수동 셋업 트리거 | commands/setup.md |
| 셋업 스크립트 | always-on 설치/제거 | setup/setup.sh · uninstall.sh · star.sh · fablize-block.md |
goals.py는 argparse·json·datetime·pathlib만 import한다. pip 설치가 필요 없다는 뜻이다. 상태는 ./.fablize/goals.json과 ledger.jsonl에 파일로 저장돼 세션이 죽어도(대화가 끊겨도) 살아남는다. 외부 골 관리 시스템 없이 자기완결적으로 동작하는 게 핵심 설계다.
block 등)을 돌려줄 수 있다. fablize 통제력의 원천.SKILL.md 파일 하나로 정의되는 "필요할 때 불려오는 지침서". YAML frontmatter의 description이 트리거 조건(언제 이 스킬을 쓸지)을 결정한다. fablize 스킬은 "2개 이상 스토리, 디버깅, 렌더 산출물" 등을 트리거로 명시한다.사용자 프롬프트 한 번이 어떤 게이트를 거쳐 "증거 없는 완료"를 막는가.
fablize의 본질은 "두 개의 후크가 작업의 입구와 출구를 지킨다"는 것이다. 입구에서는 router.sh가 작업 종류를 보고 맞는 규율을 주입하고, 출구에서는 finish-the-work.sh가 "약속만 하고 끝낸" 종료를 되돌린다. 그 사이를 goals.py의 검증 게이트가 받친다.
모델의 선의(善意)에 기대지 않는다. 입구의 router.sh는 맞는 규율만 정밀 주입하고, 출구의 finish-the-work.sh는 약속만 한 종료를 차단한다. 둘 다 stdin으로 JSON을 받아 Python으로 파싱하는 동일한 패턴의 Bash 스크립트다. "부탁"이 아니라 "관문"이라는 점이 핵심.
goals.py의 checkpoint --status complete는 빈 --evidence를 받으면 즉시 종료(sys.exit)한다. 게다가 마지막 스토리는 항상 검증 게이트라서, 실제로 돌린 명령(--verify-cmd)과 그 결과(--verify-evidence)가 없으면 "끝났다"고 선언할 수 없다. 검증이 코드로 강제된 것이다.
verification-grounding 팩의 단언: "Well-formed ≠ correct". xmllint·node --check 같은 정적 파싱은 "형식이 옳다"만 확인할 뿐 "보기/동작이 옳다"를 확인하지 못한다. 그래서 실제 렌더러에서 실행 → 스크린샷/콘솔을 직접 본다 → 결함 수정 → 재실행의 루프를 강제한다. "만든 스크린샷을 안 본 것은 관찰이 아니다"까지 못 박는다.
Stop 후크가 매번 막으면 무한루프가 된다. 그래서 stop_hook_active=True면 한 번 강제한 뒤엔 통과시킨다. 검증 팩도 "결함 없는 산출물을 N번 재렌더하는 것은 토큰 낭비"라며 "한 번 깨끗이 관찰하면 멈춰라"를 규정한다. 강제와 절제의 균형이 설계에 박혀 있다.
입구 라우터는 매칭이 안 되면 아무것도 주입하지 않고 언제나 exit 0으로 끝난다. 즉 후크 자체가 사용자의 작업을 막거나 깨뜨리지 않는다. 통제는 "차단"이 아니라 "맥락 주입 + 출구 검문"이라는 비파괴적 방식으로 이뤄진다. 안전한 하네스 설계의 모범 사례다.
파일 하나하나가 곧 메커니즘 하나다.
주목할 구조: 실행 로직(hooks·scripts)과 지침 텍스트(skills·packs)가 분리돼 있다. 후크/엔진은 결정론적 코드, 팩/스킬은 모델에게 읽히는 절차 문서다. 같은 절차(예: 조사 프로토콜)가 router가 주입하는 한 줄 요약과 packs의 전문(全文) 두 층으로 존재해, 필요할 때만 전문을 읽게 한다(컨텍스트 절약).
| 파일 | 발동 시점 | 강제하는 것 |
|---|---|---|
| router.sh | 프롬프트 제출마다 | 작업 종류에 맞는 규율 주입 |
| finish-the-work.sh | 턴 종료마다 | 약속만 한 조기종료 차단 |
| goals.py | 다중 스토리 작업 | 증거 없는 완료 + 검증 없는 최종완료 거부 |
| packs/*.txt | 신호 매칭 시 | 조사/렌더검증의 구체 절차 |
| setup.sh | 1회 수동 | CLAUDE.md 상주(always-on) 규칙 |
이 작은 레포가 가르쳐 주는, 실무에 곧장 쓰는 것들.
fablize의 가장 큰 학습 가치는 hooks의 실전 패턴이다. 입구(UserPromptSubmit)에서 맥락을 주입하고, 출구(Stop)에서 종료를 검문하는 두 후크가 핵심이다. 후크는 stdin으로 JSON을 받아 처리하고, stdout/JSON 결정으로 응답한다. 출구 후크의 차단 패턴은 이렇게 생겼다.
# finish-the-work.sh — 약속만 한 종료를 감지해 차단(개념 발췌)
# 1) 무한루프 가드: 이미 한 번 막았으면 통과
active=$(... .get('stop_hook_active', False) ...)
if [ "$active" = "True" ]; then exit 0; fi
# 2) 마지막 단락(끝 400자)만 검사 — 보고서 전체가 아니라 "맺음말"
tail = last_text[-400:]
# 3) 미래/의도 표현만 매칭(과거형 제외). 영어+한국어
promise = re.search(r"\b(I'?ll|I will|let me)\b...\b(now|implement|run|fix)\b", tail)
asks_user = re.search(r"(\?|shall i|would you like|let me know)", tail)
# 4) 약속 O + 질문 X → 턴을 막고 "지금 실제로 하라" 재주입
if promise and not asks_user:
print('{"decision":"block","reason":"...do that work now with tool calls..."}')
배울 핵심: (1) 무한루프 가드(stop_hook_active), (2) 범위 한정 검사(끝 400자만 — 본문 전체를 정규식 돌리면 오탐), (3) 의도 vs 완료 구분(과거형 제외), (4) 예외 통로(질문으로 끝나면 정당한 종료로 통과). "규율을 결정론으로 강제하되 정당한 경우는 빠져나가게 한다"는 균형 감각이 그대로 들어 있다.
# router.sh — 작업 신호별 정밀 주입(개념 발췌)
case "$low" in
*debug*|*bug*|*error*|*crash*|*"not working"*)
add "[fablize:investigation] ... investigation-protocol.txt 따르라 ..." ;;
esac
case "$low" in
*html*|*svg*|*game*|*canvas*|*chart*|*render*)
add "[fablize:grounding] ... verification-grounding-pack.txt 그라운딩 루프 ..." ;;
esac
[ -n "$emit" ] && printf '%s\n' "$emit"
exit 0 # 매칭 없으면 조용히 통과 — 작업 절대 안 막음
여기서 배울 것: "모든 규칙을 항상 주입"하면 컨텍스트가 오염되고 모델이 산만해진다. 그래서 작업 신호별로 필요한 한 줄만 주입하고, 전문은 팩 파일로 미뤄둔다. 프롬프트 엔지니어링의 "관련성 있는 컨텍스트만"을 후크 레벨에서 구현한 사례.
goals.py는 작은 상태머신이다. 스토리는 pending → in_progress → complete/failed/blocked로만 전이하고, 활성화(next)되지 않은 스토리는 체크포인트 불가, 완료엔 증거 필수, 마지막 스토리엔 검증 명령+결과 필수라는 불변식(invariant)을 코드로 박았다. 상태는 JSON 파일로 영속화돼 세션을 넘어 재개된다. "에이전트에게 책무성(accountability)을 부여하는 최소 설계"의 좋은 예다.
전체 플러그인을 깔기 전에, finish-the-work.sh 하나만 Stop 후크로 등록해 보라. "다 했다"고 거짓 종료하던 패턴이 얼마나 잡히는지 직접 체감하는 게 가장 빠른 학습이다. 그다음 라우터, 그다음 골 엔진 순으로 확장하면 된다.
"무엇이 깔려 있어야 도는가" — 매우 가볍다.
| 항목 | 요구 | 비고 |
|---|---|---|
| 호스트 | Claude Code (플러그인·hooks·skills 지원 버전) | 플러그인 메커니즘이 전제 |
| 셸 | Bash | router.sh / finish-the-work.sh / setup.sh |
| Python | Python 3 (표준 라이브러리만) | goals.py · 후크 내 JSON 파싱. pip 설치 불필요 |
| gh (선택) | GitHub CLI | star.sh 자동 별표용. 없으면 조용히 건너뜀 |
| OS | macOS / Linux (Bash 환경) | Windows는 WSL/Git-Bash 권장 |
| 외부 의존성 | 없음 (0개) | 핵심 설계 철학 |
① 오탐: "원하시면 보고서를 작성하겠습니다" 같은 선언형 제안은 약속 패턴에 걸려 잘못 차단될 수 있다 — 제안은 질문형("작성할까요?")으로 쓰면 빠져나간다. ② 중복: finish-the-work 후크가 이미 전역에 등록돼 있는 경우가 많아, setup이 이를 자동 등록하지 않는다. 중복 방지를 위해 없을 때만 수동 등록하라고 안내한다.
"플러그인을 쓰기"에서 "메커니즘을 이해하기"로.
/plugin marketplace add fivetaku/fablize → /plugin install fablize 후, "이 버그 고쳐줘"와 "이 HTML 만들어줘"를 각각 던져 주입되는 [fablize:investigation] / [fablize:grounding] 메시지가 달라지는지 관찰한다. 라우터의 동작을 눈으로 확인하는 단계.
레포 루트에서 python3 scripts/goals.py create --brief "테스트" --goal "구현::기능 추가" --goal "검증::테스트 통과"를 만든 뒤, 마지막 스토리를 --verify-cmd 없이 complete 시도해 본다. 엔진이 "검증 게이트" 때문에 거부하는 것을 직접 확인하라.
가짜 transcript JSONL을 만들어 마지막 assistant 메시지를 "I'll implement this now."로 두고 finish-the-work.sh에 stdin으로 먹여, decision:block이 나오는지 확인한다. 그다음 끝을 "Shall I implement this?"로 바꿔 질문형은 통과함을 비교한다.
router.sh에 case 절을 하나 추가해(예: *sql*|*migration* → "마이그레이션 전 백업 확인" 규율 주입), 새 작업 신호에 맞는 팩 텍스트를 packs/에 추가한다. 라우터+팩의 2층 구조를 직접 확장해 본다.
HTML/SVG 산출물 작업에서 헤드리스 브라우저(Playwright / Chrome --headless --screenshot)로 렌더 → 스크린샷을 직접 읽고 → 콘솔 에러 확인 → 수정 → 재렌더 루프를 끝까지 돌린다. "정적 파싱은 통과했지만 렌더하면 깨지는" 결함을 한 번 잡아보면 이 팩의 가치를 체득한다.
fablize를 발판으로 "에이전트 하네스 설계"까지 가는 4주 코스.
| 주차 | 주제 | 도달 목표 |
|---|---|---|
| 1주차 | Claude Code 플러그인 구조 | plugin.json·SKILL.md·hooks.json 읽고 직접 빈 플러그인 빌드 |
| 2주차 | Hooks 심화 | UserPromptSubmit / Stop / PreToolUse 등 이벤트별 입출력 규약 숙지, 후크 1개 작성 |
| 3주차 | 검증 게이트 + 상태 영속화 | goals.py식 상태머신을 직접 구현(증거 강제·파일 영속) |
| 4주차 | 평가(eval)와 A/B 측정 | fablize처럼 "효과가 측정된 규칙만 채택"하는 실험 설계 감각 익히기 |
| 이후 | 에이전트 신뢰성 일반론 | verification-grounding·investigation 같은 절차를 자기 워크플로에 일반화 |
fablize의 진짜 교훈은 코드가 아니라 방법론이다 — "좋아 보이는 규칙"이 아니라 "측정해서 효과가 확인된 규칙"만 채택하고, 능력과 절차를 구분하며, 능력의 천장에선 정직하게 에스컬레이션한다. 이 사고방식은 어떤 에이전트 시스템을 만들든 그대로 통한다. 코드는 200줄 남짓이니 통째로 읽어볼 가치가 충분하다.
fablize를 읽다 막히는 용어 빠른 참조.
| Harness | 모델을 감싸 행동을 통제하는 운영 껍데기. 모델 능력은 못 올리고, 자기 천장까지 닿게 만든다. |
| Transfer(전이) | 한 모델의 강점을 지시/하네스로 다른 모델에 옮길 수 있는가. fablize는 "절차는 전이되고 능력은 안 된다"가 핵심 발견. |
| Hook(후크) | Claude Code의 이벤트 시점 확장 지점. UserPromptSubmit(프롬프트 제출), Stop(턴 종료) 등. |
| UserPromptSubmit | 사용자 프롬프트가 제출되는 시점의 후크. fablize는 여기서 라우팅으로 규율을 주입. |
| Stop hook | 모델이 턴을 끝내려는 시점의 후크. block 결정으로 종료를 되돌릴 수 있다. |
| Early-stop(조기종료) | 실제 작업 없이 "하겠다"는 약속만 하고 끝내는 패턴. fablize가 차단하는 핵심 대상. |
| Verification grounding | 산출물을 실제 렌더러로 실행하고 출력을 눈으로 관찰해 검증하는 방식. 정적 파싱과 구별. |
| Well-formed ≠ correct | "형식이 옳다"와 "동작/표시가 옳다"는 다른 주장. 검증 그라운딩의 슬로건. |
| Investigation protocol | 디버깅 규율: 재현 먼저 → 경쟁 가설 3+ → 가설별 증거 → 전체 인과사슬 → 전/후 검증 → 기각 가설 보고. |
| Multi-story gate | 작업을 스토리로 분해하고, 증거 없는 완료와 검증 없는 최종완료를 거부하는 게이트(goals.py). |
| Skill | SKILL.md로 정의되는 "필요할 때 불려오는 지침서". frontmatter의 description이 트리거. |
| idempotent | 여러 번 실행해도 결과가 같음. setup.sh가 CLAUDE.md 블록을 중복 없이 주입하는 성질. |
| Escalate(에스컬레이션) | 능력의 천장에 막혔을 때 /effort xhigh 권유 → 더 강한 모델 → 사람 순으로 넘기는 것. |
| Always-on | setup.sh가 CLAUDE.md에 운영 블록을 상주시켜, 트리거 없이 항상 규율이 적용되는 모드. |
/plugin marketplace add fivetaku/fablize → /plugin install fablizebash ${CLAUDE_PLUGIN_ROOT}/setup/setup.sh (local 권장)