Claude Code Hooks实战:8大事件+10个脚本,打造你的AI自动化工作流

如果你还在用提示词来“请求”Claude Code帮你格式化代码、记录日志或者保护敏感文件,那么你很可能已经体验过那种不确定性带来的挫败感——模型可能会忘记你的要求,或者在不同会话中表现不一致。这种依赖AI“心情”的工作方式,在个人小项目里或许还能忍受,但在严肃的工程环境中就显得不够可靠了。

Claude Code的Hooks功能正是为了解决这个问题而生。它把那些原本写在提示词里的“建议”,变成了每次都会触发的“确定性规则”。想象一下,每次编辑TypeScript文件后,prettier都会自动运行;每次Claude尝试修改.env文件时,都会被系统级拦截;每次执行Bash命令,都会自动记录到审计日志中——这些不再是“可能发生”,而是“必然发生”。

对于开发者来说,这意味着你可以把Claude Code从一个需要不断提醒的“实习生”,转变为一个遵循严格工作流程的“资深工程师”。本文将带你深入Hooks的8大生命周期事件,通过10个实战脚本,构建一套完整的AI自动化工作流。

1. Hooks核心概念:从概率性提示到确定性规则

在深入具体配置之前,我们需要先理解Hooks的设计哲学。传统的AI编程助手工作模式是“请求-响应”式的:你通过提示词告诉AI你想要什么,AI根据理解(和当时的“心情”)决定如何执行。这种模式有两个根本问题:

第一是可靠性问题。模型可能会误解你的意图,或者在不同上下文中对同一指令做出不同反应。比如你告诉Claude“请用prettier格式化代码”,它可能这次执行了,下次却忘记了。

第二是安全性问题。你无法在系统层面确保某些操作永远不会发生。即使你反复强调“不要修改.env文件”,模型仍然有可能在特定上下文中“认为”修改是必要的。

Hooks通过引入生命周期事件和确定性脚本执行,彻底改变了这一范式。它允许你在Claude Code工作流的特定节点插入自定义的Shell命令或脚本,这些脚本会:

  • 每次必然执行,不依赖模型的“记忆”或“判断”
  • 以当前用户权限运行,可以访问和操作系统资源
  • 能够拦截和修改操作,实现细粒度的控制
  • 支持复杂的业务逻辑,通过脚本实现任意功能

1.1 Hooks的配置文件体系

Hooks配置支持多级覆盖,这让你可以在不同粒度上定义规则:

配置级别文件路径生效范围典型用途
用户级~/.claude/settings.json所有项目个人工作习惯、全局审计日志
项目级.claude/settings.json当前项目团队代码规范、项目特定检查
本地级.claude/settings.local.json仅当前用户个人调试配置、临时规则
企业策略管理员指定路径全组织范围安全合规策略、统一标准

优先级规则是:企业策略 > 本地级 > 项目级 > 用户级。这意味着你可以为整个团队设置基线规则,同时允许个人在必要时进行本地覆盖。

配置文件的基本结构如下:

{
  "hooks": {
    "事件名称": [
      {
        "matcher": "工具匹配模式",
        "hooks": [
          {
            "type": "command",
            "command": "要执行的Shell命令",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

这个结构中的三个核心要素——事件、匹配器、命令——构成了Hooks的完整逻辑链。我们接下来会逐一深入。

2. 深入8大生命周期事件:在正确的时间做正确的事

Claude Code定义了8个核心的生命周期事件,覆盖了从会话开始到结束的完整工作流。理解每个事件的触发时机和适用场景,是设计有效Hooks的关键。

2.1 PreToolUse:工具调用的守门人

触发时机:Claude Code生成工具参数后、实际执行工具调用前。

这是最常用也最强大的事件之一。PreToolUse允许你在工具执行前进行拦截、验证或修改。典型应用包括:

  • 安全审查:检查Bash命令是否包含危险操作
  • 权限控制:阻止对敏感文件的修改
  • 输入验证:确保工具参数符合规范
  • 环境准备:为工具执行设置必要的环境变量

注意:PreToolUse是唯一能够完全阻止工具执行的事件。通过返回特定的退出码或JSON响应,你可以让Claude Code放弃当前工具调用。

PreToolUse接收的输入数据包含完整的工具上下文:

{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/directory",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/test",
    "description": "清理临时目录"
  }
}

2.2 PostToolUse:执行后的清理与增强

触发时机:工具成功执行完成后立即运行。

如果说PreToolUse是“事前控制”,那么PostToolUse就是“事后处理”。它适用于那些需要在工具执行后进行的操作:

  • 自动格式化:代码编辑后运行formatter
  • 日志记录:记录工具执行的结果和性能
  • 状态同步:更新外部系统状态
  • 通知发送:工具执行完成后发送通知

PostToolUse的输入比PreToolUse多了一个tool_response字段,包含了工具执行的结果:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/src/index.ts",
    "content": "const x = 1;"
  },
  "tool_response": {
    "filePath": "/src/index.ts",
    "success": true,
    "bytesWritten": 15
  }
}

2.3 UserPromptSubmit:用户输入的预处理与验证

触发时机:用户提交提示词后、Claude开始处理前。

这个事件让你能够在Claude“看到”用户输入之前进行干预。常见用途包括:

  • 输入标准化:自动为提示词添加前缀或后缀
  • 安全检查:检测并阻止包含敏感信息的提示词
  • 上下文增强:基于当前会话状态自动补充上下文信息
  • 意图分类:分析用户意图并路由到不同的处理流程

UserPromptSubmit的输入相对简单,主要就是用户提交的原始提示词:

{
  "hook_event_name": "UserPromptSubmit",
  "prompt": "帮我写一个用户登录功能的React组件"
}

2.4 Notification:系统通知的自定义处理

触发时机:Claude Code发送系统通知时。

Claude Code会在多种情况下发送通知,比如等待用户输入、请求权限等。通过Notification事件,你可以:

  • 自定义通知方式:将系统通知转发到桌面、Slack、邮件等
  • 通知过滤:只转发重要通知,忽略次要信息
  • 通知增强:为通知添加额外上下文或操作建议
  • 自动化响应:在某些情况下自动响应通知

Notification的输入包含了通知的类型和内容:

{
  "hook_event_name": "Notification",
  "notification_type": "idle_prompt",
  "message": "Claude is waiting for your input",
  "title": "Claude Code"
}

2.5 Stop与SubagentStop:任务结束的智能判断

Stop触发时机:主Claude Code代理完成响应时(用户手动中断不触发)。

SubagentStop触发时机:子代理(Task工具)任务完成时。

这两个事件让你能够在任务结束时进行“收尾工作”或“结果评估”:

  • 成果验证:检查任务是否真正完成
  • 自动提交:代码修改后自动执行git commit
  • 报告生成:汇总任务执行结果生成报告
  • 资源清理:清理临时文件、释放资源
  • 流程衔接:基于当前结果决定下一步操作

Stop事件的特殊之处在于,它可以通过返回decision: "block"来阻止Claude停止,强制它继续工作。这在需要Claude“再检查一遍”或“补充一些内容”的场景中非常有用。

2.6 PreCompact:上下文压缩前的最后机会

触发时机:Claude Code即将执行上下文压缩操作前。

当对话历史过长时,Claude Code会自动(或手动)进行上下文压缩,以保持在模型的上下文窗口限制内。PreCompact让你有机会:

  • 重要信息备份:在压缩前备份关键对话内容
  • 摘要生成:自动生成对话摘要供后续参考
  • 压缩策略调整:基于内容重要性自定义压缩规则
  • 状态保存:将需要持久化的信息保存到外部存储

2.7 SessionStart与SessionEnd:会话生命周期的管理

SessionStart触发时机:启动新会话或恢复现有会话时。

SessionEnd触发时机:会话结束时。

这两个事件围绕整个会话生命周期,适合进行环境级别的操作:

  • 环境初始化:加载项目特定配置、设置环境变量
  • 会话恢复:从上次中断的地方继续工作
  • 资源准备:确保所需服务已启动、依赖已安装
  • 会话归档:保存完整的会话记录和成果
  • 资源释放:关闭数据库连接、停止本地服务

3. 匹配器(Matcher)精讲:精确控制触发条件

匹配器是Hooks的“过滤器”,它决定了在什么情况下触发Hook。对于PreToolUse和PostToolUse这两个工具相关的事件,匹配器是必须的;对于其他事件,匹配器是可选的。

3.1 基础匹配模式

匹配器支持多种匹配模式,从简单到复杂:

精确匹配:"Bash" 只匹配Bash工具 多选匹配:"Edit|Write" 匹配Edit或Write工具 通配匹配:"*" 或 "" 匹配所有工具 正则匹配:"Bash.*" 匹配所有以Bash开头的工具

实际使用中,我建议从精确匹配开始,确保Hook只在预期的情况下触发。过度使用通配符可能会导致意外的副作用。

3.2 MCP工具的特殊匹配

对于通过Model Context Protocol集成的外部工具,Claude Code使用特定的命名约定:

mcp__<server_name>__<tool_name>

例如:

  • mcp__github__search_repositories
  • mcp__filesystem__read_file
  • mcp__memory__create_entities

你可以针对整个MCP服务器或特定工具进行匹配:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__github__.*",
        "hooks": [{"type": "command", "command": "echo 'GitHub操作审计' >> /tmp/mcp-audit.log"}]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [{"type": "command", "command": "./scripts/validate-write.py"}]
      }
    ]
  }
}

