news 2026/9/7 5:24:28

claude-mem 的 Merged-Worktree Adoption:用虚拟指针让已合并 worktree 的记忆自动归入父项目

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
claude-mem 的 Merged-Worktree Adoption:用虚拟指针让已合并 worktree 的记忆自动归入父项目

claude-mem 的 Merged-Worktree Adoption:用虚拟指针让已合并 worktree 的记忆自动归入父项目

【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem

本文围绕 claude-mem 仓库中的设计文档 Merged-Worktree Adoption 展开,讲清一套"git worktree 分支合并后,该 worktree 产生的 observations 与会话摘要如何被父项目'收养'"的完整方案:从merged_into_project虚拟指针列的幂等迁移、git 权威检测与收养引擎、SQLite/Chroma 双端一致查询,到 worker 启动自动触发与claude-mem adoptCLI 逃生舱。读完你可以掌握"不做数据搬迁、只加指针"的记忆归属设计,以及该功能在 WorktreeAdoption.ts 等源码中的真实落地形态。

问题背景:worktree 的记忆为什么会"散落"

claude-mem 通过project字段把记忆按项目隔离:当用户在 git worktree 里工作时,项目名会被推导为复合名父项目/worktree名(见 getProjectContext),从而与父仓库的记忆区分开。这带来一个副作用:分支合并进父仓库后,那些记忆仍然挂在claude-mem/feature-x这类复合名下,父项目的上下文注入与语义搜索默认"看不见"它们。

文档给出的目标非常明确:当某个 worktree 的分支被合并进父仓库时,该 worktree 的 observations 成为父项目观察列表的一部分——不做数据搬迁、不做破坏性 schema 变更、不丢失出处(provenance)

四条关键设计决策贯穿全文(这些约束在最终实现中全部成立):

  1. observations.project不可变的出处——永远不被覆写;
  2. 合并状态是一个虚拟指针merged_into_project列),不是数据移动;
  3. Chroma 元数据与 SQLite 保持同步(完整一致的同步,而非在 SQL 侧做惰性展开);
  4. 合并检测以git 为权威git worktree list --porcelain+git branch --merged),并为 squash-merge 提供 CLI 手动覆盖。

设计纪律:允许使用的 API 与反模式清单

文档最扎实的部分是 Phase 0——"文档考古",它先用三个并行子代理摸清了代码库中所有可复用的模式,并给出白名单与黑名单。这保证了后续实现"抄正确的作业"。

允许复制的 API(原始设计定位)

需求来源文件复制什么
一次性迁移 + 标记文件的幂等结构ProcessManager.tsrunOneTimeCwdRemap的 DB 生命周期(打开、事务、finally 关闭)
ALTER TABLE ADD COLUMN幂等保护PRAGMA table_info守卫模式加列前先用PRAGMA table_info(<table>)探测列是否存在
日志组件约定logger.tslogger.info/warn/error('SYSTEM', ...)
worktree 检测worktree.tsdetectWorktree(cwd):读.git文件的gitdir:行,匹配.git/worktrees/<name>得出父仓库
项目名推导project-name.tsgetProjectContext(cwd)返回{ primary, parent, isWorktree, allProjects },worktree 下primary为复合名
多项目读取查询(待扩展处)ObservationCompiler.tsqueryObservationsMultiWHERE o.project IN (...)
Chroma 元数据挂载ChromaSync.tsbaseMetadata对象即merged_into_project的注入点
Chroma 读侧过滤SearchManager.tswhereFilter = { project: options.project }处扩展$or
CLI 入口index.ts手写switch (command)+ 动态import(),无 commander/cac
管理脚本模板cwd-remap.ts 风格的 Bun 脚本dry-run 默认、--apply门控

