CodeBuddy Code Hooks探索实践:Hooks为你的AI配上随叫随到的管家

1. CodeBuddy Code Hooks探索实践:Hooks为你的AI配上随叫随到的管家

AI 编程像在"开盲盒"?CodeBuddy Code Hooks 用事件驱动机制给 AI 配上一套"纪律系统":九大事件覆盖交互全生命周期,高危命令拦截、代码质量审查、团队规范注入三个实战案例,附五个使用技巧,一文讲透。

1.1. 目录

1.2. AI编程工具使用中的痛点

在使用AI编程工具时,我们常常会遇到以下问题:

  1. 不可控性:AI在执行过程中可能偏离预期,导致代码质量下降
  2. 缺乏拦截机制:无法在关键节点进行验证和拦截
  3. 重复性工作:需要反复提醒AI遵循特定规范
  4. 安全风险:AI可能执行危险操作而不自知
  5. 缺乏一致性:不同会话间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. 配置方法

  1. 创建.codebuddy/hooks/dangerous-command-blocker.py
  2. 添加到.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. 效果展示

CodeBuddy Code Hooks高危命令拦截示例

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. 效果展示

CodeBuddy Code Hooks代码质量审查示例

CodeBuddy Code Hooks代码质量审查结果

1.5.3. 自动格式化与规范注入

1.5.3.1. 实现思路

通过SessionStart事件在会话启动时自动注入团队规范文档,让AI在每次对话开始时就了解团队的编码标准。

1.5.3.2. 配置方法

  1. 创建.codebuddy/settings.json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash .codebuddy/hooks/inject-team-standards.sh"
          }
        ]
      }
    ]
  }
}
  1. 创建.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
  1. 创建团队规范文档

创建.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使得会话初始化自动化,生成符合规范的结果:

CodeBuddy Code Hooks规范注入效果

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开发工作流。

1.8. 参考资料

  1. Hooks官方文档
  2. 开源Claude Code模版地址
  3. 开源仓库参考