首页
/ claude-mem 从 Gemini CLI 迁移到 Antigravity CLI:以实证验证驱动的宿主平台替换方案

claude-mem 从 Gemini CLI 迁移到 Antigravity CLI:以实证验证驱动的宿主平台替换方案

2026-09-06 16:34:31作者:钟日瑜

Google 官方确认 Gemini CLI 的免费个人访问将于 2026 年 6 月 18 日停止,并由可独立运行、支持 headless 的 Antigravity CLI(agy 二进制)接替。本文以 claude-mem 仓库中的迁移计划 plans/2026-07-03-antigravity-cli-migration.md 为主体,完整复盘这次「删除 Gemini CLI 宿主集成、新增 Antigravity CLI 全功能对等集成」的迁移工程:如何划定迁移边界、如何用一台真实装机环境(B0 验证尖峰)把假设变成确认事实、如何幂等地合并 hook 配置、如何处理 MCP 配置路径的双重写入歧义,以及为什么「测试通过」在这个场景下不是充分的证据。读完后,你将掌握一套处理「第三方 CLI 换壳、官方 schema 不公开」这类平台迁移问题的完整方法论,以及 claude-mem 当前 Antigravity CLI 集成的实际实现细节。

一、为什么迁移:边界划定是第一道防线

Google 于 2026 年 5 月 19 日官宣:免费/个人层的 Gemini CLI 访问被 Antigravity CLI 取代。后者复用了 Gemini CLI 的 ~/.gemini/ 配置树,并「保留了 Gemini CLI 最关键的能力:Agent Skills、Hooks、Subagents 和 Extensions」。对 claude-mem 这样的跨 Agent 记忆插件来说,这意味着一个「宿主平台」需要替换——但它必须极其小心地划定范围:

只做宿主集成替换,绝不触碰 LLM Provider。 计划文档中的 Scope Lock 明确区分了两个完全独立的「Gemini」:

  • 要删除的:Gemini CLI host 集成——平台适配器、安装器、hook、IDE 检测入口、文档(src/cli/adapters/gemini-cli.tssrc/services/integrations/GeminiCliHooksInstaller.tstests/gemini-cli-compat.test.tsdocs/public/gemini-cli/setup.mdx)。
  • 绝对不动的:Gemini LLM/观察 provider——CLAUDE_MEM_GEMINI_API_KEYGeminiProvider.tsGeminiObservationProvider.tsSettingsDefaultsManager.ts 中的 Gemini 键、用户文档 docs/public/usage/gemini-provider.mdx 等。这是另一个 Google 产品(Gemini API),并未被弃用。

这条边界划定的价值在于:迁移前后的验证可以互为镜像——删除完成后,grep -ril "CLAUDE_MEM_GEMINI\|GeminiProvider\|GeminiObservationProvider" src/ 必须仍返回全部命中,以此反证 LLM provider 未被误伤。仓库中当前文档 docs/public/antigravity-cli/setup.mdx 的「Step 2: Configure an AI provider」一节也印证了这一点:观察提取的 provider 配置(gemini/claude/openrouter 三种)与宿主 CLI 的替换完全解耦。

另一个边界是不发明 schema:Google 的 Antigravity 文档页是无法抓取的 Angular SPA,hook 事件名/JSON 结构没有任何独立公开文档。计划中为此设立了「反模式护栏」——不得编写断言任何未经证实的 hook 事件名、CLI flag 或配置路径的安装器代码;无法确认时,复制 Gemini CLI 的原始 schema 并以内联注释 // HYPOTHESIS: verify against real Antigravity CLI install 标记,绝不静默假设正确。

二、B0 验证尖峰:把假设变成文件系统级事实

