首页
/ get-shit-done 修复 3653:graphify 自动更新 Hook 如何"看见" SDK 内部提交的 git commit

get-shit-done 修复 3653:graphify 自动更新 Hook 如何"看见" SDK 内部提交的 git commit

2026-09-04 15:37:32作者:宣利权Counsellor

本篇围绕 changeset 3653-graphify-hook-sdk-commit-visibility.md 展开,剖析 get-shit-done(GSd)中一个隐蔽的静默缺陷:PostToolUse 自动更新 Hook 因只匹配 shell 字面 git commit 而漏掉 gsd-sdk query commit 这类"壳内提交",导致知识图谱目录在每个 Phase 结束时滞后一个或多个提交且无任何报错。读完本篇,你将理解该缺陷的根因(SDK 通过 spawnSync('git', [...]) 绕过 shell 调用 git)、修复方案(Gate 2 精确匹配 gsd-sdk query commit 命令形态并排除 commit-to-subrepo 等前缀碰撞),以及 Hook 八道闸门与配套测试的完整验证逻辑。

缺陷背景:graphify 自动更新 Hook 的工作机制

GSd 内置了一个可选开启(opt-in)的知识图谱自动重建机制,由 PostToolUse Hook gsd-graphify-update.sh 承担。其设计目标是:当默认分支的 HEAD 因 git 操作前进时,自动在后台重建项目知识图谱,把产物同步到 .planning/graphs/

启用需要 .planning/config.json 中同时满足两个开关(依据 CONFIGURATION.mdgraphify.auto_update 条目,issue #3347 的 AC):

{
  "graphify": {
    "enabled": true,
    "auto_update": true
  }
}

auto_update 默认 false,保证既有用户升级后行为不变。Hook 的触发链路在文件头注释中写得很明确:只有当八道闸门全部通过时,才同步写入 .planning/graphs/.last-build-status.jsonstatus="running"),再派生一个脱离父进程的后台任务 gsd-graphify-rebuild.sh 执行 graphify update .,把 graphify-out/{graph.json,graph.html,GRAPH_REPORT.md} 拷贝进 .planning/graphs/,最后改写状态文件为 okfailed

八道闸门按快速失败(fast-fail)顺序排列:

  1. stdin 载荷存在且 tool_name == "Bash"
  2. tool_input.command 匹配"推进 HEAD 的 git 操作"(本缺陷的修复点即在此闸门);
  3. $CI 未设置(CI 环境静默跳过);
  4. 当前位于 git 仓库内(git rev-parse --git-dir 成功);
  5. 当前分支等于默认分支(git.base_branch 可覆盖,否则探测 main/master/trunk);
  6. 配置中 graphify.enabled === true && graphify.auto_update === true
  7. graphify 二进制在 PATH 上;
  8. 没有已存在且存活的构建进程(PID 锁,kill -0 探测,容忍陈旧锁)。

所有闸门的设计取向一致:任何情况下返回 0,绝不阻塞用户可见的工具调用

根因:spawnSync 让 git commit 在 Hook 的"视野"之外消失

缺陷的本质是匹配语义与调用方式错配

原 Gate 2 依赖对 Bash 工具命令字符串的子串匹配,只覆盖"shell 直连"的 git 操作。而 GSd 的提交路径有两条:Agent 既可能直接执行 git commit ...,也可能执行用户面命令 gsd-sdk query commit。后者的实际实现在 commit.ts

export function execGit(cwd: string, args: string[]): { exitCode: number; stdout: string; stderr: string } {
  const result = spawnSync('git', args, {
    cwd,
    stdio: 'pipe',
    encoding: 'utf-8',
  });
  // ...
}

execGit 通过 spawnSync('git', [...]) 直接 exec 出 git 进程,完全不经过 shell。后果是:Bash 工具上报的 tool_input.command 里只有 gsd-sdk query commit ... 字样,字面 git commit 子串永远不会出现。于是 Gate 2 对每一次 SDK 发起的提交都静默放行——没有错误、没有日志、没有告警。

被漏掉的提交并不罕见。changeset 特别指出,其中包括每个 Phase 完成时紧跟 phase.complete 的收尾提交(它负责关闭 Phase 状态并把 .planning/ 文档入库)。因此实际影响是:每个 Phase 收尾时,.planning/graphs/ 都会相对 HEAD 静默漂移一个或多个提交。图谱是后续 planner 的 load_graph_context 步骤的输入(见 INVENTORY.md 中对 planner-graphify-auto-update.md 的说明),漂移意味着 Agent 在下一个规划环节消费的是过期拓扑。

修复方案:Gate 2 精确匹配 gsd-sdk query commit 命令形态

修复落在 gsd-graphify-update.sh 的 Gate 2 上,匹配规则变为:

# Gate 2 — HEAD-advancing git op (shell-direct or exact `gsd-sdk query commit`)
case "$COMMAND" in
  *"git commit"*|*"git merge"*|*"git pull"*|*"git rebase --continue"*|*"git cherry-pick"*) ;;
  *"gsd-sdk query commit"|*"gsd-sdk query commit "*) ;;
  *) exit 0 ;;