3.3 匹配器的最佳实践

  1. 保持精确性:尽量使用最具体的匹配模式,避免意外触发
  2. 考虑大小写:匹配器区分大小写,确保与工具名完全一致
  3. 测试匹配规则:使用/hooks命令的TUI界面验证匹配效果
  4. 避免冲突:多个Hook匹配同一工具时,确保它们逻辑上兼容
  5. 文档化规则:在团队中共享匹配器约定,保持一致性

4. 环境变量与输入输出:Hook与Claude Code的通信机制

Hook脚本不是孤立运行的,它们通过标准输入接收上下文数据,通过标准输出和退出码与Claude Code通信。理解这个通信机制是编写高级Hook的关键。

4.1 环境变量全集

Claude Code为Hook执行环境注入了一系列环境变量,这些变量包含了当前操作的上下文信息:

变量名出现事件内容示例说明
CLAUDE_EVENT全部PreToolUse当前触发的事件名称
CLAUDE_TOOL_NAMEPre/PostToolUseBash工具名称
CLAUDE_TOOL_INPUTPre/PostToolUseJSON字符串工具输入参数
CLAUDE_TOOL_OUTPUTPostToolUseJSON字符串工具执行结果
CLAUDE_FILE_PATHEdit/Write/home/user/src/index.ts操作的文件路径
CLAUDE_LINE_STARTEdit/Write12编辑起始行号
CLAUDE_LINE_ENDEdit/Write15编辑结束行号
CLAUDE_NOTIFICATION_TEXTNotificationClaude needs your input通知文本内容
CLAUDE_SUBAGENT_NAMESubagentStopplanner子代理名称
CLAUDE_SUBAGENT_RESULTSubagentStopJSON结果子代理执行结果
CLAUDE_PROJECT_DIR全部/home/user/project项目根目录绝对路径

这些环境变量让Shell脚本能够轻松访问上下文信息,无需复杂的JSON解析。例如,在Bash脚本中可以直接引用:

#!/bin/bash
echo "工具名称: $CLAUDE_TOOL_NAME"
echo "文件路径: $CLAUDE_FILE_PATH"

4.2 标准输入:JSON格式的完整上下文

除了环境变量,Hook还会通过标准输入接收完整的JSON数据。这对于需要访问嵌套数据或进行复杂处理的脚本特别有用。

一个典型的PreToolUse输入:

{
  "session_id": "session_123",
  "transcript_path": "/home/user/.claude/sessions/current.jsonl",
  "cwd": "/home/user/projects/myapp",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm run build",
    "description": "构建项目",
    "working_dir": "/home/user/projects/myapp"
  }
}

在Python脚本中处理这个输入:

#!/usr/bin/env python3
import json
import sys

def main():
    try:
        # 从标准输入读取JSON
        data = json.load(sys.stdin)
        
        # 提取关键信息
        tool_name = data.get('tool_name')
        tool_input = data.get('tool_input', {})
        command = tool_input.get('command', '')
        
        # 业务逻辑处理
        if tool_name == 'Bash' and 'rm -rf' in command:
            print("检测到危险删除操作", file=sys.stderr)
            sys.exit(2)  # 阻止执行
            
    except json.JSONDecodeError as e:
        print(f"JSON解析错误: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

4.3 输出机制:简单退出码与高级JSON响应

Hook有两种方式向Claude Code反馈执行结果:

简单方式:退出码

退出码含义Claude Code行为
0成功继续执行,stdout显示给用户
2阻止阻止当前操作,stderr反馈给Claude
其他错误继续执行,stderr显示给用户

高级方式:JSON响应

对于需要更精细控制的场景,Hook可以通过stdout返回JSON:

{
  "continue": false,
  "stopReason": "检测到敏感操作,已阻止",
  "systemMessage": "请修改命令后再试",
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "命令包含危险参数"
  }
}

关键字段说明:

  • continue: 是否允许事件继续(false时优先于其他规则)
  • stopReason: continue为false时显示给用户的原因
  • systemMessage: 向用户显示的系统级消息
  • hookSpecificOutput: 事件特定的控制信息

不同事件支持的hookSpecificOutput字段不同。例如,PreToolUse可以包含permissionDecision(allow/deny/ask)和updatedInput(修改后的工具参数)。

5. 10个实战脚本:从基础到高级的完整工作流

理论讲得再多,不如实际代码来得直观。下面这10个脚本覆盖了从基础审计到高级自动化的完整场景,你可以直接使用或基于它们进行定制。

5.1 全局Bash命令审计系统

场景:需要记录所有Bash命令的执行情况,用于安全审计和问题排查。

