Vibe Coding 工程化进阶(四):用 Hooks 给 Agent 上"自动护栏"

AI 工程43 阅读约 11 分钟

前三篇给了 AI 记忆、规范、能力。但有一个致命问题:Rules 和 AGENTS.md 都是"建议",AI 大概率遵守,但不保证。 当 AI 能真实地改文件、跑命令、调 MCP 工具时,"大概率"是不够的。Hooks 就是那条"确定性的底线"——不靠 AI 自觉,靠机器强制执行。

一、Rules 和 Hooks 的本质区别

这是理解 Hooks 的钥匙:

Rules 是"概率性"的软约束(写进上下文,AI 倾向于遵守);Hooks 是"确定性"的硬约束(在关键节点运行你的代码,AI 绕不过去)。

Rules Hooks
性质 提示 / 建议 拦截 / 强制
执行者 AI(可能忘、可能违背) 你的脚本(每次必跑)
适合 风格、范式、偏好 安全红线、格式化、门禁、审计

举个对比:你可以在 Rules 里写"提交前请格式化代码"——AI 有时会忘。但你也可以加一个 Hook:每次 AI 编辑完文件,自动跑 Prettier——这就不存在忘不忘的问题了。

二、Hooks 是什么、怎么跑

Hooks 是在 Agent 生命周期的特定事件上自动触发的脚本。 它通过 stdin 收到一段描述当前动作的 JSON,可以观察、放行、拦截、改写这个动作,再通过 stdout 返回决定。

在 Cursor 里,项目级 Hooks 配置在 .cursor/hooks.json(用户级在 ~/.cursor/hooks.json)。它能挂载的关键事件包括:

事件 触发时机 典型用途
beforeShellExecution AI 执行终端命令前 拦截危险命令
afterShellExecution 命令执行后 审计输出
afterFileEdit AI 编辑文件后 自动格式化 / lint
beforeReadFile 读文件前 阻止读敏感文件 / 脱敏
beforeMCPExecution 调用 MCP 工具前 给外部工具调用加闸
beforeSubmitPrompt 提示发送前 扫描密钥 / 合规检查
stop Agent 完成时 收尾 / 触发后续流程

(Claude Code 有对应的 PreToolUse / PostToolUse / UserPromptSubmit / Stop 等事件,理念一致。)

三、决策协议:怎么"放行 / 询问 / 拦截"

命令型 Hook 读 stdin 的 JSON,按需在 stdout 返回 JSON 表达决定。以 beforeShellExecution 为例,可以返回:

  • {"permission": "allow"} —— 放行
  • {"permission": "ask", "user_message": "..."} —— 暂停,让你人工确认
  • {"permission": "deny", "agent_message": "..."} —— 拦截,并告诉 AI 为什么

退出码也有约定:0 正常;2 直接拦截(等同 deny);其它非零码默认"放行不阻断"(fail open),除非你设了 failClosed: true(脚本自己崩了也照样拦,安全场景务必打开)。

四、实战护栏一:编辑后自动格式化

最实用、零风险的入门 Hook。.cursor/hooks.json

{
  "version": 1,
  "hooks": {
    "afterFileEdit": [
      { "command": ".cursor/hooks/format.sh" }
    ]
  }
}

.cursor/hooks/format.sh

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.file_path // empty')

# 只格式化前端文件,且文件确实存在
case "$file" in
  *.ts|*.tsx|*.js|*.jsx|*.vue|*.css|*.json)
    [ -f "$file" ] && npx prettier --write "$file" >/dev/null 2>&1
    ;;
esac
exit 0

从此 AI 写出来的代码永远是格式化过的,diff 干净、不再有"风格噪声"淹没真实改动。记得 chmod +x 让脚本可执行。

五、实战护栏二:拦截危险命令

防止 AI(或被提示注入诱导后)执行毁灭性命令。配置:

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      { "command": ".cursor/hooks/guard-shell.sh", "failClosed": true }
    ]
  }
}

