
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。四条关键设计决策贯穿全文这些约束在最终实现中全部成立observations.project是不可变的出处——永远不被覆写合并状态是一个虚拟指针merged_into_project列不是数据移动Chroma 元数据与 SQLite 保持同步完整一致的同步而非在 SQL 侧做惰性展开合并检测以git 为权威git worktree list --porcelaingit 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.tsqueryObservationsMulti中WHERE o.project IN (...)Chroma 元数据挂载ChromaSync.tsbaseMetadata对象即merged_into_project的注入点Chroma 读侧过滤SearchManager.tswhereFilter { project: options.project }处扩展$orCLI 入口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沿用手写switchprocess.argv.slice(2)不得改写ProjectContext.allProjects来注入合并子项目——反向查找放在 SQL/Chroma 查询谓词里不得走SQL 先展开项目列表、再去 Chroma 过滤的惰性路线——Chroma 元数据必须成为语义搜索的权威过滤器。Phase 1schema 迁移——一列一索引幂等可重跑方案是在observations与session_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与 CLIPhase 5共同复用。最终实现位于 WorktreeAdoption.ts公共 API 与文档定义一致export async function adoptMergedWorktrees(opts: { repoPath?: string; // 默认 process.cwd() dataDirectory?: string; // 默认 DATA_DIR dryRun?: boolean; onlyBranch?: string; // squash-merge 场景的手动覆盖 }): PromiseAdoptionResult;检测流程git 子进程15 秒超时实现把 git 交互收敛在一个gitCapture(cwd, args)辅助函数里WorktreeAdoption.ts#L51-L79spawnSync(git, [-C, cwd, ...args])超过 15 秒记GIT_TIMEOUT_MS超时日志、1s 的操作打 debug 慢操作日志任何失败都降级为null并记录GIT组件警告——检测失败绝不抛错炸掉主流程。按序执行解析主仓库路径git rev-parse --path-formatabsolute --git-common-dir剥掉/.git后缀得到工作树根resolveMainRepoPath即文档中scripts/cwd-remap.ts同款处理。若当前目录不是 git 仓库直接跳过并记 debug 日志。解析父项目名getProjectContext(mainRepo).primary。枚举 worktreesgit -C mainRepo worktree list --porcelain解析worktree path与branch refs/heads/name行listWorktrees过滤掉主 worktree路径等于 mainRepo 的条目。分类为已合并若传入onlyBranch则只取该分支squash-merge 逃生舱否则git branch --merged HEAD --format%(refname:short)得到合并集合与 worktree 分支列表求交集listMergedBranches。解析 worktree 项目名对每个已合并 worktree 路径调getProjectContext(wt.path).primary得到复合名父项目/worktree名——这正是当初写入observations.project的值构成安全门只有项目名精确匹配 worktree 复合名的行才会被收养。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 NULLIS NULL子句是幂等性的关键第二次运行adoptedObservations 0。实现还做了一处文档未写明的增强——在 UPDATE 前先SELECT id ... WHERE project ? AND (merged_into_project IS NULL OR merged_into_project ?)收集待同步到 Chroma 的行 IDWorktreeAdoption.ts#L217-L232这样即使行已经指向同一个父项目也能补打 Chroma 元数据。dry-run 用异常回滚事务体末尾if (dryRun) throw new DryRunRollback()自定义错误类——事务回滚、统计数仍保留返回CLI 即可打印将要收养多少条而不写任何东西。双泳道云同步兼容实现比文档更进一步。若数据库已启用两泳道同步hasSyncLane 探测则不走裸 UPDATE而是调用 emitRemapProject——按 SyncApply 契约把remap_project变更操作排入同一事务、递增sync_rev并重置 native 行的synced_at保证云端副本也能应用这次归属变更旧库则回落到纯 UPDATE 路径WorktreeAdoption.ts#L234-L266。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。单项目路径的等价形态落在 SessionStoreadditionalConditions.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 4worker 启动自动触发且覆盖所有已知仓库文档设计是在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-L311Chroma I/O 不让启动挂死Chroma 同步失败仅记chromaFailed计数。预期行为新合并落地后首次重启日志出现Worktree adoption applied含parentProject / adoptedObservations / adoptedSummaries / chromaUpdates / chromaFailed / mergedBranches后续重启无输出幂等。Phase 5CLI 逃生舱——claude-mem adoptsquash-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 6UI 溯源徽章查看器展示父项目上下文时若某条 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.tsChromaSync.updateMergedIntoProject收到空 ID 列表 → no-op、不发起 Chroma 调用配套测试 worktree-adoption-chroma.test.ts;扩展后的多项目查询在project与merged_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 NULLsummaries 同理 一次省略该字段的 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),仅供参考