迁移计划最核心的方法论贡献是 Phase B0 —— 验证尖峰。由于开发机上已经装有 Antigravity IDE 和独立 Antigravity CLI,并且共享同一棵 ~/.gemini/ 配置树(上面还挂着 claude-mem 旧版 Gemini CLI hook 的活安装),B0 从「假设演练」直接变成了「文件系统/二进制检查」:不触发任何实时 agy 会话或模型调用(避免消耗用户 API 配额),只读取磁盘。七项确认事实:

  1. 二进制名是 agy,不是 antigravity~/.local/bin/agy(v1.0.16)才是真正的独立 headless CLI;~/.antigravity/antigravity/bin/antigravity(v1.107.0)是桌面 IDE 的内部可执行文件。检测必须用 isCommandInPath('agy')
  2. hook 配置位置确认~/.gemini/settings.json,与 Gemini CLI 用的是同一个文件。证据是旧安装器写入的 hook 就活在这个文件里,机器上 agy 每日在用——同一个文件、同一套 JSON schema(hooks.<Event>[].hooks[].{name,type,command,timeout})。不存在单独的 ~/.gemini/antigravity-cli/settings.json hook 块。
  3. hook 事件名是 7 事件的超集:磁盘实况配置里有 8 个事件,比旧安装器源码中的 7 个多一个 SessionEnd——这是旧安装器版本留下的文档/代码漂移。另外 agy --help 没有 hooks 子命令,hooks 是纯配置文件驱动(声明在 settings.json 里,没有 CLI 注册步骤)。
  4. MCP 配置路径存在两个真实文件,真歧义~/.gemini/antigravity/mcp_config.json(旧路径,已有 claude-mem 注册且在用)和 ~/.gemini/config/mcp_config.json(新路径,存在但为空)。决策是双写:两条路径都是廉价的幂等 JSON 合并,等 Phase C 的实机门槛确认真正被读取的是哪个。
  5. 规则/上下文路径确认,且现有代码本来就是对的~/.agents/rules/复数 agents)真实存在且已填充;此前第三方资料引用的单数 .agent/rules/ 对这台机器是错的。
  6. ~/.gemini/GEMINI.md 确认在用:上下文注入目标无需变更。
  7. 新架构发现(范围外的跟进项)agy plugin {list,import,install,uninstall,enable,disable,validate,link} 是一套一等公民插件市场子命令系统,agy plugin import gemini|claude 暗示原生跨工具插件迁移能力。它的 manifest schema 不实际运行无法发现,故列为未来增强而非本次范围。

B0 的结论是「用确认值而非假设进入 B1」。这一套发现直接沉淀进了最终代码:AntigravityCliHooksInstaller.ts 第 36-39 行的注释明确写着「Antigravity CLI (agy) 共享 Gemini CLI 的 ~/.gemini/ 配置树——2026-07-03 对照活安装确认(见计划文档 Phase B0 findings)。不是笔误或遗留」。

三、Phase A:删除 Gemini CLI 宿主集成

删除阶段的特征是「机械、低风险、独立可验证」,计划把它作为可整体回滚的干净单元,且要求先于 Phase B 完整执行

A1 删除专属文件src/cli/adapters/gemini-cli.ts(79 行)、src/services/integrations/GeminiCliHooksInstaller.ts(393 行)、tests/gemini-cli-compat.test.ts(204 行)、docs/public/gemini-cli/setup.mdx(192 行)。

A2 从枢纽文件注销,涉及七处:

  • src/cli/adapters/index.ts:移除 geminiCliAdapter 的 import、case 'gemini': case 'gemini-cli': 分发与 barrel 导出。
  • src/npx-cli/commands/ide-detection.ts:移除 gemini-cli 检测条目。
  • src/npx-cli/commands/install.ts:移除动态 import installGeminiCliHooks()case 'gemini-cli': 分支。
  • src/npx-cli/commands/uninstall.ts:移除 Gemini CLI hooks 卸载条目。
  • src/services/worker-service.ts:移除 handleGeminiCliCommand 的 import 与 case 'gemini-cli': 分发,更新平台 help 文本。
  • src/cli/handlers/context.ts:移除 platform === 'gemini-cli' 展示分支——但计划特意要求先验证该分支属于 hook 调用的平台展示、而非读取 CLAUDE_MEM_PROVIDER 的 LLM provider 选择,后者不得触碰。
  • src/npx-cli/index.ts 的 help 文本与 src/services/integrations/install-paths.ts 的注释同步更新。

