跳转至

Hooks 参考手册

GodotMaker 所有 Hook 的完整参考。Hook 是 Python 脚本,在 coding-agent runtime 事件上自动运行,用于执行流水线规则。

Hook 注册关系按 runner 分开维护:Claude Code 使用 agent-runtimes/claude-code/config/settings.json,Codex 使用 agent-runtimes/codex/config/hooks.json。OpenCode 使用 agent-runtimes/opencode/plugins/godotmaker-hooks.js 作为 adapter plugin。 脚本通过发布流程部署到 .godotmaker/hooks/。

OpenCode 使用降级版 runner hook adapter;它的子 Agent 权限边界见 OpenCode runtime provider 文档。 下方清单中的 SubagentStart 和 SubagentStop 适用于 Claude Code / Codex hook payload;OpenCode adapter 不会发出这类 Claude-style 生命周期事件。


Hook 清单

Hook 事件 匹配器 是否阻止? 用途
session_start.py SessionStart — 否 清除会话指标,重置状态
check_file_permissions.py PreToolUse Write|Edit 是 由 .godotmaker/current_role 驱动的角色与子代理写规则
stage_reminder.py PreToolUse Write|Edit 是 检测 stage.jsonl 追加操作,验证角色输出,注入下一角色提示
check_stage_prerequisites.py PreToolUse Agent 是 Worker 派发前,验证前置角色已完成且其产出存在
check_asset_access.py PreToolUse Read 是 在活跃角色期间,阻止主代理/root session 读取 assets/ 中的图片文件
log_subagent.py SubagentStart — 否 Claude Code / Codex:记录子代理启动指标(角色检测、agent_id)。OpenCode 不发出这个生命周期 hook。
on_subagent_stop.py SubagentStop — 是 Claude Code / Codex:串行执行 log_subagent.handle_stop + check_worker_report,避免指标文件竞态。OpenCode 不发出这个生命周期 hook。
check_completion.py Stop — 是 最终门控:仅针对 build / fixgap,若派发了 Worker 但未运行 Verifier + Reviewer 则阻止结束

详细说明

session_start.py

事件: SessionStart 是否阻止: 从不

每次会话开始时执行三件事:

  1. 清除 metrics_current.jsonl(会话日志)并重置 state.json 计数器。
  2. 删除上一会话留下的过期 .godotmaker/current_role,让下一个 /gm-* 技能写入新值。
  3. 读取 .godotmaker/version 并将 [GodotMaker vX.Y.Z] 作为 additionalContext 注入,让角色和用户知道部署的是哪个框架版本。

check_file_permissions.py

事件: PreToolUse(Write|Edit) 是否阻止: 是

读取 .godotmaker/current_role(每个 /gm-* 技能的第一个动作写入)并应用该角色的写规则。各角色摘要:

角色 可写内容
scaffold 任何内容(项目初始化)
gdd .md 规划文档、project.godot、.godotmaker/(不含 assets/)
asset ASSETS.md、.godotmaker/(生成图片通过 asset-producer 或 asset tools;用户提供的图片分析通过 Analyst)
build / fixgap e2e/ 中不可写;游戏代码(.gd / .tscn / .tres)不可直接写——必须派发 Worker
verify 仅 .godotmaker/stage.jsonl、.godotmaker/current_role 和 .godotmaker/verify_report.json(其他地方只读)
evaluate e2e/、.godotmaker/evaluation.json、.godotmaker/stage.jsonl、.godotmaker/current_role
accept / finalize 除 e2e/ 和游戏代码(.gd / .tscn / .tres)外的任何内容

在流水线角色活跃期间,通用子代理会被阻止写入 e2e/ 和规划文档(PLAN.md / STRUCTURE.md / ASSETS.md / GAP.md)。asset-producer 可以写入 assets/、references/ 和 .godotmaker/asset-generation/。

在 Runtime 支持的 Write/Edit Hook 路径上,所有子代理还会被阻止写入根 MEMORY.md 和根 memory/ 目录下的任何文件。相对路径、绝对路径、worktree 路径和普通 .. 写法会先规范化。这是工具 Hook 契约,不是文件系统沙箱;shell 写入不在这个 Hook 的覆盖范围内。

Runner 说明:这个子代理写入 gate 需要 runtime 提供 agent_id。OpenCode child session 不暴露该 payload,因此 OpenCode adapter 对 child 写入只运行无需身份的 memory scope;其他角色所有权规则仍依赖 OpenCode 原生 edit permission。Codex 和 Pi 没有等价的写入 Hook,其架构记录边界只在 prompt 层执行。

