首页
/ claude-mem 分支记忆可视化:在 Viewer 观察卡片中展示 Git 分支与 Commit SHA

claude-mem 分支记忆可视化:在 Viewer 观察卡片中展示 Git 分支与 Commit SHA

2026-09-04 09:29:13作者:宣利权Counsellor

本文基于 claude-mem 的分支记忆(branch-memory)特性开发 playbook 中 BRANCH-PARITY-02-Viewer-Branch-Display.md 阶段文档展开,讲解如何把数据库中已存储的 branchcommit_sha 观察元数据,打通"类型定义 → 分页查询 → React 卡片渲染"三层链路,最终在 http://localhost:37777 的 Viewer UI 中以分支徽章的形式呈现。读完本文,你将掌握 claude-mem Viewer 的数据流全貌、观察卡片组件的渲染机制,以及单文件 Viewer 的构建与验证流程。

1. 背景:数据库有分支信息,Viewer 却没有

claude-mem 的核心工作流是:记录 Agent 会话中的操作 → 用 AI 压缩为"观察(Observation)" → 在未来会话中重新注入相关上下文。分支记忆特性进一步要求记忆与 Git 分支边界对齐——同一条观察应当能被追溯它是在哪个分支、哪个提交上产生的。

按 playbook 的原始描述,此时系统的状态是:

  • 数据库已经在每条观察上存储了 branchcommit_sha(由 migrations 24-25 引入的列);
  • 但 React Viewer 中展示观察卡片时只有类型、项目、标题、副标题、事实、叙事和元数据,没有分支信息
  • 原因很具体:为 Viewer 供数的 PaginationHelper 查询有意省略了这两个列,导致数据虽然入库却"断供"于界面。

因此这一阶段的目标非常聚焦:给观察卡片加上分支可见性,让用户一眼看出每条观察来自哪个 git 分支,从而补全分支记忆的"视觉层"。

从源码结构看,当前仓库快照中 PaginationHelper 的 SELECT 列表确实不含 branch/commit_shaViewer 类型定义Observation 接口也尚未包含这两个字段——这与 playbook 描述的基线状态一致(该特性在 branch-memory 工作分支上开发,playbook 中标记了各任务的完成状态)。

2. 类型层:给 Observation 接口加上可选的分支字段

第一处改动发生在 Viewer 的 TypeScript 类型系统 src/ui/viewer/types.ts。当前 Observation 接口为:

export interface Observation {
  id: number;
  memory_session_id: string;
  project: string;
  merged_into_project?: string | null;
  platform_source: string;
  type: string;
  title: string | null;
  subtitle: string | null;
  narrative: string | null;
  text: string | null;
  facts: string | null;
  concepts: string | null;
  files_read: string | null;
  files_modified: string | null;
  prompt_number: number | null;
  created_at: string;
  created_at_epoch: number;
}

需要追加两个可选且可为 null 的字段:

  branch?: string | null;
  commit_sha?: string | null;

关键设计决策是"可选性":branch?: string | null 而非必填。因为存在大量早于分支记忆特性的历史观察,它们的 branch 值为 NULL——若声明为必填字段,旧数据会被类型系统误判为非法。这一约定同时体现在 Viewer 侧接口和 Worker 侧接口 src/services/worker-types.tsPaginationHelper 从后者导入 Observation 类型),playbook 在任务完成备注中特别指出:由于查询结果是泛型透传(paginate<Observation>()),类型更新后"其余部分自动生效",无需改动映射逻辑。

3. 查询层:PaginationHelper.getObservations() 补列

数据供给的"断点"在 src/services/worker/PaginationHelper.tsgetObservations() 方法。当前实现对 observations 表执行硬编码列列表的 SELECT,并按 created_at_epoch DESC 排序:

let query = `
  SELECT
    o.id,
    o.memory_session_id,
    o.project,
    o.merged_into_project,
    COALESCE(s.platform_source, 'claude') as platform_source,
    o.type,
    o.title,
    o.subtitle,
    o.narrative,
    o.text,
    o.facts,
    o.concepts,
    o.files_read,
    o.files_modified,
    o.prompt_number,
    o.created_at,
    o.created_at_epoch
  FROM observations o
  LEFT JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id
`;

改动只有一处:在列列表末尾追加 o.branch, o.commit_sha。由于结果集通过 db.prepare(query).all(...) 取出后直接断言为 Observation[],列补上之后新字段会自动出现在返回对象里,配合第 2 节的类型更新即完成整条链路。

理解这段查询的几个配套细节,有助于把握改动边界:

  • 分页的 hasMore 探测params.push(limit + 1, offset)——每次多取一行,若返回行数超过 limit 则判定 hasMore: true,随后 slice(0, limit) 截断。这是无 COUNT(*) 的轻量分页模式,新增列不影响该逻辑。
  • 项目过滤:指定 project 时匹配 o.project = ? OR o.merged_into_project = ?(支持已合并项目的归属追溯);未指定时排除 OBSERVER_SESSIONS_PROJECT(观察者自身会话,来自 src/shared/paths.ts 的常量)。
  • 返回前的清洗sanitizeObservation() 会用 stripProjectPaths()files_read/files_modified 中的绝对路径剥离为相对路径,保证卡片不泄露本机目录结构。分支字段不经过任何清洗,原样透传。

