이 글은 2026-08-24 하루의 기록이다. 기능 ID 주석이 없는 기존 코드를 소급으로 잇는 “분석가” 프롬프트를 반복 실행하며 다듬었고, 그 근거를 “구현자” 프롬프트가 그대로 물려받아 화면 버그 하나를 고쳤다.
배경 — 주석 없는 코드는 영영 이어지지 않는다
이 시스템은 코드에 남긴 기능 ID 주석으로 기능과 코드를 잇는다. 문제는 이미 짜여 있던 코드, 다른 팀이 남긴 코드다. 애초에 그 주석이 붙을 기회가 없었으니, 지금 방식으로는 영영 연결되지 않는다.
그 빈 자리를 메우려고 “코드 분석가” 역할의 프롬프트를 만들었다. 기능 정의 목록과 저장소 파일 목록·발췌를 주면, 이미 어느 파일에 그 기능이 구현돼 있는지 찾아 짝짓는 역할이다. 하루 종일 이 프롬프트를 여러 세션에 걸쳐 돌리면서 규칙을 다듬었다.
문제
- 소급으로 기능과 코드를 이을 때, 무엇을 근거로 인정하고 무엇을 버릴 것인가.
- 근거가 모자라면 어떻게 하나 — 그럴듯한 이름만으로도 일단 넣어야 하나.
- 분석가가 찾은 근거를 실제 코드 수정 작업에 어떻게 넘길 것인가.
결정 1 — “찾는 것이지 만드는 것이 아니다”를 규칙으로 못박다
프롬프트 맨 앞에 이 문장을 그대로 박았다.
그 빈자리를 메우는 것이 네 일이다 — 찾는 것이지 만드는 것이 아니다.
뒤이어 지켜야 할 규칙을 명시했다.
- 파일 이름·경로·발췌에 실제로 드러난 근거로만 판단한다. 그럴듯하다는 이유로 짝지어서는 안 된다 — 근거가 약하면 아예 넣지 않는다. 빈 목록이 틀린 답보다 낫다.
- 한 기능에 파일이 여럿일 수 있고, 한 파일이 여러 기능을 담을 수도 있다.
confidence는 근거의 강도다. high는 파일 이름·내용이 기능을 직접 가리킬 때, medium은 정황이 맞을 때, low는 추측에 가까울 때인데 — low는 애초에 넣지 않는다.reason은 “무엇을 보고 그렇게 판단했는지”를 파일명·함수명·문자열 같은 구체적 근거로 한 문장 적는다.
말로 적어 놓은 규칙이 실제로 지켜지는지는 실행해 봐야 안다. 오늘 그걸 확인했다.
결정 2 — 틀린 저장소를 뒤지다 스스로 되돌아왔다
첫 시도는 대상 저장소부터 틀렸다. baton-api 안에서 diagramSync.ts, DiagramSession.tsx, DocEditor.tsx를 찾았는데 하나도 없었다. find로 상위 디렉터리를 다시 훑어서야 실제 프런트엔드 저장소가 baton-web이라는 걸 확인했다.
cd /c/Users/parkg/Documents/Git/park-gwimong/baton/baton-web && find . -maxdepth 1
저장소를 잘못 짚었다는 걸 확인하고 나서야 실제 발췌를 볼 수 있었다. 이 실수 자체는 사소하지만, 다음 결정과 이어진다 — 틀린 곳을 뒤진 상태에서 나온 결과를 그대로 내지 않고, 저장소가 맞는지부터 재확인한 뒤에 판단을 미뤘다.
결정 3 — 근거가 약하면 빈 목록을 낸다, 실제로
저장소를 바로잡은 뒤에도 첫 판단은 보류였다. 파일명만 보고 낸 결과는 이랬다.
제공된 파일 목록과 발췌 어디에도 실시간 다중 사용자 편집을 가리키는 근거가 없다.
DiagramSession.tsx·DocEditor.tsx같은 이름이 후보로 보이지만 ‘세션/편집’이라는 이름만으로는 ‘협업’을 뜻하는지 확인할 수 없다. 근거가 약해 매칭을 넣지 않았다.
matches: []. 이름이 그럴듯해도 넣지 않는다는 규칙이 문서상의 다짐이 아니라 실제로 작동한 순간이다.
근거를 확보하려고 다음 세션에서는 파일 내용을 직접 grep했다.
grep -l -iE "websocket|socket\.io|yjs|y-webrtc|presence|collab|awareness|crdt|realtime" -r src
grep -n "409\|conflict\|merged" src/editor/DocEditor.tsx
그제야 매칭이 채워졌다. DocEditor.tsx는 상단 주석에 “저장은 ETag + 3-way 병합”이라 명시돼 있고, 409 응답 시 merged/conflicts를 ConflictReport로 보여주는 코드가 실제로 있었다 — high confidence. DiagramSection.tsx는 “이 .puml이 검토 요청에 실려 있으면 결정 전까지 잠긴다”는 주석으로 다이어그램 동시 편집 잠금을 구현하고 있었다 — medium. drafts.ts의 baseline 필드도 DocEditor의 3-way 병합과 맞물린다는 근거로 붙었다.
baton-api 쪽은 사정이 달랐다. server/gitstore.py에는 이미 기능 ID 주석이 붙어 있었다.
# BATON-FUN-COL-001 웹 협업 편집 — ETag 낙관적 잠금·3-way 병합 저장·판 이력
server/reviews.py(COL-002, 검토 워크플로우), server/phase.py도 마찬가지였다. 서버 쪽은 이미 마킹돼 있었고, 빈 자리는 프런트엔드 쪽이었다는 게 이 매칭 작업으로 드러난 셈이다.
결정 4 — 분석가의 근거를 구현자가 그대로 물려받는다
같은 파이프라인의 다음 단계로 “SW 구현 개발자” 역할 프롬프트를 썼다. 설계 산출물과 기존 코드·구조를 받아 기능을 설계대로 구현하는 역할이다. 규칙은 분석가와 결이 비슷하다.
- 설계 산출물에 근거해서만 구현한다 — 기능 정의의 입출력·사전조건, 시퀀스의 상호작용 순서, 데이터 정의를 그대로 따른다.
- 기존 파일을 고칠 때는 파일 전체를 다시 쓴다. 부분 패치가 아니고, 원본에 없는 부분을 지우지 않는다.
- 구현한 자리에 기능 ID를 주석으로 남긴다 — 코드 추적 스캔이 이 주석으로 기능과 코드를 잇는다.
- 확신이 없는 부분은 지어내지 말고 TODO로 남긴다.
이 프롬프트로 실제로 고친 건 baton-web/src/editor/DocEditor.tsx였다. 검증 판정에서 발견된 문제는 이랬다 — 기존 문서를 열었을 때 화면에서 직접 저장 경로가 사라져 있었다. 신규 문서에서만 doSave가 노출되고, 기존 문서에서 Ctrl+S를 누르면 at:open-changes만 발생했다. 그 결과 유스케이스 메인 플로우 “3. 저장한다 → 5. ETag 3-way 병합 커밋”이 화면상 강제로 “리뷰 요청” 경유 흐름으로 바뀌어 있었다.
분석가가 앞서 찾아 둔 근거 — ETag/409 병합 로직이 이미 있다는 사실 — 가 여기서 그대로 수정 대상의 위치를 알려주는 지도가 됐다. 고친 내용은 두 가지다. savebar에 기존 문서용 [저장] 버튼을 추가(!isNew && !locked 조건), Ctrl+S가 신규·기존 구분 없이 항상 doSave를 호출하도록 단순화했다. 수정 후 npx tsc --noEmit으로 컴파일까지 확인했다.
baton-api 쪽에서는 server/reviews.py의 private 헬퍼를 open_* public 이름으로 리네임하고 호출부를 갱신했고, server/app.py에서 phase gate·revise_apply·diagram_sync 관련 수정을 적용했다. 수정마다 관련 테스트를 먼저 돌렸다.
python -m pytest tests/test_reviews.py tests/test_phase.py -q
python -m pytest tests/test_server_admin.py -q -k "revise or diagram_sync or sync"
마지막에 전체 스위트를 돌려 회귀가 없는지 확인했다.
의도치 않은 함정 — 56KB를 채팅으로 옮기려던 시도
DocEditor.tsx는 56KB짜리 파일이다. 처음엔 수정 결과를 JSON 산출물 형식(파일 전체 내용을 문자열로 담아 출력)에 맞추려 했는데, 그 크기를 손으로 옮겨 적다시피 하는 과정에서 오탈자가 날 위험이 눈에 보였다.
그래서 형식을 고집하지 않고 저장소 파일을 직접 Edit으로 고쳤다. 산출물 형식은 파이프라인의 다음 단계(다른 세션이나 도구)가 결과를 읽는 계약이겠지만, 그 계약을 지키려다 실제 파일이 깨지면 본말이 전도된다. 신뢰할 수 있는 산출물은 형식이 아니라 실제로 컴파일되는 코드였다.
마무리
오늘 확인한 것은 셋이다.
- 소급 태깅은 근거의 강도를 프롬프트 규칙으로 강제하는 문제였다. “찾는 것이지 만드는 것이 아니다”는 슬로건이 아니라, 저장소를 잘못 짚었을 때 결과를 내지 않고 되돌아가게 만드는 실행 규칙으로 작동했다.
- 빈 목록을 내는 규칙이 실제로 유혹을 이겼다. 파일명만으로도 그럴듯한 매칭을 낼 수 있었던 순간에, grep으로 실제 근거를 확인하기 전까지는
matches: []를 냈다. - 분석가의 근거가 구현자의 검증 재료로 그대로 넘어갔다. ETag·409·마커 주석이라는 같은 근거가, 한쪽에서는 “이 기능은 여기 있다”는 매칭 근거였고 다른 쪽에서는 “이 자리가 고칠 자리다”는 수정 근거였다.
남은 일은 baton-api 쪽 나머지 기능(COL-002 이하)도 같은 방식으로 소급 태깅을 이어가는 것과, reviews.py·app.py의 리팩터링 결과를 커밋할지 결정하는 것이다.