配置(保存到~/.claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '\"[$(date -Iseconds)] $CLAUDE_TOOL_NAME: \(.tool_input.command)\"' >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

进阶版本:带上下文信息的详细审计

创建脚本文件~/.claude/hooks/bash_audit.sh:

#!/bin/bash
# 详细Bash命令审计脚本

set -e

# 读取JSON输入
INPUT_JSON=$(cat)
TIMESTAMP=$(date -Iseconds)
SESSION_ID="$CLAUDE_SESSION_ID"
PROJECT_DIR="$CLAUDE_PROJECT_DIR"
TOOL_NAME="$CLAUDE_TOOL_NAME"

# 使用jq提取信息
COMMAND=$(echo "$INPUT_JSON" | jq -r '.tool_input.command // ""')
DESCRIPTION=$(echo "$INPUT_JSON" | jq -r '.tool_input.description // "无描述"')
WORKING_DIR=$(echo "$INPUT_JSON" | jq -r '.tool_input.working_dir // ""')

# 构建日志条目
LOG_ENTRY="{
  \"timestamp\": \"$TIMESTAMP\",
  \"session_id\": \"$SESSION_ID\",
  \"project\": \"$PROJECT_DIR\",
  \"tool\": \"$TOOL_NAME\",
  \"command\": \"$COMMAND\",
  \"description\": \"$DESCRIPTION\",
  \"working_dir\": \"$WORKING_DIR\",
  \"user\": \"$(whoami)\",
  \"hostname\": \"$(hostname)\"
}"

# 追加到审计日志
echo "$LOG_ENTRY" >> ~/.claude/audit.jsonl

# 同时写入简单文本日志便于查看
echo "[$TIMESTAMP] $COMMAND ($DESCRIPTION)" >> ~/.claude/audit-simple.log

更新配置使用脚本:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/bash_audit.sh"
          }
        ]
      }
    ]
  }
}

5.2 TypeScript自动格式化与Lint检查

场景:团队开发中确保代码风格一致,每次编辑后自动格式化和检查。

配置:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "[[ \"$CLAUDE_FILE_PATH\" == *.ts ]] && npx prettier --write \"$CLAUDE_FILE_PATH\" && npx eslint --fix \"$CLAUDE_FILE_PATH\""
          }
        ]
      }
    ]
  }
}

完整版本:支持TypeScript和JavaScript的智能格式化

创建./claude/hooks/typescript_formatter.py:

#!/usr/bin/env python3
"""
TypeScript/JavaScript自动格式化脚本
支持prettier格式化、eslint检查、自动import排序
"""

import json
import sys
import os
import subprocess
from pathlib import Path

def should_process_file(file_path: str) -> bool:
    """判断是否为需要处理的文件类型"""
    extensions = {'.ts', '.tsx', '.js', '.jsx', '.vue', '.svelte'}
    return any(file_path.endswith(ext) for ext in extensions)

def run_command(cmd: str, cwd: str = None) -> tuple[bool, str]:
    """运行shell命令并返回结果"""
    try:
        result = subprocess.run(
            cmd,
            shell=True,
            cwd=cwd,
            capture_output=True,
            text=True,
            timeout=30  # 30秒超时
        )
        return result.returncode == 0, result.stderr or result.stdout
    except subprocess.TimeoutExpired:
        return False, "命令执行超时"
    except Exception as e:
        return False, str(e)

def main():
    try:
        # 读取Claude输入
        input_data = json.load(sys.stdin)
        file_path = input_data.get('tool_input', {}).get('file_path', '')
        
        if not file_path or not os.path.exists(file_path):
            sys.exit(0)
        
        # 检查文件类型
        if not should_process_file(file_path):
            sys.exit(0)
        
        file_dir = os.path.dirname(file_path)
        file_name = os.path.basename(file_path)
        
        print(f"正在处理文件: {file_name}", file=sys.stderr)
        
        # 1. 检查是否有package.json和必要的依赖
        package_json = os.path.join(file_dir, 'package.json')
        if not os.path.exists(package_json):
            # 向上查找package.json
            current = Path(file_dir).parent
            while current != current.parent:  # 直到根目录
                package_json = current / 'package.json'
                if package_json.exists():
                    file_dir = str(current)
                    break
                current = current.parent
        
        # 2. 运行prettier
        success, output = run_command(f'npx prettier --write "{file_path}"', cwd=file_dir)
        if not success:
            print(f"Prettier格式化失败: {output}", file=sys.stderr)
        else:
            print("✓ Prettier格式化完成", file=sys.stderr)
        
        # 3. 运行eslint(如果项目中有配置)
        eslint_configs = ['.eslintrc.js', '.eslintrc.cjs', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json']
        has_eslint = any(os.path.exists(os.path.join(file_dir, config)) for config in eslint_configs)
        
        if has_eslint:
            success, output = run_command(f'npx eslint --fix "{file_path}"', cwd=file_dir)
            if not success:
                print(f"ESLint检查失败: {output}", file=sys.stderr)
            else:
                print("✓ ESLint检查完成", file=sys.stderr)
        
        # 4. 如果有typescript,检查类型
        if file_path.endswith(('.ts', '.tsx')):
            tsconfig_path = os.path.join(file_dir, 'tsconfig.json')
            if os.path.exists(tsconfig_path):
                success, output = run_command(f'npx tsc --noEmit "{file_path}"', cwd=file_dir)
                if not success:
                    print(f"TypeScript类型检查发现问题: {output}", file=sys.stderr)
                else:
                    print("✓ TypeScript类型检查通过", file=sys.stderr)
        
    except Exception as e:
        print(f"格式化脚本执行错误: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

5.3 生产文件保护机制

场景:防止Claude意外修改生产环境配置文件或敏感目录。

配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"import json, sys, os; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); blocked=['.env', '.env.production', 'package-lock.json', '.git/', 'node_modules/', 'dist/', 'build/']; sys.exit(2 if any(p in path for p in blocked) else 0)\""
          }
        ]
      }
    ]
  }
}

增强版本:基于上下文的智能保护

创建./claude/hooks/file_protector.py:

#!/usr/bin/env python3
"""
智能文件保护系统
基于文件路径、内容、项目上下文的多维度保护
"""

import json
import sys
import os
import re
from pathlib import Path

