Superpowers Hook 机制深度解析
本文从源码层面剖析 Superpowers 项目如何利用 Hook 机制,在 AI Agent 会话启动时自动注入"超能力"引导上下文,使得 14 个预定义技能(Skills)在对话中被自动发现和调用。
目录
- 一句话总结
- [为什么需要 Hook](#为什么需要 Hook)
- [Hook 的整体架构](#Hook 的整体架构)
- 四个核心文件详解
- 多平台适配策略
- [Hook 注入的内容:引导上下文](#Hook 注入的内容:引导上下文)
- [从 Hook 到 Skill 的完整链路](#从 Hook 到 Skill 的完整链路)
- [Claude Code Hook 完整机制](#Claude Code Hook 完整机制)
- 关键设计决策
一句话总结
Superpowers 只用了一个 Hook 事件 (SessionStart),做了一件事 :在 AI Agent 会话开始时,将 using-superpowers 技能的完整内容注入会话上下文,从而驱动 Agent 在整个会话生命周期中自动发现和调用所有技能。
为什么需要 Hook
AI Agent(Claude Code、Cursor、Codex 等)启动会话时,默认不知道 Superpowers 技能的存在。技能文件(SKILL.md)只是磁盘上的静态文件------除非有某种机制在会话开始时"告诉" Agent 这些技能存在,否则它们永远不会被触发。
Hook 就是这个机制。它解决了一个根本问题:如何在 Agent 的第一次回复之前,将技能发现和调用的规则注入到会话上下文中。
没有 Hook 的情况:
用户: "帮我做一个 React Todo 应用"
Agent: (直接开始写代码,跳过设计、规划等流程)
有 Hook 的情况:
[SessionStart Hook 触发,注入 using-superpowers 引导上下文]
用户: "帮我做一个 React Todo 应用"
Agent: (识别到 brainstorming 技能适用,先进行头脑风暴设计)
Hook 的整体架构
hooks/
├── hooks.json # Claude Code 平台的 Hook 配置
├── hooks-cursor.json # Cursor 平台的 Hook 配置
├── run-hook.cmd # 跨平台 polyglot 包装脚本(同时兼容 Windows CMD 和 Unix bash)
└── session-start # 实际的 Hook 逻辑脚本(无扩展名,避免 Windows 平台问题)
再加上一个非文件系统的 Hook 实现:
.opencode/plugins/superpowers.js # OpenCode 平台的 JavaScript 插件式 Hook
数据流如下:
平台启动会话
│
├─ Claude Code ──→ hooks.json ──→ run-hook.cmd ──→ session-start ──→ JSON 输出
│ (hookSpecificOutput.additionalContext)
│
├─ Cursor ───────→ hooks-cursor.json ──→ run-hook.cmd ──→ session-start ──→ JSON 输出
│ (additional_context)
│
├─ Copilot CLI ──→ hooks.json ──→ run-hook.cmd ──→ session-start ──→ JSON 输出
│ (additionalContext)
│
└─ OpenCode ────→ superpowers.js ──→ experimental.chat.messages.transform ──→ 消息注入
四个核心文件详解
1. hooks.json --- Claude Code 平台配置
json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|clear|compact",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
"async": false
}
]
}
]
}
}
关键点:
- 事件类型
SessionStart:这是 Claude Code 定义的生命周期事件,在会话开始时触发。 matcher: "startup|clear|compact":匹配三种场景------新会话启动、清空会话、压缩上下文。注意没有resume,因为恢复会话时上下文已经存在,不需要重复注入。async: false:同步执行。这保证了 Hook 在 Agent 第一次回复之前完成,否则引导上下文可能来不及注入。${CLAUDE_PLUGIN_ROOT}:Claude Code 提供的环境变量,指向插件根目录。路径用双引号包裹,兼容 Windows 路径中的空格。
2. hooks-cursor.json --- Cursor 平台配置
json
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": "./hooks/run-hook.cmd session-start"
}
]
}
}
关键差异:
- Cursor 使用 camelCase(
sessionStart),Claude Code 使用 PascalCase(SessionStart)。 - Cursor 不需要
matcher、type、async字段。 - 使用相对路径
./hooks/...而非环境变量。
通过 .cursor-plugin/plugin.json 中的 "hooks": "./hooks/hooks-cursor.json" 声明注册。
3. run-hook.cmd --- 跨平台 polyglot 包装器
这是项目中最巧妙的工程设计之一。一个文件,同时是有效的 Windows CMD 脚本和 Unix bash 脚本:
cmd
: << 'CMDBLOCK'
@echo off
REM Windows 部分:找到 bash 并执行目标脚本
if exist "C:\Program Files\Git\bin\bash.exe" (
"C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" ...
exit /b %ERRORLEVEL%
)
REM ... 更多 bash 查找路径 ...
exit /b 0
CMDBLOCK
# Unix 部分:直接执行目标脚本
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT_NAME="$1"
shift
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
工作原理:
- 在 Windows CMD 中 :
:被视为标签(如:label),<< 'CMDBLOCK'被忽略。CMD 执行@echo off及后续批处理命令,在exit /b处停止,永远不会执行CMDBLOCK之后的 Unix 代码。 - 在 Unix bash 中 :
:是空操作(no-op),<< 'CMDBLOCK'开始一个 heredoc,所有 Windows 代码被 heredoc 吞掉。bash 从CMDBLOCK之后开始执行 Unix 代码。
设计目的是充当一个通用的 Hook 路由器:接收脚本名作为参数(如 session-start),在任何平台上找到合适的 bash 来执行它。
4. session-start --- Hook 核心逻辑
这是整个 Hook 机制的核心,执行三件事:
第一步:检测旧版技能目录并生成警告
bash
legacy_skills_dir="${HOME}/.config/superpowers/skills"
if [ -d "$legacy_skills_dir" ]; then
warning_message="⚠️ WARNING: ..."
fi
如果用户还在使用 v2.x 时代的旧技能目录,生成迁移警告。
第二步:读取 using-superpowers 技能的完整内容
bash
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading")
将整个 SKILL.md 文件内容读入内存,然后通过纯 bash 字符串替换进行 JSON 转义(避免依赖 sed/awk 等外部工具,确保 Windows Git Bash 兼容性):
bash
escape_for_json() {
local s="$1"
s="${s//\\/\\\\}" # 转义反斜杠
s="${s//\"/\\\"}" # 转义双引号
s="${s//$'\n'/\\n}" # 转义换行
s="${s//$'\r'/\\r}" # 转义回车
s="${s//$'\t'/\\t}" # 转义制表符
printf '%s' "$s"
}
这个函数经历了重要的性能优化:早期版本使用逐字符循环(${input:$i:1}),在 bash 中是 O(n²) 复杂度,在 Windows Git Bash 上需要 60+ 秒。改用 bash 参数替换后,每个模式是单次 C 级别操作,性能提升 7 倍以上。
第三步:根据平台输出不同格式的 JSON
bash
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
# Cursor:snake_case 格式
printf '{ "additional_context": "%s" }' "$session_context"
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
# Claude Code:嵌套格式
printf '{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "%s" } }' "$session_context"
else
# Copilot CLI 或其他平台:SDK 标准格式
printf '{ "additionalContext": "%s" }' "$session_context"
fi
三种不同的 JSON 格式,因为每个平台读取 Hook 输出的方式不同:
| 平台 | JSON 字段 | 检测方式 |
|---|---|---|
| Cursor | additional_context (snake_case) |
CURSOR_PLUGIN_ROOT 环境变量存在 |
| Claude Code | hookSpecificOutput.additionalContext (嵌套) |
CLAUDE_PLUGIN_ROOT 存在且 COPILOT_CLI 不存在 |
| Copilot CLI | additionalContext (顶层) |
默认分支 |
一个关键细节:Claude Code 同时读取 additional_context 和 hookSpecificOutput,但不去重,所以必须只输出一种格式,否则引导上下文会被注入两次。
5. OpenCode 的 JavaScript 实现
OpenCode 不使用文件系统 Hook,而是通过 JavaScript 插件 API:
javascript
export const SuperpowersPlugin = async ({ client, directory }) => {
return {
// Hook 1:注册技能目录
config: async (config) => {
config.skills.paths.push(superpowersSkillsDir);
},
// Hook 2:在每次 Agent 步骤时,将引导内容注入第一条用户消息
'experimental.chat.messages.transform': async (_input, output) => {
const bootstrap = getBootstrapContent();
const firstUser = output.messages.find(m => m.info.role === 'user');
// 防重复注入
if (firstUser.parts.some(p => p.text.includes('EXTREMELY_IMPORTANT'))) return;
firstUser.parts.unshift({ type: 'text', text: bootstrap });
}
};
};
关键设计:
- 使用用户消息而非系统消息注入:系统消息在 OpenCode 中每轮都会重复,造成 token 膨胀;而且多系统消息会导致 Qwen 等模型崩溃。
- 模块级缓存 :
getBootstrapContent()只在第一次调用时读取磁盘和解析 frontmatter,后续调用直接返回缓存结果。因为experimental.chat.messages.transform在每个 Agent 步骤都会触发(OpenCode 的 prompt.ts 每步都从数据库重载消息),频繁的磁盘 I/O 会显著影响性能。
多平台适配策略
Superpowers 通过三层架构实现"一套 Hook 逻辑,多平台运行":
┌─────────────────────────────────────────────────────┐
│ 第 1 层:平台清单(Plugin Manifest) │
│ │
│ .cursor-plugin/plugin.json → hooks-cursor.json │
│ .claude-plugin/plugin.json → (自动发现 hooks.json) │
│ .opencode/plugins/superpowers.js → JS API │
│ .codex-plugin/plugin.json → (无 Hook,靠技能发现) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ 第 2 层:Hook 配置(声明式 JSON) │
│ │
│ hooks.json (Claude Code + Copilot CLI 共用) │
│ hooks-cursor.json (Cursor 专用格式) │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ 第 3 层:Hook 实现(运行时脚本) │
│ │
│ run-hook.cmd → session-start (所有 shell 平台共用) │
│ superpowers.js (OpenCode 专用) │
└─────────────────────────────────────────────────────┘
Windows 兼容性
Windows 支持是 Superpowers Hook 历史上最大的工程挑战。主要问题和解决方案:
| 问题 | 解决方案 |
|---|---|
CMD 不能执行 .sh 文件 |
run-hook.cmd polyglot 包装器 |
Claude Code 自动给 .sh 文件加 bash 前缀 |
使用无扩展名的 session-start 文件 |
${VAR} 在 CMD 中不工作 |
在 polyglot 中使用 %VAR% |
bash 不在 Windows PATH 中 |
按优先级搜索多个已知路径 |
| JSON 转义的 O(n²) 性能问题 | 使用 bash 参数替换代替逐字符循环 |
| bash 5.3+ heredoc 挂起 bug | 使用 printf 代替 cat <<EOF |
| POSIX 不兼容(dash/sh) | 使用 $0 代替 ${BASH_SOURCE[0]} |
Hook 注入的内容:引导上下文
Hook 注入的实际内容是一段用 <EXTREMELY_IMPORTANT> 标签包裹的文本:
<EXTREMELY_IMPORTANT>
You have superpowers.
**Below is the full content of your 'superpowers:using-superpowers' skill:**
[using-superpowers SKILL.md 的完整内容]
</EXTREMELY_IMPORTANT>
这段引导上下文包含了 using-superpowers 技能的核心指令:
-
强制技能检查规则:对每条用户消息,Agent 必须在回复前检查是否有适用的技能。即使只有 1% 的可能性,也必须调用技能。
-
反合理化表:预定义了 Agent 常见的跳过技能的借口及其反驳:
Agent 的想法 正确做法 "这只是个简单问题" 问题也是任务,检查技能 "我需要先了解更多上下文" 技能检查在澄清问题之前 "让我先探索代码库" 技能告诉你如何探索 "我知道这是什么意思" 知道概念 ≠ 使用技能 -
技能优先级:流程技能(brainstorming、debugging)先于实现技能执行。
-
平台适配指引 :Claude Code 使用
Skill工具,Copilot CLI 使用skill工具,Gemini CLI 使用activate_skill工具。 -
子代理停止门控 :
<SUBAGENT-STOP>标签告诉被派遣的子代理跳过此技能,避免子代理重复执行引导流程。
从 Hook 到 Skill 的完整链路
以一个实际场景为例------用户在 Cursor 中输入"帮我做一个 React Todo 应用":
1. 用户打开 Cursor 会话
│
2. Cursor 读取 .cursor-plugin/plugin.json
│ → 发现 "hooks": "./hooks/hooks-cursor.json"
│
3. Cursor 解析 hooks-cursor.json
│ → 发现 sessionStart 事件,命令为 ./hooks/run-hook.cmd session-start
│
4. Cursor 执行 run-hook.cmd session-start
│ → Unix 环境下,exec bash ./hooks/session-start
│
5. session-start 脚本执行
│ ├─ 检查旧版目录(无警告)
│ ├─ 读取 skills/using-superpowers/SKILL.md
│ ├─ JSON 转义内容
│ ├─ 检测 CURSOR_PLUGIN_ROOT 环境变量
│ └─ 输出 {"additional_context": "<EXTREMELY_IMPORTANT>..."}
│
6. Cursor 将 additional_context 注入会话上下文
│
7. 用户发送消息:"帮我做一个 React Todo 应用"
│
8. Agent 处理消息前,先检查引导上下文中的规则
│ → "创建功能/组件/添加功能性"匹配 brainstorming 技能描述
│ → 即使只有 1% 可能性也必须调用
│
9. Agent 调用 Skill 工具加载 brainstorming 技能
│
10. brainstorming 技能接管流程
├─ 探索项目上下文
├─ 逐个提问澄清需求
├─ 提出 2-3 种方案
├─ 展示设计并获得用户批准
├─ 编写设计文档
└─ 过渡到 writing-plans 技能 → 再过渡到 subagent-driven-development
关键观察:Hook 只触发一次,但其注入的引导上下文在整个会话中持续生效。每次 Agent 处理新消息时,都会参考这段上下文来决定是否需要调用技能。
Claude Code Hook 完整机制
Superpowers 只使用了 SessionStart 一个 Hook 事件。但 Claude Code 本身提供了远比这丰富的 Hook 体系。理解完整的 Hook 机制,有助于理解 Superpowers 的设计取舍,也为自定义扩展提供参考。
Hook 事件全景
Claude Code 的 Hook 按触发频率分为三类:
会话级别(每个会话触发一次)
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
SessionStart |
会话开始或恢复时 | 否 |
SessionEnd |
会话终止时 | 否 |
Setup |
使用 --init-only、--init 或 --maintenance 启动时 |
否 |
轮次级别(每轮对话触发一次)
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
UserPromptSubmit |
用户提交 prompt 后、Agent 处理前 | 是,可拒绝 prompt |
UserPromptExpansion |
用户输入的命令展开为 prompt 时 | 是,可阻止展开 |
Stop |
Agent 完成回复时 | 是,可阻止停止让对话继续 |
StopFailure |
因 API 错误中断时 | 否 |
工具级别(每次工具调用触发)
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
PreToolUse |
工具调用执行前 | 是,可拦截工具调用 |
PostToolUse |
工具调用成功后 | 否(已执行) |
PostToolUseFailure |
工具调用失败后 | 否(已失败) |
PostToolBatch |
一批并行工具调用全部完成后 | 是,可中止循环 |
PermissionRequest |
权限对话框出现时 | 是,可自动允许/拒绝 |
PermissionDenied |
工具调用被权限拒绝后 | 否,但可返回 retry: true |
子代理级别
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
SubagentStart |
子代理被创建时 | 否 |
SubagentStop |
子代理完成时 | 是,可阻止子代理结束 |
TaskCreated |
通过 TaskCreate 创建任务时 |
是,可回滚创建 |
TaskCompleted |
任务被标记完成时 | 是,可阻止标记 |
TeammateIdle |
Agent Team 中的队友即将空闲 | 是,可阻止空闲 |
环境与配置级别
| 事件 | 触发时机 | 能否阻断 |
|---|---|---|
ConfigChange |
配置文件变更时 | 是(policy_settings 除外) |
CwdChanged |
工作目录变更时(如执行 cd) |
否 |
FileChanged |
被监视的文件变更时 | 否 |
InstructionsLoaded |
CLAUDE.md 或规则文件被加载时 | 否 |
WorktreeCreate |
工作树被创建时 | 是(任何非零退出码中止创建) |
WorktreeRemove |
工作树被移除时 | 否 |
PreCompact |
上下文压缩前 | 是,可阻止压缩 |
PostCompact |
上下文压缩后 | 否 |
Notification |
Claude Code 发送通知时 | 否 |
Elicitation |
MCP 服务器请求用户输入时 | 是,可拒绝 |
ElicitationResult |
用户响应 MCP elicitation 后 | 是,可阻止响应 |
五种 Hook 处理器类型
Claude Code 支持五种不同类型的 Hook 处理器:
┌────────────────────────────────────────────────────────────────┐
│ Hook 事件触发 │
│ (SessionStart / PreToolUse / PostToolUse / ...) │
└────────────────────────┬───────────────────────────────────────┘
│
┌──────────────┼──────────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ command │ │ http │ │ mcp_tool │ │ prompt │ │ agent │
│ Shell命令│ │ HTTP请求 │ │ MCP工具 │ │ LLM提示 │ │ 子代理 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
stdin 接收 POST 请求 调用已连接的 发送 prompt 创建子代理
JSON 输入 JSON body MCP 服务器工具 给 Claude 模型 可用 Read/
stdout 输出 响应 body 返回 yes/no Grep/Glob
exit code HTTP 状态码 JSON 决策 返回决策
1. Command Hook(Shell 命令) --- Superpowers 使用的类型
通过 stdin 接收 JSON 输入,通过 stdout/exit code 返回结果。最常用、最灵活。
json
{
"type": "command",
"command": "my-script.sh",
"async": false
}
2. HTTP Hook(HTTP 端点)
将事件 JSON 作为 POST 请求发送到指定 URL。适合连接外部服务或团队共享的校验逻辑。
json
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"headers": { "Authorization": "Bearer $MY_TOKEN" },
"allowedEnvVars": ["MY_TOKEN"]
}
3. MCP Tool Hook(MCP 工具)
调用已连接的 MCP 服务器上的工具。适合复用 MCP 生态能力。
json
{
"type": "mcp_tool",
"server": "my_server",
"tool": "security_scan",
"input": { "file_path": "${tool_input.file_path}" }
}
4. Prompt Hook(LLM 提示)
将事件上下文发送给 Claude 模型做单轮评估,返回 yes/no 决策。适合需要"判断"而非"规则"的场景。
json
{
"type": "prompt",
"prompt": "Does this command look safe? $ARGUMENTS"
}
5. Agent Hook(子代理)
创建一个有工具(Read/Grep/Glob)的子代理来验证条件。比 Prompt Hook 更强大但更慢。实验性功能。
json
{
"type": "agent",
"prompt": "Verify that all test files follow the naming convention"
}
Hook 的输入输出协议
所有 Hook 共享统一的输入输出协议:
输入(stdin / POST body)
json
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": { "command": "npm test" }
}
输出控制 --- 三种方式
| 方式 | 用法 | 适用场景 |
|---|---|---|
| Exit Code | exit 0 允许,exit 2 阻断 |
简单的允许/拒绝 |
| 顶层 JSON | {"decision": "block", "reason": "..."} |
阻断并提供原因 |
| hookSpecificOutput | {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny"}} |
精细控制(允许/拒绝/询问用户/修改输入) |
向 Agent 注入上下文
通过 additionalContext 字段,Hook 可以向 Claude 的上下文窗口注入信息(最多 10,000 字符):
json
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "当前分支是 feature/auth,部署目标是 staging 环境"
}
}
Superpowers 的 session-start 脚本正是通过这个机制注入引导上下文的。
Hook 配置的作用域层级
Hook 可以定义在不同位置,作用域从大到小:
| 位置 | 作用域 | 可共享 |
|---|---|---|
| 组织托管策略(Managed Policy) | 组织级别 | 管理员控制 |
~/.claude/settings.json |
用户的所有项目 | 否 |
.claude/settings.json |
单个项目 | 是(可提交到仓库) |
.claude/settings.local.json |
单个项目 | 否(gitignored) |
插件的 hooks/hooks.json |
插件启用时 | 是(随插件分发) |
| Skill/Agent 的 frontmatter | 组件激活期间 | 是(定义在组件文件中) |
Superpowers 使用的是插件级别 的 Hook(hooks/hooks.json),当用户安装并启用插件后自动生效。
Matcher 过滤机制
matcher 字段决定 Hook 何时触发,其语法取决于内容:
| Matcher 值 | 解释为 | 示例 |
|---|---|---|
"*"、"" 或省略 |
匹配所有 | 每次事件都触发 |
仅含字母、数字、_、` |
` | 精确字符串匹配 |
| 包含其他字符 | JavaScript 正则表达式 | ^Notebook 匹配以 Notebook 开头的工具 |
不同事件的 matcher 匹配不同字段:
- 工具事件 (PreToolUse 等):匹配
tool_name - SessionStart :匹配启动方式(
startup、resume、clear、compact) - SubagentStart/Stop :匹配代理类型(
general-purpose、Explore等) - FileChanged :匹配文件名(如
.envrc|.env)
Superpowers 的 hooks.json 中 "matcher": "startup|clear|compact" 精确控制了只在新会话、清空、压缩时注入,排除了 resume(恢复会话已有上下文)。
实用 Hook 场景示例
虽然 Superpowers 只用了 SessionStart,但 Claude Code 的 Hook 体系可以做很多事情:
PreToolUse --- 拦截危险命令
json
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"if": "Bash(rm *)",
"command": "block-rm.sh"
}]
}]
}
}
脚本读取 stdin 的 JSON,检查命令内容,返回 permissionDecision: "deny" 阻止执行。
PostToolUse --- 自动格式化/Lint
json
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "run-linter.sh"
}]
}]
}
}
每次文件被编辑或写入后自动运行 linter,将结果通过 additionalContext 反馈给 Agent。
UserPromptSubmit --- Prompt 校验
json
{
"hooks": {
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "validate-prompt.sh"
}]
}]
}
}
在用户提交 prompt 后、Agent 处理前进行检查,可以拒绝不合规的输入。
Stop --- 阻止 Agent 停止
json
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "check-tests-pass.sh"
}]
}]
}
}
当 Agent 准备停止回复时检查测试是否通过,未通过则 exit 2 阻止停止,让 Agent 继续工作。
Skill Frontmatter 中的 Hook
Hook 还可以直接写在 Skill 的 YAML frontmatter 中,其生命周期与 Skill 绑定:
yaml
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
once: true
---
once: true 表示该 Hook 只在会话中运行一次然后自动移除。
关键设计决策
1. 为什么只用一个 Hook 事件?
Superpowers 只使用 SessionStart,不使用 PreToolUse、PostToolUse 等其他 Hook 事件。原因是:
- 引导上下文只需注入一次,之后通过上下文窗口自然持续生效。
- 每次工具调用都触发 Hook 会带来显著的延迟开销。
using-superpowers技能中的规则已经覆盖了后续所有决策点。
2. 为什么将完整技能内容注入而不是只注入路径?
早期版本只告诉 Agent "你有技能可以用,去读文件"。但 Agent 经常跳过读取步骤或延迟读取。将完整内容直接注入确保了 Agent 从第一条消息开始就掌握所有规则。
3. 为什么 Hook 脚本没有扩展名?
session-start 没有 .sh 扩展名。因为 Claude Code 在 Windows 上会自动检测 .sh 文件并在命令前加 bash 前缀,这会破坏通过 run-hook.cmd 路由的机制。使用无扩展名文件绕过了这个自动检测。
4. 为什么使用 printf 而不是 heredoc?
bash
# 不用这种写法(bash 5.3+ 会挂起)
cat <<EOF
{"additionalContext": "$content"}
EOF
# 而是用这种
printf '{"additionalContext": "%s"}' "$content"
bash 5.3+(macOS Homebrew 默认版本)在 heredoc 中进行大变量展开时存在回归 bug,会导致脚本无限挂起。
5. 为什么 JSON 转义不用 sed/awk?
纯 bash 参数替换(${s//old/new})在所有平台上都可用,不依赖外部工具。Git Bash 的 PATH 可能不包含常用 Unix 工具,纯 bash 实现是最可靠的跨平台方案。
总结
Claude Code 提供了 30+ 种 Hook 事件、5 种处理器类型、多层作用域的完整 Hook 体系,覆盖了 Agent 生命周期的每个阶段。而 Superpowers 的设计哲学是"以最小干预实现最大效果":
- 只用一个事件 (SessionStart),一个脚本 (session-start),一次注入(using-superpowers 内容)------在 30+ 种可用事件中,只选择了最关键的一个
- 通过 polyglot 包装器和平台检测,一份核心逻辑支持 4 个平台 × 3 个操作系统
- 注入的引导上下文通过上下文窗口在整个会话中持续生效,无需 PreToolUse/PostToolUse 等高频 Hook 持续干预
- 引导上下文中的规则(1% 原则、反合理化表、技能优先级)驱动 Agent 自动发现和使用 14 个技能
这种设计使得 Superpowers 成为一个"安装即生效"的插件------用户安装后,无需任何额外配置或手动操作,Agent 就会在合适的时机自动调用正确的技能。同时,理解 Claude Code 完整的 Hook 体系,也为你在此基础上构建自定义工作流(如自动 lint、危险命令拦截、prompt 校验等)提供了清晰的路径。