CodeBuddy Code Hooks探索实践:Hooks为你的AI配上随叫随到的管家
1. CodeBuddy Code Hooks探索实践:Hooks为你的AI配上随叫随到的管家
AI 编程像在"开盲盒"?CodeBuddy Code Hooks 用事件驱动机制给 AI 配上一套"纪律系统":九大事件覆盖交互全生命周期,高危命令拦截、代码质量审查、团队规范注入三个实战案例,附五个使用技巧,一文讲透。
1.1. 目录
1.2. AI编程工具使用中的痛点
在使用AI编程工具时,我们常常会遇到以下问题:
- 不可控性:AI在执行过程中可能偏离预期,导致代码质量下降
- 缺乏拦截机制:无法在关键节点进行验证和拦截
- 重复性工作:需要反复提醒AI遵循特定规范
- 安全风险:AI可能执行危险操作而不自知
- 缺乏一致性:不同会话间AI的行为可能不一致
这些痛点使得AI编程体验充满不确定性,就像"开盲盒"一样。
1.3. CodeBuddy Code Hooks是什么
1.3.1. 概念定义
CodeBuddy Code Hooks是一种事件驱动的机制,允许开发者在AI执行过程中的特定节点插入自定义逻辑。
它就像给AI配备了一套"纪律系统",让AI在关键时刻能够按照预设规则执行特定操作。
1.3.2. 核心价值
- 可控性:通过预设规则确保AI行为符合预期
- 安全性:在危险操作前进行拦截和验证
- 一致性:确保AI在不同场景下遵循相同规范
- 自动化:减少人工干预,提高开发效率
1.3.3. 应用场景
- 代码质量自动审查
- 安全操作拦截
- 团队规范自动注入
- 自动化测试与验证
- 开发流程标准化
1.4. Hooks的九大事件详解
CodeBuddy Code Hooks提供了九个关键事件,覆盖了AI交互的完整生命周期:
1.4.1. SessionStart(会话开始)
在AI会话启动时触发,适合用于:
- 注入团队规范
- 加载项目上下文
- 设置初始环境
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'cat .codebuddy/team-standards.md'"
}
]
}
]
}
}
1.4.2. UserPromptSubmit(用户提交提示)
用户每次提交提示时触发,适合用于:
- 提示预处理
- 意图分析
- 上下文补充
1.4.3. PreToolUse(工具使用前)
AI调用工具前触发,适合用于:
- 参数验证
- 安全检查
- 权限控制
def validate_tool_usage(tool_name, tool_args):
if tool_name == "execute_command":
cmd = tool_args.get("command", "")
if "rm -rf" in cmd and "/etc/" in cmd:
return {
"action": "block",
"systemPrompt": "🚫 危险命令:禁止删除系统目录!"
}
return {"action": "allow"}
1.4.4. PostToolUse(工具使用后)
AI调用工具后触发,适合用于:
- 结果验证
- 自动化测试
- 质量检查
1.4.5. AssistantMessageCreate(助手消息创建)
AI生成消息时触发,适合用于:
- 内容过滤
- 格式标准化
- 合规检查
1.4.6. Stop(会话结束)
会话结束时触发,适合用于:
- 清理临时文件
- 保存会话记录
- 生成报告
1.4.7. 其他事件
还包括:
- ToolError:工具执行错误时
- PreToolUseWithOutput:工具使用前(带输出)
- PostToolUseWithOutput:工具使用后(带输出)
1.5. 实践案例
1.5.1. 高危命令拦截
1.5.1.1. 痛点背景
AI可能执行危险命令如rm -rf /etc/*导致系统损坏。
1.5.1.2. Hooks解决方案
通过PreToolUse事件拦截危险命令:
import re
import json
import sys
# 危险命令规则
_VALIDATION_RULES = [
# 文件删除类
(r"rm\s+-rf\s+/(etc|usr|bin|sbin|var|sys|proc|dev)", "🚨 极度危险: 删除系统目录会导致系统崩溃!"),
(r"rm\s+-rf\s+.*\*\*", "🚨 危险: 递归删除通配符可能误删重要文件"),
# 权限修改类
(r"chmod\s+777\s+/(?!home|tmp|var/tmp)", "⚠️ 安全风险: chmod 777 会开放所有权限,建议使用 755 或 644"),
(r"chmod\s+-R\s+777", "🚨 严重安全风险: 递归设置 777 权限会造成巨大安全隐患!"),
# 磁盘操作类
(r"dd\s+.*of=/dev/(sd|hd|nvme)", "🚨 极度危险: dd 写入磁盘设备会覆盖所有数据!"),
(r"mkfs\.", "🚨 极度危险: 格式化操作会清空磁盘数据!"),
# 网络安全类
(r"curl.*\|\s*(bash|sh)", "🚨 安全风险: 不要直接执行远程脚本!请先下载检查后再运行"),
(r"wget.*\|\s*(bash|sh)", "🚨 安全风险: 不要直接执行远程脚本!请先下载检查后再运行"),
]
def _validate_command(command: str) -> list[tuple[str, str]]:
"""验证命令是否安全"""
issues = []
for pattern, message in _VALIDATION_RULES:
if re.search(pattern, command):
level = "🚨" if "🚨" in message else "⚠️"
issues.append((level, message))
return issues
def main():
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"❌ 错误: 无效的 JSON 输入: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
if tool_name != "Bash":
sys.exit(0)
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")
if not command:
sys.exit(0)
issues = _validate_command(command)
if issues:
print("\n" + "=" * 60, file=sys.stderr)
print("🛡️ 高危命令检测", file=sys.stderr)
print("=" * 60, file=sys.stderr)
print(f"📝 命令: {command}\n", file=sys.stderr)
has_critical = any(level == "🚨" for level, _ in issues)
for level, message in issues:
print(f"{message}", file=sys.stderr)
print("\n" + "=" * 60, file=sys.stderr)
if has_critical:
print("🚫 此命令已被阻止执行!", file=sys.stderr)
sys.exit(2)
else:
print("⚡ 建议修改后再执行", file=sys.stderr)
sys.exit(2)
if __name__ == "__main__":
main()
1.5.1.3. 配置方法
- 创建
.codebuddy/hooks/dangerous-command-blocker.py - 添加到
.codebuddy/settings.json:
{
"description": "拦截危险命令执行",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 .codebuddy/hooks/dangerous-command-blocker.py",
"timeout": 10
}
]
}
]
}
}
1.5.1.4. 测试验证
输入危险命令测试:
> rm -rf /etc/passwd
1.5.1.5. 效果展示
1.5.2. 代码质量自动审查
1.5.2.1. 痛点背景
确保Next.js项目符合最佳实践,包括正确的文件结构、组件模式和TypeScript使用规范。
1.5.2.2. Hooks解决方案
通过PostToolUse事件自动审查代码质量:
{
"description": "强制执行 Next.js 最佳实践,包括正确的文件结构、组件模式和 TypeScript 使用规范。通过自动化代码审查和建议,验证 Next.js App Router 约定、Server/Client 组件模式、正确的导入方式和 TypeScript 使用。提供关于代码质量和 Next.js 最佳实践遵循情况的实时反馈。",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "bash -c 'input=$(cat); FILE_PATH=$(echo \"$input\" | jq -r \".tool_input.file_path // empty\"); SUCCESS=$(echo \"$input\" | jq -r \".tool_response.success // false\"); if [ \"$SUCCESS\" = \"true\" ] && [[ \"$FILE_PATH\" =~ \\.(js|jsx|ts|tsx)$ ]] && [[ ! \"$FILE_PATH\" =~ node_modules ]]; then echo \"🔍 Next.js 代码质量检查器:正在审查 $FILE_PATH...\"; ISSUES=0; if [ -f \"$FILE_PATH\" ]; then if [[ \"$FILE_PATH\" =~ app/.* ]]; then echo \"📁 检测到 App Router 文件: $FILE_PATH\"; if [[ \"$FILE_PATH\" =~ page\\.(js|jsx|ts|tsx)$ ]] && ! grep -q \"export default function\" \"$FILE_PATH\" 2>/dev/null && ! grep -q \"export default async function\" \"$FILE_PATH\" 2>/dev/null; then echo \"❌ Page 组件必须导出默认函数\" >&2; ((ISSUES++)); fi; if [[ \"$FILE_PATH\" =~ layout\\.(js|jsx|ts|tsx)$ ]] && ! grep -q \"children\" \"$FILE_PATH\" 2>/dev/null; then echo \"❌ Layout 组件应该接收 children 属性\" >&2; ((ISSUES++)); fi; if [[ \"$FILE_PATH\" =~ page\\.(js|jsx|ts|tsx)$ ]] && ! grep -q \"Metadata\" \"$FILE_PATH\" 2>/dev/null && ! grep -q \"metadata\" \"$FILE_PATH\" 2>/dev/null; then echo \"⚠️ 建议添加 metadata 导出以优化 SEO\"; fi; if grep -q \"use client\" \"$FILE_PATH\" 2>/dev/null; then echo \"🖥️ 检测到客户端组件\"; if ! grep -E \"(useState|useEffect|onClick|onChange|onSubmit)\" \"$FILE_PATH\" 2>/dev/null; then echo \"⚠️ 客户端组件没有交互功能 - 考虑改为服务端组件\"; fi; else echo \"🚀 服务端组件(默认)\"; if grep -E \"(useState|useEffect|onClick|onChange|onSubmit)\" \"$FILE_PATH\" 2>/dev/null; then echo \"❌ 服务端组件中包含交互功能 - 请添加 \\\"use client\\\" 指令\" >&2; ((ISSUES++)); fi; fi; fi; if [[ \"$FILE_PATH\" =~ \\.(jsx|tsx)$ ]]; then if ! grep -q \"import.*React\" \"$FILE_PATH\" 2>/dev/null && grep -q \"<\" \"$FILE_PATH\" 2>/dev/null; then echo \"⚠️ JSX 未导入 React(Next.js 17+ 会自动处理)\"; fi; if ! grep -q \"FC\\|FunctionComponent\" \"$FILE_PATH\" 2>/dev/null && grep -q \"props\" \"$FILE_PATH\" 2>/dev/null && [[ \"$FILE_PATH\" =~ \\.tsx$ ]]; then echo \"💡 建议为 TypeScript 使用 React.FC 或显式的 prop 类型\"; fi; fi; if [[ \"$FILE_PATH\" =~ \\.js$ ]] && [ -f \"tsconfig.json\" ]; then echo \"📝 TypeScript 项目中的 JavaScript 文件: $FILE_PATH\"; echo \"💡 建议迁移到 TypeScript 以获得更好的类型安全\"; fi; if grep -q \"next/image\" \"$FILE_PATH\" 2>/dev/null; then echo \"✅ 使用 next/image 优化图片\"; elif grep -q \"<img\" \"$FILE_PATH\" 2>/dev/null; then echo \"🖼️ 检测到常规 <img> 标签\"; echo \"💡 建议使用 next/image 以获得更好的性能\"; fi; if grep -q \"next/link\" \"$FILE_PATH\" 2>/dev/null; then echo \"✅ 使用 next/link 进行导航\"; elif grep -q \"<a\" \"$FILE_PATH\" 2>/dev/null && ! grep -q \"http\" \"$FILE_PATH\" 2>/dev/null; then echo \"🔗 检测到用于内部链接的常规 <a> 标签\"; echo \"💡 内部导航请使用 next/link\"; fi; if grep -q \"getServerSideProps\\|getStaticProps\" \"$FILE_PATH\" 2>/dev/null; then echo \"⚠️ 检测到 Pages Router 数据获取方法\"; echo \"💡 建议迁移到 App Router 使用服务端组件\"; fi; if grep -q \"className=.*{\" \"$FILE_PATH\" 2>/dev/null; then echo \"🎨 检测到动态 className\"; if ! grep -q \"clsx\\|classnames\\|cn(\" \"$FILE_PATH\" 2>/dev/null; then echo \"💡 建议使用 clsx 或类似工具进行 className 拼接\"; fi; fi; if [ $ISSUES -eq 0 ]; then echo \"✅ $FILE_PATH 代码质量检查通过\"; else echo \"❌ 在 $FILE_PATH 中发现 $ISSUES 个代码质量问题\" >&2; exit 2; fi; else echo \"❌ 文件 $FILE_PATH 未找到\"; fi; else echo \"ℹ️ 跳过代码质量检查(不是 JavaScript/TypeScript 文件或操作失败)\"; fi'",
"timeout": 20
}
]
}
]
}
}
1.5.2.3. 测试验证
输入提示:
帮我检查一下本地 nextjs 代码是否符合最佳实践
1.5.2.4. 效果展示
1.5.3. 自动格式化与规范注入
1.5.3.1. 实现思路
通过SessionStart事件在会话启动时自动注入团队规范文档,让AI在每次对话开始时就了解团队的编码标准。
1.5.3.2. 配置方法
- 创建
.codebuddy/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash .codebuddy/hooks/inject-team-standards.sh"
}
]
}
]
}
}
- 创建
.codebuddy/hooks/inject-team-standards.sh:
#!/bin/bash
# 读取团队规范文档
STANDARDS_FILE="$CODEBUDDY_CODE_PROJECT_DIR/.codebuddy/team-standards.md"
if [[ -f "$STANDARDS_FILE" ]]; then
# 输出规范内容(会被注入到AI的上下文)
cat "$STANDARDS_FILE"
# 记录日志
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Team standards injected" \
>> "$CODEBUDDY_CODE_PROJECT_DIR/.codebuddy/logs/session.log"
else
echo "⚠️ 警告:未找到团队规范文档"
fi
- 创建团队规范文档
创建.codebuddy/team-standards.md:
# 团队开发规范 - React前端项目
## 代码风格
### React组件规范
- ✅ 使用函数式组件 + Hooks
- ❌ 禁止使用类组件(class Component)
- ✅ 组件文件名使用PascalCase: `UserProfile.tsx`
- ✅ Props类型必须显式定义接口
### TypeScript规范
- ✅ 严格模式启用(`strict: true`)
- ❌ 禁止使用`any`,必要时使用`unknown`
- ✅ 接口命名以`I`开头或使用`type`定义
- ✅ 工具函数返回类型必须显式声明
1.5.3.3. 验证
输入prompt:
帮我创建一个用户资料编辑组件
1.5.3.4. 效果展示
通过SessionStart Hook使得会话初始化自动化,生成符合规范的结果:
1.6. 使用技巧
1.6.1. 活学活用,渐进式启用 Hooks
从简单开始:先实现基本的日志记录,再逐步加入复杂功能。Hooks的学习和应用还是有一定成本,建议不要一次启用所有Hook,先从最基础、最痛点的场景来考虑使用不同类型的hooks来实现。
另外尽可能详细地注释,在设定文件中为每个Hook添加说明注释,辅助理解。
1.6.2. 充分测试,定期维护,再投生产
在本地环境中进行多次测试所有Hook,再进行配置项目级或用户级的Hooks。同时定期检查和更新Hook设定,移除不需要的规则。
# 测试 SessionStart
echo '{}' | python3 .codebuddy/hooks/01-session-start.py
# 测试 UserPromptSubmit(带参数)
echo '{"message":"开发登录功能"}' | python3 .codebuddy/hooks/02-user-prompt-submit.py
# 查看输出是否为合法 JSON
1.6.3. 时机恰当,多层防护,而非事后补救
不是事后检查,而是事前拦截 + 事中修正 + 事后验证。尽可能地将通用规则放在用户设定,项目特定规则放在项目设定。
1.6.3.1. 第1层:事前拦截(SessionStart)
# .codebuddy/hooks/01-session-start.py
{
"systemPrompt": """
## 强制规则(不可违反)
- 所有 API 必须使用 RESTful 风格
- 密码必须使用 bcrypt 加密
- SQL 必须使用参数化查询
"""
}
1.6.3.2. 第2层:事中修正(PostToolUse)
# .codebuddy/hooks/05-post-tool-use.py
def check_tool_result(tool_name, result):
if tool_name == "write_to_file":
# 实时检查写入的代码
if "password" in result and "bcrypt" not in result:
return {
"systemPrompt": "⚠️ 检测到明文密码!必须使用 bcrypt.hash() 加密"
}
1.6.3.3. 第3层:事后验证(Stop)
# .codebuddy/hooks/07-stop.py
def final_check():
# 全量扫描,确保没有遗漏
run_linter()
run_security_scan()
run_tests()
1.6.4. 精细化明确控制,而非模糊限制
理念:不是笼统的"不允许",而是哪个工具 + 哪个文件 + 什么条件。
# ❌ 错误做法:粗粒度限制
{
"systemPrompt": "不要修改配置文件" # 太笼统!
}
# ✅ 正确做法:精细化控制
# 维度1: 工具级控制
def should_allow_tool(tool_name, args):
# 只拦截特定工具
if tool_name == "write_to_file":
file_path = args.get("filePath")
# 维度2: 文件级控制
if file_path.endswith(".env"):
return {
"action": "block",
"systemPrompt": "❌ 禁止直接写入 .env,请使用 .env.example"
}
# 维度3: 内容级控制
content = args.get("content", "")
if "API_KEY" in content and "sk-" in content:
return {
"action": "block",
"systemPrompt": "❌ 检测到硬编码 API Key!请使用环境变量"
}
# 维度4: 条件级控制
if tool_name == "execute_command":
cmd = args.get("command")
if "rm -rf" in cmd and not os.getenv("ALLOW_DESTRUCTIVE"):
return {
"action": "warn",
"systemPrompt": "⚠️ 危险命令!需要先设置 ALLOW_DESTRUCTIVE=true"
}
1.6.5. 闭环反馈,AI自我学习,而非反复犯错
设定实现闭环的反馈机制,借助Hook注入AI上下文,形成错误 → 反馈 → 修正 → 记忆工作流。
1.6.5.1. 步骤1: 检测错误
# PostToolUse Hook
def check_code_quality(tool_result):
if "write_to_file" in tool_result:
issues = lint_code(tool_result["content"])
if issues:
return {
"systemPrompt": f"""
⚠️ 代码质量问题:
{format_issues(issues)}
请立即修正以上问题,并在下次生成代码时避免类似错误。
"""
}
1.6.5.2. 步骤2: 注入上下文(关键)
# 返回的 systemPrompt 会自动注入 AI 上下文
{
"systemPrompt": """
## 本次会话学到的经验(必须遵守)
❌ 刚才的错误:
- 使用了字符串拼接 SQL: "SELECT * FROM users WHERE id=" + userId
✅ 正确做法:
- 使用参数化查询: db.query("SELECT * FROM users WHERE id=?", [userId])
📌 请在后续所有数据库操作中应用此规则
"""
}
1.6.5.3. 步骤3: AI自动记忆
# 场景:AI学习安全编码
# 第1次错误
AI生成代码:
const query = "SELECT * FROM users WHERE name='" + userName + "'"
PostToolUse Hook反馈:
"⚠️ SQL注入风险!使用参数化查询: db.query('...WHERE name=?', [userName])"
# 第2次(同一会话)
AI生成代码:
const query = "SELECT * FROM orders WHERE user_id=?"
db.query(query, [userId]) ✅ 自动修正!
# 第3次(新会话,但有SessionStart Hook)
SessionStart加载历史经验:
"本项目始终使用参数化查询,禁止字符串拼接SQL"
AI生成代码:
db.query("SELECT * FROM products WHERE id=?", [id]) ✅ 从一开始就正确!
1.7. 总结:告别随缘,拥抱确定性
以前用AI编程,是不是总感觉像在开盲盒?我们和AI明明说得很好,但是在执行过程一转身AI就忘记了,直到改坏我们的代码或者我们中途不得不强制中断。
而有了CodeBuddy Code的Hooks得以终结!它就像给AI的行为设定了一套"纪律",你规定好在什么时候(比如编辑文件后)、做什么事(比如运行命令),它就必须准确地、按时地去执行!
而且这种"纪律"可以由开发者来掌握,开发者作为这个系统的设计者和编排者,其工作效率和对项目质量的把控能力都将得到质的提升。这意味着,在与AI协作过程中,开发者/人类重新掌握了和AI协作的主动权!
当前仅CodeBuddy Code支持Hooks,欢迎大家使用和交流、吃狗粮,帮我们打磨更好的产品体验,定制自己的AI开发工作流。