反模式清单(实现中均被遵守)

  • 不得覆写observations.project/session_summaries.project(不可变出处);
  • 不得为合并记忆新建 Chroma 集合——部署使用单一共享cm__claude-mem集合,按元数据过滤;
  • 不得引入ghCLI 依赖——只用git子进程;
  • 不得使用 SQLite 不支持的ALTER TABLE ... ADD COLUMN IF NOT EXISTS,用PRAGMA table_info守卫代替;
  • 不得引入 CLI 框架(commander/cac/yargs),沿用手写switch+process.argv.slice(2)
  • 不得改写ProjectContext.allProjects来注入合并子项目——反向查找放在 SQL/Chroma 查询谓词里;
  • 不得走"SQL 先展开项目列表、再去 Chroma 过滤"的惰性路线——Chroma 元数据必须成为语义搜索的权威过滤器。

Phase 1:schema 迁移——一列一索引,幂等可重跑

方案是在observationssession_summaries上各加一个可空merged_into_project TEXT列和一个索引,幂等性靠PRAGMA table_info列存在性检查实现(O(1),重跑零成本)。

最终落地的迁移代码在 SessionStore.ensureMergedIntoProjectColumns,在构造函数迁移链中被调用(调用点),与文档设计的形态一致,仅有一个工程化差异:CREATE INDEX被移到了if块外、用IF NOT EXISTS自幂等,从而每次启动都会走到且无副作用:

