概述
AIFlare 结合 Claude Code 的 Hook(自动触发器)和 Skill(代理执行),自动捕获 AI 代理的工作上下文。从会话开始到代码 push 全程,记录对话、提交意图与分组关系,所有捕获操作即使失败也不会中断当前工作。
会话 ID 路由
Claude Code 会话启动或 resume 时,SessionStart Hook 运行。此时不会调用服务器。
Hook 将 Claude Code 分配的 session_id 以 `export CLAUDE_SESSION_ID='<id>'` 的形式 append 到 `$CLAUDE_ENV_FILE`。之后此会话中 spawn 出的所有 Bash 子进程都通过环境变量继承同一个会话 ID。
当多个 Claude Code 会话在同一工作目录下并行运行时,这是 capture.js 决定性地识别自身所属会话的关键信号。若没有该环境变量,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 记录。WorkSession 在首次提交时由 PUT /api/v1/work-sessions/prompt 调用惰性创建。会话结束时(SessionEnd Hook)会清理 .context-capture/ 中的累积对话、offset、delta、pending-question 文件(不会调用服务器)。
对话捕获
自动记录用户输入的提示词和 AI 的回复。
UserPromptSubmit Hook 保存用户输入,Stop Hook 保存 AI 回复——都以 JSONL 格式追加到 `.context-capture/.claude-prompts-{SESSION_ID}` 文件中。
每当 AI 代理完成回复时,Stop Hook 运行并将回复文本追加到同一文件中。若 `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 内置工具执行后,PostToolUse(matcher="AskUserQuestion") Hook 立即 touch 一个空标记文件。
创建 `.context-capture/.pending-question-{SESSION_ID}`。下次提交时 capture.js 会读取它并转换为 payload 中的 `continuation: true`,然后立即删除该文件。
当代理使用 AskUserQuestion 向用户提问,用户回答后再提交时,用户的答案会进入 delta,看上去像「新的 user 输入」。此标志让服务器仍将其与前一次提交分到同一组,而不是开启新组。
限制
- •若代理用纯文本提问(未使用 AskUserQuestion 工具),则没有信号——后续提交可能会分到新组。
- •没有 AskUserQuestion 的代理(Gemini CLI、Codex 等)会始终发送 continuation=false。
提交后处理
git commit 完成后,PostToolUse(Bash, if="Bash(git commit:*)") Hook 立即执行三项工作。
- 1.会话对话上传——将累积的完整 JSONL 发送到 PUT /api/v1/work-sessions/prompt(5 秒超时)。如果 WorkSession 还不存在,会在此时惰性创建。
- 2.Delta 提取——基于行数(消息索引)仅截取上次捕获之后的新对话行,写入 .claude-conversation-delta-{SESSION_ID} 并更新 offset 文件。
- 3.Skill 调用强制——输出 hookSpecificOutput 消息,确保 context-capture Skill 必然运行。
# 대화 파일 (.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 表的可空自引用列 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` 提取要推送的 commit 哈希列表,然后运行 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 — 对每个被推送的 commit 读取 aiflare/timeline/v1:entries/<hash>.json,并批量 POST 到 /captures/batch
- •sync-sessions.js — 汇总这些条目的 distinct claudeSessionId,读取 aiflare/sessions/v1:sessions/<id>.jsonl,脱敏后按会话 PUT 到 /work-sessions/prompt
- •本地专用分支(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,并按角色(user / assistant)分别渲染。
时间线显示示例