未设置角色时,表示当前没有活跃的 /gm-* 流水线角色。该 Hook 只记录文件操作,不阻止写入,因此用户可以在 GodotMaker 项目目录中正常开启普通 coding-agent 对话。

同时为每次文件操作记录 FILE_WRITE / FILE_EDIT 指标事件。

stage_reminder.py

事件: PreToolUse(Write|Edit) 是否阻止: 是

当 /gm-* 技能向 .godotmaker/stage.jsonl 追加角色完成事件时触发。每行格式为 {"role": <role>, "ts": <iso>}。

  1. 验证角色产出 — 读取 config/stage_schemas.json(键为角色名,非阶段编号),检查 files 是否存在并运行 checks 中的程序化验证器。验证失败则阻止追加。
  2. 注入提示 — 通过 ROLE_NEXT 表指向下一个角色的 /gm-* 指令。

程序化检查:

检查 角色 断言内容
plan_all_verified build PLAN.md 中每个任务行的状态为 verified(无 pending / in_progress / completed)
gap_archived fixgap GAP.md 已被移动到 .godotmaker/gaps/<iteration>/GAP.md

角色产出 Schema 位于 config/stage_schemas.json。当前结构: - scaffold → project.godot - gdd → GDD.md、PLAN.md、STRUCTURE.md - evaluate → .godotmaker/evaluation.json - finalize → .godotmaker/final_report.json - asset / verify / accept 依赖各自 SKILL.md 中的 Resume Check。

check_stage_prerequisites.py

事件: PreToolUse(Agent) 是否阻止: 是

仅对驱动 Worker 编排的两个角色执行检查:

角色 前置角色 额外检查
build gdd 已记录在 stage.jsonl 中 project.godot 存在(scaffold 产物,生命周期内唯一)
fixgap evaluate 已记录在 stage.jsonl 中 (通过 evaluate schema → .godotmaker/evaluation.json 验证)

Hook 还会重新验证前置角色在 config/stage_schemas.json 中的 files——对于 build,确认 GDD.md、PLAN.md、STRUCTURE.md 仍在磁盘上;对于 fixgap,确认 .godotmaker/evaluation.json 仍存在。

其他有派发行为的角色(如 asset 派发 Analyst 或 asset-producer)通过 SKILL.md 的 Resume Check 进行自我验证;其前置条件不适合这个 Hook 的角色完成模型。仅检查主代理(gm-* 技能本身),不检查子-子代理的派发。

check_asset_access.py

事件: PreToolUse(Read) 是否阻止: 是

仅在流水线角色活跃时(存在 .godotmaker/current_role),阻止主代理读取 assets/ 中的图片文件。 图片扩展名:.png、.jpg、.jpeg、.svg、.webp、.gif、.bmp、.tga。

没有活跃角色的普通对话被允许。runtime 提供子代理身份时,子代理被允许。 OpenCode child session 不暴露同等的 agent_id payload,因此它的 adapter 只在 root stage session 上保留这个 gate。非图片文件(.json、.ogg)被允许。

目的:强制主代理将资源分析委托给 Analyst 子代理,而不是消耗上下文来读取原始图片数据。

log_subagent.py

事件: SubagentStart(以及 on_subagent_stop.py 调用它处理 SubagentStop) 是否阻止: 从不

Runner 支持:仅 Claude Code / Codex。OpenCode adapter 不发出 Claude-style 子代理生命周期 hook。

SubagentStart: 检测角色并记录 SUBAGENT_START 指标,包括 agent_id、agent_type、role、description。

角色检测顺序: 1. 运行时提供的 agent_type — 如果 Claude Code 传入的 agent_type 匹配 KNOWN_ROLES(worker、verifier、reviewer、analyst、asset-producer),则使用该值。这是 Claude Code 在调用 Agent({subagent_type: "verifier", ...}) 时加上的结构化标识,代理无法伪造。 2. 描述前缀回退 — 如果 agent_type 是通用值,则回退到 detect_role_from_description: 1. asset-producer: → asset-producer 2. analyst: → analyst 3. worker: → worker 4. verifier: / verify: → verifier 5. reviewer: / review: → reviewer