A3 测试tests/install-error-matrix.test.ts 的 IDE 矩阵移除 'gemini-cli'tests/infrastructure/plugin-distribution.test.ts 移除对 GeminiCliHooksInstaller.ts 出现在发行清单中的断言。

A4 文档docs/public/docs.json 移除 "Gemini CLI Integration" 导航组;installation.mdxintroduction.mdxREADME.md 中的支持列表与 npx claude-mem install --ide gemini-cli 示例同步删除。

A5 谨慎处理共享代码src/shared/transcript-parser.ts 中的 isGeminiTranscriptFormat()/extractLastMessageFromGeminiTranscript() 解析 Gemini CLI 的转录文件——计划要求删除前先 grep 调用方,若摘要管线仍在使用则保留,「不要盲目删除」。

A6 验证清单

# 功能引用归零(CHANGELOG.md 历史除外)
grep -ril "gemini-cli\|GeminiCliHooksInstaller\|geminiCliAdapter" src/ tests/ docs/public/ README.md
# LLM provider 未被误伤(应返回全部原有命中)
grep -ril "CLAUDE_MEM_GEMINI\|GeminiProvider\|GeminiObservationProvider" src/
# 构建与完整测试
npm run build-and-sync
npm test

四、Phase B:Antigravity CLI 全对等集成的实现

B1:IDE 检测就地升级,不 fork 重复条目

ide-detection.ts 中已有的 antigravity 条目原本仅覆盖 MCP-only 场景。计划选择在原条目上升级而不是新增 antigravity-cli 重复 id——因为 Antigravity IDE 与 CLI 共享同一个 ~/.gemini/antigravity 配置命名空间,两条几乎相同的条目会干扰安装选择器:

{
  id: 'antigravity',
  label: 'Antigravity',
  detected: existsSync(join(home, '.gemini', 'antigravity')) || isCommandInPath('agy'),
  hint: 'hooks + MCP integration',
},

当前仓库中这段代码即为最终形态:目录探测(IDE/CLI 任一)与 agy 命令探测取或,isCommandInPath 在 Windows 上走 where.exe、其他平台走 whichide-detection.ts)。

B2:新安装器 AntigravityCliHooksInstaller.ts

计划要求整份复制 GeminiCliHooksInstaller.ts 作为起点(Cursor/Windsurf/Gemini 安装器之间互抄是代码库既有模式;「44% 可削减重复」的安装器工厂抽象明确列为范围外)。最终落地的 AntigravityCliHooksInstaller.ts 体现了计划中的每个决策:

共享配置路径常量(第 40-58 行):

const GEMINI_CONFIG_DIR = path.join(homedir(), '.gemini');
const GEMINI_SETTINGS_PATH = path.join(GEMINI_CONFIG_DIR, 'settings.json');
const GEMINI_MD_PATH = path.join(GEMINI_CONFIG_DIR, 'GEMINI.md');

// B0 发现两个真实且真歧义的 MCP 配置路径 —— 双写直到 Phase C 实机门槛
// 确认 agy 实际读取哪一个
const ANTIGRAVITY_MCP_CONFIG_PATHS = [
  path.join(GEMINI_CONFIG_DIR, 'antigravity', 'mcp_config.json'),
  path.join(GEMINI_CONFIG_DIR, 'config', 'mcp_config.json'),
];

const RULES_CONTEXT_PATH = path.join(homedir(), '.agents', 'rules', 'claude-mem-context.md');

注意 RULES_CONTEXT_PATH 的注释还记录了一个被 Phase C 实机测试证实的错误:旧版 MCP-only 的 ANTIGRAVITY_CONFIGprocess.cwd() 作为基准,会把规则文件写进「安装器碰巧运行的目录」而非用户的全局规则目录——B0 确认的 ~/.agents/rules/(复数、家目录相对)才是对的。

事件映射表(第 66-74 行)是 B0 确认值的直接落地,有一个与计划文本的关键差异:

