Hooks:ECC 的运行时

3,173 字 · 预计阅读 9 分钟

前面几节的 Agents、Skills、Rules,本质上都是提示词:写给模型看,模型可能听,也可能不听。

ECC 真正「硬」的部分在 Hooks。如果说前面几节是 ECC 的知识库,Hooks 就是它的运行时:不经过模型判断,到点就执行。

这一节是本章的重点。


一、全景:8 个事件,27 条注册

多个事件与 Hook 注册构成运行时

hooks/hooks.json 一共 285 行,用到了 8 个事件:

事件 条数 主要干什么
SessionStart 2 加载上一次会话的摘要、学到的习惯(本章 第7节)
UserPromptSubmit 1 跑项目自定义的提示词规则
PreToolUse 10 拦危险命令、保护配置文件、提醒压缩、先查后改
PostToolUse 2 两个「分发器」,内部再分十几个小检查
PostToolUseFailure 2 MCP 健康检查、Skill 运行统计
PreCompact 1 压缩前保存摘要(本章 第7节)
Stop 8 格式化 + 类型检查、console.log 检查、保存会话
SessionEnd 1 收尾标记

之前学了 PreToolUse、PostToolUse、Stop 三个,ECC 用到了 8 个。

和记忆有关的 SessionStart、PreCompact 留到 第7节,这一节集中看其余的。


二、三层结构:定位 → 闸门 → 脚本

Hook 经定位、门控后执行脚本

打开 hooks.json 你可能会吓一跳:每一条 command 都是一长串 node -e "...",几百个字符挤在一行。

拆开看,其实是三层:

hooks.json 里的一条命令
   │
   ├─ ① 定位:找到 ECC 装在哪
   │        (插件目录?~/.claude?插件缓存?旧名字 everything-claude-code?挨个找)
   │
   ├─ ② 闸门:run-with-flags.js 判断这个 Hook 现在该不该跑
   │
   │     node scripts/hooks/run-with-flags.js  pre:config-protection  scripts/hooks/config-protection.js  standard,strict
   │                                           └─ Hook ID              └─ 真正干活的脚本                    └─ 哪些档位启用
   │
   └─ ③ 脚本:config-protection.js 做真正的检查

第 ① 层为什么写这么长?插件里的 Hook 可以用 CLAUDE_PLUGIN_ROOT 环境变量找到插件目录,但 ECC 还要兼容手动安装、旧版本名字等好几种情况,

只能挨个去找。全部用 Node 写而不用 bash,是为了 Windows 也能跑。

这一层是工程兼容,读的时候可以跳过。重点看 ② 和 ③,它们都在 scripts/hooks/ 目录下。


三、闸门:三个档位 + 单独开关

Hook 分档位并可独立开关

每个 Hook 都有一个固定的 ID,比如 pre:bash:tmux-reminder、post:edit:console-warn、stop:check-console-log。闸门根据三个环境变量决定放不放行:

export ECC_HOOKS_ENABLED=true        # 总开关
export ECC_HOOK_PROFILE=standard     # 档位:minimal | standard | strict
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,stop:desktop-notify"   # 单独关掉某几个

核心逻辑在 scripts/lib/hook-flags.js,简化后就这几行:

function isHookEnabled(hookId, { profiles }) {
  if (!areHooksEnabled()) return false;                // 总开关关了
  if (getDisabledHookIds().has(hookId)) return false;  // 被单独点名关掉
  const profile = getHookProfile();                    // 当前档位,默认 standard
  return parseProfiles(profiles).includes(profile);    // 这个 Hook 属不属于当前档位
}

三个档位:

档位 开了什么 适合
minimal 只留底线:拦 --no-verify、项目自定义规则、会话保存 想用 ECC,但不想被打扰
standard(默认) 再加质量检查:配置保护、先查后改、批量格式化、console.log 检查、压缩提醒 大多数人
strict 再加更多提醒:tmux 提醒、push 前提醒、提交前质量检查 要求最严的项目

之前讲的插件 userConfig,就是这里的另一个入口:环境变量没设,闸门就去读你在插件选项里填的值。

为什么要这么设计?「为了 Hook 而 Hook,挂得越多,Agent 越慢。」

ECC 有几十个 Hook,不可能每个人都要全部。

给每个 Hook 一个 ID,给整体分几个档位,用户就能按需关掉,而不用去改 hooks.json。

小技巧:Hook 的 ID 和说明并没有写在 hooks.json 里,而是放在旁边的 hooks/hooks.metadata.json。原因是 Claude Code 会校验 hooks.json,出现它不认识的字段就报错。ECC 用一份「旁注文件」存元数据,再用 CI 脚本检查两边是否对得上。校验器认什么,就只写什么。


四、性能:分发器 + 进程内执行

本文是会员内容

全文 3,173 字,已试读约 30%。 开通专业版可阅读全文,并解锁全部课程与后续更新。

开通专业版

支持 7 天无理由退款 · 早鸟期前 500 名自动升级永久卡