claude-mem 中 Claude Code 与 Cursor 双平台 Hook 体系的功能对齐(Feature Parity)解析
本文围绕 cursor-hooks/PARITY.md 展开,完整解析 claude-mem 项目为 Cursor 实现的 Hook 集成与 Claude Code 原生 Hook 之间的逐项功能对齐情况:从会话初始化、上下文注入、观察(Observation)捕获到会话摘要的六大能力对比,以及 Cursor 平台限制下的补齐方案(如自动刷新的 .cursor/rules/ 规则文件)。读完本文,你可以准确判断两套 Hook 体系的差异点与实现边界,并结合仓库源码验证每一项对齐结论背后的真实实现。
背景:为什么需要一份 Hook 对齐文档
claude-mem 的核心机制是通过宿主 Agent(Claude Code、Cursor 等)的 Hook 系统,在会话生命周期关键节点(开始、提交提示词、工具执行后、会话结束)调用本地 worker 服务,完成"会话登记 → 观察捕获 → AI 摘要 → 上下文回注"的闭环。项目最初面向 Claude Code 实现了完整的 Hook 集(SessionStart、UserPromptSubmit、PostToolUse、Stop 等事件),随后为了支持 Cursor,又实现了一套基于 Cursor Hook 事件(beforeSubmitPrompt、afterMCPExecution、afterShellExecution、afterFileEdit、stop)的等价能力。
两个平台的 Hook 事件模型并不相同——事件名称、触发时机、可获取的输入数据(例如 Cursor 没有 transcript 路径)都有差异。因此 PARITY.md 的存在意义,就是作为一份"功能对齐验收清单",逐条回答:Cursor 版是否覆盖了 Claude Code 版的每一项能力?差距在哪里?如何用平台特性补齐?
一、Hook 事件映射总表
PARITY.md 开篇给出了两套 Hook 的映射关系,这是全文的骨架:
| Claude Code Hook | Cursor Hook | 状态 | 说明 |
|---|---|---|---|
SessionStart → context-hook.js |
beforeSubmitPrompt → context-inject.sh |
✅ Partial | 上下文可获取,但无法直接注入 |
SessionStart → user-message-hook.js |
(可选)user-message.sh |
⚠️ Optional | 无 SessionStart 等价物,可挂在 beforeSubmitPrompt |
UserPromptSubmit → new-hook.js |
beforeSubmitPrompt → session-init.sh |
✅ Complete | 会话初始化、隐私检查、斜杠命令剥离 |
PostToolUse → save-hook.js |
afterMCPExecution + afterShellExecution → save-observation.sh |
✅ Complete | 工具观察捕获 |
PostToolUse → (文件编辑) |
afterFileEdit → save-file-edit.sh |
✅ Complete | 文件编辑观察捕获 |
Stop → summary-hook.js |
stop → session-summary.sh |
⚠️ Partial | 摘要生成(无 transcript 访问) |
映射配置在仓库中的实际位置
- 仓库内的映射模板是 cursor-hooks/hooks.json,它将五个 Cursor 事件分别绑定到对应脚本:
beforeSubmitPrompt依次执行session-init.sh与context-inject.sh;afterMCPExecution与afterShellExecution共用save-observation.sh;afterFileEdit绑定save-file-edit.sh;stop绑定session-summary.sh。 - 从源码结构看,当前安装器 src/services/integrations/CursorHooksInstaller.ts 生成的
hooks.json已切换为"统一 CLI 模式":不再逐个绑定 shell 脚本,而是把每个事件都指向同一条命令bun <worker-service.cjs 绝对路径> hook cursor <command>,其中<command>取值session-init、context、observation、file-edit、summarize。也就是说,session-init.sh等脚本描述的逻辑,现在由 CLI 统一分发到 src/cli/handlers/ 下的 TypeScript handler 实现,PARITY.md 中的"脚本名 ↔ Claude Code hook 名"映射在概念层面仍然成立,只是执行体从 shell 脚本收敛到了统一 CLI 入口。 - 安装器还支持
project(当前目录.cursor/)、user(~/.cursor/)、enterprise(macOS 的/Library/Application Support/Cursor、Linux 的/etc/cursor、Windows 的ProgramData\Cursor)三种安装层级,见 CursorHooksInstaller.ts 的getTargetDir()。
二、六大功能项的逐项对齐
PARITY.md 将对比拆分为六个小节,下面逐一展开,并给出仓库内的源码级佐证。
1. 会话初始化(new-hook.js ↔ session-init.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| Worker 健康检查 | ✅ 75 次重试(15s) | ✅ 75 次重试(15s) | ✅ Match |
| 会话初始化 API 调用 | ✅ /api/sessions/init |
✅ /api/sessions/init |
✅ Match |
| 隐私检查处理 | ✅ 检查 skipped + reason |
✅ 检查 skipped + reason |
✅ Match |
| 斜杠剥离 | ✅ 剥离前导 / |
✅ 剥离前导 / |
✅ Match |
| SDK agent 初始化 | ✅ /sessions/{id}/init |
❌ 不需要 | ✅ N/A(Cursor 特有) |
结论:完整对齐(SDK agent 初始化对 Cursor 不适用)。
源码佐证:Cursor 侧最终落到 src/cli/handlers/session-init.ts 的 sessionInitHandler,可以确认 PARITY 表中每一行的实际实现:
/api/sessions/init调用:handler 通过executeWithWorkerFallback('/api/sessions/init', 'POST', {...})发起请求,body 包含contentSessionId、project、prompt、platformSource(见 session-init.ts#L113-L125)。项目名通过getProjectContext(cwd).primary从工作目录推导,与 PARITY 表中"项目名提取"对齐。- 隐私检查:响应中的
skipped+reason === 'private'会被显式处理并静默跳过后续注入(session-init.ts#L143-L148),与文档"Worker 执行隐私检查、hook 尊重skipped标志"的描述一致。 - 超时预算:对 Codex 平台,worker 启动等待使用
HOOK_TIMEOUTS.POST_SPAWN_WAIT(15 秒),见 src/shared/hook-constants.ts。这解释了 PARITY 表中"15s"这一数值:它是 spawn 后等待 daemon 就绪的超时上限(Linux 上通常 <1s,macOS 挂载 Chroma 时 6–8s),并非固定重试总时长,具体重试策略由executeWithWorkerFallback封装。 - 此外 handler 还处理了两个 PARITY 表未单列但属于初始化职责的分支:
shouldTrackProject(cwd)排除名单检查,以及内部协议载荷(isInternalProtocolPayload)直接跳过。
2. 上下文注入(context-hook.js ↔ context-inject.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| Worker 健康检查 | ✅ 75 次重试 | ✅ 75 次重试 | ✅ Match |
| 上下文获取 | ✅ /api/context/inject |
✅ /api/context/inject |
✅ Match |
| 输出格式 | ✅ 含 hookSpecificOutput 的 JSON |
✅ 写入 .cursor/rules/ 文件 |
✅ Alternative |
| 项目名提取 | ✅ getProjectName(cwd) |
✅ basename(workspace_root) |
✅ Match |
| 自动刷新 | ✅ 每次会话开始 | ✅ 每次提交提示词 | ✅ Enhanced |
结论:完整对齐,Cursor 通过"自动更新的规则文件"达成等价效果。
工作原理(继承自 PARITY.md):
- Hook 把上下文写入
.cursor/rules/claude-mem-context.mdc; - 该文件带有
alwaysApply: true的 frontmatter; - Cursor 会在所有聊天会话中自动纳入这条规则;
- 每次提交提示词时上下文都会刷新。
Claude Code 侧的对照实现是 src/cli/handlers/context.ts 的 contextHandler:它请求 /api/context/inject?projects=...&platformSource=...(Claude Code 平台还会追加 colors=true 用于终端彩色展示),最终把结果放进 hookSpecificOutput.additionalContext(context.ts#L61-L62、L147-L153)——这正是 PARITY 表中"JSON with hookSpecificOutput"一行的来源。两个平台请求的是同一个 worker 端点,只是"投递通道"不同:Claude Code 走 hook 输出的 additionalContext,Cursor 走规则文件。
Cursor 侧的文件写入逻辑可在 CursorHooksInstaller.ts 中找到对应:writeContextFile(workspacePath, context) 被用于安装时生成初始上下文(L256-L275);若 worker 未运行或无存量记忆,则写入占位内容"No context yet. Complete your first session..."(L235-L250)。配套的 cursor-hooks/CONTEXT-INJECTION.md 进一步说明上下文在三个时间点刷新:提交前(context-inject.sh)、worker 摘要落库后自动更新、会话结束(session-summary.sh)兜底;项目通过 ~/.claude-mem/cursor-projects.json 注册(注册文件路径见 CursorHooksInstaller.ts#L23),从而允许 worker 在任意平台产生新摘要后自动回写该项目的规则文件。
3. 用户消息展示(user-message-hook.js ↔ user-message.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| 带颜色的上下文获取 | ✅ /api/context/inject?colors=true |
✅ /api/context/inject?colors=true |
✅ Match |
| 输出通道 | ✅ stderr | ✅ stderr | ✅ Match |
| 展示格式 | ✅ emoji 排版 | ✅ emoji 排版 | ✅ Match |
| Hook 触发点 | ✅ SessionStart | ⚠️ 可选(无 SessionStart) | ⚠️ Cursor 限制 |
结论:可选项——Cursor 没有 SessionStart 等价事件。文档注明:如需要,可将其挂到 beforeSubmitPrompt,但输出可能过于冗长,因此默认可选。
从 context.ts 的实现可以看到"颜色 + 终端输出"这条链路在统一 CLI 中依然保留:colors=true 的二次请求仅在 CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT === 'true' 且平台非 Codex 时触发,结果连同 http://localhost:<port> 的 viewer 地址一起放入 systemMessage(用户可见提示),与模型消费的 additionalContext 分离——对应源文件头部注释中"MODEL_CONTEXT vs USER_HINT"的 IO 纪律(context.ts#L1-L6)。
4. 观察捕获(save-hook.js ↔ save-observation.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| Worker 健康检查 | ✅ 75 次重试 | ✅ 75 次重试 | ✅ Match |
| 工具名提取 | ✅ 来自 tool_name |
✅ 来自 tool_name 或 "Bash" |
✅ Match |
| 工具输入捕获 | ✅ 完整 JSON | ✅ 完整 JSON | ✅ Match |
| 工具响应捕获 | ✅ 完整 JSON | ✅ 完整 JSON 或 output | ✅ Match |
| 隐私标签剥离 | ✅ Worker 负责 | ✅ Worker 负责 | ✅ Match |
| 错误处理 | ✅ Fire-and-forget | ✅ Fire-and-forget | ✅ Match |
| Shell 命令映射 | ✅ N/A(独立 hook) | ✅ 映射为 "Bash" 工具 | ✅ Enhanced |
结论:完整对齐,且因 shell 命令映射而增强。
对应实现是 src/cli/handlers/observation.ts 的 observationHandler:当 toolName 为空时直接放行;cwd 缺失则抛出错误;shouldTrackProject(cwd) 过滤排除项目;随后通过 executeWithWorkerFallback('/api/sessions/observations', 'POST', {...}) 上报 tool_name、tool_input、tool_response、cwd 及 agent 元数据(observation.ts#L14-L39)。"Fire-and-forget + 失败静默"的语义由 isWorkerFallback(result) 分支体现:worker 不可用时不阻塞、不报错,直接返回成功退出码。隐私标签(如 <private>)由 worker 侧处理,hook 不重复剥离,这与 PARITY 的"Tag Stripping"一节一致(仓库中对应 src/utils/tag-stripping.ts 等工具)。
5. 文件编辑捕获(N/A ↔ save-file-edit.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| 文件路径提取 | N/A | ✅ 来自 file_path |
✅ New |
| 编辑详情 | N/A | ✅ 来自 edits 数组 |
✅ New |
| 工具名 | N/A | ✅ "write_file" | ✅ New |
| 编辑摘要 | N/A | ✅ 由 edits 生成 | ✅ New |
结论:Cursor 新增能力。cursor-hooks/README.md 对该 hook 的说明是:捕获 agent 的文件编辑,将编辑视为 write_file 工具调用,并把编辑摘要写进 observation。在统一 CLI 模式下,该能力由 bun worker-service.cjs hook cursor file-edit 命令承载(CursorHooksInstaller.ts#L160-L162)。
6. 会话摘要(summary-hook.js ↔ session-summary.sh)
| Feature | Claude Code | Cursor | 状态 |
|---|---|---|---|
| Worker 健康检查 | ✅ 75 次重试 | ✅ 75 次重试 | ✅ Match |
| Transcript 解析 | ✅ 提取最后消息 | ❌ 无 transcript 访问 | ⚠️ Cursor 限制 |
| 摘要 API 调用 | ✅ /api/sessions/summarize |
✅ /api/sessions/summarize |
✅ Match |
| 最后消息提取 | ✅ 来自 transcript | ❌ 空字符串 | ⚠️ Cursor 限制 |
| 错误处理 | ✅ Fire-and-forget | ✅ Fire-and-forget | ✅ Match |
结论:部分对齐——Cursor 的 stop hook 拿不到 transcript 路径,最后消息只能以空字符串提交,因此摘要准确度可能下降;worker 会退而依据本会话期间已存储的 observations 生成摘要。
对照 src/cli/handlers/summarize.ts 可以精确理解这个差异的来源:Claude Code 侧的 summarizeHandler 会读取 transcriptPath,用 extractLastAssistantTurn 取出最后一条 assistant 消息及其模型信息,经 stripMemoryTags 剥离记忆标签后,连同 last_assistant_message、observedModel、observedBilling 一起 POST 到 /api/sessions/summarize(summarize.ts#L88-L160);并且对"无 transcript""无最后消息"的情况都有显式的静默跳过分支。Cursor 平台没有 transcriptPath 可传,因此只能走到"空字符串 + observations 兜底"的路径——这正是 PARITY 表中两个 ⚠️ 的实质。
三、实现细节层面的对齐
PARITY.md 的 "Implementation Details" 一节归纳了四个横切维度,均判定为 Match:
| 维度 | 说明 |
|---|---|
| Worker 健康检查 | 两平台同为 75 次重试 × 200ms ≈ 15 秒 |
| 错误处理 | Claude Code:带日志的 fire-and-forget;Cursor:优雅退出(exit 0),适配其 hook 系统 |
| 隐私处理 | Worker 执行隐私检查,hook 尊重 skipped 标志 |
| 标签剥离 | Worker 统一处理 <private> 与 <claude-mem-context> 标签,hook 无需剥离 |
这些约定在统一 CLI 的 handler 中同样得到贯彻:每个 handler 文件头部都标注了 IO 纪律(纯函数式返回 HookResult,禁止直接写 stdout/stderr/调用 process.exit),退出码统一收敛到 src/shared/hook-constants.ts 的 HOOK_EXIT_CODES(SUCCESS: 0 / BLOCKING_ERROR: 2),Windows 平台超时统一乘 1.5 系数(getTimeout())。这保证了"两平台行为一致"不仅是文档承诺,也在退出码与超时预算层面有代码约束。
四、Cursor 的能力缺口与增强项
缺口(Cursor 平台限制)
直接上下文注入:已解决——通过自动更新的规则文件(.cursor/rules/claude-mem-context.mdc,alwaysApply: true)实现,每次提示词都刷新。- Transcript 访问:Cursor hook 不提供 transcript 路径,导致摘要准确度下降;缓解方案是 worker 基于会话期间存储的 observations 生成摘要。
- SessionStart Hook:Cursor 无会话开始事件,用户消息展示只能作为可选项,可挂到
beforeSubmitPrompt。 - SDK Agent 会话:Cursor 不使用 SDK agent 模式,
/sessions/{id}/init调用不适用,判定为 N/A。
增强(Cursor 独有)
- Shell 命令捕获:把 shell 命令映射为 "Bash" 工具的 observation,超出 Claude Code 版的覆盖范围;
- 文件编辑捕获:独立的
afterFileEdithook(见上文第 5 节); - MCP 工具捕获:通过
afterMCPExecution单独捕获 MCP 工具使用。
也就是说,PARITY 的结论并非单向"补齐差距":Cursor 版在工具事件粒度上反而比 Claude Code 版更细(MCP、Shell、FileEdit 三个独立事件),形成双向对齐。
五、总体结论与验证方式
PARITY.md 的总结矩阵:
| 类别 | 状态 |
|---|---|
| 核心功能 | ✅ 完整对齐 |
| 会话管理 | ✅ 完整对齐 |
| 观察捕获 | ✅ 完整对齐(增强) |
| 上下文注入 | ✅ 完整对齐(经由规则文件) |
| 摘要生成 | ⚠️ 部分(无 transcript) |
| 用户体验 | ⚠️ 部分(无 SessionStart) |
总体:Cursor hook 实现达成了与 Claude Code hook 的完整功能对等——会话初始化、上下文注入(经自动更新的 .cursor/rules/ 文件)、观察捕获(MCP 工具、shell 命令、文件编辑)全部覆盖;摘要生成可用但受限于 transcript 缺失。
在仓库中如何验证这些结论
- 事件映射与命令模板:cursor-hooks/hooks.json 与 CursorHooksInstaller.ts(统一 CLI 模式下的事件→命令矩阵)。
- 六大功能项的 handler 实现:session-init.ts、context.ts、observation.ts、summarize.ts。
- 超时与退出码预算:hook-constants.ts。
- 上下文注入的完整机制(规则文件格式、三次刷新时机、项目注册表):cursor-hooks/CONTEXT-INJECTION.md 与 CursorHooksInstaller.ts(worker 侧自动回写
updateCursorContextForProject)。 - 安装/排障入口:
claude-mem cursor install|uninstall|status子命令见 CursorHooksInstaller.ts#L427-L477,用户层依赖(jq、curl、bash,worker 端口默认 37777)说明见 cursor-hooks/README.md。
需要说明的适用前提:PARITY.md 以 shell 脚本(*.sh)为对比对象,而当前仓库的安装器已默认生成统一 CLI 模式的 hooks.json,两者的事件语义与 API 端点完全一致,本文以"脚本名 → CLI 子命令"的对应关系来解读文档表格。若你的 Cursor 项目此前以遗留脚本模式安装,claude-mem cursor status 会提示 "Legacy shell scripts (consider reinstalling for unified CLI)"(CursorHooksInstaller.ts#L385-L393),可据此判断并重装。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00