.cursor/hooks/guard-shell.sh

#!/bin/bash
input=$(cat)
cmd=$(echo "$input" | jq -r '.command // empty')

# 黑名单:删根、强推、重置硬盘、管道直跑网络脚本等
if echo "$cmd" | grep -Eq 'rm -rf (/|~|\*)|git push .*--force|:\(\)\{|mkfs|curl .*\| *sh'; then
  echo '{"permission":"deny","agent_message":"该命令被安全 Hook 拦截:包含高危操作。"}'
  exit 0
fi
echo '{"permission":"allow"}'
exit 0

这一个脚本,可能就帮你挡住一次"AI 自信地 rm -rf 了不该删的目录"的事故。failClosed: true 保证连脚本本身异常时也不放行。

六、实战护栏三:提交前扫描密钥

防止密钥被 AI 不小心写进代码或带进上下文。挂 beforeSubmitPromptafterFileEdit

#!/bin/bash
input=$(cat)
text=$(echo "$input" | jq -r '.. | strings' 2>/dev/null)

if echo "$text" | grep -Eq '(AKIA[0-9A-Z]{16}|sk-[A-Za-z0-9]{20,}|-----BEGIN .*PRIVATE KEY-----)'; then
  echo '{"permission":"deny","agent_message":"检测到疑似密钥,已拦截。请改用环境变量。"}'
  exit 0
fi
echo '{"permission":"allow"}'
exit 0

更进阶可以接 gitleaks 之类成熟工具,但核心思路一样:把"别提交密钥"从一句口头叮嘱,变成一道机器闸门。

七、用好 Hooks 的几条原则

  1. 要快:Hook 卡在每个动作前,慢了整个体验都拖垮。重活异步做,别在 Hook 里干几十秒的事。
  2. 想清楚失败语义:安全类(拦截危险命令)用 failClosed: true,宁可错杀;体验类(格式化)失败就放过(fail open),别把人卡死。
  3. 事件选最窄的:能用 afterFileEdit 就别用全量 postToolUse;能加 matcher 缩小范围就加。
  4. 确认依赖存在:脚本里用到 jq/prettier/node,先确认它们在 Hook 运行环境的 $PATH 里,否则 Hook 静默失效。
  5. 项目 Hook 进 Git:让整个团队共享同一套护栏——这正是"团队级安全基线"的落地方式。

八、四块拼图合体

到这里,工程化的四块拼图凑齐了,各司其职:

拼图 角色 解决
AGENTS.md / CLAUDE.md 记忆 AI 知道"项目是什么"
Rules 规范 AI 知道"该怎么写"
MCP 感官与手脚 AI 能"接触外部、动手干活"
Hooks 护栏 关键动作"被强制把关"

记忆让它不跑偏,规范让它写得对,MCP 让它干得了,Hooks 让它出不了大事。四者叠加,才把 Vibe Coding 从"一个人手感好时的爽",变成"一个团队、长期、可信赖的工程能力"。

九、小结

  • Hooks 是确定性硬约束,补上 Rules"靠自觉"的漏洞。
  • hooks.json 里挂事件(编辑后、执行命令前、提示发送前……),脚本通过 JSON 决定 allow/ask/deny,退出码 2 拦截,安全场景用 failClosed
  • 三个高价值护栏:编辑后自动格式化、拦截危险命令、扫描密钥。
  • 原则:快、失败语义清晰、事件选窄、确认依赖、Hook 进版本库。

至此,《Vibe Coding 工程化进阶》系列完结。从给 AI 装记忆、立规范、接世界,到上护栏——你已经有了一整套把 AI 编程"工程化、团队化、可信化"的方法论。剩下的,就是在你自己的项目里把这四块拼图一块块拼起来。

附件(我在 Lenovo 的相关分享文件):
lenovo-vibe-stage.zip

相关文章

评论 (0)

还没有评论,来抢沙发