class FileProtector:
    def __init__(self):
        self.protected_patterns = [
            # 配置文件
            r'\.env(\..*)?$',  # .env, .env.local, .env.production等
            r'config\/.*\.(json|yaml|yml|toml)$',
            r'^docker-compose\.ya?ml$',
            r'^package-lock\.json$',
            r'^yarn\.lock$',
            
            # 版本控制
            r'\.git\/',
            r'\.svn\/',
            r'\.hg\/',
            
            # 构建产物
            r'\/dist\/',
            r'\/build\/',
            r'\/out\/',
            r'\/node_modules\/',
            r'\/target\/',  # Rust/Cargo
            r'\/__pycache__\/',
            
            # 敏感数据
            r'\/secrets\/',
            r'\/keys\/',
            r'\/certificates\/',
            r'\.pem$',
            r'\.key$',
            r'\.crt$',
            
            # 系统文件
            r'^\/etc\/',
            r'^\/usr\/',
            r'^\/var\/',
            r'^\/root\/',
        ]
        
        # 项目特定的保护规则
        self.project_specific_rules = self.load_project_rules()
    
    def load_project_rules(self):
        """加载项目特定的保护规则"""
        rules = []
        
        # 检查项目根目录是否有.claude/protected-paths.txt
        project_dir = os.environ.get('CLAUDE_PROJECT_DIR', '')
        if project_dir:
            rules_file = os.path.join(project_dir, '.claude', 'protected-paths.txt')
            if os.path.exists(rules_file):
                with open(rules_file, 'r') as f:
                    for line in f:
                        line = line.strip()
                        if line and not line.startswith('#'):
                            rules.append(line)
        
        return rules
    
    def is_protected(self, file_path: str, tool_input: dict) -> tuple[bool, str]:
        """检查文件是否受保护,返回(是否保护, 原因)"""
        
        # 1. 检查项目特定规则
        for pattern in self.project_specific_rules:
            if re.search(pattern, file_path):
                return True, f"匹配项目保护规则: {pattern}"
        
        # 2. 检查全局模式
        for pattern in self.protected_patterns:
            if re.search(pattern, file_path):
                return True, f"匹配全局保护模式: {pattern}"
        
        # 3. 检查文件内容(如果是编辑操作)
        if 'content' in tool_input:
            content = tool_input.get('content', '')
            # 检测可能包含敏感信息的内容
            sensitive_patterns = [
                r'password\s*[:=]\s*[\'\"][^\'\"]+[\'\"]',
                r'api[_-]?key\s*[:=]\s*[\'\"][^\'\"]+[\'\"]',
                r'secret\s*[:=]\s*[\'\"][^\'\"]+[\'\"]',
                r'token\s*[:=]\s*[\'\"][^\'\"]+[\'\"]',
            ]
            
            for pattern in sensitive_patterns:
                if re.search(pattern, content, re.IGNORECASE):
                    return True, "内容可能包含敏感信息"
        
        # 4. 检查文件权限(如果是重要系统文件)
        try:
            stat_info = os.stat(file_path)
            # 如果文件属于root或其他系统用户
            import pwd
            file_owner = pwd.getpwuid(stat_info.st_uid).pw_name
            if file_owner not in [os.environ.get('USER'), '']:
                return True, f"文件属于系统用户: {file_owner}"
        except:
            pass
        
        return False, ""
    
    def check_and_protect(self, input_data: dict) -> int:
        """主检查逻辑"""
        tool_name = input_data.get('tool_name', '')
        tool_input = input_data.get('tool_input', {})
        file_path = tool_input.get('file_path', '')
        
        if not file_path:
            return 0  # 没有文件路径,不保护
        
        # 解析绝对路径
        if not os.path.isabs(file_path):
            cwd = input_data.get('cwd', os.getcwd())
            file_path = os.path.join(cwd, file_path)
        
        protected, reason = self.is_protected(file_path, tool_input)
        
        if protected:
            print(f"⚠️ 文件保护触发: {reason}", file=sys.stderr)
            print(f"文件路径: {file_path}", file=sys.stderr)
            print(f"如需修改此文件,请先更新保护规则", file=sys.stderr)
            return 2  # 退出码2表示阻止
        
        return 0  # 允许执行

def main():
    try:
        input_data = json.load(sys.stdin)
        protector = FileProtector()
        exit_code = protector.check_and_protect(input_data)
        sys.exit(exit_code)
    except Exception as e:
        print(f"保护脚本执行错误: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

5.4 Markdown文档自动优化

场景:自动为Markdown文档中的代码块添加语言标签,优化格式。

配置:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_optimizer.py"
          }
        ]
      }
    ]
  }
}

创建./claude/hooks/markdown_optimizer.py:

#!/usr/bin/env python3
"""
Markdown文档自动优化脚本
功能:
1. 为代码块自动检测并添加语言标签
2. 优化标题层级
3. 清理多余空行
4. 标准化列表格式
"""

import json
import sys
import os
import re
from typing import List, Tuple

class MarkdownOptimizer:
    def __init__(self):
        self.language_patterns = {
            'python': [
                (r'^\s*(import|from)\s+\w+', re.MULTILINE),
                (r'^\s*def\s+\w+\s*\(', re.MULTILINE),
                (r'^\s*class\s+\w+', re.MULTILINE),
                (r'\bprint\(', re.IGNORECASE),
                (r'\bself\.', re.IGNORECASE),
            ],
            'javascript': [
                (r'\b(function|const|let|var)\s+\w+', re.IGNORECASE),
                (r'=>|console\.(log|error|warn)', re.IGNORECASE),
                (r'^\s*import\s+.*from', re.MULTILINE),
                (r'^\s*export\s+(default\s+)?', re.MULTILINE),
            ],
            'typescript': [
                (r':\s*(string|number|boolean|any)\b', re.IGNORECASE),
                (r'interface\s+\w+', re.IGNORECASE),
                (r'type\s+\w+\s*=', re.IGNORECASE),
            ],
            'bash': [
                (r'^#!.*\b(bash|sh)\b', re.MULTILINE),
                (r'\b(echo|grep|sed|awk|find)\b', re.IGNORECASE),
                (r'\$(\(|{)', re.IGNORECASE),
                (r'^\s*#', re.MULTILINE),  # 注释
            ],
            'sql': [
                (r'\b(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\b', re.IGNORECASE),
                (r'\bFROM\b', re.IGNORECASE),
                (r'\bWHERE\b', re.IGNORECASE),
                (r'\bJOIN\b', re.IGNORECASE),
            ],
            'json': [
                (r'^\s*[{\[]', re.MULTILINE),
                (r'\".*?\".*?:', re.IGNORECASE),
            ],
            'yaml': [
                (r'^\s*\w+:', re.MULTILINE),
                (r'^\s*- ', re.MULTILINE),
                (r'^\s*\w+:\s*\|', re.MULTILINE),
            ],
            'dockerfile': [
                (r'^\s*FROM\s+', re.IGNORECASE),
                (r'^\s*RUN\s+', re.IGNORECASE),
                (r'^\s*COPY\s+', re.IGNORECASE),
                (r'^\s*EXPOSE\s+', re.IGNORECASE),
            ],
        }
    
    def detect_language(self, code: str) -> str:
        """检测代码块的语言"""
        code = code.strip()
        if not code:
            return 'text'
        
        # 按优先级检查各种语言
        for lang, patterns in self.language_patterns.items():
            for pattern, flags in patterns:
                if re.search(pattern, code, flags):
                    return lang
        
        # 特殊检查:如果看起来像配置但没匹配上
        if re.search(r'^\s*[\w-]+\s*[:=]', code, re.MULTILINE):
            return 'yaml'  # 默认当作yaml处理
        
        return 'text'
    
    def optimize_code_blocks(self, content: str) -> str:
        """优化代码块:添加语言标签、标准化格式"""
        
        # 匹配代码块的正则表达式
        # 支持三种反引号数量:```、````、`````
        fence_pattern = r'(?ms)^([ \t]{0,3})(`{3,5})([^\n]*)\n(.*?)(\n\1\2[ \t]*$)'
        
        def process_code_block(match):
            indent, fence, info, body, closing = match.groups()
            info = info.strip()
            
            # 如果已经有语言标签,保留
            if info and not info.startswith('{'):
                return match.group(0)
            
            # 检测语言
            lang = self.detect_language(body)
            
            # 返回处理后的代码块
            return f"{indent}{fence}{lang}\n{body}{closing}"
        
        # 处理所有代码块
        content = re.sub(fence_pattern, process_code_block, content)
        
        return content
    
    def optimize_headings(self, content: str) -> str:
        """优化标题:确保层级合理、添加锚点"""
        lines = content.split('\n')
        result = []
        heading_levels = [0] * 6  # h1-h6的计数
        
        for line in lines:
            # 检查是否是Markdown标题
            match = re.match(r'^(#{1,6})\s+(.+)$', line)
            if match:
                level = len(match.group(1))
                title = match.group(2).strip()
                
                # 更新层级计数
                heading_levels[level-1] += 1
                # 重置更低层级的计数
                for i in range(level, 6):
                    heading_levels[i] = 0
                
                # 生成带锚点的标题(可选)
                # anchor = re.sub(r'[^\w\s-]', '', title.lower())
                # anchor = re.sub(r'[-\s]+', '-', anchor)
                # line = f"{'#' * level} {title} {{#{anchor}}}"
            
            result.append(line)
        
        return '\n'.join(result)
    
    def clean_whitespace(self, content: str) -> str:
        """清理多余空白:空行、行尾空格等"""
        # 移除行尾空格
        content = re.sub(r'[ \t]+$', '', content, flags=re.MULTILINE)
        
        # 标准化换行符
        content = content.replace('\r\n', '\n').replace('\r', '\n')
        
        # 清理多余空行(最多连续两个空行)
        content = re.sub(r'\n{3,}', '\n\n', content)
        
        # 确保文件以换行符结束
        if content and not content.endswith('\n'):
            content += '\n'
        
        return content
    
    def standardize_lists(self, content: str) -> str:
        """标准化列表格式"""
        lines = content.split('\n')
        result = []
        
        for i, line in enumerate(lines):
            # 检查无序列表
            if re.match(r'^(\s*)[-*+]\s+', line):
                # 标准化为短横线
                line = re.sub(r'^(\s*)[*+]\s+', r'\1- ', line)
            
            # 检查有序列表,确保数字连续
            match = re.match(r'^(\s*)(\d+)\.\s+', line)
            if match:
                indent, num = match.groups()
                # 这里可以添加逻辑来重新编号,但通常保持原样即可
            
            result.append(line)
        
        return '\n'.join(result)
    
    def optimize(self, content: str) -> str:
        """执行完整的Markdown优化"""
        original = content
        
        # 执行各个优化步骤
        content = self.optimize_code_blocks(content)
        content = self.optimize_headings(content)
        content = self.standardize_lists(content)
        content = self.clean_whitespace(content)
        
        return content

