만들고 있는 Obsidian 플러그인은 원문 자료를 LLM으로 읽어 개념별 노트로 정리하고, 같은 대상을 다룬 노트가 둘 생기면 하나로 합친다. 7편에서는 이 병합 판단의 기준을 “같은 대상을 같은 범위로 다루면, 출처가 달라도 합친다”로 바꿨다. 그 덕에 영어 노트 “Claude Code”와 한국어 노트 “클로드 코드”가 처음으로 합쳐졌다. 이번 글은 그 뒤에 남은 자잘한 문제 두 개를 고치고 확인하는 과정에서 생긴 일이다. 결론부터 말하면, 고친 뒤 돌린 실제 테스트는 깨끗하게 통과했지만 고친 코드는 그 테스트에서 한 번도 실행되지 않았다.
합친 노트의 이름이 파일명과 달랐다 (frontmatter title)
한국어 노트와 영어 노트를 합칠 때는 결과를 한국어로 쓰게 해 두었다. 그런데 합쳐진 노트를 열어
보니 파일 이름은 Claude Code.md인데 노트 안의 제목(frontmatter의 title)은 “클로드 코드”였다.
심리적 안전감 노트 셋을 합친 결과도 파일 이름은 심리적 안전감.md인데 제목은 LLM이 새로 지은
“심리적 안전감의 핵심 원칙과 리더십 전략”이었다.
보기만 어색한 게 아니었다. 이 플러그인은 제목이 같은 노트끼리 묶어서 중복을 찾는데, 그때 파일 이름보다 노트 안의 제목을 먼저 본다. 제목이 파일명과 다르면 다음 스캔에서 같은 이름의 노트와 묶이지 못한다. 병합 결과의 제목은 플러그인이 저장 직전에 파일명으로 맞추게 했다. 파일명은 플러그인이 위키 전체에서 겹치지 않게 관리하니, 제목도 그만큼 겹치지 않는다.
이름을 맞추고 나니 하나가 더 걸렸다. 본문은 한국어인데 파일 이름은 영어인 셈이다. 그래서 한국어
이름과 영어 이름의 노트를 합칠 때는 한국어 이름 쪽을 합칠 대상으로 고르게 했다. 이제 두 노트는
클로드 코드.md로 합쳐진다.
대상을 바꿨더니 영어 노트에 “클로드 코드”가 보였다 (위키 링크 별칭)
이 수정을 실제 노트로 시험해 보니 의도대로 클로드 코드.md로 합쳐졌다. 그런데 이번엔 다른 노트들이
이상해졌다. 영어로 쓰인 노트 넷이 원래 Claude Code를 가리키던 링크를 갖고 있었는데, 병합 뒤
그 링크가 문장 속에서 “클로드 코드”로 보였다. 영어 문장 한가운데 한국어가 끼어든 것이다.
원인은 병합 뒤 링크를 새 노트로 옮기는 코드였다. 이 코드는 링크 별칭(보이는 글자)이 옛 노트 이름과 같으면 새 노트 이름으로 바꾼다. 일부러 넣은 동작이다. 노트를 나누거나 합쳐 대상이 바뀌면 옛 이름이 새 대상을 제대로 설명하지 못하기 때문이다. 하지만 “Claude Code”와 “클로드 코드”는 같은 대상의 다른 언어 이름이라, 옛 이름도 맞는 이름이었다. 별칭 없이 경로만 있던 링크도 새 파일 이름을 그대로 보여 주니 마찬가지였다.
그래서 옛 이름과 새 이름 중 한쪽만 한글일 때, 즉 언어만 바뀌는 경우에는 보이던 글자를 그대로 두게
했다. 별칭이 옛 이름이면 그대로 두고, 별칭이 없던 링크에는 옛 이름을 별칭으로 붙인다. 링크는
[[…/클로드 코드|Claude Code]]가 되고, 영어 노트에서는 계속 “Claude Code”로 보인다. 같은 언어끼리는
원래 동작 그대로다. 단위 테스트를 더하고 새 버전을 냈다.
실제 노트로 다시 돌렸더니, 깨끗했다 (LLM 비결정성)
새 버전을 테스트용 보관소에 설치하고 늘 하던 대로 돌렸다. 원문 여섯 개를 처음부터 넣어 노트를 만들고, 병합·분할 제안 스캔을 제안이 두 번 연속 하나도 안 나올 때까지 반복한다. 스캔 두 번 만에 멈췄다. 깨진 링크는 없었고, 모든 노트의 제목이 파일명과 같았으며, 영어 노트에 “클로드 코드”가 보이는 곳도 없었다.
통과라고 적기 전에 병합 기록을 열어 봤다. 이번엔 한국어 원문도 “클로드 코드”가 아니라 영어 이름 “Claude Code”로 노트를 만들었다. 같은 이름이 둘 생겼으니 둘은 그대로 합쳐졌고, 이름이 바뀌지 않았으니 링크도 옛 이름 그대로였다. 한국어 이름과 영어 이름의 노트가 만나는 장면 자체가 생기지 않은 것이다. 고친 두 경로, 한국어 이름을 대상으로 고르는 코드와 링크 글자를 지키는 코드는 이 테스트에서 한 번도 실행되지 않았다. “영어 노트에 클로드 코드가 보이는 곳 0개”는 고쳐서가 아니라 그럴 일이 없어서 나온 숫자였다.
LLM이 노트를 만드는 한, 같은 원문을 넣어도 매번 같은 장면이 나오지 않는다. 지난번엔 “클로드 코드”였던 노트 이름이 이번엔 “Claude Code”였다. 다시 돌리면 또 어떻게 나올지 모른다.
병합 직전 장면을 그대로 되살렸다 (git 커밋으로 테스트 상태 복원)
그래서 운에 맡기지 않고 그 장면을 직접 만들었다. 이 테스트는 라운드마다 보관소 상태를 git 저장소에
기록해 두는데, 지난번 테스트에 영어 Claude Code.md와 한국어 클로드 코드.md가 나란히 있고 영어
노트 넷이 Claude Code를 가리키던, 병합 직전의 커밋이 남아 있었다. 그 시점의 노트 41개와 원문 6개,
작업 기록, 그리고 중복 후보를 찾는 데 쓰는 캐시까지 50개 파일을 그대로 되살렸다. 캐시까지 되살린 건 그때와 같은
후보가 잡히고, 그때 내린 “같은 대상” 판정이 그대로 쓰이게 하기 위해서다.
그 상태에서 스캔을 한 번 돌리자 Claude Code를 클로드 코드로 합치자는 제안이 나왔다. 실행 뒤 영어
노트의 링크 여섯 개를 병합 전과 하나씩 비교했다.
| 병합 전 | 병합 후 |
|---|---|
[[…/Claude Code]] (4개) | [[…/클로드 코드|Claude Code]] |
[[…/Claude Code|Claude Code]] (2개) | [[…/클로드 코드|Claude Code]] |
여섯 개 모두 새 노트를 가리키면서 “Claude Code”로 보였다. 합쳐진 클로드 코드.md는 제목이 파일명과
같았고, 본문은 한국어였으며, 두 노트의 사실이 모두 남아 있었다. 이번에야 고친 코드가 실제로 돌았다는
걸 확인했다.
덤: “진행 중”이라는 보고를 믿을 뻔했다 (AI 에이전트 작업 보고)
이 테스트는 원래 다른 AI 에이전트에게 맡겼다. 첫 시도에서 에이전트는 “테스트는 별도 작업으로 진행 중”이라고 답하고 끝났다. 테스트 결과가 기록되는 저장소를 열어 보니 새 기록은 하나도 없었고, 보관소에는 옛 버전의 플러그인이 그대로 깔려 있었다. 실제로는 시작조차 안 한 것이다. 다시 시키자 이번엔 중간에 멈췄고, 역시 아무것도 바뀌지 않았다. 대화 기록을 통째로 물려받지 않는 새 에이전트에게 처음부터 다시 맡기고 나서야 테스트가 끝까지 돌았다. 테스트 결과를 판단할 때와 마찬가지로, 진행 상황도 보고가 아니라 남은 기록으로 확인해야 했다.
남는 생각
테스트가 통과했다는 것과 고친 코드가 실행됐다는 것은 다른 말이다. 평소의 코드라면 같은 입력에 같은 경로를 지나가니 둘이 거의 같은 뜻이지만, LLM이 중간에 끼면 같은 입력으로도 다른 장면이 나온다. 이번 테스트는 결과만 보면 흠잡을 데가 없었는데, 정작 확인하려던 장면은 그 안에 없었다.
통과를 확인할 때는 결과 숫자보다 먼저 “고친 경로를 실제로 지나갔는가”를 봐야 했다. 지나가지 않았다면, 그 장면이 있던 시점의 상태를 되살려 다시 돌리면 된다. 테스트마다 상태를 기록해 둔 게 이번에 그 시점을 그대로 꺼내 쓸 수 있게 해 줬다.