handle_stop: 由 on_subagent_stop.py 调用。通过 metrics.outcome.normalize_report 读取报告类型与状态——与报告 Hook 完全相同的入口。从匹配的启动事件中查找角色。记录 SUBAGENT_STOP 指标以及结果特定事件:WORKER_DONE、VERIFIER_PASS、 ASSET_PRODUCER_PARTIAL 等。

只有 Worker stop 会额外产生诊断:失败、部分完成、报告被拒绝或未验证交接写入 一条有界的 worker_error。稳定字段包括 task_id、attempt、stage、 runtime、agent_id、run_id、trace_id、error_type、可选的 classification/exit_code、summary、error_fingerprint、有界的 evidence_paths、retryable 和 repeat_count。大型 Tests/Build 输出会增加 有界的 output_digest、output_bytes 和 output_tail 引用,但不会保存完整输出。 字段只从拥有它的 Worker 章节 读取;缺失值保持为空,不从其他角色或任意粘贴输出中猜测。相同的 (agent_id, error_fingerprint) 只写一次。Worker 成功时不写诊断;诊断也不会 注入后续 prompt 或自动提升为项目记忆。

worker_error 的产生沿用现有 on_subagent_stop.py 支持范围:仅 Claude Code 和 Codex。由于 Codex 对所有委派角色都暴露通用 worker agent type,其输出必须 能被识别为 Worker 报告;silent 的通用 delegate 保持未分类。OpenCode 与 Pi 保留 trace/报告证据,但不产生该事件;补齐 Runtime 生命周期 parity 属于独立工作。

每条 SUBAGENT_STOP 都带 outcome_kind:

outcome_kind 含义
terminal 报告通过校验,写入唯一的结果特定事件
rejected_attempt 报告 Hook 拦截了这次 stop,不写结果事件
unverified 未通过校验即被放行,不写结果事件

只有 terminal 才写结果特定事件,因此未通过校验的报告既不会被读成结果,也不会 抢先覆盖重试的真实终态。unverified 覆盖两种「放行但未接受报告」的情况:下文的 防死锁 force-allow,以及必须携带 machine outcome 块的角色在没有合法块时走到这一步。

on_subagent_stop.py

事件: SubagentStop 是否阻止: 是(委托给 check_worker_report)

Runner 支持:仅 Claude Code / Codex。OpenCode adapter 不发出 Claude-style 子代理生命周期 hook。

SubagentStop 事件的单一分发器。读取一次 stdin 后串行运行:

  1. check_worker_report.evaluate(data) — 判定结论(无副作用)
  2. log_subagent.handle_stop(data, verdict=…) — 按该结论标记并记录指标(从不阻止)
  3. check_worker_report.apply_verdict(verdict) — 输出决定(可能阻止)

为什么先验证: 结论决定这次 stop 是终态记录还是被拒绝的尝试。先记录再验证会把被 拒绝的尝试写成本次运行的结果,随后去重保护会丢弃重试的真实状态。

为什么使用分发器: Claude Code 默认并行运行多个 SubagentStop Hook。两个处理程序都会操作 metrics_current.jsonl——log_subagent 读取,check_worker_report 写入——这会导致间歇性的 JSONDecodeError 崩溃。在单个进程内串行执行消除了竞态条件。

check_worker_report.py

事件: SubagentStop(通过 on_subagent_stop.py 调用) 是否阻止: 是

仅在 /gm-* 流水线角色活跃时验证子代理角色的报告格式和内容。没有 .godotmaker/current_role 时,普通子代理对话被允许,该 Hook 不阻止。

格式检测流程: 1. 通过 metrics.outcome.normalize_report 解析 report_type 与 status——先读 machine outcome 块,再走 Markdown 分层(精确标记 → 正则 → 回退) 2. 若该角色必须携带 machine outcome 块 → 校验该块,fail closed 3. 若检测到 report_type → 检查该类型所需的章节 4. 若 report_type 为 None 但从启动事件得知角色 → 阻止并要求提交格式化报告

Machine outcome 块: asset-producer 报告必须以恰好一个带 gm_outcome_version 的 fenced JSON 对象结尾。它才是状态协议,Markdown 标题只是人类可读摘要。Hook、 metrics 与 /gm-asset 管理端通过同一个 parser 读取它,因此错乱的标题不会再产生 report_type: unknown / status: UNKNOWN 记录,同时管理端却读到了正确状态。