def main():
    try:
        # 读取Claude输入
        input_data = json.load(sys.stdin)
        file_path = input_data.get('tool_input', {}).get('file_path', '')
        
        if not file_path or not os.path.exists(file_path):
            sys.exit(0)
        
        # 只处理Markdown文件
        if not file_path.lower().endswith(('.md', '.markdown', '.mdx')):
            sys.exit(0)
        
        # 读取文件内容
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # 优化内容
        optimizer = MarkdownOptimizer()
        optimized = optimizer.optimize(content)
        
        # 如果内容有变化,写入文件
        if optimized != content:
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(optimized)
            
            # 输出变更统计
            original_lines = content.count('\n')
            optimized_lines = optimized.count('\n')
            changes = sum(1 for a, b in zip(content.split('\n'), optimized.split('\n')) if a != b)
            
            print(f"✓ Markdown优化完成", file=sys.stderr)
            print(f"  文件: {os.path.basename(file_path)}", file=sys.stderr)
            print(f"  变更行数: {changes}", file=sys.stderr)
            print(f"  总行数: {original_lines} → {optimized_lines}", file=sys.stderr)
        
    except Exception as e:
        print(f"Markdown优化错误: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == '__main__':
    main()

5.5 桌面通知集成

场景:当Claude需要用户输入或完成重要操作时,发送桌面通知。

配置:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/desktop_notifier.sh"
          }
        ]
      }
    ]
  }
}

创建~/.claude/hooks/desktop_notifier.sh:

#!/bin/bash
# 跨平台桌面通知脚本
# 支持macOS、Linux、Windows(通过WSL)

set -e

# 读取JSON输入
INPUT_JSON=$(cat)
NOTIFICATION_TYPE=$(echo "$INPUT_JSON" | jq -r '.notification_type // "unknown"')
MESSAGE=$(echo "$INPUT_JSON" | jq -r '.message // "Claude Code通知"')
TITLE=$(echo "$INPUT_JSON" | jq -r '.title // "Claude Code"')

# 根据通知类型设置优先级和图标
case "$NOTIFICATION_TYPE" in
    "permission_prompt")
        PRIORITY="critical"
        ICON="dialog-warning"
        URGENCY="high"
        ;;
    "idle_prompt")
        PRIORITY="normal"
        ICON="dialog-information"
        URGENCY="normal"
        ;;
    "auth_success")
        PRIORITY="low"
        ICON="dialog-information"
        URGENCY="low"
        ;;
    *)
        PRIORITY="normal"
        ICON="dialog-information"
        URGENCY="normal"
        ;;
esac

# 检测操作系统并发送通知
if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS
    osascript -e "display notification \"$MESSAGE\" with title \"$TITLE\""
    
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
    # Linux - 尝试多种通知方式
    
    # 1. 首先尝试notify-send(需要libnotify)
    if command -v notify-send &> /dev/null; then
        notify-send -u "$URGENCY" -i "$ICON" "$TITLE" "$MESSAGE"
    
    # 2. 尝试kdialog(KDE)
    elif command -v kdialog &> /dev/null; then
        kdialog --title "$TITLE" --msgbox "$MESSAGE"
    
    # 3. 尝试zenity(GNOME)
    elif command -v zenity &> /dev/null; then
        zenity --info --title="$TITLE" --text="$MESSAGE" --width=300
    
    # 4. 最后尝试简单的终端通知
    else
        echo -e "\n\033[1;33m⚠️ $TITLE\033[0m"
        echo -e "\033[0;36m$MESSAGE\033[0m\n"
    fi

elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
    # Windows
    powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('$MESSAGE', '$TITLE')"
fi

# 可选:同时记录到日志文件
LOG_DIR="$HOME/.claude/notifications"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).log"

echo "$(date -Iseconds) | $NOTIFICATION_TYPE | $TITLE | $MESSAGE" >> "$LOG_FILE"

# 对于重要通知,还可以发送到其他渠道
if [[ "$NOTIFICATION_TYPE" == "permission_prompt" ]]; then
    # 这里可以集成Slack、Teams等通知
    # 例如:curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$MESSAGE\"}" YOUR_SLACK_WEBHOOK_URL
    :
fi

5.6 会话启动欢迎系统

场景:每次启动Claude Code会话时,显示项目信息、环境状态和待办事项。

配置:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/session_welcome.sh"
          }
        ]
      }
    ]
  }
}

创建~/.claude/hooks/session_welcome.sh:

#!/bin/bash
# 会话启动欢迎脚本
# 显示项目信息、环境状态、Git状态等

set -e

# 读取JSON输入
INPUT_JSON=$(cat)
SESSION_SOURCE=$(echo "$INPUT_JSON" | jq -r '.source // "startup"')
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"

# 定义颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# 欢迎横幅
echo -e "${PURPLE}"
echo "╔══════════════════════════════════════════════════════════╗"
echo "║                   Claude Code 工作区                     ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo -e "${NC}"

# 显示会话信息
echo -e "${CYAN}📅 会话信息${NC}"
echo -e "  启动时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo -e "  会话来源: $SESSION_SOURCE"
echo -e "  会话ID: $(echo "$INPUT_JSON" | jq -r '.session_id // "N/A"')"
echo ""