esac

新增分支的设计有两点值得注意:

1. 只匹配"精确命令形态",而非裸子串 commit 模式写成 *"gsd-sdk query commit"(命令以此结尾)或 *"gsd-sdk query commit "*(其后还有空格与参数),覆盖 gsd-sdk query commit "docs: x" --files ... 以及 npx gsd-sdk query commit ... 这类经 npx 转发的形态。之所以用带空格的子串而不是 *"commit"**"commit- "*,是为了精确锁定"用户面调用、触发 SDK 内部 spawnSync('git', 'commit', ...)"的那一条路径。

2. 主动排除前缀碰撞的兄弟动词。 SDK 中存在一个名字前缀完全包含 commit 的动词 commit-to-subrepo(注册于 command-static-catalog-domain.ts,并在 command-manifest.non-family.ts 中标记为 mutation: true)。若匹配规则写成 *"gsd-sdk query commit"*(星号紧跟 commit),gsd-sdk query commit-to-subrepo ... 会被误判命中。changeset 明确说明:新规则不会匹配 commit-to-subrepo 这类兄弟动词——从源码结构看,该动词面向子仓库路径(--files packages/foo)操作提交,不推进外层仓库 HEAD,命中它只会触发无意义的重建。

边界决策:为什么其他 gsd-sdk query 动词保持不匹配

changeset 还给出了一条容易被忽略但很关键的边界:其余 gsd-sdk query 动词继续不匹配。具体点名了三个:

动词 行为特征 是否触发 git 是否纳入 Gate 2
commit SDK 内部 spawnSync('git', ['commit', ...]) 是(推进 HEAD) 是(本次修复新增)
commit-to-subrepo 面向子仓库路径的提交 不推进外层 HEAD 否(前缀碰撞,显式排除)
phase.complete 修改 .planning/ 下的 Phase 状态文档
roadmap.update-plan-progress 更新 roadmap 进度文档
state.begin-phase 变更 .planning/STATE.md 状态

后三个动词只改写 markdown 状态文件、自身不调用 git,如果把它们纳入匹配,每次状态变更都会引发一次图谱重建(spurious rebuild per state mutation),而 HEAD 根本没动,重建出的图谱与旧图等价,纯属浪费。这个取舍体现了该 Hook 匹配规则的第一性原则:只匹配"确实推进 HEAD"的动作,无论它发生在 shell 层还是 SDK 层。

验证:测试如何锁死匹配边界

配套的回归测试位于 graphify-auto-update.test.cjs 的 "HEAD-advancing command matchers" 组中,用临时 git 仓库 + mock 的 graphify 二进制驱动真实 Hook 脚本,断言依据是 .planning/graphs/.last-build-status.json 是否被写出:

必须分发的命令(含 #3653 修复点)

git commit -m fix
git merge feature
git pull --ff-only
git rebase --continue
git cherry-pick abc123
gsd-sdk query commit "docs: probe" --files .planning/STATE.md
npx gsd-sdk query commit "docs: probe" --files .planning/STATE.md

其中两条 SDK 形态命令正是本次修复新增的覆盖:测试注释直接引用了根因——"gsd-sdk query commit invokes git via spawnSync('git', [...]), so the substring git commit never appears in tool_input.command. The hook must match the user-facing SDK invocation directly."(测试环境限定 POSIX,Windows 上因需 bash 执行 .sh Hook 而 skip。)

必须不分发的命令(负例)

gsd-sdk query commit-to-subrepo "msg" --files packages/foo
gsd-sdk query phase.complete 109
gsd-sdk query roadmap.update-plan-progress 109 W001
gsd-sdk query state.begin-phase 110

负例断言 Hook 返回 0 且不产生状态文件,分别对应上文两类边界:前缀碰撞排除与非 HEAD 推进动词排除。这组正负用例组合,把 changeset 描述的匹配契约完整翻译成了可重复执行的回归护栏。

小结

#3653 是一类"静默失效"型缺陷的典型样本:Hook 的匹配逻辑建立在"git 操作必然以 shell 命令形式出现"的隐含假设上,而 SDK 的 spawnSync 直连方式打破了这一假设,且全链路无错误、无日志,只能靠产物(.planning/graphs/)与 HEAD 的比对才能发现。修复本身很克制——只给 Gate 2 增加一条精确的命令形态匹配,并显式划清 commit-to-subrepo 与其他状态类动词的边界,避免"多匹配"引入比"少匹配"更麻烦的无谓重建。对阅读该仓库的其他 Hook(如工作流守卫、上下文监控)而言,这条 changeset 也提示了一个通用检查点:凡是依赖 tool_input.command 子串匹配做语义判断的 Hook,都需要确认"命令字面量"与"实际副作用"之间不存在 SDK/二进制中转层的偏移。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384