字段 规则
gm_outcome_version 1
report_type 必须等于该子代理被派发时的角色
status DONE、PARTIAL 或 FAILED
unit_id 非空字符串
outputs 路径数组对象,键只能是 sources、runtime、prompts、reports、request、result
validation {passed: bool, levels?: {L0–L5: bool}, notes?: string}
blockers 字符串数组;只有 status 为 DONE 时才可为空

DONE 还要求 validation.passed 为真;PARTIAL 与 FAILED 至少需要一条 blocker。 字段缺失或非法会带着字段名被拒绝,该次 stop 记录为 rejected_attempt;若最终被 force-allow 放行,则记录为 unverified。两者都不会写结果事件。

角色绑定: 子代理被派发时的角色(payload 的 agent_type,回退到其 subagent_start 事件)优先于报告对自身的任何声明;block 声明了不同的 report_type 会 fail closed。报告无法自行升格或降格,因此记录里不可能出现一个角色 的 role 配另一个角色的 outcome。log_subagent.classify_stop 会复核同一等式,覆盖 hook 完全没有校验的路径(force-allow,或没有活跃的流水线角色)。

每角色必需章节:

角色 必需章节
worker Status, Files Changed, Tests, Build
verifier Overall, Results, Adversarial Probes
reviewer Reviewers Matched, ECS Review, Issues Found, Summary
analyst Status, Asset Summary, Art Style Summary, Files Generated
asset-producer Status, Production Unit, Outputs, Tools, Validation, Handoff

Worker 专项深度检查: - check_test_substance() — Tests 章节必须包含单元测试的实际通过/失败输出 - check_resource_paths() — .gd 文件中的 res:// 路径必须存在 - check_classname_conflicts() — class_name 声明不得与 Godot 内置名称冲突

进度提示: 验证成功后,注入一条进度摘要(已完成的 Worker、Verifier、Reviewer 数量)作为附加上下文。

Reviewer 内容检查: ECS Review 和 Issues Found 章节各自必须有至少 50 个字符的内容,防止空洞或走过场的审查。

防死锁: BLOCK_LIMIT = 2,每个 agent_id 最多 2 次拦截——超过 2 次后强制放行并给出警告,而不是永远阻止。

Force-allow 只解除对 agent 的阻塞,并不表示接受该报告。这次 stop 记为 unverified 且不写任何结果特定事件,未通过校验的报告因此永远无法占据本次运行的 终态。对必须携带 machine outcome 块的角色,警告还会明确告知该报告不是终态、其产物 不得被注册。

已知缺口: - Verifier 报告:没有 Hook 验证 Verifier 确实运行了测试(仅检查格式) - 没有针对每个 Worker 的截图验证(截图是 /gm-evaluate 中评估器的职责)

check_completion.py

事件: Stop 是否阻止: 是

当活跃的 gm-* 技能尝试结束对话时的最终门控。仅对 Worker 派发角色(build、fixgap)生效;对其他所有角色该 Hook 是空操作,它们通过各自 SKILL.md 的 Resume Check 自我执行规则。

Worker 派发严谨性检查: 如果本次会话中派发了任何 Worker,则 Verifier 和 Reviewer 都必须也已运行(根据 gm-build / gm-fixgap 规则)。如果只运行了 Worker,Hook 会阻止并列出缺失的角色。

防死锁: BLOCK_LIMIT = 5 — 同一会话中 5 次阻止后,强制放行并给出警告,而不是永远阻止。


事件流程图

SessionStart
  └── session_start.py (清除指标)

PreToolUse(Write|Edit)
  ├── check_file_permissions.py (从 current_role 读取每角色写规则)
  └── stage_reminder.py (验证 stage.jsonl 追加,注入下一角色指针)

PreToolUse(Agent)
  └── check_stage_prerequisites.py (若前置角色未完成则阻止 build/fixgap)

PreToolUse(Read)
  └── check_asset_access.py (活跃角色期间阻止主代理读取 assets/ 中的图片)

SubagentStart
  └── log_subagent.py (Claude Code / Codex:记录启动 + 角色)

SubagentStop
  └── on_subagent_stop.py (Claude Code / Codex:串行报告 gate)

Stop
  └── check_completion.py (仅 build/fixgap 的严谨性检查;其他角色为空操作)

已知缺口(TODO)

  1. Verifier 测试执行: 没有 Hook 验证 Verifier 确实运行了测试(而不只是报告了格式正确的结果)。抽查仅在提示层面进行。