// 7 个确认存活的 hook 事件(B0)。SessionEnd 被刻意排除:
// 'session-complete' 在 src/cli/handlers/index.ts 中没有处理器——
// 它被有意移除(见 CHANGELOG.md:777),因为 worker 会自我完成。
const ANTIGRAVITY_EVENT_TO_INTERNAL_EVENT: Record<string, string> = {
  'SessionStart': 'context',
  'BeforeAgent': 'session-init',
  'AfterAgent': 'observation',
  'BeforeTool': 'observation',
  'AfterTool': 'observation',
  'Notification': 'observation',
  'PreCompress': 'summarize',
};

计划 B2 原文要求「使用确认的 8 事件映射」(含 SessionEnd→session-complete),但最终实现落到了 7 个——因为内部事件 session-complete 已经没有处理器,注册了也没有消费方。测试用例 tests/antigravity-cli-compat.test.ts 显式锁定了这一决策:expect(src).not.toContain("'SessionEnd':")。这是「磁盘上合法」与「本系统需要」之间的一次主动取舍,值得注意。(文档页 docs/public/antigravity-cli/setup.mdx 的「What gets captured」表格仍列出 8 行,将 SessionEnd 一并标注为已确认——文档与实现存在这处已知差异。)

hook 命令与幂等合并buildHookCommand 生成 "${bunPath}" "${workerPath}" hook antigravity-cli ${internalEvent}(对反斜杠做了 Windows 转义),每个事件生成一个 matcher: '*' 的 group,hook 统一命名为 claude-mem、超时 10000ms。合并逻辑 mergeHooksIntoSettings(第 129-163 行)是从 Gemini 安装器原样复制的通用 JSON-group 合并:按 name === 'claude-mem' 定位既有 group 与 hook,存在则原位替换(保证幂等,不重复追加),不存在才 push。读配置时若 JSON 损坏则拒绝覆盖用户设置并抛错(第 104-120 行)。

MCP 双写(第 203-216 行):registerAntigravityMcp 对两个候选路径逐个调用 seedEmptyMcpConfigFile + 复用自 McpIntegrations.tswriteMcpJsonConfig。这里有一个精细的边界处理:B0 发现 ~/.gemini/config/mcp_config.json0 字节空文件(CLI 应用数据目录旁边新建的占位符),而 readJsonSafe 的契约是「对空文件抛错以防真实数据损坏」——这个契约必须为其他所有调用方保持完整,于是安装器在委托共享写者之前,只对零字节文件预填 {}(第 197-201 行);读取路径上的 readMcpConfigTolerantly 做同样的空文件容忍(第 296-300 行),真实损坏内容仍按「corrupt」处理。

安装/卸载/状态分发器handleAntigravityCliCommand(subcommand, args) 支持 install/uninstall/statusstatus 按要求报告两个 MCP 配置路径各自的注册状态(第 456-464 行);卸载函数按名字精准移除 claude-mem 的 hook 与 mcpServers['claude-mem'] 条目、清理 GEMINI.md 与规则文件中的 <claude-mem-context> 标签块,其余用户配置原样保留(第 327-393 行)。

B3:新适配器 antigravity-cli.ts

