diff --git a/docs/appendix/C.manifest.json b/docs/appendix/C.manifest.json
index 5a9e823..7a1dded 100644
--- a/docs/appendix/C.manifest.json
+++ b/docs/appendix/C.manifest.json
@@ -6,7 +6,22 @@
"category": "event",
"wire_type": "PreToolUse",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:414-423",
+ "utils/hooks.ts:3394-3436",
+ "services/tools/toolHooks.ts (executePreToolHooks)"
+ ],
+ "trigger": "工具被调度执行之前;可用 exit 2 / decision:'block' 阻断本次工具调用,hook 输出会作为 system reminder 反馈给模型。",
+ "payload": [
+ "session_id",
+ "transcript_path",
+ "cwd",
+ "permission_mode?",
+ "agent_id?",
+ "agent_type?",
+ "hook_event_name='PreToolUse'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id"
]
},
{
@@ -14,7 +29,18 @@
"category": "event",
"wire_type": "PostToolUse",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:436-446",
+ "utils/hooks.ts:3450-3477",
+ "services/tools/toolHooks.ts:56"
+ ],
+ "trigger": "工具调用成功完成之后;hook 可通过 decision:'block' 给模型注入反馈(不影响工具结果本身)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostToolUse'",
+ "tool_name",
+ "tool_input",
+ "tool_response",
+ "tool_use_id"
]
},
{
@@ -22,7 +48,19 @@
"category": "event",
"wire_type": "PostToolUseFailure",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:448-459",
+ "utils/hooks.ts:3492-3527",
+ "services/tools/toolHooks.ts:212"
+ ],
+ "trigger": "工具调用失败 / 被中断之后;payload 携带 error 字符串与 is_interrupt 标记,hook 可向模型注入修复建议。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostToolUseFailure'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id",
+ "error",
+ "is_interrupt?"
]
},
{
@@ -30,7 +68,22 @@
"category": "event",
"wire_type": "Notification",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:473-482",
+ "utils/hooks.ts:3570-3592",
+ "services/notifier.ts:25",
+ "cli/print.ts:1366",
+ "services/mcp/elicitationHandler.ts:183",
+ "services/mcp/elicitationHandler.ts:283",
+ "services/mcp/elicitationHandler.ts:298",
+ "services/mcp/elicitationHandler.ts:307"
+ ],
+ "trigger": "需要向用户弹出系统通知前(待权限确认 / 待 elicitation / 输入空闲提醒等);hook 可替换或抑制通知。在 REPL 之外执行。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Notification'",
+ "message",
+ "title?",
+ "notification_type"
]
},
{
@@ -38,7 +91,15 @@
"category": "event",
"wire_type": "UserPromptSubmit",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:484-491",
+ "utils/hooks.ts:3826-3855",
+ "utils/processUserInput/processUserInput.ts:182"
+ ],
+ "trigger": "用户在 REPL 中提交 prompt 之后、消息入队 QueryEngine 之前;hook 可改写 prompt 或注入额外 system 消息。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='UserPromptSubmit'",
+ "prompt"
]
},
{
@@ -46,7 +107,17 @@
"category": "event",
"wire_type": "SessionStart",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:493-502",
+ "utils/hooks.ts:3867-3892",
+ "utils/sessionStart.ts:132"
+ ],
+ "trigger": "会话开始或 resume/clear/compact 后重启 session;hook 可注入 system reminder(如自定义 agent 引导语)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SessionStart'",
+ "source='startup'|'resume'|'clear'|'compact'",
+ "agent_type?",
+ "model?"
]
},
{
@@ -54,7 +125,17 @@
"category": "event",
"wire_type": "SessionEnd",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:758-765",
+ "utils/hooks.ts:4097-4141",
+ "utils/gracefulShutdown.ts:473",
+ "screens/REPL.tsx:1774",
+ "commands/clear/conversation.ts:69"
+ ],
+ "trigger": "会话结束(logout / clear / resume 切换 / prompt_input_exit 等);同步触发,关停 Ink 之后直接写 stderr。默认 1.5s 超时(CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 覆盖)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SessionEnd'",
+ "reason ∈ EXIT_REASONS"
]
},
{
@@ -62,7 +143,16 @@
"category": "event",
"wire_type": "Stop",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:513-527",
+ "utils/hooks.ts:3639-3697",
+ "query/stopHooks.ts:180"
+ ],
+ "trigger": "主线程在一轮回合结束、准备进入 idle 之前;hook 可 exit 2 / decision:'block' 让模型继续执行,用于强制完成额外步骤。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Stop'",
+ "stop_hook_active",
+ "last_assistant_message?"
]
},
{
@@ -70,7 +160,19 @@
"category": "event",
"wire_type": "StopFailure",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:529-538",
+ "utils/hooks.ts:3594-3627",
+ "query.ts:1174",
+ "query.ts:1181",
+ "query.ts:1263"
+ ],
+ "trigger": "回合以 assistant 错误(API 错误 / 取消 / overload)收尾时,与 Stop 互斥;payload 携带最后一条 assistant message 与错误字段,主要用于审计 / 告警。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='StopFailure'",
+ "error (SDKAssistantMessageError, 缺省 'unknown')",
+ "error_details?",
+ "last_assistant_message?"
]
},
{
@@ -78,7 +180,16 @@
"category": "event",
"wire_type": "SubagentStart",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:540-548",
+ "utils/hooks.ts:3932-3952",
+ "tools/AgentTool/runAgent.ts:532"
+ ],
+ "trigger": "AgentTool 创建并启动一个 subagent 之前;payload 给出 agent_id + agent_type,便于 hook 给子 agent 注入额外 system reminder。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SubagentStart'",
+ "agent_id",
+ "agent_type"
]
},
{
@@ -86,7 +197,19 @@
"category": "event",
"wire_type": "SubagentStop",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:550-567",
+ "utils/hooks.ts:3639-3697",
+ "query/stopHooks.ts:180"
+ ],
+ "trigger": "subagent 完成回合、准备返回结果给父 agent 之前;Stop 的 subagent 变体,executeStopHooks 在传入 subagentId 时切到此事件。可阻断让子 agent 继续工作。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SubagentStop'",
+ "stop_hook_active",
+ "agent_id",
+ "agent_transcript_path",
+ "agent_type",
+ "last_assistant_message?"
]
},
{
@@ -94,7 +217,18 @@
"category": "event",
"wire_type": "PreCompact",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:569-577",
+ "utils/hooks.ts:3961-4025",
+ "services/compact/compact.ts:413",
+ "services/compact/compact.ts:818",
+ "commands/compact/compact.ts:160"
+ ],
+ "trigger": "压缩流程开始之前(手动 /compact 或 token 自动压缩);hook 可返回 newCustomInstructions 覆盖默认指令,并把 displayMessage 显示给用户。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PreCompact'",
+ "trigger='manual'|'auto'",
+ "custom_instructions: string|null"
]
},
{
@@ -102,7 +236,17 @@
"category": "event",
"wire_type": "PostCompact",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:579-589",
+ "utils/hooks.ts:4034-4089",
+ "services/compact/compact.ts:723",
+ "services/compact/compact.ts:1069"
+ ],
+ "trigger": "压缩完成、summary 已写入会话之后;hook 输出仅作为 displayMessage 展示给用户,不参与后续模型上下文。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostCompact'",
+ "trigger='manual'|'auto'",
+ "compact_summary"
]
},
{
@@ -110,7 +254,19 @@
"category": "event",
"wire_type": "PermissionRequest",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:425-434",
+ "utils/hooks.ts:4157-4192",
+ "utils/permissions/permissions.ts:409",
+ "cli/structuredIO.ts:798",
+ "hooks/toolPermission/PermissionContext.ts:222"
+ ],
+ "trigger": "权限引擎判定需要弹出权限对话框之前;hook 可程序化批准 / 拒绝,免去交互。在 REPL(含 SDK structuredIO 通道)与 PermissionContext 内都会触发。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PermissionRequest'",
+ "tool_name",
+ "tool_input",
+ "permission_suggestions?: PermissionUpdate[]"
]
},
{
@@ -118,7 +274,18 @@
"category": "event",
"wire_type": "PermissionDenied",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:461-471",
+ "utils/hooks.ts:3529-3562",
+ "services/tools/toolExecution.ts:1081"
+ ],
+ "trigger": "权限引擎判定一次工具调用被拒绝之后,先于错误结果返回模型;fire-and-forget,主要用于审计 / 告警。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PermissionDenied'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id",
+ "reason"
]
},
{
@@ -126,7 +293,15 @@
"category": "event",
"wire_type": "Setup",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:504-511",
+ "utils/hooks.ts:3902-3922",
+ "utils/sessionStart.ts:203"
+ ],
+ "trigger": "会话初始化 / 周期性维护阶段,紧随 SessionStart 之后;payload trigger 区分 'init' 与 'maintenance'。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Setup'",
+ "trigger='init'|'maintenance'"
]
},
{
@@ -134,7 +309,16 @@
"category": "event",
"wire_type": "TeammateIdle",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:591-599",
+ "utils/hooks.ts:3709-3729",
+ "query/stopHooks.ts:403"
+ ],
+ "trigger": "teammate(多人协作中的另一个 agent)即将进入 idle 状态时;hook 可 exit 2 让其继续工作,用于强制完成轮询任务。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TeammateIdle'",
+ "teammate_name",
+ "team_name"
]
},
{
@@ -142,7 +326,19 @@
"category": "event",
"wire_type": "TaskCreated",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:601-612",
+ "utils/hooks.ts:3745-3773",
+ "tools/TaskCreateTool/TaskCreateTool.ts:93"
+ ],
+ "trigger": "TaskCreateTool 创建任务前;hook 可阻断创建并向模型回馈拒绝理由(如校验任务标题命名规范)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TaskCreated'",
+ "task_id",
+ "task_subject",
+ "task_description?",
+ "teammate_name?",
+ "team_name?"
]
},
{
@@ -150,7 +346,19 @@
"category": "event",
"wire_type": "TaskCompleted",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:614-625",
+ "utils/hooks.ts:3789-3817",
+ "tools/TaskUpdateTool/TaskUpdateTool.ts:235"
+ ],
+ "trigger": "TaskUpdateTool 将任务置为 completed 之前;hook 可阻断完成(例如强制要求附录 / 验收脚本通过)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TaskCompleted'",
+ "task_id",
+ "task_subject",
+ "task_description?",
+ "teammate_name?",
+ "team_name?"
]
},
{
@@ -158,7 +366,20 @@
"category": "event",
"wire_type": "Elicitation",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:627-643",
+ "utils/hooks.ts:4470-4523",
+ "services/mcp/elicitationHandler.ts:228"
+ ],
+ "trigger": "MCP 服务器发起 elicitation 请求、需要向用户弹出对话框前;hook 可程序化返回 accept/decline/cancel 与表单内容,跳过 UI。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Elicitation'",
+ "mcp_server_name",
+ "message",
+ "mode='form'|'url'?",
+ "url?",
+ "elicitation_id?",
+ "requested_schema?"
]
},
{
@@ -166,7 +387,19 @@
"category": "event",
"wire_type": "ElicitationResult",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:645-660",
+ "utils/hooks.ts:4525-4575",
+ "services/mcp/elicitationHandler.ts:273"
+ ],
+ "trigger": "用户已经响应 MCP elicitation、即将把结果发回服务器之前;hook 可观察或改写最终响应(含 decline 转 block)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='ElicitationResult'",
+ "mcp_server_name",
+ "elicitation_id?",
+ "mode='form'|'url'?",
+ "action='accept'|'decline'|'cancel'",
+ "content?"
]
},
{
@@ -174,7 +407,18 @@
"category": "event",
"wire_type": "ConfigChange",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:670-678",
+ "utils/hooks.ts:4214-4239",
+ "utils/settings/changeDetector.ts:292",
+ "utils/settings/changeDetector.ts:344",
+ "utils/skills/skillChangeDetector.ts:267"
+ ],
+ "trigger": "配置文件 watcher 检测到 user/project/local/policy settings 或 skills 目录变化时;hook 可 exit 2 阻断本次变更生效(policy_settings 例外,hook 仅审计、不可阻断)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='ConfigChange'",
+ "source ∈ CONFIG_CHANGE_SOURCES",
+ "file_path?"
]
},
{
@@ -182,7 +426,17 @@
"category": "event",
"wire_type": "WorktreeCreate",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:709-716",
+ "utils/hooks.ts:4928-4958",
+ "utils/worktree.ts:716",
+ "utils/worktree.ts:913",
+ "utils/worktree.ts:1262"
+ ],
+ "trigger": "需要为本会话创建 worktree 时;若用户配置了该 hook,则替代默认 git worktree 流程,stdout 中的路径作为新 worktree 根目录。无配置时回退到 git worktree。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='WorktreeCreate'",
+ "name (slug,已经过 validateWorktreeSlug)"
]
},
{
@@ -190,7 +444,16 @@
"category": "event",
"wire_type": "WorktreeRemove",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:718-725",
+ "utils/hooks.ts:4967-5003",
+ "utils/worktree.ts:827",
+ "utils/worktree.ts:968"
+ ],
+ "trigger": "会话结束 / 用户主动清理 worktree 时;若 hook 配置存在则替代默认 git worktree remove;返回 false 表示无 hook 配置、走默认路径。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='WorktreeRemove'",
+ "worktree_path"
]
},
{
@@ -198,7 +461,21 @@
"category": "event",
"wire_type": "InstructionsLoaded",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:695-707",
+ "utils/hooks.ts:4335-4369",
+ "utils/claudemd.ts:1060",
+ "utils/attachments.ts:1760"
+ ],
+ "trigger": "CLAUDE.md 或 .claude/rules/*.md 等指令文件被加载进上下文时(session_start / nested_traversal / path_glob_match / include / compact 五种场景);fire-and-forget,仅用于观察与审计,不能阻断。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='InstructionsLoaded'",
+ "file_path",
+ "memory_type ∈ {User, Project, Local, Managed}",
+ "load_reason ∈ INSTRUCTIONS_LOAD_REASONS",
+ "globs?",
+ "trigger_file_path?",
+ "parent_file_path?"
]
},
{
@@ -206,7 +483,16 @@
"category": "event",
"wire_type": "CwdChanged",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:727-735",
+ "utils/hooks.ts:4260-4276",
+ "utils/hooks/fileChangedWatcher.ts:148"
+ ],
+ "trigger": "会话 cwd 发生切换之后(如 /cd 命令、worktree 切换);hook 输出可声明 watchPaths 让 FileChanged watcher 重新订阅,并向 REPL 推 systemMessage。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='CwdChanged'",
+ "old_cwd",
+ "new_cwd"
]
},
{
@@ -214,7 +500,16 @@
"category": "event",
"wire_type": "FileChanged",
"source_files": [
- "entrypoints/sdk/coreSchemas.ts:355-383"
+ "entrypoints/sdk/coreSchemas.ts:737-745",
+ "utils/hooks.ts:4278-4294",
+ "utils/hooks/fileChangedWatcher.ts:85"
+ ],
+ "trigger": "FileChangedWatcher 监听到 watchPaths 内文件 add/change/unlink 之后;hook 可输出新的 watchPaths 动态扩缩订阅集,输出失败将作为系统消息提示用户。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='FileChanged'",
+ "file_path",
+ "event='change'|'add'|'unlink'"
]
},
{
@@ -248,6 +543,7 @@
],
"counts": {
"events": 27,
- "command_types": 4
+ "command_types": 4,
+ "events_with_trigger": 27
}
}
diff --git a/docs/appendix/C.md b/docs/appendix/C.md
index a2a3985..131190a 100644
--- a/docs/appendix/C.md
+++ b/docs/appendix/C.md
@@ -4,38 +4,39 @@
- HOOK_EVENTS:27 个
- Hook command type:4 类
+- 已补齐 trigger/payload:27/27
## HOOK_EVENTS(来源:`entrypoints/sdk/coreSchemas.ts:355-383`)
-| 事件名 |
-|---|
-| `PreToolUse` |
-| `PostToolUse` |
-| `PostToolUseFailure` |
-| `Notification` |
-| `UserPromptSubmit` |
-| `SessionStart` |
-| `SessionEnd` |
-| `Stop` |
-| `StopFailure` |
-| `SubagentStart` |
-| `SubagentStop` |
-| `PreCompact` |
-| `PostCompact` |
-| `PermissionRequest` |
-| `PermissionDenied` |
-| `Setup` |
-| `TeammateIdle` |
-| `TaskCreated` |
-| `TaskCompleted` |
-| `Elicitation` |
-| `ElicitationResult` |
-| `ConfigChange` |
-| `WorktreeCreate` |
-| `WorktreeRemove` |
-| `InstructionsLoaded` |
-| `CwdChanged` |
-| `FileChanged` |
+| 事件名 | 触发时机 | Payload 字段 | 源码位置 |
+|---|---|---|---|
+| `PreToolUse` | 工具被调度执行之前;可用 exit 2 / decision:'block' 阻断本次工具调用,hook 输出会作为 system reminder 反馈给模型。 | `session_id`
`transcript_path`
`cwd`
`permission_mode?`
`agent_id?`
`agent_type?`
`hook_event_name='PreToolUse'`
`tool_name`
`tool_input`
`tool_use_id` | schema: `entrypoints/sdk/coreSchemas.ts:414-423`
dispatch: `utils/hooks.ts:3394-3436`
call: `services/tools/toolHooks.ts (executePreToolHooks)` |
+| `PostToolUse` | 工具调用成功完成之后;hook 可通过 decision:'block' 给模型注入反馈(不影响工具结果本身)。 | `...BaseHookInput`
`hook_event_name='PostToolUse'`
`tool_name`
`tool_input`
`tool_response`
`tool_use_id` | schema: `entrypoints/sdk/coreSchemas.ts:436-446`
dispatch: `utils/hooks.ts:3450-3477`
call: `services/tools/toolHooks.ts:56` |
+| `PostToolUseFailure` | 工具调用失败 / 被中断之后;payload 携带 error 字符串与 is_interrupt 标记,hook 可向模型注入修复建议。 | `...BaseHookInput`
`hook_event_name='PostToolUseFailure'`
`tool_name`
`tool_input`
`tool_use_id`
`error`
`is_interrupt?` | schema: `entrypoints/sdk/coreSchemas.ts:448-459`
dispatch: `utils/hooks.ts:3492-3527`
call: `services/tools/toolHooks.ts:212` |
+| `Notification` | 需要向用户弹出系统通知前(待权限确认 / 待 elicitation / 输入空闲提醒等);hook 可替换或抑制通知。在 REPL 之外执行。 | `...BaseHookInput`
`hook_event_name='Notification'`
`message`
`title?`
`notification_type` | schema: `entrypoints/sdk/coreSchemas.ts:473-482`
dispatch: `utils/hooks.ts:3570-3592`
call: `services/notifier.ts:25`
call: `cli/print.ts:1366`
call: `services/mcp/elicitationHandler.ts:183`
call: `services/mcp/elicitationHandler.ts:283`
call: `services/mcp/elicitationHandler.ts:298`
call: `services/mcp/elicitationHandler.ts:307` |
+| `UserPromptSubmit` | 用户在 REPL 中提交 prompt 之后、消息入队 QueryEngine 之前;hook 可改写 prompt 或注入额外 system 消息。 | `...BaseHookInput`
`hook_event_name='UserPromptSubmit'`
`prompt` | schema: `entrypoints/sdk/coreSchemas.ts:484-491`
dispatch: `utils/hooks.ts:3826-3855`
call: `utils/processUserInput/processUserInput.ts:182` |
+| `SessionStart` | 会话开始或 resume/clear/compact 后重启 session;hook 可注入 system reminder(如自定义 agent 引导语)。 | `...BaseHookInput`
`hook_event_name='SessionStart'`
`source='startup'\|'resume'\|'clear'\|'compact'`
`agent_type?`
`model?` | schema: `entrypoints/sdk/coreSchemas.ts:493-502`
dispatch: `utils/hooks.ts:3867-3892`
call: `utils/sessionStart.ts:132` |
+| `SessionEnd` | 会话结束(logout / clear / resume 切换 / prompt_input_exit 等);同步触发,关停 Ink 之后直接写 stderr。默认 1.5s 超时(CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 覆盖)。 | `...BaseHookInput`
`hook_event_name='SessionEnd'`
`reason ∈ EXIT_REASONS` | schema: `entrypoints/sdk/coreSchemas.ts:758-765`
dispatch: `utils/hooks.ts:4097-4141`
call: `utils/gracefulShutdown.ts:473`
call: `screens/REPL.tsx:1774`
call: `commands/clear/conversation.ts:69` |
+| `Stop` | 主线程在一轮回合结束、准备进入 idle 之前;hook 可 exit 2 / decision:'block' 让模型继续执行,用于强制完成额外步骤。 | `...BaseHookInput`
`hook_event_name='Stop'`
`stop_hook_active`
`last_assistant_message?` | schema: `entrypoints/sdk/coreSchemas.ts:513-527`
dispatch: `utils/hooks.ts:3639-3697`
call: `query/stopHooks.ts:180` |
+| `StopFailure` | 回合以 assistant 错误(API 错误 / 取消 / overload)收尾时,与 Stop 互斥;payload 携带最后一条 assistant message 与错误字段,主要用于审计 / 告警。 | `...BaseHookInput`
`hook_event_name='StopFailure'`
`error (SDKAssistantMessageError, 缺省 'unknown')`
`error_details?`
`last_assistant_message?` | schema: `entrypoints/sdk/coreSchemas.ts:529-538`
dispatch: `utils/hooks.ts:3594-3627`
call: `query.ts:1174`
call: `query.ts:1181`
call: `query.ts:1263` |
+| `SubagentStart` | AgentTool 创建并启动一个 subagent 之前;payload 给出 agent_id + agent_type,便于 hook 给子 agent 注入额外 system reminder。 | `...BaseHookInput`
`hook_event_name='SubagentStart'`
`agent_id`
`agent_type` | schema: `entrypoints/sdk/coreSchemas.ts:540-548`
dispatch: `utils/hooks.ts:3932-3952`
call: `tools/AgentTool/runAgent.ts:532` |
+| `SubagentStop` | subagent 完成回合、准备返回结果给父 agent 之前;Stop 的 subagent 变体,executeStopHooks 在传入 subagentId 时切到此事件。可阻断让子 agent 继续工作。 | `...BaseHookInput`
`hook_event_name='SubagentStop'`
`stop_hook_active`
`agent_id`
`agent_transcript_path`
`agent_type`
`last_assistant_message?` | schema: `entrypoints/sdk/coreSchemas.ts:550-567`
dispatch: `utils/hooks.ts:3639-3697`
call: `query/stopHooks.ts:180` |
+| `PreCompact` | 压缩流程开始之前(手动 /compact 或 token 自动压缩);hook 可返回 newCustomInstructions 覆盖默认指令,并把 displayMessage 显示给用户。 | `...BaseHookInput`
`hook_event_name='PreCompact'`
`trigger='manual'\|'auto'`
`custom_instructions: string\|null` | schema: `entrypoints/sdk/coreSchemas.ts:569-577`
dispatch: `utils/hooks.ts:3961-4025`
call: `services/compact/compact.ts:413`
call: `services/compact/compact.ts:818`
call: `commands/compact/compact.ts:160` |
+| `PostCompact` | 压缩完成、summary 已写入会话之后;hook 输出仅作为 displayMessage 展示给用户,不参与后续模型上下文。 | `...BaseHookInput`
`hook_event_name='PostCompact'`
`trigger='manual'\|'auto'`
`compact_summary` | schema: `entrypoints/sdk/coreSchemas.ts:579-589`
dispatch: `utils/hooks.ts:4034-4089`
call: `services/compact/compact.ts:723`
call: `services/compact/compact.ts:1069` |
+| `PermissionRequest` | 权限引擎判定需要弹出权限对话框之前;hook 可程序化批准 / 拒绝,免去交互。在 REPL(含 SDK structuredIO 通道)与 PermissionContext 内都会触发。 | `...BaseHookInput`
`hook_event_name='PermissionRequest'`
`tool_name`
`tool_input`
`permission_suggestions?: PermissionUpdate[]` | schema: `entrypoints/sdk/coreSchemas.ts:425-434`
dispatch: `utils/hooks.ts:4157-4192`
call: `utils/permissions/permissions.ts:409`
call: `cli/structuredIO.ts:798`
call: `hooks/toolPermission/PermissionContext.ts:222` |
+| `PermissionDenied` | 权限引擎判定一次工具调用被拒绝之后,先于错误结果返回模型;fire-and-forget,主要用于审计 / 告警。 | `...BaseHookInput`
`hook_event_name='PermissionDenied'`
`tool_name`
`tool_input`
`tool_use_id`
`reason` | schema: `entrypoints/sdk/coreSchemas.ts:461-471`
dispatch: `utils/hooks.ts:3529-3562`
call: `services/tools/toolExecution.ts:1081` |
+| `Setup` | 会话初始化 / 周期性维护阶段,紧随 SessionStart 之后;payload trigger 区分 'init' 与 'maintenance'。 | `...BaseHookInput`
`hook_event_name='Setup'`
`trigger='init'\|'maintenance'` | schema: `entrypoints/sdk/coreSchemas.ts:504-511`
dispatch: `utils/hooks.ts:3902-3922`
call: `utils/sessionStart.ts:203` |
+| `TeammateIdle` | teammate(多人协作中的另一个 agent)即将进入 idle 状态时;hook 可 exit 2 让其继续工作,用于强制完成轮询任务。 | `...BaseHookInput`
`hook_event_name='TeammateIdle'`
`teammate_name`
`team_name` | schema: `entrypoints/sdk/coreSchemas.ts:591-599`
dispatch: `utils/hooks.ts:3709-3729`
call: `query/stopHooks.ts:403` |
+| `TaskCreated` | TaskCreateTool 创建任务前;hook 可阻断创建并向模型回馈拒绝理由(如校验任务标题命名规范)。 | `...BaseHookInput`
`hook_event_name='TaskCreated'`
`task_id`
`task_subject`
`task_description?`
`teammate_name?`
`team_name?` | schema: `entrypoints/sdk/coreSchemas.ts:601-612`
dispatch: `utils/hooks.ts:3745-3773`
call: `tools/TaskCreateTool/TaskCreateTool.ts:93` |
+| `TaskCompleted` | TaskUpdateTool 将任务置为 completed 之前;hook 可阻断完成(例如强制要求附录 / 验收脚本通过)。 | `...BaseHookInput`
`hook_event_name='TaskCompleted'`
`task_id`
`task_subject`
`task_description?`
`teammate_name?`
`team_name?` | schema: `entrypoints/sdk/coreSchemas.ts:614-625`
dispatch: `utils/hooks.ts:3789-3817`
call: `tools/TaskUpdateTool/TaskUpdateTool.ts:235` |
+| `Elicitation` | MCP 服务器发起 elicitation 请求、需要向用户弹出对话框前;hook 可程序化返回 accept/decline/cancel 与表单内容,跳过 UI。 | `...BaseHookInput`
`hook_event_name='Elicitation'`
`mcp_server_name`
`message`
`mode='form'\|'url'?`
`url?`
`elicitation_id?`
`requested_schema?` | schema: `entrypoints/sdk/coreSchemas.ts:627-643`
dispatch: `utils/hooks.ts:4470-4523`
call: `services/mcp/elicitationHandler.ts:228` |
+| `ElicitationResult` | 用户已经响应 MCP elicitation、即将把结果发回服务器之前;hook 可观察或改写最终响应(含 decline 转 block)。 | `...BaseHookInput`
`hook_event_name='ElicitationResult'`
`mcp_server_name`
`elicitation_id?`
`mode='form'\|'url'?`
`action='accept'\|'decline'\|'cancel'`
`content?` | schema: `entrypoints/sdk/coreSchemas.ts:645-660`
dispatch: `utils/hooks.ts:4525-4575`
call: `services/mcp/elicitationHandler.ts:273` |
+| `ConfigChange` | 配置文件 watcher 检测到 user/project/local/policy settings 或 skills 目录变化时;hook 可 exit 2 阻断本次变更生效(policy_settings 例外,hook 仅审计、不可阻断)。 | `...BaseHookInput`
`hook_event_name='ConfigChange'`
`source ∈ CONFIG_CHANGE_SOURCES`
`file_path?` | schema: `entrypoints/sdk/coreSchemas.ts:670-678`
dispatch: `utils/hooks.ts:4214-4239`
call: `utils/settings/changeDetector.ts:292`
call: `utils/settings/changeDetector.ts:344`
call: `utils/skills/skillChangeDetector.ts:267` |
+| `WorktreeCreate` | 需要为本会话创建 worktree 时;若用户配置了该 hook,则替代默认 git worktree 流程,stdout 中的路径作为新 worktree 根目录。无配置时回退到 git worktree。 | `...BaseHookInput`
`hook_event_name='WorktreeCreate'`
`name (slug,已经过 validateWorktreeSlug)` | schema: `entrypoints/sdk/coreSchemas.ts:709-716`
dispatch: `utils/hooks.ts:4928-4958`
call: `utils/worktree.ts:716`
call: `utils/worktree.ts:913`
call: `utils/worktree.ts:1262` |
+| `WorktreeRemove` | 会话结束 / 用户主动清理 worktree 时;若 hook 配置存在则替代默认 git worktree remove;返回 false 表示无 hook 配置、走默认路径。 | `...BaseHookInput`
`hook_event_name='WorktreeRemove'`
`worktree_path` | schema: `entrypoints/sdk/coreSchemas.ts:718-725`
dispatch: `utils/hooks.ts:4967-5003`
call: `utils/worktree.ts:827`
call: `utils/worktree.ts:968` |
+| `InstructionsLoaded` | CLAUDE.md 或 .claude/rules/*.md 等指令文件被加载进上下文时(session_start / nested_traversal / path_glob_match / include / compact 五种场景);fire-and-forget,仅用于观察与审计,不能阻断。 | `...BaseHookInput`
`hook_event_name='InstructionsLoaded'`
`file_path`
`memory_type ∈ {User, Project, Local, Managed}`
`load_reason ∈ INSTRUCTIONS_LOAD_REASONS`
`globs?`
`trigger_file_path?`
`parent_file_path?` | schema: `entrypoints/sdk/coreSchemas.ts:695-707`
dispatch: `utils/hooks.ts:4335-4369`
call: `utils/claudemd.ts:1060`
call: `utils/attachments.ts:1760` |
+| `CwdChanged` | 会话 cwd 发生切换之后(如 /cd 命令、worktree 切换);hook 输出可声明 watchPaths 让 FileChanged watcher 重新订阅,并向 REPL 推 systemMessage。 | `...BaseHookInput`
`hook_event_name='CwdChanged'`
`old_cwd`
`new_cwd` | schema: `entrypoints/sdk/coreSchemas.ts:727-735`
dispatch: `utils/hooks.ts:4260-4276`
call: `utils/hooks/fileChangedWatcher.ts:148` |
+| `FileChanged` | FileChangedWatcher 监听到 watchPaths 内文件 add/change/unlink 之后;hook 可输出新的 watchPaths 动态扩缩订阅集,输出失败将作为系统消息提示用户。 | `...BaseHookInput`
`hook_event_name='FileChanged'`
`file_path`
`event='change'\|'add'\|'unlink'` | schema: `entrypoints/sdk/coreSchemas.ts:737-745`
dispatch: `utils/hooks.ts:4278-4294`
call: `utils/hooks/fileChangedWatcher.ts:85` |
## Hook command type(来源:`schemas/hooks.ts`)
diff --git a/scripts/data/hooks-trigger-map.json b/scripts/data/hooks-trigger-map.json
new file mode 100644
index 0000000..3cdda99
--- /dev/null
+++ b/scripts/data/hooks-trigger-map.json
@@ -0,0 +1,402 @@
+{
+ "_comment": "附录 C · Hooks 事件触发时机与 payload 字段手工库。schema_source 指 entrypoints/sdk/coreSchemas.ts;dispatch_source 指 utils/hooks.ts 内 execute*Hooks 函数;call_sites 是把上述 dispatch 函数串到产品流程的调用点。所有 file:line 范围基于 source_commit = 290fdc9481a70612bc5823aa4ed225c52c52aad3。",
+ "source_commit": "290fdc9481a70612bc5823aa4ed225c52c52aad3",
+ "events": {
+ "PreToolUse": {
+ "trigger": "工具被调度执行之前;可用 exit 2 / decision:'block' 阻断本次工具调用,hook 输出会作为 system reminder 反馈给模型。",
+ "payload": [
+ "session_id",
+ "transcript_path",
+ "cwd",
+ "permission_mode?",
+ "agent_id?",
+ "agent_type?",
+ "hook_event_name='PreToolUse'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:414-423",
+ "dispatch_source": "utils/hooks.ts:3394-3436",
+ "call_sites": ["services/tools/toolHooks.ts (executePreToolHooks)"]
+ },
+ "PostToolUse": {
+ "trigger": "工具调用成功完成之后;hook 可通过 decision:'block' 给模型注入反馈(不影响工具结果本身)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostToolUse'",
+ "tool_name",
+ "tool_input",
+ "tool_response",
+ "tool_use_id"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:436-446",
+ "dispatch_source": "utils/hooks.ts:3450-3477",
+ "call_sites": ["services/tools/toolHooks.ts:56"]
+ },
+ "PostToolUseFailure": {
+ "trigger": "工具调用失败 / 被中断之后;payload 携带 error 字符串与 is_interrupt 标记,hook 可向模型注入修复建议。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostToolUseFailure'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id",
+ "error",
+ "is_interrupt?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:448-459",
+ "dispatch_source": "utils/hooks.ts:3492-3527",
+ "call_sites": ["services/tools/toolHooks.ts:212"]
+ },
+ "PermissionDenied": {
+ "trigger": "权限引擎判定一次工具调用被拒绝之后,先于错误结果返回模型;fire-and-forget,主要用于审计 / 告警。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PermissionDenied'",
+ "tool_name",
+ "tool_input",
+ "tool_use_id",
+ "reason"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:461-471",
+ "dispatch_source": "utils/hooks.ts:3529-3562",
+ "call_sites": ["services/tools/toolExecution.ts:1081"]
+ },
+ "Notification": {
+ "trigger": "需要向用户弹出系统通知前(待权限确认 / 待 elicitation / 输入空闲提醒等);hook 可替换或抑制通知。在 REPL 之外执行。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Notification'",
+ "message",
+ "title?",
+ "notification_type"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:473-482",
+ "dispatch_source": "utils/hooks.ts:3570-3592",
+ "call_sites": [
+ "services/notifier.ts:25",
+ "cli/print.ts:1366",
+ "services/mcp/elicitationHandler.ts:183",
+ "services/mcp/elicitationHandler.ts:283",
+ "services/mcp/elicitationHandler.ts:298",
+ "services/mcp/elicitationHandler.ts:307"
+ ]
+ },
+ "UserPromptSubmit": {
+ "trigger": "用户在 REPL 中提交 prompt 之后、消息入队 QueryEngine 之前;hook 可改写 prompt 或注入额外 system 消息。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='UserPromptSubmit'",
+ "prompt"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:484-491",
+ "dispatch_source": "utils/hooks.ts:3826-3855",
+ "call_sites": ["utils/processUserInput/processUserInput.ts:182"]
+ },
+ "SessionStart": {
+ "trigger": "会话开始或 resume/clear/compact 后重启 session;hook 可注入 system reminder(如自定义 agent 引导语)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SessionStart'",
+ "source='startup'|'resume'|'clear'|'compact'",
+ "agent_type?",
+ "model?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:493-502",
+ "dispatch_source": "utils/hooks.ts:3867-3892",
+ "call_sites": ["utils/sessionStart.ts:132"]
+ },
+ "SessionEnd": {
+ "trigger": "会话结束(logout / clear / resume 切换 / prompt_input_exit 等);同步触发,关停 Ink 之后直接写 stderr。默认 1.5s 超时(CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS 覆盖)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SessionEnd'",
+ "reason ∈ EXIT_REASONS"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:758-765",
+ "dispatch_source": "utils/hooks.ts:4097-4141",
+ "call_sites": [
+ "utils/gracefulShutdown.ts:473",
+ "screens/REPL.tsx:1774",
+ "commands/clear/conversation.ts:69"
+ ]
+ },
+ "Stop": {
+ "trigger": "主线程在一轮回合结束、准备进入 idle 之前;hook 可 exit 2 / decision:'block' 让模型继续执行,用于强制完成额外步骤。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Stop'",
+ "stop_hook_active",
+ "last_assistant_message?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:513-527",
+ "dispatch_source": "utils/hooks.ts:3639-3697",
+ "call_sites": ["query/stopHooks.ts:180"]
+ },
+ "StopFailure": {
+ "trigger": "回合以 assistant 错误(API 错误 / 取消 / overload)收尾时,与 Stop 互斥;payload 携带最后一条 assistant message 与错误字段,主要用于审计 / 告警。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='StopFailure'",
+ "error (SDKAssistantMessageError, 缺省 'unknown')",
+ "error_details?",
+ "last_assistant_message?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:529-538",
+ "dispatch_source": "utils/hooks.ts:3594-3627",
+ "call_sites": [
+ "query.ts:1174",
+ "query.ts:1181",
+ "query.ts:1263"
+ ]
+ },
+ "SubagentStart": {
+ "trigger": "AgentTool 创建并启动一个 subagent 之前;payload 给出 agent_id + agent_type,便于 hook 给子 agent 注入额外 system reminder。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SubagentStart'",
+ "agent_id",
+ "agent_type"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:540-548",
+ "dispatch_source": "utils/hooks.ts:3932-3952",
+ "call_sites": ["tools/AgentTool/runAgent.ts:532"]
+ },
+ "SubagentStop": {
+ "trigger": "subagent 完成回合、准备返回结果给父 agent 之前;Stop 的 subagent 变体,executeStopHooks 在传入 subagentId 时切到此事件。可阻断让子 agent 继续工作。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='SubagentStop'",
+ "stop_hook_active",
+ "agent_id",
+ "agent_transcript_path",
+ "agent_type",
+ "last_assistant_message?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:550-567",
+ "dispatch_source": "utils/hooks.ts:3639-3697",
+ "call_sites": ["query/stopHooks.ts:180"]
+ },
+ "PreCompact": {
+ "trigger": "压缩流程开始之前(手动 /compact 或 token 自动压缩);hook 可返回 newCustomInstructions 覆盖默认指令,并把 displayMessage 显示给用户。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PreCompact'",
+ "trigger='manual'|'auto'",
+ "custom_instructions: string|null"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:569-577",
+ "dispatch_source": "utils/hooks.ts:3961-4025",
+ "call_sites": [
+ "services/compact/compact.ts:413",
+ "services/compact/compact.ts:818",
+ "commands/compact/compact.ts:160"
+ ]
+ },
+ "PostCompact": {
+ "trigger": "压缩完成、summary 已写入会话之后;hook 输出仅作为 displayMessage 展示给用户,不参与后续模型上下文。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PostCompact'",
+ "trigger='manual'|'auto'",
+ "compact_summary"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:579-589",
+ "dispatch_source": "utils/hooks.ts:4034-4089",
+ "call_sites": [
+ "services/compact/compact.ts:723",
+ "services/compact/compact.ts:1069"
+ ]
+ },
+ "PermissionRequest": {
+ "trigger": "权限引擎判定需要弹出权限对话框之前;hook 可程序化批准 / 拒绝,免去交互。在 REPL(含 SDK structuredIO 通道)与 PermissionContext 内都会触发。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='PermissionRequest'",
+ "tool_name",
+ "tool_input",
+ "permission_suggestions?: PermissionUpdate[]"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:425-434",
+ "dispatch_source": "utils/hooks.ts:4157-4192",
+ "call_sites": [
+ "utils/permissions/permissions.ts:409",
+ "cli/structuredIO.ts:798",
+ "hooks/toolPermission/PermissionContext.ts:222"
+ ]
+ },
+ "Setup": {
+ "trigger": "会话初始化 / 周期性维护阶段,紧随 SessionStart 之后;payload trigger 区分 'init' 与 'maintenance'。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Setup'",
+ "trigger='init'|'maintenance'"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:504-511",
+ "dispatch_source": "utils/hooks.ts:3902-3922",
+ "call_sites": ["utils/sessionStart.ts:203"]
+ },
+ "TeammateIdle": {
+ "trigger": "teammate(多人协作中的另一个 agent)即将进入 idle 状态时;hook 可 exit 2 让其继续工作,用于强制完成轮询任务。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TeammateIdle'",
+ "teammate_name",
+ "team_name"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:591-599",
+ "dispatch_source": "utils/hooks.ts:3709-3729",
+ "call_sites": ["query/stopHooks.ts:403"]
+ },
+ "TaskCreated": {
+ "trigger": "TaskCreateTool 创建任务前;hook 可阻断创建并向模型回馈拒绝理由(如校验任务标题命名规范)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TaskCreated'",
+ "task_id",
+ "task_subject",
+ "task_description?",
+ "teammate_name?",
+ "team_name?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:601-612",
+ "dispatch_source": "utils/hooks.ts:3745-3773",
+ "call_sites": ["tools/TaskCreateTool/TaskCreateTool.ts:93"]
+ },
+ "TaskCompleted": {
+ "trigger": "TaskUpdateTool 将任务置为 completed 之前;hook 可阻断完成(例如强制要求附录 / 验收脚本通过)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='TaskCompleted'",
+ "task_id",
+ "task_subject",
+ "task_description?",
+ "teammate_name?",
+ "team_name?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:614-625",
+ "dispatch_source": "utils/hooks.ts:3789-3817",
+ "call_sites": ["tools/TaskUpdateTool/TaskUpdateTool.ts:235"]
+ },
+ "Elicitation": {
+ "trigger": "MCP 服务器发起 elicitation 请求、需要向用户弹出对话框前;hook 可程序化返回 accept/decline/cancel 与表单内容,跳过 UI。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='Elicitation'",
+ "mcp_server_name",
+ "message",
+ "mode='form'|'url'?",
+ "url?",
+ "elicitation_id?",
+ "requested_schema?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:627-643",
+ "dispatch_source": "utils/hooks.ts:4470-4523",
+ "call_sites": ["services/mcp/elicitationHandler.ts:228"]
+ },
+ "ElicitationResult": {
+ "trigger": "用户已经响应 MCP elicitation、即将把结果发回服务器之前;hook 可观察或改写最终响应(含 decline 转 block)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='ElicitationResult'",
+ "mcp_server_name",
+ "elicitation_id?",
+ "mode='form'|'url'?",
+ "action='accept'|'decline'|'cancel'",
+ "content?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:645-660",
+ "dispatch_source": "utils/hooks.ts:4525-4575",
+ "call_sites": ["services/mcp/elicitationHandler.ts:273"]
+ },
+ "ConfigChange": {
+ "trigger": "配置文件 watcher 检测到 user/project/local/policy settings 或 skills 目录变化时;hook 可 exit 2 阻断本次变更生效(policy_settings 例外,hook 仅审计、不可阻断)。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='ConfigChange'",
+ "source ∈ CONFIG_CHANGE_SOURCES",
+ "file_path?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:670-678",
+ "dispatch_source": "utils/hooks.ts:4214-4239",
+ "call_sites": [
+ "utils/settings/changeDetector.ts:292",
+ "utils/settings/changeDetector.ts:344",
+ "utils/skills/skillChangeDetector.ts:267"
+ ]
+ },
+ "WorktreeCreate": {
+ "trigger": "需要为本会话创建 worktree 时;若用户配置了该 hook,则替代默认 git worktree 流程,stdout 中的路径作为新 worktree 根目录。无配置时回退到 git worktree。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='WorktreeCreate'",
+ "name (slug,已经过 validateWorktreeSlug)"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:709-716",
+ "dispatch_source": "utils/hooks.ts:4928-4958",
+ "call_sites": [
+ "utils/worktree.ts:716",
+ "utils/worktree.ts:913",
+ "utils/worktree.ts:1262"
+ ]
+ },
+ "WorktreeRemove": {
+ "trigger": "会话结束 / 用户主动清理 worktree 时;若 hook 配置存在则替代默认 git worktree remove;返回 false 表示无 hook 配置、走默认路径。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='WorktreeRemove'",
+ "worktree_path"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:718-725",
+ "dispatch_source": "utils/hooks.ts:4967-5003",
+ "call_sites": [
+ "utils/worktree.ts:827",
+ "utils/worktree.ts:968"
+ ]
+ },
+ "InstructionsLoaded": {
+ "trigger": "CLAUDE.md 或 .claude/rules/*.md 等指令文件被加载进上下文时(session_start / nested_traversal / path_glob_match / include / compact 五种场景);fire-and-forget,仅用于观察与审计,不能阻断。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='InstructionsLoaded'",
+ "file_path",
+ "memory_type ∈ {User, Project, Local, Managed}",
+ "load_reason ∈ INSTRUCTIONS_LOAD_REASONS",
+ "globs?",
+ "trigger_file_path?",
+ "parent_file_path?"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:695-707",
+ "dispatch_source": "utils/hooks.ts:4335-4369",
+ "call_sites": [
+ "utils/claudemd.ts:1060",
+ "utils/attachments.ts:1760"
+ ]
+ },
+ "CwdChanged": {
+ "trigger": "会话 cwd 发生切换之后(如 /cd 命令、worktree 切换);hook 输出可声明 watchPaths 让 FileChanged watcher 重新订阅,并向 REPL 推 systemMessage。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='CwdChanged'",
+ "old_cwd",
+ "new_cwd"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:727-735",
+ "dispatch_source": "utils/hooks.ts:4260-4276",
+ "call_sites": ["utils/hooks/fileChangedWatcher.ts:148"]
+ },
+ "FileChanged": {
+ "trigger": "FileChangedWatcher 监听到 watchPaths 内文件 add/change/unlink 之后;hook 可输出新的 watchPaths 动态扩缩订阅集,输出失败将作为系统消息提示用户。",
+ "payload": [
+ "...BaseHookInput",
+ "hook_event_name='FileChanged'",
+ "file_path",
+ "event='change'|'add'|'unlink'"
+ ],
+ "schema_source": "entrypoints/sdk/coreSchemas.ts:737-745",
+ "dispatch_source": "utils/hooks.ts:4278-4294",
+ "call_sites": ["utils/hooks/fileChangedWatcher.ts:85"]
+ }
+ }
+}
diff --git a/scripts/gen-hooks-table.ts b/scripts/gen-hooks-table.ts
index c30e42d..1604b2d 100644
--- a/scripts/gen-hooks-table.ts
+++ b/scripts/gen-hooks-table.ts
@@ -4,11 +4,12 @@
*
* - 解析 entrypoints/sdk/coreSchemas.ts 中的 `HOOK_EVENTS` 数组(27 个事件)。
* - 解析 schemas/hooks.ts 中四种 hook command type(command / prompt / http / agent)。
+ * - 合并 scripts/data/hooks-trigger-map.json 手工库的触发时机 + payload 字段。
*
* 用法:
* bun scripts/gen-hooks-table.ts [--source-path ] [--diff-summary]
*/
-import { readFileSync } from "node:fs";
+import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
import {
parseArgs,
@@ -27,6 +28,20 @@ const sourceCommit = getSourceCommit(sourcePath);
const coreSchemasPath = join(sourcePath, "entrypoints/sdk/coreSchemas.ts");
const hooksSchemaPath = join(sourcePath, "schemas/hooks.ts");
+const triggerMapPath = "scripts/data/hooks-trigger-map.json";
+
+type TriggerEntry = {
+ trigger: string;
+ payload: string[];
+ schema_source: string;
+ dispatch_source: string;
+ call_sites: string[];
+};
+
+type TriggerMap = {
+ source_commit: string;
+ events: Record;
+};
function extractHookEvents(text: string): { events: string[]; start: number; end: number } {
const m = text.match(/export const HOOK_EVENTS\s*=\s*\[([\s\S]*?)\]\s*as\s+const/);
@@ -53,20 +68,55 @@ function extractHookCommandTypes(text: string): { name: string; line: number }[]
return out.sort((a, b) => a.name.localeCompare(b.name));
}
+function loadTriggerMap(): TriggerMap {
+ if (!existsSync(triggerMapPath)) {
+ throw new Error(
+ `Missing trigger map at ${triggerMapPath}; needed to render trigger/payload columns.`,
+ );
+ }
+ return JSON.parse(readFileSync(triggerMapPath, "utf8")) as TriggerMap;
+}
+
const coreText = readFileSync(coreSchemasPath, "utf8");
const hooksText = readFileSync(hooksSchemaPath, "utf8");
const eventInfo = extractHookEvents(coreText);
const cmdTypes = extractHookCommandTypes(hooksText);
+const triggerMap = loadTriggerMap();
+
+// Drift gate: warn loudly when source_commit or event set diverges. Do not
+// silently render stale data — leaving a TODO in the rendered table is safer
+// than pretending the manual map matches the bundled source.
+const driftWarnings: string[] = [];
+if (triggerMap.source_commit !== sourceCommit) {
+ driftWarnings.push(
+ `trigger-map source_commit ${triggerMap.source_commit} != current ${sourceCommit}; verify map is still accurate`,
+ );
+}
+const missingFromMap = eventInfo.events.filter((e) => !triggerMap.events[e]);
+const extraInMap = Object.keys(triggerMap.events).filter(
+ (e) => !eventInfo.events.includes(e),
+);
+if (missingFromMap.length > 0) {
+ driftWarnings.push(`events missing from trigger map: ${missingFromMap.join(", ")}`);
+}
+if (extraInMap.length > 0) {
+ driftWarnings.push(`events in trigger map but not in HOOK_EVENTS: ${extraInMap.join(", ")}`);
+}
const items: ManifestItem[] = [
- ...eventInfo.events.map(
- (e): ManifestItem => ({
+ ...eventInfo.events.map((e): ManifestItem => {
+ const entry = triggerMap.events[e];
+ return {
name: e,
category: "event",
wire_type: e,
- source_files: [`entrypoints/sdk/coreSchemas.ts:${eventInfo.start}-${eventInfo.end}`],
- }),
- ),
+ source_files: entry
+ ? [entry.schema_source, entry.dispatch_source, ...entry.call_sites]
+ : [`entrypoints/sdk/coreSchemas.ts:${eventInfo.start}-${eventInfo.end}`],
+ trigger: entry?.trigger ?? "TODO: 待补 trigger",
+ payload: entry?.payload ?? ["TODO: 待补 payload"],
+ };
+ }),
...cmdTypes.map(
(t): ManifestItem => ({
name: t.name,
@@ -79,13 +129,40 @@ const items: ManifestItem[] = [
const manifest = {
source_commit: sourceCommit,
items,
- counts: { events: eventInfo.events.length, command_types: cmdTypes.length },
+ counts: {
+ events: eventInfo.events.length,
+ command_types: cmdTypes.length,
+ events_with_trigger: eventInfo.events.filter((e) => triggerMap.events[e]).length,
+ },
};
const manifestPath = "docs/appendix/C.manifest.json";
const prev = readManifest(manifestPath);
writeManifest(manifestPath, manifest);
+function renderPayloadCell(payload: string[]): string {
+ // Escape pipes for markdown table cells and join with
so each field
+ // sits on its own visual line without breaking the row.
+ return payload
+ .map((p) => p.replace(/\|/g, "\\|"))
+ .map((p) => `\`${p}\``)
+ .join("
");
+}
+
+function renderTriggerCell(trigger: string): string {
+ return trigger.replace(/\|/g, "\\|").replace(/\n/g, " ");
+}
+
+function renderSourcesCell(entry?: TriggerEntry): string {
+ if (!entry) return "—";
+ const lines = [
+ `schema: \`${entry.schema_source}\``,
+ `dispatch: \`${entry.dispatch_source}\``,
+ ...entry.call_sites.map((s) => `call: \`${s}\``),
+ ];
+ return lines.join("
");
+}
+
const md = [
`# 附录 C · Hooks 事件表`,
``,
@@ -93,12 +170,25 @@ const md = [
``,
`- HOOK_EVENTS:${eventInfo.events.length} 个`,
`- Hook command type:${cmdTypes.length} 类`,
+ `- 已补齐 trigger/payload:${manifest.counts.events_with_trigger}/${eventInfo.events.length}`,
``,
+ ...(driftWarnings.length > 0
+ ? [
+ `> ⚠️ 触发表与源码可能存在漂移:`,
+ ...driftWarnings.map((w) => `> - ${w}`),
+ ``,
+ ]
+ : []),
`## HOOK_EVENTS(来源:\`entrypoints/sdk/coreSchemas.ts:${eventInfo.start}-${eventInfo.end}\`)`,
``,
- `| 事件名 |`,
- `|---|`,
- ...eventInfo.events.map((e) => `| \`${e}\` |`),
+ `| 事件名 | 触发时机 | Payload 字段 | 源码位置 |`,
+ `|---|---|---|---|`,
+ ...eventInfo.events.map((e) => {
+ const entry = triggerMap.events[e];
+ const trigger = entry ? renderTriggerCell(entry.trigger) : "TODO: 待补 trigger";
+ const payload = entry ? renderPayloadCell(entry.payload) : "TODO: 待补 payload";
+ return `| \`${e}\` | ${trigger} | ${payload} | ${renderSourcesCell(entry)} |`;
+ }),
``,
`## Hook command type(来源:\`schemas/hooks.ts\`)`,
``,
@@ -110,6 +200,9 @@ const md = [
writeFile("docs/appendix/C.md", md);
if (has("--diff-summary")) printDiffSummary("C", prev, manifest);
+if (driftWarnings.length > 0) {
+ for (const w of driftWarnings) console.warn(`[C] drift: ${w}`);
+}
console.log(
- `[C] wrote docs/appendix/C.md + manifest (events=${eventInfo.events.length}, cmd_types=${cmdTypes.length})`,
+ `[C] wrote docs/appendix/C.md + manifest (events=${eventInfo.events.length}, cmd_types=${cmdTypes.length}, with_trigger=${manifest.counts.events_with_trigger})`,
);