private ensureMergedIntoProjectColumns(): void { const obsCols = this.db .query('PRAGMA table_info(observations)') .all() as TableColumnInfo[]; if (!obsCols.some(c => c.name === 'merged_into_project')) { this.db.run('ALTER TABLE observations ADD COLUMN merged_into_project TEXT'); } this.db.run( 'CREATE INDEX IF NOT EXISTS idx_observations_merged_into ON observations(merged_into_project)' ); // session_summaries 侧同构:idx_summaries_merged_into }

验证方式(文档原样保留):启动 worker 无迁移报错;sqlite3 ~/.claude-mem/claude-mem.db ".schema observations"能看到新列;重启 worker 不出现 ALTER 报错(守卫生效);.indices observations列出idx_observations_merged_into。两条迁移反模式守卫同样保留:不用ADD COLUMN IF NOT EXISTS;也不为此迁移递增schema_versions(那张表记录的是编号迁移历史,而列存在性检查本身即幂等)。

Phase 2:收养引擎——git 权威检测 + SQL/Chroma 双写一致

核心是一个可复用函数:给定父仓库路径,检测所有"已合并的 worktree 分支",并把merged_into_project同时打到 SQLite 行与 Chroma 元数据上。它被 worker 启动(Phase 4)与 CLI(Phase 5)共同复用。最终实现位于 WorktreeAdoption.ts,公共 API 与文档定义一致:

export async function adoptMergedWorktrees(opts: { repoPath?: string; // 默认 process.cwd() dataDirectory?: string; // 默认 DATA_DIR dryRun?: boolean; onlyBranch?: string; // squash-merge 场景的手动覆盖 }): Promise<AdoptionResult>;

检测流程(git 子进程,15 秒超时)

实现把 git 交互收敛在一个gitCapture(cwd, args)辅助函数里(WorktreeAdoption.ts#L51-L79):spawnSync('git', ['-C', cwd, ...args]),超过 15 秒记GIT_TIMEOUT_MS超时日志、>1s 的操作打 debug 慢操作日志,任何失败都降级为null并记录GIT组件警告——检测失败绝不抛错炸掉主流程。

按序执行:

  1. 解析主仓库路径git rev-parse --path-format=absolute --git-common-dir,剥掉/.git后缀得到工作树根(resolveMainRepoPath,即文档中scripts/cwd-remap.ts同款处理)。若当前目录不是 git 仓库,直接跳过并记 debug 日志。
  2. 解析父项目名getProjectContext(mainRepo).primary
  3. 枚举 worktreesgit -C <mainRepo> worktree list --porcelain,解析worktree <path>branch refs/heads/<name>行(listWorktrees),过滤掉主 worktree(路径等于 mainRepo 的条目)。
  4. 分类为"已合并":若传入onlyBranch则只取该分支(squash-merge 逃生舱);否则git branch --merged HEAD --format='%(refname:short)'得到合并集合,与 worktree 分支列表求交集(listMergedBranches)。
  5. 解析 worktree 项目名:对每个已合并 worktree 路径调getProjectContext(wt.path).primary,得到复合名父项目/worktree名——这正是当初写入observations.project的值,构成安全门:只有项目名精确匹配 worktree 复合名的行才会被收养。
  6. SQL 事务:打开自己的 DB 句柄(openConfiguredSqliteDatabase),先PRAGMA table_info确认两表列都已存在(若迁移尚未运行则跳过整个收养并记日志"will run after migration"),然后在单个db.transaction中对每个目标 worktree 执行:
UPDATE observations SET merged_into_project = ? WHERE project = ? AND merged_into_project IS NULL UPDATE session_summaries SET merged_into_project = ? WHERE project = ? AND merged_into_project IS NULL

IS NULL子句是幂等性的关键:第二次运行adoptedObservations = 0。实现还做了一处文档未写明的增强——在 UPDATE 前先SELECT id ... WHERE project = ? AND (merged_into_project IS NULL OR merged_into_project = ?)收集待同步到 Chroma 的行 ID(WorktreeAdoption.ts#L217-L232),这样即使行"已经指向同一个父项目",也能补打 Chroma 元数据。

  1. dry-run 用异常回滚:事务体末尾if (dryRun) throw new DryRunRollback()(自定义错误类)——事务回滚、统计数仍保留返回,CLI 即可打印"将要收养多少条"而不写任何东西。
  2. 双泳道云同步兼容:实现比文档更进一步。若数据库已启用两泳道同步(hasSyncLane 探测),则不走裸 UPDATE,而是调用 emitRemapProject——按 SyncApply 契约把remap_project变更操作排入同一事务、递增sync_rev并重置 native 行的synced_at,保证云端副本也能应用这次归属变更;旧库则回落到纯 UPDATE 路径(WorktreeAdoption.ts#L234-L266)。
  3. Chroma 元数据同步(全量一致,非惰性):SQL 事务提交后,对本次收集的行 ID 批量调用 ChromaSync.updateMergedIntoProject(入参类型 MergedIntoProjectTarget:{ docType, sqliteId }列表),按sqlite_id过滤查出文档 ID,把merged_into_project合并进元数据后chroma_update_documents一次写回。失败策略与文档一致:不回滚 SQL——SQL 是权威,Chroma 是派生索引;chromaFailed计数入结果,日志打Worktree adoption Chroma patch failed (SQL already committed),下次运行对同集合重打即可(元数据写成相同值是幂等 no-op)。

单分支错误被try/catch包住并收集进errors[](日志Worktree adoption skipped branch,配套 formatAdoptionErrors 把对象数组渲染成worktree: error; ...字符串避免日志出现[object Object]),其余分支继续执行。

Phase 3:查询管道——把指针变成第二条匹配轴

让"已收养"的记忆在两条读取路径上都可见:

SQLite 侧(ObservationCompiler.ts):多项目查询的 WHERE 从

WHERE o.project IN (${projectPlaceholders})

变为

WHERE (o.project IN (${projectPlaceholders}) OR o.merged_into_project IN (${projectPlaceholders}))

projects数组需双绑到两个占位符组(ObservationCompiler.ts#L53、#L81 两处 observation 查询,#L111 为 summary 变体,用ss.merged_into_project)。单项目路径的等价形态落在 SessionStore:additionalConditions.push('(o.project = ? OR o.merged_into_project = ?)')(summaries 侧见 #L2760)。注意o.project IN (...)谓词并未删除——合并行谓词是叠加而非替换。

Chroma 侧SearchManager的 project 过滤改为$or: [{ project }, { merged_into_project }],语义搜索(/search端点与 MCP 工具)因此直接命中被收养的行——这就是"Chroma 元数据是语义搜索权威过滤器"的落地。

新观测的元数据ChromaSync挂载新 observation 时把merged_into_project纳入baseMetadata(未设置时省略该字段,因为 Chroma 拒绝null元数据值——文档要求的"omit if unset"模式)。从此每条新记录从第一次同步起就与$or过滤器兼容;存量行则靠 Phase 2 的收养引擎补打。

ContextBuilder 兼容性generateContext()projects = input?.projects ?? context.allProjects无需改动,扩展后的 WHERE 子句完成了全部工作——worktree 本地查询(projects=[父, 父/wt])与父项目查询都自然收敛到正确集合。

Phase 4:worker 启动自动触发(且覆盖所有已知仓库)

文档设计是在runOneTimeCwdRemap()之后调用adoptMergedWorktrees({}),非标记门控——每次 worker 启动都跑,因为 git 状态会变化而引擎幂等。最终实现有一个更完善的演化:worker 启动时调用的是 adoptMergedWorktreesForAllKnownRepos,它只读打开 claude-mem.db,从pending_messages.cwd收集出所有已知父仓库集合,逐个执行收养,单仓库失败只告警不中断(worker-service.ts#L497)——这样无论你此刻在哪个目录启动了 worker,其它仓库上已发生但未收养的合并也能被补上。

三条生命周期守卫在源码中全部可见:

  • 不阻塞启动:调用以then(...)异步执行,错误被吞掉并记日志,启动流程继续;
  • 不晚于dbManager.initialize()产生句柄冲突:引擎自管 DB 句柄并在finally中关闭(WorktreeAdoption.ts#L309-L311);
  • Chroma I/O 不让启动挂死:Chroma 同步失败仅记chromaFailed计数。

预期行为:新合并落地后首次重启日志出现Worktree adoption applied(含parentProject / adoptedObservations / adoptedSummaries / chromaUpdates / chromaFailed / mergedBranches);后续重启无输出(幂等)。

Phase 5:CLI 逃生舱——claude-mem adopt

squash-merge 不会出现在git branch --merged里,自动检测必然漏掉它;CLI 就是为这个场景加通用覆盖能力而设。

命令模块 src/npx-cli/commands/runtime.ts 提供runAdoptCommand({ dryRun, onlyBranch }),入口按仓库惯例注册在 src/npx-cli/index.ts 的case 'adopt'中(参数解析形态):

npx claude-mem adopt --dry-run # 打印将收养的内容,不写任何数据 npx claude-mem adopt # 写入并打印各计数 npx claude-mem adopt --branch feature/foo # 强制收养该分支(squash-merge 场景)

输出包含:父项目名、扫描的 worktree 数、命中的已合并分支、收养的 observations/summaries 数、Chroma 更新数,以及chromaFailed > 0时的黄色提示(will retry on next run)和逐条红色! <worktree>: <error>

验收标准(文档保留):dry-run 不产生 DB 变更与 Chroma 写入;--branch接受与 worktree 复合名一致的分支名;不要求从 worktree 目录执行——检测永远向上解析到 common-dir;未知命令仍走既有报错路径(CLI 模式不变)。

Phase 6:UI 溯源徽章

查看器展示父项目上下文时,若某条 observation 源自已合并 worktree,需在卡片上显示 "merged → 父项目" 徽章,同时保留原project字段渲染(project= 出处,merged_into_project= 现居地,两者都有意义,不可互相替换)。涉及 ObservationCard.tsx 与 viewer 类型(merged_into_project?: string | null);徽章默认可见、无需开关,hover 显示完整目标项目名;在父项目视图中不得隐藏被合并的 observation——可见性正是这项功能的目的。

Phase 7:验证与反模式 grep 检查

单元测试(仓库中的对应测试文件可继续深挖):

  • adoptMergedWorktrees({ dryRun: true })对含[merged, unmerged, squash-merged]worktree 的 fixture 仓库 → 分类符合预期(配套测试 worktree-adoption-errors.test.ts);
  • ChromaSync.updateMergedIntoProject收到空 ID 列表 → no-op、不发起 Chroma 调用(配套测试 worktree-adoption-chroma.test.ts);
  • 扩展后的多项目查询在projectmerged_into_project混合命中下返回并集,按created_at_epoch DESC排序(observation-compiler.test.ts)。

集成测试:启动 worker → 在claude-mem/test-wt下制造合成 observations → 模拟git merge→ 重启 worker → 对claude-mem的 context-inject API 返回 test-wt 的观测;同样流程用 squash-merge 复现自动收养漏检 → 运行claude-mem adopt --branch test-wt后 API 返回它们;连跑两次adopt,第二次报告adoptedObservations: 0, chromaUpdates: 0。迁移本身的幂等性由 session-store-migrations.test.ts 覆盖,合并项目的 ID 水合由 merged-project-id-hydration.test.ts 覆盖。

落地前的 grep 巡检(文档原样给出,可复制执行):

# 没有任何人改写 project 字段 rg "UPDATE observations SET project" src/ # (预期:除既有 CWD remap 外零命中) # 收养只通过 IS NULL 守卫写入 rg "merged_into_project" src/ -C2 # (预期:所有 UPDATE 位置都带 "IS NULL" 谓词) # CLI 已注册 rg "case 'adopt'" src/npx-cli/index.ts # (预期:一处命中) # Chroma 元数据扩展存在 rg "merged_into_project" src/services/sync/ChromaSync.ts # (预期:baseMetadata 与 updateMergedIntoProject 均有命中) # 没有引入 gh CLI rg "\\bgh\\s+(pr|issue|api)" src/ scripts/ # (预期:.github/workflows/ 之外零命中)

可逆性、爆炸半径与架构一致性

  • 可逆UPDATE observations SET merged_into_project = NULL(summaries 同理)+ 一次省略该字段的 Chromaupdate_documents,即可完整恢复到收养前状态。没有任何东西被销毁。
  • 爆炸半径:对既有数据零风险(不写project字段);Chroma 侧只改元数据(嵌入向量不动);查询扩展只是附加 OR 子句——既有查询返回结果不变。
  • 架构一致性:从源码结构看,整条链路与既有的 CWD remap 一次性迁移(runOneTimeCwdRemap)共享相同的生命周期与日志惯例,Chroma 元数据同步沿用逐观测挂载模式;实现规模与文档估算(合计约 400 LOC)相符,并额外长出了两泳道云同步适配(remap-outbox.ts)与全仓库批量收养两个文档之外但完全兼容原设计的增强。

一句话总结这套设计的工程价值:用一个可空列 + 两条索引 + 两个$or/OR谓词,就让"合并"这一 git 事件在记忆系统里变成了自动、幂等、可逆、双端一致的归属迁移——而不是一次数据搬迁。

【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:23:27

智谱AI ZCode体验官招募:AI编程助手Coding Plan与3亿Token实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:23:17

手把手教你将frida-server封装为Magisk模块实现开机自启

简介&#xff1a;MagiskFrida 是一套用于安卓设备的 Magisk 模块方案&#xff0c;面向逆向工程与安全测试人员&#xff0c;解决 Frida 服务端无法在系统启动时以超级用户权限自动运行的问题。整个资源包体积仅 9KB&#xff0c;却包含 13 个文件&#xff0c;核心文件涵盖安装脚本…

作者头像 李华
网站建设 2026/9/7 5:22:45

MT7681实战解析:从电路设计到烧录调试的完整指南

简介&#xff1a;这份资源围绕联发科 WIFI 芯片 MT7681 展开&#xff0c;提供完整电路图与配套使用资料&#xff0c;适合物联网嵌入式开发者、硬件工程师及智能家居方案设计人员参考&#xff0c;可帮助理解 MT7681 的引脚定义、电源设计、射频匹配及外围电路搭建&#xff0c;降…

作者头像 李华
网站建设 2026/9/7 5:22:43

FPGA SRIO例程跑不通?从IP核配置到双端通信的完整调试指南

简介&#xff1a;FPGA SRIO例程是一份面向FPGA开发者的Serial RapidIO接口设计与回环验证资源&#xff0c;适用于学习高速串行通信协议、Verilog HDL编程以及FPGA工程调试的工程师。资源围绕SRIO回环传输机制展开&#xff0c;可帮助理解发送接收链路、CRC校验、错误处理及仿真测…

作者头像 李华