mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-01 06:33:27 +08:00
222 lines
8.6 KiB
Markdown
222 lines
8.6 KiB
Markdown
# 钩子
|
||
|
||
钩子是事件驱动的自动化程序,在 Claude Code 工具执行前后触发。它们用于强制执行代码质量、及早发现错误以及自动化重复性检查。
|
||
|
||
## 钩子如何工作
|
||
|
||
```
|
||
用户请求 → Claude 选择工具 → PreToolUse 钩子运行 → 工具执行 → PostToolUse 钩子运行
|
||
```
|
||
|
||
* **PreToolUse** 钩子在工具执行前运行。它们可以**阻止**(退出码 2)或**警告**(stderr 输出但不阻止)。
|
||
* **PostToolUse** 钩子在工具完成后运行。它们可以分析输出但不能阻止执行。
|
||
* **Stop** 钩子在每次 Claude 响应后运行。
|
||
* **SessionStart/SessionEnd** 钩子在会话生命周期的边界处运行。
|
||
* **PreCompact** 钩子在上下文压缩前运行,适用于保存状态。
|
||
|
||
## 本插件中的钩子
|
||
|
||
### PreToolUse 钩子
|
||
|
||
| 钩子 | 匹配器 | 行为 | 退出码 |
|
||
|------|---------|----------|-----------|
|
||
| **开发服务器拦截器** | `Bash` | 在 tmux 外阻止 `npm run dev` 等命令 — 确保日志可访问 | 2 (拦截) |
|
||
| **Tmux 提醒器** | `Bash` | 对长时间运行命令(npm test、cargo build、docker)建议使用 tmux | 0 (警告) |
|
||
| **Git 推送提醒器** | `Bash` | 在 `git push` 前提醒检查变更 | 0 (警告) |
|
||
| **文档文件警告器** | `Write` | 对非标准 `.md`/`.txt` 文件发出警告(允许 README、CLAUDE、CONTRIBUTING、CHANGELOG、LICENSE、SKILL、docs/、skills/);跨平台路径处理 | 0 (警告) |
|
||
| **策略性压缩提醒器** | `Edit\|Write` | 建议在逻辑间隔(约每 50 次工具调用)手动执行 `/compact` | 0 (警告) |
|
||
| **InsAIts 安全监控器(可选加入)** | `Bash\|Write\|Edit\|MultiEdit` | 对高信号工具输入的可选安全扫描。除非设置 `ECC_ENABLE_INSAITS=1`,否则禁用。对关键发现进行拦截,对非关键发现发出警告,并将审计日志写入 `.insaits_audit_session.jsonl`。需要 `pip install insa-its`。[详情](../../../scripts/hooks/insaits-security-monitor.py) | 2 (拦截关键) / 0 (警告) |
|
||
|
||
### PostToolUse 钩子
|
||
|
||
| 钩子 | 匹配器 | 功能 |
|
||
|------|---------|-------------|
|
||
| **PR 记录器** | `Bash` | 在 `gh pr create` 后记录 PR URL 和审查命令 |
|
||
| **构建分析** | `Bash` | 构建命令后的后台分析(异步,非阻塞) |
|
||
| **质量门** | `Edit\|Write\|MultiEdit` | 在编辑后运行快速质量检查 |
|
||
| **Prettier 格式化** | `Edit` | 编辑后使用 Prettier 自动格式化 JS/TS 文件 |
|
||
| **TypeScript 检查** | `Edit` | 在编辑 `.ts`/`.tsx` 文件后运行 `tsc --noEmit` |
|
||
| **console.log 警告** | `Edit` | 警告编辑的文件中存在 `console.log` 语句 |
|
||
|
||
### 生命周期钩子
|
||
|
||
| 钩子 | 事件 | 功能 |
|
||
|------|-------|-------------|
|
||
| **会话开始** | `SessionStart` | 加载先前上下文并检测包管理器 |
|
||
| **预压缩** | `PreCompact` | 在上下文压缩前保存状态 |
|
||
| **Console.log 审计** | `Stop` | 每次响应后检查所有修改的文件是否有 `console.log` |
|
||
| **会话摘要** | `Stop` | 当转录路径可用时持久化会话状态 |
|
||
| **模式提取** | `Stop` | 评估会话以提取可抽取的模式(持续学习) |
|
||
| **成本追踪器** | `Stop` | 发出轻量级的运行成本遥测标记 |
|
||
| **会话结束标记** | `SessionEnd` | 生命周期标记和清理日志 |
|
||
|
||
## 自定义钩子
|
||
|
||
### 禁用钩子
|
||
|
||
在 `hooks.json` 中移除或注释掉钩子条目。如果作为插件安装,请在您的 `~/.claude/settings.json` 中覆盖:
|
||
|
||
```json
|
||
{
|
||
"hooks": {
|
||
"PreToolUse": [
|
||
{
|
||
"matcher": "Write",
|
||
"hooks": [],
|
||
"description": "Override: allow all .md file creation"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 运行时钩子控制(推荐)
|
||
|
||
使用环境变量控制钩子行为,无需编辑 `hooks.json`:
|
||
|
||
```bash
|
||
# minimal | standard | strict (default: standard)
|
||
export ECC_HOOK_PROFILE=standard
|
||
|
||
# Disable specific hook IDs (comma-separated)
|
||
export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
|
||
```
|
||
|
||
配置文件:
|
||
|
||
* `minimal` —— 仅保留必要的生命周期和安全钩子。
|
||
* `standard` —— 默认;平衡的质量 + 安全检查。
|
||
* `strict` —— 启用额外的提醒和更严格的防护措施。
|
||
|
||
### 编写你自己的钩子
|
||
|
||
钩子是 shell 命令,通过 stdin 接收 JSON 格式的工具输入,并且必须在 stdout 上输出 JSON。
|
||
|
||
**基本结构:**
|
||
|
||
```javascript
|
||
// my-hook.js
|
||
let data = '';
|
||
process.stdin.on('data', chunk => data += chunk);
|
||
process.stdin.on('end', () => {
|
||
const input = JSON.parse(data);
|
||
|
||
// Access tool info
|
||
const toolName = input.tool_name; // "Edit", "Bash", "Write", etc.
|
||
const toolInput = input.tool_input; // Tool-specific parameters
|
||
const toolOutput = input.tool_output; // Only available in PostToolUse
|
||
|
||
// Warn (non-blocking): write to stderr
|
||
console.error('[Hook] Warning message shown to Claude');
|
||
|
||
// Block (PreToolUse only): exit with code 2
|
||
// process.exit(2);
|
||
|
||
// Always output the original data to stdout
|
||
console.log(data);
|
||
});
|
||
```
|
||
|
||
**退出码:**
|
||
|
||
* `0` —— 成功(继续执行)
|
||
* `2` —— 阻止工具调用(仅限 PreToolUse)
|
||
* 其他非零值 —— 错误(记录日志但不阻止)
|
||
|
||
### 钩子输入模式
|
||
|
||
```typescript
|
||
interface HookInput {
|
||
tool_name: string; // "Bash", "Edit", "Write", "Read", etc.
|
||
tool_input: {
|
||
command?: string; // Bash: the command being run
|
||
file_path?: string; // Edit/Write/Read: target file
|
||
old_string?: string; // Edit: text being replaced
|
||
new_string?: string; // Edit: replacement text
|
||
content?: string; // Write: file content
|
||
};
|
||
tool_output?: { // PostToolUse only
|
||
output?: string; // Command/tool output
|
||
};
|
||
}
|
||
```
|
||
|
||
### 异步钩子
|
||
|
||
对于不应阻塞主流程的钩子(例如,后台分析):
|
||
|
||
```json
|
||
{
|
||
"type": "command",
|
||
"command": "node my-slow-hook.js",
|
||
"async": true,
|
||
"timeout": 30
|
||
}
|
||
```
|
||
|
||
异步钩子在后台运行。它们不能阻止工具执行。
|
||
|
||
## 常用钩子配方
|
||
|
||
### 警告 TODO 注释
|
||
|
||
```json
|
||
{
|
||
"matcher": "Edit",
|
||
"hooks": [{
|
||
"type": "command",
|
||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const ns=i.tool_input?.new_string||'';if(/TODO|FIXME|HACK/.test(ns)){console.error('[Hook] New TODO/FIXME added - consider creating an issue')}console.log(d)})\""
|
||
}],
|
||
"description": "Warn when adding TODO/FIXME comments"
|
||
}
|
||
```
|
||
|
||
### 阻止创建大文件
|
||
|
||
```json
|
||
{
|
||
"matcher": "Write",
|
||
"hooks": [{
|
||
"type": "command",
|
||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller, focused modules');process.exit(2)}console.log(d)})\""
|
||
}],
|
||
"description": "Block creation of files larger than 800 lines"
|
||
}
|
||
```
|
||
|
||
### 使用 ruff 自动格式化 Python 文件
|
||
|
||
```json
|
||
{
|
||
"matcher": "Edit",
|
||
"hooks": [{
|
||
"type": "command",
|
||
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.py$/.test(p)){const{execFileSync}=require('child_process');try{execFileSync('ruff',['format',p],{stdio:'pipe'})}catch(e){}}console.log(d)})\""
|
||
}],
|
||
"description": "Auto-format Python files with ruff after edits"
|
||
}
|
||
```
|
||
|
||
### 要求新源文件附带测试文件
|
||
|
||
```json
|
||
{
|
||
"matcher": "Write",
|
||
"hooks": [{
|
||
"type": "command",
|
||
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file found for: '+p);console.error('[Hook] Expected: '+testPath);console.error('[Hook] Consider writing tests first (/tdd)')}}console.log(d)})\""
|
||
}],
|
||
"description": "Remind to create tests when adding new source files"
|
||
}
|
||
```
|
||
|
||
## 跨平台注意事项
|
||
|
||
钩子逻辑在 Node.js 脚本中实现,以便在 Windows、macOS 和 Linux 上具有跨平台行为。保留了少量 shell 包装器用于持续学习的观察者钩子;这些包装器受配置文件控制,并具有 Windows 安全的回退行为。
|
||
|
||
## 相关
|
||
|
||
* [rules/common/hooks.md](../rules/common/hooks.md) —— 钩子架构指南
|
||
* [skills/strategic-compact/](../../../skills/strategic-compact) —— 策略性压缩技能
|
||
* [scripts/hooks/](../../../scripts/hooks) —— 钩子脚本实现
|