首页
/ claude-mem 中 Claude Code 与 Cursor 双平台 Hook 体系的功能对齐(Feature Parity)解析

claude-mem 中 Claude Code 与 Cursor 双平台 Hook 体系的功能对齐(Feature Parity)解析

2026-09-06 11:56:05作者:冯爽妲Honey

本文围绕 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 集(SessionStartUserPromptSubmitPostToolUseStop 等事件),随后为了支持 Cursor,又实现了一套基于 Cursor Hook 事件(beforeSubmitPromptafterMCPExecutionafterShellExecutionafterFileEditstop)的等价能力。

两个平台的 Hook 事件模型并不相同——事件名称、触发时机、可获取的输入数据(例如 Cursor 没有 transcript 路径)都有差异。因此 PARITY.md 的存在意义,就是作为一份"功能对齐验收清单",逐条回答:Cursor 版是否覆盖了 Claude Code 版的每一项能力?差距在哪里?如何用平台特性补齐?

一、Hook 事件映射总表

PARITY.md 开篇给出了两套 Hook 的映射关系,这是全文的骨架:

Claude Code Hook Cursor Hook 状态 说明
SessionStartcontext-hook.js beforeSubmitPromptcontext-inject.sh ✅ Partial 上下文可获取,但无法直接注入
SessionStartuser-message-hook.js (可选)user-message.sh ⚠️ Optional 无 SessionStart 等价物,可挂在 beforeSubmitPrompt
UserPromptSubmitnew-hook.js beforeSubmitPromptsession-init.sh ✅ Complete 会话初始化、隐私检查、斜杠命令剥离
PostToolUsesave-hook.js afterMCPExecution + afterShellExecutionsave-observation.sh ✅ Complete 工具观察捕获
PostToolUse → (文件编辑) afterFileEditsave-file-edit.sh ✅ Complete 文件编辑观察捕获
Stopsummary-hook.js stopsession-summary.sh ⚠️ Partial 摘要生成(无 transcript 访问)

映射配置在仓库中的实际位置

  • 仓库内的映射模板是 cursor-hooks/hooks.json,它将五个 Cursor 事件分别绑定到对应脚本:beforeSubmitPrompt 依次执行 session-init.shcontext-inject.shafterMCPExecutionafterShellExecution 共用 save-observation.shafterFileEdit 绑定 save-file-edit.shstop 绑定 session-summary.sh
  • 从源码结构看,当前安装器 src/services/integrations/CursorHooksInstaller.ts 生成的 hooks.json 已切换为"统一 CLI 模式":不再逐个绑定 shell 脚本,而是把每个事件都指向同一条命令 bun <worker-service.cjs 绝对路径> hook cursor <command>,其中 <command> 取值 session-initcontextobservationfile-editsummarize。也就是说,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.tsgetTargetDir()

二、六大功能项的逐项对齐

PARITY.md 将对比拆分为六个小节,下面逐一展开,并给出仓库内的源码级佐证。

1. 会话初始化(new-hook.jssession-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.tssessionInitHandler,可以确认 PARITY 表中每一行的实际实现:

  • /api/sessions/init 调用:handler 通过 executeWithWorkerFallback('/api/sessions/init', 'POST', {...}) 发起请求,body 包含 contentSessionIdprojectpromptplatformSource(见 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.jscontext-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.tscontextHandler:它请求 /api/context/inject?projects=...&platformSource=...(Claude Code 平台还会追加 colors=true 用于终端彩色展示),最终把结果放进 hookSpecificOutput.additionalContextcontext.ts#L61-L62L147-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.jsuser-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.jssave-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.tsobservationHandler:当 toolName 为空时直接放行;cwd 缺失则抛出错误;shouldTrackProject(cwd) 过滤排除项目;随后通过 executeWithWorkerFallback('/api/sessions/observations', 'POST', {...}) 上报 tool_nametool_inputtool_responsecwd 及 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.jssession-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_messageobservedModelobservedBilling 一起 POST 到 /api/sessions/summarizesummarize.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.tsHOOK_EXIT_CODESSUCCESS: 0 / BLOCKING_ERROR: 2),Windows 平台超时统一乘 1.5 系数(getTimeout())。这保证了"两平台行为一致"不仅是文档承诺,也在退出码与超时预算层面有代码约束。

四、Cursor 的能力缺口与增强项

缺口(Cursor 平台限制)

  1. 直接上下文注入已解决——通过自动更新的规则文件(.cursor/rules/claude-mem-context.mdcalwaysApply: true)实现,每次提示词都刷新。
  2. Transcript 访问:Cursor hook 不提供 transcript 路径,导致摘要准确度下降;缓解方案是 worker 基于会话期间存储的 observations 生成摘要。
  3. SessionStart Hook:Cursor 无会话开始事件,用户消息展示只能作为可选项,可挂到 beforeSubmitPrompt
  4. SDK Agent 会话:Cursor 不使用 SDK agent 模式,/sessions/{id}/init 调用不适用,判定为 N/A。

增强(Cursor 独有)

  1. Shell 命令捕获:把 shell 命令映射为 "Bash" 工具的 observation,超出 Claude Code 版的覆盖范围;
  2. 文件编辑捕获:独立的 afterFileEdit hook(见上文第 5 节);
  3. MCP 工具捕获:通过 afterMCPExecution 单独捕获 MCP 工具使用。

也就是说,PARITY 的结论并非单向"补齐差距":Cursor 版在工具事件粒度上反而比 Claude Code 版更细(MCP、Shell、FileEdit 三个独立事件),形成双向对齐。

五、总体结论与验证方式

PARITY.md 的总结矩阵:

类别 状态
核心功能 ✅ 完整对齐
会话管理 ✅ 完整对齐
观察捕获 ✅ 完整对齐(增强)
上下文注入 ✅ 完整对齐(经由规则文件)
摘要生成 ⚠️ 部分(无 transcript)
用户体验 ⚠️ 部分(无 SessionStart)

总体:Cursor hook 实现达成了与 Claude Code hook 的完整功能对等——会话初始化、上下文注入(经自动更新的 .cursor/rules/ 文件)、观察捕获(MCP 工具、shell 命令、文件编辑)全部覆盖;摘要生成可用但受限于 transcript 缺失。

在仓库中如何验证这些结论

需要说明的适用前提: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),可据此判断并重装。

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