
git blame은 누가, 언제, 뭘 바꿨는지 보여준다. whyso는 왜 바꿨는지를 보여준다.
문제
코드 한 줄이 왜 이렇게 생겼는지 알고 싶을 때, 우리가 할 수 있는 것은 git blame과 커밋 메시지를 보는 것이다.
$ git blame internal/handler/page.go
a3f1b2c (parkjunwoo 2026-03-08) func CreatePage(c *gin.Context) {
누가, 언제, 뭘 바꿨는지는 나온다. 왜 바꿨는지는 나오지 않는다.
커밋 메시지에 이유를 쓰면 되지 않느냐고? 현실은 이렇다:
fix: update handler
refactor: clean up
wip
좋은 커밋 메시지를 쓰는 것은 규율의 문제다. 아무리 팀 컨벤션을 세워도, 새벽 3시에 버그를 고치면서 “이 변경의 맥락과 근거를 커밋 메시지에 정성껏 기록해야지"라고 생각하는 사람은 드물다.
그런데 AI 코딩 시대에는 상황이 다르다. 변경의 이유가 이미 기록되어 있다.
Claude Code의 세션 데이터
Claude Code는 모든 대화를 ~/.claude/projects/ 아래에 JSONL 파일로 저장한다. 각 레코드에는 사용자 메시지, AI 응답, tool_use(Write, Edit, Bash) 호출, 그리고 이것들을 연결하는 parentUuid 체인이 들어 있다.
사용자: "whyso validate 명령어 구현 시작해"
→ AI: Write internal/crosscheck/crosscheck.go
→ AI: Edit internal/crosscheck/crosscheck.go
→ AI: "초기 crosscheck 패키지 생성 — SSaC↔OpenAPI operationId 매칭 검증"
파일이 왜 생겼는지, 왜 수정됐는지가 대화 그 자체에 남아 있다. 커밋 메시지보다 풍부하고, 의도적으로 작성할 필요도 없다. 대화를 했다면 자동으로 기록된다.
whyso는 이 데이터를 파싱해서 파일별 변경 히스토리를 추출한다.
파일의 생애가 보인다
file: internal/crosscheck/crosscheck.go
created: 2026-03-08T14:23:01Z
history:
- timestamp: 2026-03-08T14:23:01Z
session: 09351222-d7be-41fe-994f-87c2d7067e5d
user_request: "whyso validate 명령어 구현 시작해"
answer: "초기 crosscheck 패키지 생성 — SSaC↔OpenAPI operationId 매칭 검증"
tool: Write
- timestamp: 2026-03-09T10:15:33Z
session: 4e9b4e5e-3a50-43f2-be6e-e5db228ecc3b
user_request: "x-sort 컬럼이 DDL에 있는지 검증 추가해"
answer: "x-sort/x-filter 컬럼 → DDL 교차 검증 추가"
tool: Edit
- timestamp: 2026-03-10T09:41:22Z
session: b2e43b4f-cb21-4286-975d-1eb9de8a16c0
user_request: "Func 스펙 교차 검증도 추가해"
answer: "@call ↔ Func spec 인자 수/타입 교차 검증 추가"
tool: Edit
왜 만들어졌고, 어떤 요청으로 어떻게 진화했는지. 파일 하나의 전체 생애가 보인다.
왜 이것이 필요한가
코드 리뷰의 빈 칸
PR을 리뷰할 때 “이 변경은 왜 했지?“라는 질문을 하게 되는 순간이 있다. 커밋 메시지에 이유가 없으면 작성자에게 물어봐야 한다. 작성자가 기억을 못 하면 코드를 다시 읽으며 추론해야 한다.
AI 코딩 환경에서는 이 질문의 답이 세션 데이터에 있다. whyso는 그 답을 꺼내준다.
온보딩
새로 합류한 팀원이 코드베이스를 이해하는 가장 빠른 방법은 “이 코드가 왜 이렇게 됐는지"를 아는 것이다. git log는 변경의 시간순 나열이고, 문서는 현재 상태의 스냅샷이다. whyso의 파일별 히스토리는 그 사이 — 의도의 연쇄 — 를 보여준다.
자기 기록
혼자 개발할 때도 3개월 전에 왜 이렇게 짰는지 기억나지 않는 순간이 온다. whyso는 과거의 나와 AI 사이의 대화를 복원해준다. 커밋 메시지에 쓰지 않은 맥락이 거기에 있다.
git blame과의 관계
whyso는 git blame을 대체하지 않는다. 보완한다.
| git blame | whyso | |
|---|---|---|
| 누가 | O | — |
| 언제 | O | O |
| 뭘 바꿨는지 | O (라인 단위) | O (tool_use 단위) |
| 왜 바꿨는지 | △ (커밋 메시지) | O (사용자 요청 + AI 설명) |
| 데이터 소스 | git 히스토리 | Claude Code 세션 |
git blame이 커밋 기반의 “공식 기록"이라면, whyso는 대화 기반의 “작업 일지"다. 공식 기록에 쓰지 못한 맥락이 작업 일지에 남아 있다.
AI가 자기 기록을 읽는 구조
whyso의 가장 흥미로운 사용 사례는 사람이 아니라 AI 자신이 읽는 것이다.
Claude Code는 세션이 끝나면 모든 것을 잊는다. 다음 세션에서 같은 프로젝트를 열면, 어제 내가 왜 그렇게 짰는지, 어떤 선택지를 검토하고 버렸는지, 사용자가 어떤 맥락에서 요청했는지 — 전부 없다. 코드를 읽으며 추론할 뿐이다.
CLAUDE.md에 한 줄을 추가한다고 상상해보자:
파일 수정 전 반드시 `whyso history <file>` 실행. 히스토리가 있으면 읽은 뒤 수정.
이 한 줄이 바꾸는 것:
되돌리면 안 되는 변경을 안다
파일을 수정하려고 읽었는데 뭔가 이상해 보이는 코드가 있다. 현재 상태만 보면 “실수 아닌가?” 하고 고치고 싶다. 그런데 whyso 히스토리에 “사용자가 명시적으로 이 구조를 요청했고, 이유는 성능이 아니라 가독성 때문"이라고 적혀 있으면 건드리지 않는다.
버린 선택지를 다시 제안하지 않는다
이전 세션에서 “A 방식으로 해볼까?” → “아니, B로 가자"라는 대화가 있었는데, 새 세션의 AI는 그걸 모른다. 그래서 또 A를 제안한다. 사용자 입장에서 짜증나는 일이다. whyso가 있으면 이미 검토하고 기각한 방향을 안다.
연속된 작업의 맥락을 잇는다
“어제 리팩토링 이어서 해줘"라고 하면 지금은 diff를 보고 추론해야 한다. whyso 히스토리가 있으면 어제 세션의 사용자 요청과 AI의 판단 근거를 바로 읽는다. 추론이 아니라 사실에 기반한 연속 작업이 된다.
기록이 세션을 넘어 누적된다
이 구조가 되면 whyso의 히스토리가 세션을 거듭할수록 쌓인다. 이번 세션에서 수정한 이유도 다음 세션의 AI에게 전달된다. 세션 간 기억이 없는 문제가 파일 단위로 풀린다. 별도의 메모리 시스템 없이도.
whyso에 기록된 건 AI가 한 일이다. 그런데 AI는 그걸 기억하지 못한다. 자기가 만든 기록이 자기에게 가장 필요한 상황. 이것이 whyso가 AI 코딩 도구와 결합했을 때의 독특한 지점이다.
사람에게 whyso는 “좋은 커밋 메시지를 자동으로 남겨주는 도구"다. AI에게 whyso는 세션을 넘는 기억이다.
사용법
설치
go install github.com/park-jun-woo/whyso/cmd/whyso@latest
파일 히스토리 조회
# 단일 파일
whyso history README.md
# 디렉토리 전체
whyso history internal/ --all
# 파일 구조를 미러링해서 출력
whyso history . --all --output .file-histories/
# JSON 형식
whyso history README.md --format json
세션 목록
whyso sessions
작동 방식
parentUuid 체인 역추적
JSONL의 모든 레코드는 uuid와 parentUuid로 연결되어 있다. AI가 파일을 수정한 tool_use에서 parentUuid 체인을 거슬러 올라가면, 그 수정을 촉발한 사용자의 원래 요청에 도달한다.
user message (uuid: A) ← "x-sort 컬럼이 DDL에 있는지 검증 추가해"
→ assistant tool_use (parentUuid: A)
→ tool_result (parentUuid: B)
→ assistant Edit (parentUuid: C) ← 이 수정의 이유 = A
중간에 tool_result 타입의 메시지가 끼어 있으므로, type == "user"이면서 content가 문자열인 것만 추출하면 진짜 사용자 요청을 찾을 수 있다.
설계 결정
Write/Edit만 추적한다. Claude Code의 Write와 Edit tool_use는 파일 경로와 변경 내용을 명시적으로 포함한다. Bash 명령어의 파일 조작(rm, mv, cp)은 휴리스틱으로 감지하지만, Write/Edit가 주된 추적 대상이다. Claude Code는 파일 수정 시 Write/Edit을 사용하도록 설계되어 있으므로, 이 두 도구만으로도 대부분의 변경을 포착한다.
서브에이전트도 포함한다. Claude Code가 복잡한 작업을 처리할 때 서브에이전트를 생성하는 경우가 있다. 서브에이전트의 세션은 부모 세션 디렉토리 하위에 저장된다. whyso는 이 서브에이전트 세션도 파싱해서 완전한 히스토리를 구축한다.
증분 업데이트. --output 옵션으로 디렉토리에 출력할 때, 이미 파싱된 세션은 건너뛴다. 세션이 수백 개로 늘어나도 매번 전체를 다시 파싱하지 않는다.
수치
whyso 프로젝트 자체의 개발 과정을 whyso로 추적한 결과:
| 항목 | 수치 |
|---|---|
| 세션 수 | 17 |
| Write 호출 | 660 |
| Edit 호출 | 1,415 |
| Bash 파일 조작 | 206 |
| 변경된 고유 파일 수 | 480 |
17개 세션에서 480개 파일의 변경 히스토리가 추출됐다. 각 파일에 “왜 만들어졌고, 왜 바뀌었는지"가 기록되어 있다.
한계
- Claude Code 전용이다. 다른 AI 코딩 도구의 세션 데이터는 형식이 다르다. 현재는 Claude Code의 JSONL 형식만 지원한다.
- 텍스트 파일만 추적한다. Write/Edit tool_use는 텍스트 파일을 대상으로 한다. 이미지, 바이너리 파일의 변경은 추적하지 않는다.
- 세션 데이터가 로컬에 있어야 한다.
~/.claude/projects/에 세션 파일이 있어야 한다. 삭제했거나 다른 머신에서 작업했다면 그 기록은 없다.
정리
AI와 함께 코드를 작성하는 시대에, 변경의 이유는 더 이상 커밋 메시지에만 의존할 필요가 없다. 대화 자체가 기록이고, 그 기록에서 파일별 변경 히스토리를 자동으로 추출할 수 있다.
git blame이 “누가 언제 뭘 바꿨는지"를 보여준다면, whyso는 “왜 바꿨는지"를 보여준다.