Claude Code Hooks实战:8大事件+10个脚本,打造你的AI自动化工作流
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_repositoriesmcp__filesystem__read_filemcp__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 匹配器的最佳实践
- 保持精确性:尽量使用最具体的匹配模式,避免意外触发
- 考虑大小写:匹配器区分大小写,确保与工具名完全一致
- 测试匹配规则:使用
/hooks命令的TUI界面验证匹配效果 - 避免冲突:多个Hook匹配同一工具时,确保它们逻辑上兼容
- 文档化规则:在团队中共享匹配器约定,保持一致性
4. 环境变量与输入输出:Hook与Claude Code的通信机制
Hook脚本不是孤立运行的,它们通过标准输入接收上下文数据,通过标准输出和退出码与Claude Code通信。理解这个通信机制是编写高级Hook的关键。
4.1 环境变量全集
Claude Code为Hook执行环境注入了一系列环境变量,这些变量包含了当前操作的上下文信息:
| 变量名 | 出现事件 | 内容示例 | 说明 |
|---|---|---|---|
CLAUDE_EVENT | 全部 | PreToolUse | 当前触发的事件名称 |
CLAUDE_TOOL_NAME | Pre/PostToolUse | Bash | 工具名称 |
CLAUDE_TOOL_INPUT | Pre/PostToolUse | JSON字符串 | 工具输入参数 |
CLAUDE_TOOL_OUTPUT | PostToolUse | JSON字符串 | 工具执行结果 |
CLAUDE_FILE_PATH | Edit/Write | /home/user/src/index.ts | 操作的文件路径 |
CLAUDE_LINE_START | Edit/Write | 12 | 编辑起始行号 |
CLAUDE_LINE_END | Edit/Write | 15 | 编辑结束行号 |
CLAUDE_NOTIFICATION_TEXT | Notification | Claude needs your input | 通知文本内容 |
CLAUDE_SUBAGENT_NAME | SubagentStop | planner | 子代理名称 |
CLAUDE_SUBAGENT_RESULT | SubagentStop | JSON结果 | 子代理执行结果 |
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())
# 确保以句
更多推荐



所有评论(0)