개요
AIFlare는 Claude Code의 Hook(자동 트리거)과 Skill(에이전트 실행)을 조합하여, AI 에이전트의 작업 컨텍스트를 자동으로 캡처합니다. 세션 시작부터 코드 push까지 전 과정에서 대화·커밋 의도·그룹 관계를 기록하며, 모든 캡처 동작은 실패해도 현재 작업을 중단하지 않습니다.
세션 ID 라우팅
Claude Code 세션이 시작되거나 resume될 때 SessionStart Hook이 실행됩니다. 이 시점에 서버 호출은 발생하지 않습니다.
Hook은 Claude Code가 부여한 session_id를 `$CLAUDE_ENV_FILE`에 `export CLAUDE_SESSION_ID='<id>'` 형태로 append합니다. 이후 이 세션에서 spawn되는 모든 Bash 자식 프로세스가 동일한 세션 ID를 환경 변수로 상속받습니다.
병렬 Claude Code 세션이 같은 워킹 디렉토리에서 동작할 때, capture.js가 어떤 세션 소속인지 결정적으로 식별하기 위한 핵심 신호입니다. 환경 변수가 없으면 mtime 기반 휴리스틱으로 엉뚱한 세션의 파일을 골라 캡처가 잘못 라우팅될 수 있습니다.
# Claude Code 세션 시작 / resume SessionStart Hook 실행 → echo "export CLAUDE_SESSION_ID='<session_id>'" >> $CLAUDE_ENV_FILE → 이후 모든 Bash 자식 프로세스가 CLAUDE_SESSION_ID 상속 → capture.js 가 환경 변수로 세션을 결정적으로 식별
WorkSession 레코드는 이 시점에 생성되지 않습니다. 첫 커밋 시점의 PUT /api/v1/work-sessions/prompt 호출에 의해 lazy 생성됩니다. 세션 종료 시(SessionEnd Hook)에는 .context-capture/ 의 누적 대화·offset·delta·pending-question 파일을 정리합니다 (서버 호출 없음).
대화 캡처
사용자가 입력한 프롬프트와 AI의 응답을 모두 자동으로 기록합니다.
UserPromptSubmit Hook이 사용자 입력을, Stop Hook이 AI 응답을 JSONL 형식으로 `.context-capture/.claude-prompts-{SESSION_ID}` 파일에 누적 저장합니다.
AI 에이전트가 응답을 완료할 때마다 Stop Hook이 실행되어 응답 텍스트를 같은 파일에 append합니다. `stop_hook_active=true`이면 무한 루프 방지를 위해 즉시 종료합니다.
{"role":"user","content":"설정 변경해줘"} {"role":"assistant","content":"설정을 변경했습니다. config.yml의 timeout 값을..."} {"role":"user","content":"테스트도 추가해"} {"role":"assistant","content":"테스트를 추가했습니다. ConfigServiceTest에..."}
파일명에 세션 ID가 포함되어 세션별로 대화가 분리됩니다. 이 대화 기록은 커밋 시 delta 추출되어 해당 커밋의 '관련 프롬프트'로 대시보드에 표시됩니다.
AskUserQuestion 신호
Claude Code의 AskUserQuestion built-in tool이 실행된 직후, PostToolUse(matcher="AskUserQuestion") Hook이 빈 플래그 파일을 touch합니다.
`.context-capture/.pending-question-{SESSION_ID}` 파일을 생성합니다. 이 파일은 다음 커밋 시점에 capture.js가 읽어 payload의 `continuation: true`로 변환하고 즉시 삭제합니다.
에이전트가 AskUserQuestion으로 사용자에게 질문 → 사용자 답변 → 후속 커밋 시나리오에서, 사용자 답변이 delta에 포함되어 '새 user 메시지'가 있는 것처럼 보입니다. 이 경우에도 같은 그룹으로 묶기 위해 'AskUserQuestion 연속' 신호를 남깁니다.
한계
- •에이전트가 AskUserQuestion tool 없이 평문으로 질문한 뒤 받은 답변은 신호가 없어 새 그룹으로 분리될 수 있습니다.
- •Gemini CLI, Codex 등 AskUserQuestion이 없는 에이전트는 continuation이 항상 false입니다.
커밋 후 처리
git commit 완료 직후 자동 실행되는 PostToolUse(Bash, if="Bash(git commit:*)") Hook이 3가지 역할을 수행합니다.
- 1.세션 대화 전송 — 누적된 전체 JSONL을 PUT /api/v1/work-sessions/prompt로 전송합니다 (5초 timeout). WorkSession이 없으면 이 시점에 lazy 생성됩니다.
- 2.Delta 추출 — 줄 수(메시지 인덱스) 기반으로 이전 캡처 이후의 새 대화 JSONL만 잘라내 .claude-conversation-delta-{SESSION_ID} 파일로 저장하고, 오프셋 파일을 갱신합니다.
- 3.Skill 호출 강제 — context-capture Skill 실행을 보장하는 hookSpecificOutput 메시지를 출력합니다.
# 대화 파일 (.claude-prompts-{SESSION_ID}) — 6줄 누적 Line 1-2: 첫 번째 작업 대화 (user + assistant) Line 3-4: 두 번째 작업 대화 (user + assistant) # ── 첫 번째 커밋 → delta: Line 1-4 (전체) ── Line 5-6: 세 번째 작업 대화 (user + assistant) # ── 두 번째 커밋 → delta: Line 5-6 (새 대화만) ──
각 커밋 시점에 이전 캡처 이후에 새로 발생한 대화만 정확히 추출되어, 해당 커밋의 EntryConversation으로 저장됩니다.
컨텍스트 캡처
context-capture Skill이 커밋 직후 자동 활성화되어, 에이전트가 자신의 대화 컨텍스트를 분석하여 요약 데이터를 생성하고 capture.js를 실행합니다.
capture.js는 인자로 받은 메타데이터에 더해, `--claude-session-id` → `$CLAUDE_SESSION_ID` → mtime 휴리스틱 순으로 세션 ID를 결정하고, `--conversation-snippet`이 생략되면 delta 파일을 자동으로 읽어 포함시킨 뒤 POST /api/v1/captures를 호출합니다.
node .claude/skills/context-capture/scripts/capture.js \ --title "Getting Started 가이드 한국어 번역 키 추가" \ --intent "ko.json에 docs.gettingStarted 키 추가하여 i18n 지원" \ --commit-hash "e395786" \ --agent-type "CLAUDE_CODE" \ --changed-files "ko.json" \ --tag "FEATURE" # conversationSnippet 은 .claude-conversation-delta-<id> 에서 자동 추출 # continuation 은 .pending-question-<id> 존재 여부로 자동 계산 # claudeSessionId 는 $CLAUDE_SESSION_ID 환경 변수에서 자동 결정
캡처 데이터 필드
- •title — 작업 제목 (50자 이내)
- •intent — 왜 이 변경이 필요했는가 (Problem → Solution → Effect)
- •alternatives — 검토했지만 선택하지 않은 대안
- •diffSummary — 핵심 변경 사항 요약
- •tag — 작업 성격 (FEATURE, BUGFIX, REFACTORING, TEST, DOCS)
- •changedFiles — 변경된 파일 목록
- •conversationSnippet — delta 파일에서 자동으로 추출된 사용자·AI 대화 조각
- •continuation — .pending-question 플래그 존재 여부로 자동 계산되는 그룹 연속 신호
그룹 판정 (서버측)
POST /api/v1/captures 처리 내부의 CaptureService.resolveGroupRootId가 한 사용자 요청에서 파생된 연속 커밋들을 같은 그룹으로 묶습니다.
timeline_entries 테이블의 nullable self-reference 컬럼 group_root_id로 표현합니다. NULL이면 root이거나 단독, 값이 있으면 해당 ID의 root에 속한 멤버입니다. 프론트엔드는 COALESCE(groupRootId, id)를 그룹 키로 사용합니다.
판정 규칙
| 직전 엔트리 | delta에 user 메시지 | continuation | 결정 |
|---|---|---|---|
| 없음 | — | — | 새 root |
| 있음 | false | — | 같은 그룹 (agent 단독 연속 커밋) |
| 있음 | true | true | 같은 그룹 (AskUserQuestion 연속) |
| 있음 | true | false | 새 root (사용자가 새 요청 시작) |
# (A) Agent 단독 연속 커밋 T0 User: "A, B, C 해줘" T1 commit A → delta has user → 새 root (101, root=NULL) T2 commit B → delta no user → 같은 그룹 (102, root=101) T3 commit C → delta no user → 같은 그룹 (103, root=101) # (B) AskUserQuestion 중간 개입 T0 User: "리팩토링 해줘" T1 commit part1 → 새 root (201, root=NULL) T2 Agent AskUserQuestion → .pending-question 생성 T3 User: "B 방식으로" T4 commit part2 → continuation=true → 같은 그룹 (202, root=201) # (C) 새 사용자 요청 T0 User: "A 해줘" → commit (301, root=NULL) T1 User: "이제 B 해줘" (평문) → commit continuation=false → 새 root (302, root=NULL)
Push 시점 동기화
git push 실행 시 .git/hooks/pre-push가 자동으로 실행되어, 로컬에 저장된 캡처를 백엔드로 전달합니다.
`git log <remote>..<local> --format=%H`로 push되는 커밋 해시 목록을 추출한 뒤, sync-captures.js(로컬 aiflare/timeline/v1 브랜치의 각 엔트리를 POST /api/v1/captures/batch)와 sync-sessions.js(레다크션된 세션 트랜스크립트를 PUT /api/v1/work-sessions/prompt)를 실행합니다.
git push → pre-push hook 발동 (.git/hooks/pre-push) → git log <remote>..<local> --format=%H 로 커밋 해시 추출 → node sync-captures.js <hashes...> → aiflare/timeline/v1:entries/<hash>.json 읽기 → POST /api/v1/captures/batch → node sync-sessions.js <hashes...> → 푸시된 엔트리의 claudeSessionId 수집 (dedup) → aiflare/sessions/v1:sessions/<id>.jsonl 읽기 → 레다크션 → PUT /api/v1/work-sessions/prompt
동기화 파이프라인
- •sync-captures.js — push 되는 각 커밋마다 aiflare/timeline/v1:entries/<hash>.json 을 읽어 /captures/batch 로 일괄 POST
- •sync-sessions.js — 위 엔트리의 distinct claudeSessionId 를 모아 aiflare/sessions/v1:sessions/<id>.jsonl 을 읽고, 레다크션 후 세션별 /work-sessions/prompt 로 PUT
- •로컬 전용 브랜치(aiflare/timeline/v1, aiflare/sessions/v1)는 pre-push가 무조건 push를 거부 — git 으로는 절대 머신을 벗어나지 않습니다
hook이 실패해도 push 자체는 차단되지 않습니다. pre-push가 설치되지 않았거나 sync 호출이 실패하면 엔트리는 로컬 aiflare/timeline/v1 브랜치에 남아 있다가 다음 sync 성공 시 백엔드에 도달합니다.
대시보드에서의 결과
위 단계들이 합쳐져 AIFlare 대시보드에서 의미 있는 타임라인이 만들어집니다.
같은 워크세션(claudeSessionId)으로 묶인 캡처가 하나의 세션 그룹으로 표시됩니다. 하나의 작업 세션에서 발생한 여러 커밋을 한눈에 파악할 수 있습니다.
같은 group_root_id로 연결된 연속 커밋은 root 엔트리 아래에 묶여 표시됩니다. agent 단독 연속 커밋, AskUserQuestion 연속 커밋이 한 묶음으로 시각화됩니다.
각 엔트리의 '관련 프롬프트' 패널은 EntryConversation에 저장된 delta JSONL을 role별(user / assistant)로 파싱하여 보여줍니다.
타임라인 표시 예시