4. 渲染层:ObservationCard 中的分支徽章

展示逻辑落在 src/ui/viewer/components/ObservationCard.tsx。当前卡片的头部结构(card-header-left 容器内)依次渲染四枚徽章:

<div className="card-header-left">
  <span className={`card-type type-${observation.type}`}>{observation.type}</span>
  <span className={`card-source source-${observation.platform_source || 'claude'}`}>
    {observation.platform_source || 'claude'}
  </span>
  <span className="card-project">{observation.project}</span>
  {observation.merged_into_project && (
    <span className="card-merged-badge" title={`Merged into ${observation.merged_into_project}`}>
      merged → {observation.merged_into_project}
    </span>
  )}
</div>

playbook 对该卡片的具体改造规格如下,逐条对应现有代码模式:

  1. 位置:分支徽章放在 card-header-left 内、现有 card-project 徽章旁(对应上面代码中约第 51 行附近)。
  2. 条件渲染:仅在 observation.branch 为 truthy 时渲染——迁移前的观察该字段为 null,徽章必须静默缺省,不能占位。这与 card-merged-badge 的条件渲染写法({observation.merged_into_project && ...})完全同构,可直接参照。
  3. 图标与文本:一个简化的 git 分支分叉 SVG 图标(fork 造型)加分支名,整体样式仿照现有 card-project 的 span 写法。
  4. Commit SHA 缩写(可选增强):在分支名旁/下方展示 commit_sha 前 7 个字符(commit_sha.slice(0, 7)),使用等宽字体。
  5. 配色区分:徽章颜色需与项目徽章可区分——playbook 建议使用 var(--color-text-muted) 文字搭配柔和背景。

配套的 .card-branch 新 CSS 类规格(写入 Viewer 样式模板 src/ui/viewer-template.html):

属性 取值 依据
字号 ~11px .card-project 徽章一致
背景 var(--color-surface-hover) 一类柔和表面色 playbook 建议值
圆角 与现有徽章一致(模板中徽章圆角为 3px) 参照 viewer-template.html.card-merged-badgeborder-radius: 3px
SHA 部分 等宽字体 保证十六进制串等宽对齐

作为参照,现有徽章体系在模板中的定义是:.card-project 仅设 color: var(--color-text-muted).card-merged-badge 则是 9px 小字号、background: var(--color-type-badge-bg)、1px 边框、opacity: 0.85 的弱化样式。分支徽章介于两者之间——比 merged 徽章醒目、比 type 徽章克制——符合"次要元数据"的视觉层级。

5. 构建与验证:单文件 Viewer 的产出链路

Viewer 不是独立部署的 Web 应用,而是构建为单文件 HTML(内嵌 CSS/JS):

验证流程(playbook 的任务清单):

  1. package.json 中确认构建脚本:生产构建入口为 npm run build-and-sync,其展开为 npm run build && npm run sync-marketplace && node scripts/restart-marketplace-worker.cjs
  2. 类型检查有独立脚本:typecheck:viewertsc --noEmit -p src/ui/viewer/tsconfig.json,可在完整构建前单独快速校验 Viewer 侧改动(本例中类型层与组件层改动都在这里被覆盖);
  3. 运行 npm test 确认无回归;
  4. 检查构建产物 plugin/ui/viewer.html 中是否出现新的 .card-branch 样式与渲染分支。

6. 放到分支记忆全景中看

本 playbook 是 Branch-Parity 阶段的第 02 部分,同目录下还有:

  • BRANCH-PARITY-01-Chroma-Branch-Sync.md:让向量检索层(ChromaDB)感知分支边界——在 StoredObservation/StoredSummary/ChromaMetadata 三处接口加字段、在 formatObservationDocs() 中把分支元数据写进 Chroma 文档、在 buildWhereFilter() 中用 { commit_sha: { $in: commitShas } }$or(兼容无 commit_sha 的旧文档)实现分支感知过滤;
  • BRANCH-PARITY-03-MCP-Output-And-Verification.md:MCP 输出层的收尾与整体验证。

三个阶段共同指向同一架构思路:分支元数据在 SQLite 中一次性落库(migrations 24-25 给 observations 加列,Phase 01 还补了 session_summaries 的同名列),此后每一层消费方——分页查询、Chroma 同步、MCP 工具输出——都只是"把已有列透传出去"。Viewer 展示(本文主题)是这条透传链路的最后一环,让分支记忆从"数据可查"变成"用户可见"。

7. 小结

  • 改动极小、链路极清晰:可选字段进两个 Observation 接口(types.tsworker-types.ts),SELECT 补两列(PaginationHelper.ts),卡片条件渲染一枚徽章(ObservationCard.tsx),即可让分支元数据从数据库直达界面;
  • 向后兼容是硬性约束branch?: string | null 的可选声明与"truthy 才渲染徽章"的组合,保证迁移前的旧观察在类型系统与 UI 上都自然降级为"无分支",不产生占位或报错;
  • 验证闭环完整typecheck:viewernpm testnpm run build-and-sync 产出 plugin/ui/viewer.html,构建产物可人工核验新徽章的渲染结果。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384