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 不小心写进代码或带进上下文。挂 beforeSubmitPrompt 或 afterFileEdit:
#!/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 的几条原则
- 要快:Hook 卡在每个动作前,慢了整个体验都拖垮。重活异步做,别在 Hook 里干几十秒的事。
- 想清楚失败语义:安全类(拦截危险命令)用
failClosed: true,宁可错杀;体验类(格式化)失败就放过(fail open),别把人卡死。 - 事件选最窄的:能用
afterFileEdit就别用全量postToolUse;能加 matcher 缩小范围就加。 - 确认依赖存在:脚本里用到
jq/prettier/node,先确认它们在 Hook 运行环境的$PATH里,否则 Hook 静默失效。 - 项目 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)
还没有评论,来抢沙发