# 显示项目信息
if [[ -n "$PROJECT_DIR" && -d "$PROJECT_DIR" ]]; then
    echo -e "${CYAN}📁 项目信息${NC}"
    echo -e "  项目路径: $PROJECT_DIR"
    
    # 检测项目类型
    if [[ -f "$PROJECT_DIR/package.json" ]]; then
        PROJECT_NAME=$(jq -r '.name // "未命名项目"' "$PROJECT_DIR/package.json")
        PROJECT_VERSION=$(jq -r '.version // "未指定版本"' "$PROJECT_DIR/package.json")
        echo -e "  项目名称: $PROJECT_NAME"
        echo -e "  项目版本: $PROJECT_VERSION"
        
        # 显示Node.js版本
        if command -v node &> /dev/null; then
            NODE_VERSION=$(node --version)
            echo -e "  Node版本: $NODE_VERSION"
        fi
    fi
    
    # 显示Git状态
    if [[ -d "$PROJECT_DIR/.git" ]]; then
        cd "$PROJECT_DIR"
        BRANCH=$(git branch --show-current 2>/dev/null || echo "N/A")
        COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo "N/A")
        LAST_COMMIT=$(git log -1 --format="%h - %s" --abbrev-commit 2>/dev/null || echo "N/A")
        
        echo -e "  Git分支: $BRANCH"
        echo -e "  提交数量: $COMMIT_COUNT"
        echo -e "  最后提交: $LAST_COMMIT"
        
        # 检查是否有未提交的更改
        if [[ -n $(git status --porcelain 2>/dev/null) ]]; then
            echo -e "  ${YELLOW}⚠ 有未提交的更改${NC}"
        else
            echo -e "  ${GREEN}✓ 工作区干净${NC}"
        fi
    fi
    echo ""
fi

# 显示环境信息
echo -e "${CYAN}⚙️  环境信息${NC}"
echo -e "  系统: $(uname -s) $(uname -r)"
echo -e "  用户: $(whoami)"
echo -e "  主机: $(hostname)"
echo -e "  终端: $TERM"
echo ""

# 显示待办事项(如果存在)
TODO_FILE="$PROJECT_DIR/.claude/todo.md"
if [[ -f "$TODO_FILE" ]]; then
    echo -e "${CYAN}📋 待办事项${NC}"
    # 显示前5个待办事项
    grep -E '^\s*[-*+]\s+\[ \]' "$TODO_FILE" | head -5 | while read -r line; do
        echo -e "  ${YELLOW}○${NC} ${line#*\[ \]}"
    done
    TOTAL_TODOS=$(grep -c '^\s*[-*+]\s+\[ \]' "$TODO_FILE")
    if [[ $TOTAL_TODOS -gt 5 ]]; then
        echo -e "  ... 还有 $((TOTAL_TODOS - 5)) 个待办事项"
    fi
    echo ""
fi

# 显示可用命令提示
echo -e "${CYAN}💡 快捷提示${NC}"
echo -e "  ${GREEN}/hooks${NC} - 管理Hooks配置"
echo -e "  ${GREEN}/config${NC} - 编辑项目配置"
echo -e "  ${GREEN}/clear${NC} - 清空会话历史"
echo -e "  ${GREEN}/rewind${NC} - 回退到检查点"
echo ""

# 根据项目类型显示特定提示
if [[ -f "$PROJECT_DIR/package.json" ]]; then
    SCRIPTS=$(jq -r '.scripts | keys[]' "$PROJECT_DIR/package.json" 2>/dev/null | tr '\n' ' ')
    if [[ -n "$SCRIPTS" ]]; then
        echo -e "${CYAN}📦 可用npm脚本${NC}"
        echo -e "  $SCRIPTS"
        echo ""
    fi
fi

echo -e "${GREEN}🚀 会话已就绪,开始工作吧!${NC}"

5.7 子代理任务统计与分析

场景:跟踪和分析子代理(Task工具)的执行情况,优化任务分配和性能。

配置:

{
  "hooks": {
    "SubagentStop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/subagent_analyzer.sh"
          }
        ]
      }
    ]
  }
}

创建~/.claude/hooks/subagent_analyzer.sh:

#!/bin/bash
# 子代理任务分析脚本
# 收集任务执行数据,用于性能分析和优化

set -e

# 读取JSON输入
INPUT_JSON=$(cat)
SUBAGENT_NAME="$CLAUDE_SUBAGENT_NAME"
SUBAGENT_RESULT="$CLAUDE_SUBAGENT_RESULT"
SESSION_ID=$(echo "$INPUT_JSON" | jq -r '.session_id // "unknown"')
TIMESTAMP=$(date -Iseconds)

# 解析结果JSON
if [[ -n "$SUBAGENT_RESULT" ]]; then
    RESULT_JSON=$(echo "$SUBAGENT_RESULT" | jq -c '.')
    SUCCESS=$(echo "$RESULT_JSON" | jq -r '.success // false')
    DURATION=$(echo "$RESULT_JSON" | jq -r '.duration // 0')
    TASK_TYPE=$(echo "$RESULT_JSON" | jq -r '.task_type // "unknown"')
    OUTPUT_LENGTH=$(echo "$RESULT_JSON" | jq -r '.output | length // 0')
else
    SUCCESS="false"
    DURATION=0
    TASK_TYPE="unknown"
    OUTPUT_LENGTH=0
fi

# 构建分析记录
ANALYSIS_RECORD=$(cat <<EOF
{
  "timestamp": "$TIMESTAMP",
  "session_id": "$SESSION_ID",
  "subagent_name": "$SUBAGENT_NAME",
  "task_type": "$TASK_TYPE",
  "success": $SUCCESS,
  "duration_ms": $DURATION,
  "output_length": $OUTPUT_LENGTH,
  "metadata": {
    "cwd": "$PWD",
    "user": "$(whoami)",
    "hostname": "$(hostname)"
  }
}
EOF
)

# 保存到分析数据库
ANALYSIS_DIR="$HOME/.claude/analysis"
mkdir -p "$ANALYSIS_DIR"

# 1. 追加到JSONL日志
echo "$ANALYSIS_RECORD" >> "$ANALYSIS_DIR/subagents.jsonl"

# 2. 更新统计摘要
STATS_FILE="$ANALYSIS_DIR/subagent_stats.json"
if [[ ! -f "$STATS_FILE" ]]; then
    cat > "$STATS_FILE" <<EOF
{
  "total_tasks": 0,
  "successful_tasks": 0,
  "failed_tasks": 0,
  "total_duration_ms": 0,
  "by_subagent": {},
  "by_task_type": {},
  "recent_tasks": []
}
EOF
fi

