
하네스를 다른 리포지토리에 이식했더니 전부 망가졌다 ── 암묵적으로 의존하고 있던 5가지 환경 전제
요약
CLAUDE.md와 같은 AI 가이드라인을 다른 리포지토리로 이식할 때 발생하는 문제점과 해결책을 다룹니다. 단순 복사가 아닌 디렉토리 구조, 명명 규칙 등 환경적 전제를 고려한 재구축의 중요성을 강조합니다.
핵심 포인트
- CLAUDE.md는 코드뿐만 아니라 리포지토리의 문맥(Context)에 의존함
- 단순 복사 시 디렉토리 구조, 명명 규칙, CI 설정 등의 불일치로 오류 발생
- 하네스 이식 시 '복사 붙여넣기'가 아닌 환경에 맞춘 '재구축'이 필요함
- 암묵적 환경 전제를 명시화하여 AI의 성능 저하를 방지해야 함
자랑하던 CLAUDE.md를 새 리포지토리에 복사했더니, AI가 무엇 하나 제대로 작동하지 않았다
반년 동안 공들여 키운 CLAUDE.md가 있다. 규칙도 스킬도 설정도, 나의 메인 리포지토리(Main Repository)에서는 거의 완벽하게 돌아가고 있었다. Claude Code는 나의 의도를 미리 읽고, 테스트를 작성하며, 명명 규칙(Naming Convention)도 맞춰주었다.
새로운 리포지토리를 시작했을 때, 나는 망설임 없이 그것을 통째로 복사했다. .claude/ 디렉토리째로, CLAUDE.md도 통째로. "이것만 있으면 첫날부터 최고의 AI를 얻을 수 있다"라고 생각했다.
결과는 처참했다.
AI는 있지도 않은 디렉토리에 파일을 두려고 했다. 이전 프로젝트의 명명 규칙으로 함수를 만들었다. 존재하지 않는 테스트 명령어를 실행하다가 멈춰버렸다. 같은 하네스(Harness), 같은 모델, 같은 나. 그런데도 출력의 질이 완전히 달랐다.
처음에는 CLAUDE.md의 작성 방식이 잘못된 것인지 의심했다. 문구를 다시 읽어봐도 이상한 지시는 보이지 않았다. 이전 리포지토리에서는 실제로 잘 작동하던 문장들이었다. 문제는 문장 속에 있지 않았다.
이때 깨달았다. 하네스는 코드가 아니라 문맥(Context)이다. 이식할 수 있다고 생각하는 순간 망가진다. 문장을 복사해도, 그 문장이 의지하고 있던 환경은 복사되지 않는다.
먼저 결론부터: 망가진 것은 5가지 환경 전제
오랫동안 운용한 하네스일수록, 적혀 있지 않은 전제를 흡수하고 있다. 그것은 문장 속을 아무리 뒤져봐도 나오지 않는다. 리포지토리라는 환경 측에 잠재되어 있기 때문이다. 내가 이식하다가 망가뜨린 전제는 다음 5가지였다.
- 디렉토리 구조
- 명명 규칙 (Naming Convention)
- 테스트의 존재
- CI 설정
- 팀의 암묵지
대응 관계를 한 장으로 정리한 것이 이 표다. 전제별로 이식 대상에서 망가지는 정도와 명시화하는 방법을 나열해 두었다.

