규칙도 썩는다
앞의 두 글에서 규칙을 어떻게 나누고 어떻게 쌓았는지 썼다. 이번엔 반대다. 쌓아둔 규칙을
다시 읽어보니 틀린 것들이 있었다.
틀린 방식이 세 가지였다. 동작은 맞는데 이유가 틀린 것, 가리키던 대상이 사라진 것, 그리고
숫자를 재본 적이 없는 것.
셋 다 에러가 나지 않는다. 그래서 읽어보기 전에는 몰랐다.
하나 — 동작은 맞았고 이유가 틀렸다
작업이 클 때 어느 실행 모드를 쓸지 정하는 규칙을 이렇게 써놨었다.
모드 선택: 독립 작업 다수 → ultrawork, 끝까지 가야 하는 단일 목표 → ralph
두 모드를 병렬 선택지로 기술했다. 작업이 여러 갈래면 앞의 것, 하나로 이어지면 뒤의 것.
말이 되는 것 같았다.
플러그인 소스를 열어보니 아니었다.1
| 근거 | 내용 |
|---|---|
keyword-detector.mjs:1823-1827 |
hasRalph && !hasUltrawork 이면 ultrawork 를 강제 활성화한다 |
README.md:410 |
ralph 는 “automatically includes ultrawork’s parallel execution” |
ultrawork/SKILL.md:9 |
ultrawork 는 “a component, not a standalone persistence mode” |
ralph/SKILL.md:13 |
ralph 가 ultrawork 를 “wraps” 한다 |
ralph 가 ultrawork 를 포함하고 있었다. 코드 레벨로 강제된다. 그러니 “병렬 작업이 여럿
이냐”는 애초에 선택 기준이 될 수 없었다. 둘 다 병렬로 돈다.
실제 갈림길은 다른 데 있었다. ralph/SKILL.md:29 에 이렇게 적혀 있다 — “User wants manual
control over completion → use ultrawork directly”. 완료 시점을 누가 판단하느냐가 기준
이었다. 그렇게 고쳤다.
- 독립 작업 다수→ultrawork, 끝까지 가야 하는 단일 목표→ralph
+ 완료 시점을 직접 판단하면→ultrawork, 완주·검증까지 맡기면→ralph(= ultrawork 포함)
여기서 중요한 건 커밋에 적어둔 마지막 줄이다.
동작 변화는 없다 — 기존 규칙도 결과적으로 맞는 모드를 골랐고, 틀린 것은 이유 설명이었다.
결과가 맞았으므로 틀린 걸 알아챌 방법이 없었다. 잘 돌아가고 있었다. 소스를 읽어보기
전까지는.
이런 규칙이 위험한 이유는 경계 상황에서 무너지기 때문이다. 평소엔 우연히 맞는 답이
나오지만, 애매한 경우가 오면 틀린 기준으로 판단한다. 그리고 그때도 에러는 안 난다.
둘 — 가리키던 것이 사라졌다
같은 줄을 17일 뒤에 또 고쳤다. 이번엔 틀린 이유가 다르다.
플러그인이 5.0.0 으로 올라가면서 스킬을 41개에서 31개로 줄였고, 그 과정에서 ultrawork
가 삭제됐다. CHANGELOG 에 “removes 17 legacy names outright” 라고 적혀 있다.
플러그인 지침 파일을 5.0.0 으로 갱신한 것이 8월 24일이고, 이 줄을 고친 것이 27일이다.
그 사이 사흘 동안 없는 모드로 라우팅하고 있었다.2
확인은 간단했다.
ls .../oh-my-claudecode/5.0.0/skills/ | grep -c ultrawork → 0
4.15.10 에는 41개, 5.0.0 에는 31개
앞의 경우와 성격이 다르다. 그때는 동작이 맞았고 이유만 틀렸다. 이번엔 동작 자체가 깨졌다.
없는 이름을 가리키고 있으니 그 규칙은 아무것도 하지 못한다.
그런데도 에러는 안 났다. 규칙 파일은 그냥 텍스트라서, 존재하지 않는 것을 가리켜도
아무도 검사하지 않는다.
이게 컨텍스트 파일의 구조적 약점이다. 코드였다면 import 가 깨져 빌드가 실패했을 것이다.
문서는 조용히 낡는다.
셋 — 재본 적 없는 숫자를 써왔다
세 번째는 이 블로그 때문에 발견했다.
규칙 팩 하나를 일부러 제외하면서 근거로 “≈40K” 라는 숫자를 적어뒀었다. 상시 로드 비용이
크다는 뜻이다. 글에 이 수치를 인용하려고 검증했더니 — 재본 값이 아니었다.3
실제로 재보니 이랬다.
| 대상 | 파일 | 줄 | 글자 |
|---|---|---|---|
제외한 common 팩 |
10 | 545 | 17,429 |
| 도입한 4개 팩 | 21 | 1,970 | 61,405 |
어느 단위로 봐도 40K 가 아니다. 어디서 온 숫자인지도 모른다.
다만 여기서 결론까지 뒤집지는 않았다. 제외 근거 자체는 유효했다. 다만 근거가 숫자가
아니라 구조였을 뿐이다 — common 10개 파일 전부 frontmatter 가 없어서, 특정 언어 파일을
다룰 때만 로드되는 스코프가 아예 없다. 그래서 모든 세션에 실린다.
grep -l 'paths:' → 0
숫자를 지우고 이 사실을 근거로 바꿨다. 그리고 경고를 하나 남겼다 — 측정한 버전이 도입
시점과 다르므로 이 값은 “도입 당시”가 아니라 “재측정 시점”의 값이다.
세 가지가 다른 종류다
정리하면 이렇다.
| 유형 | 동작 | 어떻게 발견했나 |
|---|---|---|
| 근거가 틀림 | 정상 | 소스를 읽어봄 |
| 대상이 사라짐 | 깨짐 | 버전 올린 뒤 확인 |
| 숫자가 미검증 | 정상 | 인용하려다 검증 |
첫 번째와 세 번째는 동작에 문제가 없었다. 그래서 계기가 없으면 영원히 안 고쳐진다.
내가 소스를 열어보거나, 블로그에 쓰려고 다시 재보지 않았으면 그대로 남아 있었을 것이다.
세 번째가 특히 그렇다. 규칙 파일에 숫자를 적으면 그 순간부터 그건 근거처럼 보인다.
재봤는지 아닌지는 파일에 안 남는다. 그래서 지금은 실측한 값에 날짜와 기준 버전을 같이
적는다. “2026-08-26, ECC d8409a4 기준” 처럼.
그래서 무엇을 바꿨나
이 세 건을 겪고 나서 규칙 쓰는 방식을 바꿨다.
숫자에는 출처와 날짜를 같이 적는다. 안 적힌 숫자는 다음에 인용할 때 또 검증해야 한다.
남의 도구를 가리키는 규칙에는 근거 위치를 적는다. “ralph 는 ultrawork 를 포함한다”가
아니라 “ralph/SKILL.md:13 이 wraps 라고 명시” 라고 쓴다. 그러면 다음에 확인할 때 어디를
볼지 알 수 있다.
버전이 올라가면 그 도구를 가리키는 규칙을 훑는다. 자동으로 깨지지 않으므로 사람이 봐야
한다. 지금은 이걸 자동화하지 못했다 — 스킬 목록을 대조해 없는 이름을 찾아내는 검사는 만들
수 있을 것 같은데, 아직 안 만들었다.
한계
이 글에는 전후 수치가 없다. 규칙을 고쳐서 무엇이 얼마나 좋아졌는지 재지 못했다.
첫 번째 건은 커밋 자체가 “동작 변화 없음”이라고 적고 있으니 잴 것이 없다. 두 번째 건은
깨져 있던 걸 고쳤지만, 그 규칙이 없는 이름을 가리키던 사흘 동안 무엇이 얼마나 잘못됐는지는
로그가 없어 모른다. 세 번째는 문서 정확성 문제다.
그래서 “이렇게 하니 산출물이 몇 % 좋아졌다”고는 못 쓴다. 쓸 수 있는 건 하나다 —
틀린 줄 몰랐던 규칙 세 개를 찾아서 고쳤고, 셋 다 에러 없이 조용히 틀려 있었다.