클로드 코드 작업 이력 시각화 — 3주 동안 뭘 만들었는지 기억나지 않을 때
지난주에 만든 것도 가물가물할 때가 있어요. 터미널 창을 닫으면 작업 기록이 머릿속에서도 사라지죠. 클로드 코드는 모든 대화를 ~/.claude/projects/ 아래 jsonl 파일로 조용히 기록합니다.
이 글을 읽고 나면 20줄짜리 스크립트로 작업 이력을 날짜별 타임라인으로 복원할 수 있어요.
준비물
이 실습에는 다음이 필요합니다.
- Node.js 18 이상 (터미널에서
node --version으로 확인) - Claude Code 데스크톱 앱 사용 경험 (최소 3~5일 이상 작업한 프로젝트)
- 텍스트 에디터 (VSCode나 vim 등)
로컬 파일만 읽으므로 API 키는 필요 없습니다.
클로드 코드는 어디에 기록을 남기나요?
macOS나 리눅스라면 ~/.claude/projects/ 경로를 열어보세요. 윈도우는 %USERPROFILE%\.claude\projects\에 있습니다. 프로젝트별로 폴더가 나뉘어 있고, 각 폴더 안에 세션별 jsonl 파일이 담깁니다.
제 경우 blog-publisher 프로젝트 폴더에 42개 jsonl 파일이 있었어요. 파일 이름은 세션 아이디입니다. 2a78d93d-0d8c-4bec-bff1-5e2e9964e392.jsonl 같은 식이죠.
파일을 하나 열어보면 이런 구조예요.
{"type":"user","timestamp":"2026-08-25T14:31:05.456Z","cwd":"/home/user/th-team/blog-publisher","gitBranch":"main","message":{"role":"user","content":"게이트 통과율을 높이려면 어떻게 해야 할까?"}}
{"type":"assistant","timestamp":"2026-08-25T14:31:15.789Z","message":{"role":"assistant","content":"게이트 통과율을 높이려면..."}}
{"type":"attachment","timestamp":"2026-08-25T14:31:20.001Z","cwd":"/home/user/th-team/blog-publisher"}
한 줄이 JSON 객체 하나입니다. assistant, attachment 처럼 다른 종류도 같은 파일에 섞여 쌓이니, 내가 타이핑한 것만 뽑으려면 type === 'user' 로 걸러야 해요.
타임라인 추출 스크립트를 짜봅시다
이제 jsonl 파일들을 읽어서 "언제 무슨 작업을 했는지" 타임라인으로 만들어볼게요.
1단계: 파일 목록 읽기
프로젝트 폴더 안의 jsonl 파일을 전부 찾습니다.
const fs = require('fs');
const path = require('path');
const projectDir = path.join(process.env.HOME, '.claude/projects/-home-user-projects-blog');
const files = fs.readdirSync(projectDir).filter(f => f.endsWith('.jsonl'));
console.log(`찾은 세션 파일: ${files.length}개`);
이렇게 실행하면 → 찾은 세션 파일: 42개 같은 출력이 나옵니다. 프로젝트 경로는 여러분 환경에 맞게 바꾸세요. .claude/projects/ 아래 폴더 이름은 실제 프로젝트 절대경로의 슬래시를 하이픈으로 바꾼 형태예요.
2단계: 각 파일 파싱해서 사용자 메시지 추출
파일을 하나씩 열어서 사용자 메시지만 뽑아냅니다.
const events = [];
files.forEach(filename => {
const filepath = path.join(projectDir, filename);
const lines = fs.readFileSync(filepath, 'utf-8').split('\n').filter(Boolean);
lines.forEach(line => {
try {
const event = JSON.parse(line);
if (event.type === 'user' && event.message) {
const content = typeof event.message.content === 'string'
? event.message.content
: '';
if (!content) return;
const dt = new Date(event.timestamp);
const kst = new Date(dt.getTime() + 9 * 60 * 60 * 1000);
events.push({
date: kst.toISOString().split('T')[0],
time: kst.toISOString().split('T')[1].substring(0, 5),
message: content.split('\n')[0]
});
}
} catch (e) {
// 파싱 실패한 줄은 건너뜀
}
});
});
console.log(`추출한 메시지: ${events.length}건`);
입력: jsonl 파일 42개 → 출력: 추출한 메시지: 347건
제 경우 3주 동안 347번 질문했더라고요. 타임스탬프는 UTC로 저장돼 있어서 KST로 변환하는 코드를 넣었습니다.
3단계: 날짜별로 그룹화해서 타임라인 출력
날짜별로 묶어서 출력합니다.
const byDate = {};
events.forEach(e => {
if (!byDate[e.date]) byDate[e.date] = [];
byDate[e.date].push(e);
});
const dates = Object.keys(byDate).sort();
dates.forEach(date => {
console.log(`\n[${date}] ────────`);
byDate[date].forEach(e => {
console.log(`- ${e.time} ${e.message}`);
});
});
이렇게 실행하면 이런 식으로 나와요.
[2026-08-25] ────────
- 14:31 게이트 통과율을 높이려면 어떻게 해야 할까?
- 15:42 이 오류 메시지가 뭘 의미하는지 알려줘
- 18:03 테스트 커버리지를 어떻게 올리지?
[2026-08-26] ────────
- 09:15 어제 만든 스크립트가 왜 안 도는지 모르겠어
- 10:33 내부 중복 게이트 임계값을 조정해야 할까?
날짜마다 뭘 고민했는지 한눈에 보입니다.
프로젝트와 브랜치를 함께 추적하려면?
하루 동안 여러 작업을 오가면 맥락이 섞여요. user 레코드에는 cwd와 gitBranch 필드가 함께 들어있습니다. 프로젝트 이름만 보면 "blog-publisher에서 뭘 했다" 정도만 알 수 있지만, 브랜치까지 찍으면 기능 단위로 묶입니다.
2단계 코드로 돌아가서, events.push 에 넘기는 객체의 마지막 줄 끝에 쉼표를 붙이고 그 아래에 두 줄을 얹으세요.
project: path.basename(event.cwd || ''),
branch: event.gitBranch || 'main'
cwd는 user 레코드에 직접 들어있어서 별도 상태 변수가 필요 없어요. event.cwd를 바로 쓰면 됩니다.
찍는 쪽은 3단계의 안쪽 반복문입니다. 거기 있던 출력 한 줄을 아래 두 줄로 교체하세요. 브랜치가 기본값이 아닐 때만 꼬리표에 붙이면 화면이 덜 지저분해요.
const tag = e.branch !== 'main' ? `[${e.project}:${e.branch}]` : `[${e.project}]`;
console.log(`- ${e.time} ${tag} ${e.message}`);
실행하면 이렇게 나옵니다.
[2026-08-29] ────────
- 11:05 [blog-publisher:feat/gate-refactor] 검증자 피드백 반영
- 14:32 [thundorun] 관리자 대시보드 차트 렌더링 개선
- 16:48 [blog-publisher:feat/gate-refactor] 내부 중복 임계값 조정
같은 프로젝트지만 브랜치가 다르면 별개 작업이죠. 저는 이걸로 기능 개발 타임라인을 추적해요.
서브에이전트 기록을 걸러내면 더 정확해집니다
클로드 코드가 자동으로 돌린 서브에이전트 기록도 jsonl에 쌓입니다. isSidechain 필드가 true인 레코드가 그거예요. 제가 직접 물어본 것과 섞이면 타임라인이 부풀어 오릅니다.
2단계 코드의 if (event.type === 'user' && event.message) 블록 첫 줄에 이걸 추가하세요.
if (event.isSidechain) return;
이 한 줄로 347건이 219건으로 줄었어요. 실제로 제가 타이핑한 질문만 남으니 "그날 뭘 고민했나"가 훨씬 명확하게 보입니다.
여러 프로젝트를 한 타임라인으로 합치고 싶다면 ~/.claude/projects/ 아래 폴더를 순회하면 됩니다. fs.readdirSync로 프로젝트 폴더 목록을 먼저 뽑고, 각 폴더의 jsonl을 읽으세요. 저는 3개 프로젝트를 합쳐서 주간 보고를 만들어요.
흔한 실수와 해결법
처음 돌렸을 때 빈 결과가 나왔어요. 프로젝트 폴더 이름을 틀리게 적었더라고요. .claude/projects/ 아래 실제 폴더 이름은 ls ~/.claude/projects/로 먼저 확인하세요.
두 번째는 JSON 파싱 에러예요. 일부 줄이 잘린 파일이 있어서 try-catch로 감쌌습니다. 347건 중 3건 스킵됐는데 타임라인 보는 데는 지장 없었어요.
타임존 문제도 있었습니다. 타임스탬프가 UTC라서 제 시각과 9시간 차이 났죠. 위 2단계 코드는 UTC에 9시간을 더해 KST로 변환합니다.
자주 묻는 질문
Q. 여러 프로젝트를 한 번에 보려면? A. .claude/projects/ 아래 모든 폴더를 순회하면 됩니다. fs.readdirSync로 프로젝트 폴더 목록을 먼저 뽑고, 각 폴더의 jsonl을 읽으세요.
Q. 어시스턴트 응답도 함께 보고 싶어요. A. type === 'assistant' 레코드도 같은 방식으로 추출하면 됩니다. 응답이 길어서 첫 100자만 자르는 게 좋아요.
Q. 특정 키워드가 들어간 대화만 찾을 수 있나요? A. 메시지 추출할 때 if (content.includes('게이트')) 같은 필터를 추가하세요. 저는 "배포" 키워드로 14건을 찾았고, 절반이 금요일 저녁이었어요.
Q. 이 스크립트를 cron으로 돌려서 매주 요약을 받을 수 있나요? A. 가능합니다. 출력을 마크다운 파일로 저장하게 수정하고, 0 9 * * 1 cron 표현식으로 매주 월요일 아침에 돌리면 지난주 작업 요약이 생겨요.
마무리
스크립트 전체는 30줄 정도예요. 한 번 만들어두면 "지난달에 뭐 했더라?" 할 때마다 꺼내 쓸 수 있습니다.
저는 이걸로 주간 회고를 자동화했어요. 매주 월요일 아침 타임라인을 보면서 지난주 시간을 어디에 썼는지 점검합니다. 같은 문제를 반복해서 물어본 게 보이면 문서화하거나 스크립트로 만들죠.
다음 단계로는 작업 시간 통계를 내보는 것도 좋습니다. 첫 메시지와 마지막 메시지 타임스탬프 차이로 세션 길이를 계산하면, 이번 달 순수 코딩 시간을 측정할 수 있어요.