# 使用jq更新统计
jq --arg name "$SUBAGENT_NAME" \
   --arg type "$TASK_TYPE" \
   --argjson success "$SUCCESS" \
   --argjson duration "$DURATION" \
   '
    .total_tasks += 1 |
    .total_duration_ms += $duration |
    
    if $success == true then
        .successful_tasks += 1
    else
        .failed_tasks += 1
    end |
    
    .by_subagent[$name] = (.by_subagent[$name] // {
        total: 0,
        successful: 0,
        failed: 0,
        total_duration: 0
    }) |
    .by_subagent[$name].total += 1 |
    .by_subagent[$name].total_duration += $duration |
    
    if $success == true then
        .by_subagent[$name].successful += 1
    else
        .by_subagent[$name].failed += 1
    end |
    
    .by_task_type[$type] = (.by_task_type[$type] // {
        total: 0,
        successful: 0,
        failed: 0
    }) |
    .by_task_type[$type].total += 1 |
    
    if $success == true then
        .by_task_type[$type].successful += 1
    else
        .by_task_type[$type].failed += 1
    end |
    
    .recent_tasks = [
        {
            timestamp: "'"$TIMESTAMP"'",
            subagent: $name,
            task_type: $type,
            success: $success,
            duration_ms: $duration
        }
    ] + .recent_tasks[0:49]  # 保留最近50个任务
   ' "$STATS_FILE" > "$STATS_FILE.tmp" && mv "$STATS_FILE.tmp" "$STATS_FILE"

# 3. 生成性能报告(每100个任务生成一次)
TASK_COUNT=$(jq -r '.total_tasks' "$STATS_FILE")
if [[ $((TASK_COUNT % 100)) -eq 0 ]]; then
    REPORT_FILE="$ANALYSIS_DIR/report_$(date +%Y%m%d_%H%M%S).md"
    
    cat > "$REPORT_FILE" <<EOF
# Claude Code 子代理性能报告
**生成时间:** $(date '+%Y-%m-%d %H:%M:%S')  
**统计周期:** 最近100个任务  
**总任务数:** $TASK_COUNT  

## 总体统计
- 成功任务: $(jq -r '.successful_tasks' "$STATS_FILE")
- 失败任务: $(jq -r '.failed_tasks' "$STATS_FILE")
- 成功率: $(jq -r 'if .total_tasks > 0 then (.successful_tasks / .total_tasks * 100) else 0 end | floor' "$STATS_FILE")%
- 平均耗时: $(jq -r 'if .total_tasks > 0 then (.total_duration_ms / .total_tasks) else 0 end | floor' "$STATS_FILE")ms

## 按子代理统计
$(jq -r '.by_subagent | to_entries[] | "- \(.key): \(.value.total) tasks, \(.value.successful)成功, \(.value.failed)失败, 平均\(if .value.total > 0 then (.value.total_duration / .value.total | floor) else 0 end)ms"' "$STATS_FILE")

## 按任务类型统计
$(jq -r '.by_task_type | to_entries[] | "- \(.key): \(.value.total) tasks, \(.value.successful)成功, \(.value.failed)失败"' "$STATS_FILE")

## 建议
1. 考虑优化耗时较长的子代理
2. 分析失败任务的共同模式
3. 根据任务类型调整资源分配
EOF
    
    echo "📊 已生成第 $((TASK_COUNT / 100)) 份性能报告: $REPORT_FILE" >&2
fi

# 4. 实时反馈(针对长时间或失败的任务)
if [[ "$DURATION" -gt 10000 ]]; then  # 超过10秒
    echo "⏱️  任务 '$SUBAGENT_NAME' 执行时间较长: ${DURATION}ms" >&2
fi

if [[ "$SUCCESS" == "false" ]]; then
    echo "❌ 任务 '$SUBAGENT_NAME' 执行失败" >&2
    # 可以在这里触发警报或通知
fi

5.8 上下文压缩前的智能备份

场景:在Claude Code压缩对话上下文前,自动备份重要信息,防止关键内容丢失。

配置:

{
  "hooks": {
    "PreCompact": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/context_backup.sh"
          }
        ]
      }
    ]
  }
}

创建~/.claude/hooks/context_backup.sh:

#!/bin/bash
# 上下文压缩前的智能备份脚本
# 自动识别并备份重要对话内容

set -e

# 读取JSON输入
INPUT_JSON=$(cat)
TRIGGER=$(echo "$INPUT_JSON" | jq -r '.trigger // "auto"')
TRANSCRIPT_PATH=$(echo "$INPUT_JSON" | jq -r '.transcript_path // ""')
SESSION_ID=$(echo "$INPUT_JSON" | jq -r '.session_id // "unknown"')
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 备份目录
BACKUP_DIR="$HOME/.claude/backups"
mkdir -p "$BACKUP_DIR"

# 会话特定备份目录
SESSION_BACKUP_DIR="$BACKUP_DIR/session_${SESSION_ID}"
mkdir -p "$SESSION_BACKUP_DIR"

echo "🔍 开始分析对话上下文..." >&2

if [[ -z "$TRANSCRIPT_PATH" || ! -f "$TRANSCRIPT_PATH" ]]; then
    echo "⚠️  无法找到对话记录文件" >&2
    exit 0
fi

# 1. 完整备份当前对话状态
BACKUP_FILE="$SESSION_BACKUP_DIR/full_backup_${TIMESTAMP}.jsonl"
cp "$TRANSCRIPT_PATH" "$BACKUP_FILE"
echo "✓ 完整备份已保存: $(basename "$BACKUP_FILE")" >&2

# 2. 提取关键信息(代码片段、决策、重要讨论)
KEY_INFO_FILE="$SESSION_BACKUP_DIR/key_info_${TIMESTAMP}.md"

# 使用jq和grep提取关键内容
{
    echo "# 关键信息摘要"
    echo "**备份时间:** $(date '+%Y-%m-%d %H:%M:%S')"
    echo "**会话ID:** $SESSION_ID"
    echo "**触发类型:** $TRIGGER"
    echo ""
    
    echo "## 重要代码修改"
    echo ""
    
    # 提取所有代码编辑操作
    jq -r 'select(.role == "assistant" and .content[0].type == "tool_calls") | .content[0].tool_calls[] | select(.type == "tool_call" and (.name == "Edit" or .name == "Write")) | "### 文件: \(.input.file_path)\n```\n\(.input.content)\n```\n"' "$TRANSCRIPT_PATH" | head -10
    
    echo ""
    echo "## 关键决策点"
    echo ""
    
    # 提取包含决策关键词的消息
    grep -i -B2 -A2 "决定\|决策\|选择\|方案\|建议" "$TRANSCRIPT_PATH" | head -20 | sed 's/^/    /'
    
    echo ""
    echo "## 待办事项和问题"
    echo ""
    
    # 提取TODO和问题
    grep -i -B1 -A1 "TODO\|FIXME\|BUG\|问题\|待办" "$TRANSCRIPT_PATH" | head -15 | sed 's/^/    /'
    
    echo ""
    echo "## 会话统计"
    echo ""
    
    # 统计信息
    TOTAL_MESSAGES=$(jq -c '.' "$TRANSCRIPT_PATH" | wc -l)
    USER_MESSAGES=$(grep -c '"role": "user"' "$TRANSCRIPT_PATH")
    ASSISTANT_MESSAGES=$(grep -c '"role": "assistant"' "$TRANSCRIPT_PATH")
    TOOL_CALLS=$(grep -c '"type": "tool_call"' "$TRANSCRIPT_PATH")
    
    echo "- 总消息数: $TOTAL_MESSAGES"
    echo "- 用户消息: $USER_MESSAGES"
    echo "- 助手消息: $ASSISTANT_MESSAGES"
    echo "- 工具调用: $TOOL_CALLS"
    
} > "$KEY_INFO_FILE"

echo "✓ 关键信息摘要已保存: $(basename "$KEY_INFO_FILE")" >&2

# 3. 生成压缩建议
COMPRESSION_ADVICE_FILE="$SESSION_BACKUP_DIR/compression_advice_${TIMESTAMP}.txt"

