跳转至

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 文档。 下方清单中的 SubagentStartSubagentStop 适用于 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/

Runner 说明:这个子代理写入 gate 需要 runtime 提供 agent_id。OpenCode child session 不暴露该 payload,因此 OpenCode adapter 不会对 child session 运行这个 Python 子代理写入 gate;该边界依赖 OpenCode 原生 agent edit permission。

未设置角色时,表示当前没有活跃的 /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。当前结构: - scaffoldproject.godot - gddGDD.mdPLAN.mdSTRUCTURE.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.mdPLAN.mdSTRUCTURE.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_idagent_typeroledescription

角色检测顺序: 1. 运行时提供的 agent_type — 如果 Claude Code 传入的 agent_type 匹配 KNOWN_ROLESworkerverifierrevieweranalystasset-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_DONEVERIFIER_PASSASSET_PRODUCER_PARTIAL 等。

每条 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_typestatus——先读 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 DONEPARTIALFAILED
unit_id 非空字符串
outputs 路径数组对象,键只能是 sourcesruntimepromptsreportsrequestresult
validation {passed: bool, levels?: {L0–L5: bool}, notes?: string}
blockers 字符串数组;只有 statusDONE 时才可为空

DONE 还要求 validation.passed 为真;PARTIALFAILED 至少需要一条 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, Memory Entry
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 派发角色(buildfixgap)生效;对其他所有角色该 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 确实运行了测试(而不只是报告了格式正确的结果)。抽查仅在提示层面进行。