antigravity-cli.ts 以 79 行的 Gemini 适配器为底本,三个关键点:

  • 环境变量回退链(第 8-13 行):r.cwd ?? GEMINI_CWD ?? GEMINI_PROJECT_DIR ?? CLAUDE_PROJECT_DIR ?? process.cwd()。这是 B0 唯一没能闭环的一点——hook 在触发前无法发现 Antigravity 专属 env var——代码在此处保留了计划要求的诚实标记:// unverified: confirm Antigravity sets GEMINI_* env vars on first real hook firing
  • AfterAgent 的 prompt 映射(第 28-32 行):把 prompt_response 规约为 toolName: 'AntigravityProvider' + 标准 toolInput/toolResponse 三元组;BeforeTool 无响应时打 _preExecution: true 标记(第 34-36 行);Notification 映射为 AntigravityNotification(第 38-45 行)。
  • ANSI 剥离(第 68-69 行):formatOutput 用正则从 systemMessage 中删除转义序列——这是从 Gemini 适配器继承的真实 bug 修复(hook 输出中的原始 [31m 之类序列),终端类 CLI 没有理由免疫同类问题。

注册在 adapters/index.tscase 'antigravity': case 'antigravity-cli': return antigravityCliAdapter;

B4:枢纽文件注册与旧路径收敛

  • worker-service.ts 中新增 case 'antigravity-cli': 分发到 handleAntigravityCliCommand,且 hook 命令的平台 help 文本更新为 claude-code, codex, cursor, antigravity-cli, raw
  • install.ts 的交互安装器 case 'antigravity': 分支动态 import 新的合并安装器(hooks + MCP),取代旧的 MCP-only 路径。
  • McpIntegrations.ts 中旧的独立 ANTIGRAVITY_CONFIG 条目被移除,注释明确写道:'antigravity' is intentionally absent here. It graduated from an ...(升级为合并安装器)——落实了计划「不能留下两条都声称安装 Antigravity MCP 的活代码路径」的要求。
  • context.ts 保留了平台特化展示分支:platform === 'antigravity-cli' ? additionalContext : ''

B5:测试——锁死 B0 确认值

tests/antigravity-cli-compat.test.ts 镜像了被删除的 Gemini 测试的结构,但测的是真实确认行为而非假设

  • 安装器侧:逐条断言 7 事件映射表、断言 hook 命令串含 hook antigravity-cli不含 hook gemini-cli、断言目标是共享的 ~/.gemini 树而非独立 Antigravity 文件、断言双写两个 MCP 候选路径、断言复用 writeMcpJsonConfig 而非重写、断言规则文件写到复数家目录路径 ~/.agents/rules/
  • 适配器侧:cwd 回退链(无 env 时落回 process.cwd()、显式 cwd 优先、空 cwd 抛 adapter rejected input: invalid_cwd)、AfterAgent/BeforeTool/Notification 的字段规约、ANSI 剥离(\x1b[31mRed text\x1b[0mRed text)、continue 默认 true 与 additionalContext 透传。
  • 文件末尾有一段值得借鉴的工程注记(第 141-150 行):B0 空 MCP 配置文件的边界场景刻意没有提交自动化回归测试——因为 Bun 的 homedir() 不会重读运行期重赋值的 process.env.HOME,任何试图重定向 GEMINI_CONFIG_DIR 的测试都会在真实 ~/.gemini 上操作,而这个陷阱若在 CI 无人值守运行时「每次运行都改动一台真实机器的活配置树」,危害大于收益。该边界改用一次性独立进程脚本人工验证。

五、Phase C:硬门槛——「测试通过」不充分的唯一场景

Phase C 是整个计划中最反直觉的部分:在 CI 与全量测试之上,还设了一道不可由 CI 替代的手动验证门槛。逻辑是:hook 是否真的在 agy 会话里触发、MCP 工具是否真的可调用,这些只有活会话才能证明。清单包括:

  1. 全仓 grep 清扫:gemini-cli/GeminiCliHooksInstaller/geminiCliAdapterCHANGELOG.md 之外零残留;旧的 ANTIGRAVITY_CONFIG MCP-only 代码路径零残留。
  2. 全量测试套件通过(不是仅构建通过);npm run build-and-sync 后 worker 干净重启。
  3. 在本机真实运行 claude-mem install --ide antigravity:必须对既有活 hooks 产生超集/no-op 安全合并——不得重复或损坏 ~/.gemini/settings.json 里已存在的 claude-mem 命名 hook 条目(这正是 mergeHooksIntoSettingsname 幂等替换要保障的性质)。
  4. 启动真实 agy 会话(交互式或 agy -p "..." print 模式),执行一次工具调用,确认 hook 触发(检查 worker 日志/数据库中是否有测试运行时间戳之后的新捕获观察)。
  5. 确认 GEMINI.md 上下文注入不回归;通过活会话确认 agy 到底读 ~/.gemini/antigravity/mcp_config.json~/.gemini/config/mcp_config.json 还是两者——这是解析 B0 遗留双写歧义的最终手段。
  6. 任何一项失败都不合并——修掉 schema/路径假设后重新验证。
  7. 不手改 CHANGELOG(按仓库约定由自动化生成)。

计划末尾的 /do 执行说明补充了工序纪律:Phase A 必须先于 Phase B 完整执行并保持为可回滚单元;B0 已完成,执行者必须直接基于确认值构建,不得重新推导或重新假设;唯一开放项(MCP 双写歧义)按 B2 实现双写、交给 Phase C 硬门槛消解;门槛通过后开 PR 并交接 /babysit 做 CI 监控。

六、当前仓库中集成的最终形态

迁移完成后,Antigravity CLI 集成在仓库中的触点收敛为:

触点 文件 职责
安装器 src/services/integrations/AntigravityCliHooksInstaller.ts 7 事件 hook 幂等合并进 ~/.gemini/settings.jsonGEMINI.md~/.agents/rules/ 上下文占位;双写 MCP 配置;install/uninstall/status 分发
平台适配器 src/cli/adapters/antigravity-cli.ts hook 输入规约(cwd 回退链、AfterAgent/BeforeTool/Notification 映射)与输出格式化(ANSI 剥离)
适配器注册 src/cli/adapters/index.ts antigravity / antigravity-cli 两个 id 归一
IDE 检测 src/npx-cli/commands/ide-detection.ts ~/.gemini/antigravity 目录或 agy 命令探测
命令入口 src/npx-cli/index.ts / src/services/worker-service.ts claude-mem antigravity-cli <cmd> 与 worker 侧分发
兼容测试 tests/antigravity-cli-compat.test.ts 锁定 B0 确认值与适配器行为
用户文档 docs/public/antigravity-cli/setup.mdx 安装步骤、8 事件表、MCP 双写说明、排障与卸载

用户侧操作面(详见 docs/public/antigravity-cli/setup.mdx):

# 安装(自动检测 agy / ~/.gemini/antigravity)
npx claude-mem install --ide antigravity

# 验证
npx claude-mem status
cat ~/.gemini/settings.json | grep claude-mem
cat ~/.gemini/antigravity/mcp_config.json | grep claude-mem
cat ~/.gemini/config/mcp_config.json | grep claude-mem

# 日常管理
npx claude-mem antigravity-cli install | status | uninstall

安装后重启 agy,会话开始时经 GEMINI.md、MCP 服务器与规则文件三条通道注入历史上下文;agy plugin 插件市场路径作为未来增强被记录在文档中,本次未实现。

七、这套迁移方法论的可复用要点

plans/2026-07-03-antigravity-cli-migration.md 及其落地实现中,可以提炼出面对「第三方平台换壳、schema 不公开」类迁移的四条实践:

  1. 先划边界再动手:明确「只删宿主集成、不动 LLM provider」这类看似同名的两个子系统,并给删除阶段配备镜像验证(grep 归零 + provider 命中不变),让误伤可被机器检出。
  2. 用只读实地检查替代假设:在真实装机的文件系统上直接读配置、对照二进制版本,把「Google 说 hooks 沿用」这种厂商声明落实为「同一个文件、同一套 schema、8 个事件名」的可引用事实;凡读不到的点(如 Antigravity 专属 env var)保留 // unverified 标记,不悄悄填满。
  3. 歧义处用幂等双写换时间:MCP 配置路径两个候选文件都无法证伪时,选择两次廉价的幂等 JSON 合并,而不是凭第三方博客赌一个——并在文档中如实记录「双写、待实机确认」。
  4. 把「测试不够」显式化:hook 是否真的触发、MCP 工具是否真的可达,属于活系统性质,自动化测试覆盖不了;与其让 CI 给出虚假安全感,不如设一道明确「不能跳过、不能由 CI 代替」的手动合并门槛,并规定失败即不合并。

这套「事实表(Allowed facts)→ 验证尖峰(B0)→ 对等实现(B1-B6)→ 硬门槛(Phase C)」的骨架,本身就是该仓库处理平台迁移的标准范式,也为后续 agy plugin 市场这类新集成路径的接入留好了跟进入口。

登录后查看全文
热门项目推荐
相关项目推荐