{
    echo "压缩策略建议"
    echo "=============="
    echo ""
    
    # 分析对话结构,给出压缩建议
    echo "1. 可以安全压缩的内容:"
    echo "   - 早期的问候和确认消息"
    echo "   - 重复的代码审查意见"
    echo "   - 已解决的讨论线程"
    echo ""
    
    echo "2. 建议保留的内容:"
    echo "   - 最近的代码修改"
    echo "   - 未解决的问题讨论"
    echo "   - 项目配置和架构决策"
    echo ""
    
    echo "3. 压缩后建议添加的摘要:"
    echo "   - 项目当前状态"
    echo "   - 主要完成的工作"
    echo "   - 待解决的问题"
    echo ""
    
} > "$COMPRESSION_ADVICE_FILE"

echo "✓ 压缩建议已生成" >&2

# 4. 更新会话元数据
META_FILE="$SESSION_BACKUP_DIR/session_meta.json"
if [[ ! -f "$META_FILE" ]]; then
    cat > "$META_FILE" <<EOF
{
  "session_id": "$SESSION_ID",
  "created_at": "$(date -Iseconds)",
  "backups": [],
  "compression_count": 0,
  "last_activity": "$(date -Iseconds)"
}
EOF
fi

# 更新元数据
jq --arg timestamp "$TIMESTAMP" \
   --arg trigger "$TRIGGER" \
   '
    .last_activity = (now | strftime("%Y-%m-%dT%H:%M:%SZ")) |
    .compression_count += 1 |
    .backups += [{
        timestamp: $timestamp,
        trigger: $trigger,
        files: [
            "full_backup_\($timestamp).jsonl",
            "key_info_\($timestamp).md",
            "compression_advice_\($timestamp).txt"
        ]
    }] |
    .backups = .backups[-10:]  # 只保留最近10次备份
   ' "$META_FILE" > "$META_FILE.tmp" && mv "$META_FILE.tmp" "$META_FILE"

echo "✅ 上下文备份完成" >&2
echo "   备份位置: $SESSION_BACKUP_DIR" >&2
echo "   触发原因: $TRIGGER" >&2
echo "   当前备份次数: $(jq -r '.compression_count' "$META_FILE")" >&2

5.9 用户提示词安全过滤与增强

场景:在用户提交提示词给Claude处理前,进行安全检查、标准化和上下文增强。

配置:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/prompt_processor.py"
          }
        ]
      }
    ]
  }
}

创建~/.claude/hooks/prompt_processor.py:

#!/usr/bin/env python3
"""
用户提示词处理器
功能:
1. 安全检查:检测并阻止危险或敏感提示词
2. 标准化:统一提示词格式,添加必要上下文
3. 增强:基于项目状态自动补充信息
4. 路由:根据提示词内容路由到不同处理流程
"""

import json
import sys
import os
import re
import hashlib
from typing import Dict, List, Tuple, Optional
from pathlib import Path

class PromptProcessor:
    def __init__(self):
        self.project_dir = os.environ.get('CLAUDE_PROJECT_DIR', '')
        self.session_id = ''
        
        # 安全规则:阻止的提示词模式
        self.blocked_patterns = [
            # 系统操作
            (r'删除.*系统文件', '禁止操作系统文件'),
            (r'格式化.*硬盘', '禁止磁盘格式化操作'),
            (r'关机|重启|shutdown|reboot', '禁止系统关机操作'),
            
            # 敏感信息
            (r'显示.*密码|密钥|token', '禁止访问敏感信息'),
            (r'读取.*\.env|config.*secret', '禁止读取配置文件中的秘密'),
            (r'导出.*数据库.*全部', '禁止全量数据库导出'),
            
            # 危险操作
            (r'rm\s+-rf\s+/', '禁止根目录删除'),
            (r'chmod\s+[0-7]{3,4}\s+.*', '禁止危险权限修改'),
            (r'dd\s+.*/dev/', '禁止底层设备操作'),
            
            # 法律风险
            (r'破解|盗版|非法', '禁止违法内容'),
            (r'钓鱼|诈骗|欺诈', '禁止欺诈内容'),
            (r'攻击|入侵|渗透', '禁止攻击性内容'),
        ]
        
        # 警告模式:需要用户确认的模式
        self.warning_patterns = [
            (r'删除.*node_modules', '删除node_modules目录可能影响项目'),
            (r'清空.*数据库', '数据库清空操作不可逆'),
            (r'修改.*生产环境', '生产环境修改需要特别谨慎'),
            (r'重启.*服务', '服务重启可能导致中断'),
        ]
        
        # 项目上下文关键词
        self.project_context_keywords = self.load_project_context()
    
    def load_project_context(self) -> Dict[str, List[str]]:
        """加载项目上下文信息"""
        context = {
            'files': [],
            'technologies': [],
            'commands': [],
            'recent_changes': []
        }
        
        if not self.project_dir or not os.path.isdir(self.project_dir):
            return context
        
        try:
            # 检测项目类型和技术栈
            if os.path.exists(os.path.join(self.project_dir, 'package.json')):
                context['technologies'].append('nodejs')
                # 读取package.json中的脚本
                try:
                    with open(os.path.join(self.project_dir, 'package.json'), 'r') as f:
                        pkg = json.load(f)
                        if 'scripts' in pkg:
                            context['commands'].extend(pkg['scripts'].keys())
                except:
                    pass
            
            if os.path.exists(os.path.join(self.project_dir, 'pyproject.toml')):
                context['technologies'].append('python')
            
            if os.path.exists(os.path.join(self.project_dir, 'Cargo.toml')):
                context['technologies'].append('rust')
            
            if os.path.exists(os.path.join(self.project_dir, 'go.mod')):
                context['technologies'].append('go')
            
            # 读取最近修改的文件(如果有git)
            git_dir = os.path.join(self.project_dir, '.git')
            if os.path.exists(git_dir):
                try:
                    import subprocess
                    result = subprocess.run(
                        ['git', 'log', '--name-only', '--oneline', '-10'],
                        cwd=self.project_dir,
                        capture_output=True,
                        text=True
                    )
                    if result.returncode == 0:
                        files = set()
                        for line in result.stdout.split('\n'):
                            if line and not line.startswith(' ') and '.' in line:
                                files.add(line.strip())
                        context['recent_changes'] = list(files)[:5]
                except:
                    pass
            
        except Exception as e:
            print(f"加载项目上下文失败: {e}", file=sys.stderr)
        
        return context
    
    def safety_check(self, prompt: str) -> Tuple[bool, Optional[str], List[str]]:
        """安全检查:返回(是否阻止, 阻止原因, 警告列表)"""
        warnings = []
        
        # 检查阻止模式
        for pattern, reason in self.blocked_patterns:
            if re.search(pattern, prompt, re.IGNORECASE):
                return True, reason, warnings
        
        # 检查警告模式
        for pattern, warning in self.warning_patterns:
            if re.search(pattern, prompt, re.IGNORECASE):
                warnings.append(warning)
        
        return False, None, warnings
    
    def standardize_prompt(self, prompt: str) -> str:
        """标准化提示词格式"""
        # 移除多余空白
        prompt = re.sub(r'\s+', ' ', prompt.strip())
        
        # 确保以句

更多推荐