第四部分:实战应用场景
本节目的:学习真实项目中的Hook应用
⏱️ 预计时间:1-1.5小时
4.1 Git自动化工作流
场景1:提交前自动检查
需求:在git commit前自动运行代码检查,包括:
- 代码风格检查(lint)
- 敏感信息检查(API Key等)
- 分支保护(禁止直接提交main)
完整脚本 .claude/hooks/git-pre-commit-checker.py:
python
#!/usr/bin/env python3
"""
Git提交前检查系统
在执行git commit前自动运行多项检查
"""
import sys
import json
import subprocess
import re
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
# 配置
CONFIG = {
'protected_branches': ['main', 'master', 'production'],
'secret_patterns': [
r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\']?[\w-]{20,}',
r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\']?[\w-]{8,}',
r'(?i)(access[_-]?token|auth[_-]?token)\s*[=:]\s*["\']?[\w-]{20,}',
r'sk-[a-zA-Z0-9]{20,}', # OpenAI API Key
r'ghp_[a-zA-Z0-9]{36,}', # GitHub Token
],
}
def run_command(cmd, timeout=60):
"""运行命令并返回结果"""
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return result.returncode, result.stdout, result.stderr
except subprocess.TimeoutExpired:
return -1, '', 'Command timed out'
except Exception as e:
return -1, '', str(e)
def check_branch():
"""检查分支规则"""
code, stdout, _ = run_command('git rev-parse --abbrev-ref HEAD')
if code != 0:
return True, "无法获取当前分支"
branch = stdout.strip()
if branch in CONFIG['protected_branches']:
return False, f"X 禁止直接提交到受保护分支: {branch}\n请使用Pull Request"
return True, f"当前分支: {branch}"
def check_secrets():
"""检查敏感信息"""
code, stdout, _ = run_command('git diff --cached')
if code != 0:
return True, "无法获取diff"
findings = []
for pattern in CONFIG['secret_patterns']:
if re.search(pattern, stdout):
findings.append(f"发现可疑模式: {pattern[:40]}...")
if findings:
return False, "X 发现可能的敏感信息:\n" + '\n'.join(findings)
return True, "敏感信息检查通过"
def check_lint():
"""代码风格检查"""
code, stdout, _ = run_command('git diff --cached --name-only --diff-filter=ACMR')
if code != 0:
return True, "无法获取变更文件列表"
files = [f for f in stdout.strip().split('\n') if f]
py_files = [f for f in files if f.endswith('.py')]
js_files = [f for f in files if f.endswith(('.js', '.ts', '.jsx', '.tsx'))]
errors = []
# Python文件检查
if py_files:
code, stdout, stderr = run_command(f'ruff check {" ".join(py_files)}')
if code != 0:
errors.append(f"Python代码问题:\n{stdout or stderr}")
# JavaScript/TypeScript文件检查
if js_files:
code, stdout, stderr = run_command(f'npx eslint {" ".join(js_files)} --quiet')
if code != 0:
errors.append(f"JS/TS代码问题:\n{stdout or stderr}")
if errors:
return False, '\n'.join(errors)
return True, "代码风格检查通过"
def main():
# 读取输入
try:
input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
return
tool_name = input_data.get('tool_name', '')
command = input_data.get('tool_input', {}).get('command', '')
# 只处理git commit命令
if tool_name != 'Bash' or 'git commit' not in command:
return
# 运行检查
checks = [
('分支检查', check_branch),
('敏感信息', check_secrets),
('代码风格', check_lint),
]
results = []
all_passed = True
# 并行执行检查
with ThreadPoolExecutor(max_workers=3) as executor:
future_to_check = {executor.submit(check[1]): check[0] for check in checks}
for future in as_completed(future_to_check):
check_name = future_to_check[future]
try:
passed, message = future.result()
results.append((check_name, passed, message))
if not passed:
all_passed = False
except Exception as e:
results.append((check_name, False, f"检查异常: {str(e)}"))
all_passed = False
# 输出报告
print("\n" + "="*60, file=sys.stderr)
print("Git提交前检查报告", file=sys.stderr)
print("="*60, file=sys.stderr)
for name, passed, message in results:
status = "V PASS" if passed else "X FAIL"
print(f"\n{status} {name}", file=sys.stderr)
print(f" {message}", file=sys.stderr)
print("\n" + "="*60, file=sys.stderr)
# 输出决策
if not all_passed:
decision = {
"decision": "ask",
"message": "检查未通过,是否仍要继续提交?"
}
print(json.dumps(decision, ensure_ascii=False))
else:
print("所有检查通过,允许提交", file=sys.stderr)
if __name__ == '__main__':
main()配置:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/git-pre-commit-checker.py",
"timeout": 120
}
]
}
]
}
}4.2 代码质量保障
场景:Markdown文章质量检查
需求:保存Markdown文章后自动检查:
- 字数统计
- 标题检查
- 段落数量
脚本 .claude/hooks/post-article-quality.py:
python
#!/usr/bin/env python3
"""
PostToolUse Hook - 文章质量检查
保存Markdown文件后自动进行质量检查
"""
import sys
import json
from pathlib import Path
def check_article_quality(file_path):
"""检查文章质量"""
try:
content = Path(file_path).read_text(encoding='utf-8')
except Exception as e:
return f"无法读取文件: {e}"
# 统计指标
char_count = len(content)
word_count = len(content.split())
has_title = content.strip().startswith('#')
paragraphs = [p for p in content.split('\n\n') if p.strip()]
paragraph_count = len(paragraphs)
# 生成报告
report = []
report.append("\n" + "="*50)
report.append("文章质量检查报告")
report.append("="*50)
report.append(f"\n字符数: {char_count} {'V' if char_count > 500 else '! 偏短'}")
report.append(f"词数: {word_count}")
report.append(f"标题: {'V 有' if has_title else 'X 缺少一级标题'}")
report.append(f"段落数: {paragraph_count} {'V' if paragraph_count > 3 else '! 偏少'}")
# 建议
suggestions = []
if char_count < 500:
suggestions.append("- 建议增加内容,至少500字")
if not has_title:
suggestions.append("- 建议添加一级标题(# 标题)")
if paragraph_count < 3:
suggestions.append("- 建议增加段落,改善可读性")
if suggestions:
report.append("\n改进建议:")
report.extend(suggestions)
else:
report.append("\nV 文章质量良好!")
report.append("="*50 + "\n")
return '\n'.join(report)
def main():
try:
input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
return
tool_name = input_data.get('tool_name', '')
tool_input = input_data.get('tool_input', {})
file_path = tool_input.get('file_path', '')
# 只处理Write工具和.md文件
if tool_name != 'Write' or not file_path.endswith('.md'):
return
# 检查质量
report = check_article_quality(file_path)
print(report, file=sys.stderr)
if __name__ == '__main__':
main()4.3 完整配置示例
综合配置 .claude/settings.json:
json
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/user-prompt-enhance.py",
"timeout": 5
}
]
}
],
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/pre-protect-production.py",
"timeout": 5
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/pre-block-dangerous-cmd.py",
"timeout": 5
},
{
"type": "command",
"command": "python .claude/hooks/git-pre-commit-checker.py",
"timeout": 120
}
]
}
],
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/post-auto-format.py",
"timeout": 30
},
{
"type": "command",
"command": "python .claude/hooks/post-article-quality.py",
"timeout": 10
}
]
},
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/post-auto-backup.py",
"timeout": 10
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/session-start-check.py",
"timeout": 10
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "python .claude/hooks/notification-desktop.py",
"timeout": 5
}
]
}
]
}
}