이 기사는 이 표를 풀어내는 것이다. 마지막에는 이식을 '복사 붙여넣기'에서 '재구축'으로 바꾸는 도입 단계를 제시한다.
먼저 세 가지를 정리하자: 암묵·동적·공간의 3가지 망가지는 방식
하네스가 망가지는 방식을 다루는 글을 나는 지금까지 두 편 써왔다. 혼동을 피하기 위해, 이번 이야기가 어디에 위치하는지 먼저 짚어두겠다.
- 암묵적 하네스 (Implicit Harness): 코드베이스 안에 처음부터 매몰되어 있는 제약. Martin Fowler가 말하는, 적혀 있지 않은데도 효력을 발휘하는 전제 (Fowler의 암묵적 하네스에서 다룸).
- 동적 붕괴 (Dynamic Collapse): 시간이 흐름에 따라 하네스가 현실과 어긋나며 발생하는 열화. 이것은 시간축의 문제였다 (3개월 운용하여 동적으로 망가진 5가지 순간에서 다룸).
- 본 기사의 이식 문제: 하네스를 다른 환경으로 옮겼을 때 망가지는 공간축의 의존. 같은 시각, 같은 완성도라도 놓는 장소가 바뀌면 효력을 잃는다.
시간에 의해 망가지는 것이 동적 붕괴라면, 장소에 의해 망가지는 것이 이식이다. 이번에는 후자를 5가지로 나누어 살펴보겠다.
(1) 디렉토리 구조: 경로를 쓰는 순간 이식성을 잃는다
가장 먼저 망가진 것이 이것이었다. 나의 CLAUDE.md에는 "설정은 src/config/에 둔다", "유틸리티는 src/lib/utils/에 모은다"와 같은 기술이 산재해 있었다.
새 리포지토리에는 그 구조가 없었다. src/조차 없었다. AI는 순순히 CLAUDE.md를 믿고 존재하지 않는 경로에 파일을 만들었고, import가 전부 실패했다.
여기서 유효한 것이 AI 코딩의 context 문서 가이드가 말하는 원칙이다. context에는 두 종류가 있다. 의도적으로 전달하는 Explicit Context와, 프로젝트 구조나 기존 코드 패턴으로부터 읽어낼 수 있는 Implicit Context이다 (AGENTS.md 가이드).
디렉토리 구조는 본래 Implicit Context에 속한다. AI는 리포지토리를 보면 구조를 파악할 수 있다. 그런데 나는 그것을 Explicit하게 고정 경로로 써넣고 있었다. 그래서 이식 대상의 현실과 충돌했다.
실제로 AGENTS.md의 설계 가이드는 "file system structure는 변화가 너무 빨라 정확성을 유지할 수 없으므로 넣지 마라"라고까지 말하고 있다 (AGENTS.md 가이드). 고정 경로는 이식 이전에, 동일 리포지토리 내에서도 부패하기 쉽다.
명시화하는 방법은 역설적이다. 경로를 쓰지 않는 것이 이식성으로 이어진다. '설정 파일의 위치'를 역할로 기술하고, 구체적인 경로는 AI가 찾게 한다. src/config/라고 고정해서 쓰지 않고, "설정은 프로젝트의 config용 디렉토리에 모은다"라고 역할로 기술한다.
(2) 명명 규칙: 실례 없는 규칙은 이전 리포의 습관을 운반한다
다음에 깨달은 것은 명명의 어긋남이었다. 이전 리포는 함수를 camelCase, 파일을 kebab-case...
로 통일되어 있었다. CLAUDE.md에는 그 규약을 명문화해 두지 않았다.
적어두지 않았음에도 규약이 일치했던 이유는, AI가 기존 코드를 읽고 모방했기 때문이다. 이것 또한 Implicit Context (암묵적 문맥)가 작용했던 사례다.
새로운 리포지토리는 기존 코드가 거의 없었다. 모방할 대상이 없다. AI에게 이전 리포지토리에 대한 기억은 없다. 그래서 일반적인 기본값, 즉 모델의 학습 편향 (Learning Bias)에 따라 명명하기 시작했다. 결과적으로 파일마다 명명 방식이 제각각이 되었다.
여기서의 걸림돌은 두 가지 층위로 나뉜다. 이전 리포지토리에서는 "쓰지 않아도 작동했지만", 새로운 리포지토리에서는 "쓰지 않았기 때문에 작동하지 않는다". 동일한 공백이 환경에 따라 정반대의 결과를 낳는다.
AGENTS.md 가이드는 포함해야 할 항목으로 "linter가 자동으로 강제하지 않는 비자명한 코드 스타일 규약"을 꼽고 있다 (AGENTS.md 가이드). 명명 규약은 바로 이것이다.
나의 수정은 단순했다. 규약을 글로 쓰고, 실제 사례를 두 개 나열했다. getUserProfile()과 user-profile.ts처럼 함수와 파일의 쌍을 보여주는 것이다. AI는 규칙 문장보다 실제 사례로부터 규약을 파악한다. 하나만 있으면 우연과 구별할 수 없으므로, 최소 두 개를 나열하는 것이 요령이었다.
부작용도 배웠다. 실제 사례를 너무 많이 쓰면, 이번에는 그것이 이전 리포지토리와 동일한 고정값(Fixed Value)이 되어 다시 이식성을 떨어뜨린다. 실제 사례는 규약을 전달하기 위한 최소한으로 제한하고, 모두 포괄하려고 하지 마라. 두 사례로 규약이 전달된다면, 세 번째 사례는 예산 낭비였다.
(3) 테스트의 존재: 당연히 있을 것이라는 전제가 되어 있었다
이전 리포지토리에는 테스트가 있었다. 그래서 나의 하네스(Harness)는 "구현 후에 테스트를 작성한다", npm test로 확인한다"라고 당연하다는 듯이 지시하고 있었다.
새로운 리포지토리에는 테스트 프레임워크조차 들어있지 않았다. AI는 npm test를 실행하려다 Missing script: test 메시지와 함께 멈춰 섰다. 거기서 더 나아가지 못하고, 멋대로 테스트 기반을 도입하려다 스코프(Scope)가 팽창했다.
테스트의 존재는 하네스가 암묵적으로 쥐고 있던 가장 큰 전제 중 하나였다. "테스트를 작성하라"는 지시는 테스트가 있는 환경에서만 의미를 갖는다.
공식 베스트 프랙티스(Best Practices) 또한 context 파일에는 빌드(Build), 테스트(Test), 린트(Lint), 개발(Dev)의 정확한 명령어를 적도록 권장하고 있다 (Claude Code Docs: Best practices). 역설적으로 말하면, 그 정확한 명령어야말로 환경마다 다른 것이다. 복사해서 붙여넣기(Copy-paste)로 옮길 수 없는 것의 대표 사례다.
파괴 정도는 중간 단계다. 치명적이지는 않지만, AI가 멈출지 혹은 폭주할지를 결정하는 분기점이 된다.
명시화하는 방법은 테스트 실행 명령어를 정확히 적는 것에 더해, "테스트가 없을 경우의 대체 검증 방법"을 병기하는 것이었다. 테스트가 없다면 타입 체크(Type Check)를 통과시킨다거나, 로컬에서 실행하여 동작을 확인한다는 식의 대체 수단을 한 줄 덧붙인다. 이렇게 하면 AI는 기반 시스템을 직접 만들려고 달려들지 않게 된다.
여기서 나의 방심도 깨달았다. 이전 리포지토리에서는 테스트가 당연히 있었기에, 하네스에 검증 수단을 하나만 적어두었다. 전제가 하나뿐인 지시는 그 전제가 무너지는 순간 갈 곳을 잃는다. 대체 수단을 하나만 덧붙여도 AI는 환경의 차이를 스스로 흡수할 수 있게 되었다.
(4) CI 설정: lint와 format의 방식은 리포지토리 외부에 있다
CI 주변도 조용히 망가져 있었다. 이전 리포지토리는 GitHub Actions를 통해 lint와 format을 돌리고, 커밋(Commit) 전에 정렬이 수행되도록 되어 있었다. 나의 하네스는 "정렬 후 커밋한다"라고만 적혀 있었다.
새로운 리포지토리에는 CI가 없었고, prettier도 eslint도 들어있지 않았다. AI는 정렬 명령어를 찾았지만 찾지 못했고, 자기 방식대로 코드를 작성했다. 이전 리포지토리의 CI가 배후에서 흡수해주던 불일치가 그대로 표면으로 드러난 것이다.
여기서 효과를 본 것은 AGENTS.md의 권장 사항인 "정확한 플래그가 포함된 빌드 명령어", "테스트 절차", "기본값과 다른 코드 스타일 규약"이다 (Claude Code 설정 파일 가이드). CI가 암묵적으로 수행하던 정렬 작업은 하네스에 직접 적어야만 이식 대상에서도 작동한다.
나는 lint와 format의 정확한 명령어를 하네스에 직접 적었다. npm run lint -- --fix와 같이 플래그까지 포함해서 적는다. CI가 대신해주던 방식을 AI가 스스로 실행할 수 있는 형태로 떨어뜨린다. 이로써 이식 대상에서도 정렬 방식이 일치하게 되었다.
이러한 전제가 눈에 잘 띄지 않는 이유는 CI가 성공 또는 실패 여부만 반환하기 때문이다. 정렬의 세부 사항은 CI 로그 속에 가라앉고, 하네스(Harness)에도 개발자의 의식 속으로 떠오르지 않는다. 이전 리포지토리에서 편했던 것은 AI가 대충 작성해도 CI가 나중에 고쳐주었기 덕분이었다. 그 안전망 전체를 잃어버린 것이 이식 대상이었다. 안전망이 암묵적인 전제였음을, 제거해보고 나서야 깨달았다.
(5) 팀의 암묵지: 왜 그렇게 하는지가 빠진다
마지막은 가장 눈에 잘 띄지 않는 전제였다. 이전 리포에는 '이 레이어에서는 예외를 무시하지 않는다', '외부 API 호출은 반드시 재시도(retry)를 거친다'와 같이, 팀에서 공유하던 관례가 있었다.
그중 상당수는 CLAUDE.md에 '이렇게 하라'라고만 적혀 있었다. 이유는 쓰여 있지 않았다. 이유는 팀의 머릿속, 즉 리포지토리 밖에 존재했다.
새로운 리포는 다른 팀, 다른 도메인이었다. '이렇게 하라'는 규칙만 남아 있고, 왜 그렇게 해야 하는지는 알 수 없다. AI는 규칙을 기계적으로 적용하여 불필요한 상황에서도 재시도를 거치고, 오히려 동작을 읽기 어렵게 만들었다.
공식 문서에서는 아키텍처상의 판단은 이유(rationale)와 함께 작성할 것을 권장하고 있다 (Claude Code Docs: Best practices). 이유가 없는 규칙은 적용 범위를 알 수 없어 과도하게 혹은 부족하게 작용한다.
이 전제가 무너지는 정도는 서서히 오는 유형이다. 당장 붕괴하지 않는다. 하지만 이유 없는 규칙들이 쌓이면, AI의 판단이 조금씩 도메인에서 벗어나게 된다. 동적 붕괴에 가까운 조용한 열화다.
명시화하는 방법은 규칙에 '왜'를 한 줄 추가하는 것이다. '외부 API는 재시도한다 (이유: 해당 API는 일시 오류가 많기 때문에)'라고 적으면, AI는 이유가 적용되지 않는 상황에서는 규칙을 적용하지 않을 수 있다.
왜 범용 템플릿으로는 채워지지 않는가
여기까지 읽고 '그럼 범용 템플릿을 사용하면 되지 않나'라고 생각할 수도 있다. 나도 그렇게 생각해서 배포된 CLAUDE.md 템플릿을 시도해봤다. 하지만 이것 역시 효과가 없었다.
이유는 명확하다. 범용 템플릿이 다룰 수 있는 것은 어떤 프로젝트에도 공통적인 부분뿐이다. 이번에 망가진 다섯 가지 전제는 모두 프로젝트 고유의 Implicit Context에 뿌리를 두고 있다. 템플릿은 구조의 틀은 제공하지만, 그 틀을 채울 고유한 사실은 제공할 수 없다.
context 설계 지식에서는 최전선의 LLM이 확실히 따를 수 있는 지시는 약 150~200개 정도이며, 한 줄 한 줄이 매 세션마다 이 예산을 소모한다고 한다 (AGENTS.md 가이드). 범용 템플릿을 통째로 붙이면, 효과가 없는 일반론이 예산을 차지하고 고유한 사실이 희미해진다.
실제로 여러 실무 가이드에서는 CLAUDE.md를 150줄 미만으로 유지할 것을 권장한다 (CLAUDE.md Best Practices 2026). 짧고, 정확하며, 명확하게 도움이 되는 것만 남기는 것이다. 범용 템플릿의 반대 방향으로 운영하는 것이다.
하네스의 해석은 팀마다, 회사마다 다르다. 이는 이전에 하네스 엔지니어링 5사의 해석에서도 작성한 바와 같다. 해석이 다르면, 범용화할 수 있는 부분은 더욱 작아진다.
이식을 '복사-붙여넣기'에서 '재구축'으로 바꾸는 도입 단계
마지막으로, 내가 다음 이식부터 실제로 밟고 있는 절차를 공유하겠다. 복사-붙여넣기를 금지하고, 다섯 가지 전제를 하나씩 환경에 맞게 재정비하는 흐름이다.
- 구 하네스에서 고정 경로(fixed path)를 모두 제거한다.
src/config/와 같은 경로 기술을 찾아 역할 설명으로 바꾼다. 이로써 전제(1)가 사라진다. - - 명명 규칙을 실례와 함께 다시 쓴다. 새 리포의 언어/프레임워크에 맞춰 함수와 파일 쌍을 2가지씩 나열한다. 전제(2)를 환경에 접지시킨다. -
- 테스트와 검증의 현실을 확인한다. 테스트 프레임워크 유무를 보고, 실행 명령어 또는 대체 검증 중 반드시 하나를 작성한다. 전제(3)을 채운다. -
- lint 및 format 명령어를 직접 기재한다. CI에 맡겨진 관례를 AI가 스스로 실행할 수 있는 형태로 떨어뜨린다. 전제(4)를 이식한다. -
- 남은 규칙에 이유를 한 줄씩 추가한다. 이유를 쓸 수 없는 규칙은 이전 리포 고유의 암묵지이므로 일단 삭감한다. 전제(5)를 압축한다.
이 다섯 단계를 거치면, 하네스는 이전 리포의 복제품을 벗어나 그 환경을 위해 재조립된 것이 된다. 반년 동안 키운 하네스를 그대로 옮길 수 없는 것은 아쉽다. 하지만 옮길 수 없다고 해서 전부 망가뜨리려 하기보다는, 재구축하는 전제에서 임하는 편이 훨씬 빨랐다.
하네스는 코드가 아니라 문맥이다. 문맥은 환경에 달라붙어 있다. 그래서 이식할 수 있다고 생각한 순간 무너진다. 옮기려고 하지 말고, 환경마다 키워내는 것. 그것이 나의 결론이다.
Discussion
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기