news 2026/9/5 18:08:14

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
get-shit-done 修复 3653:graphify 自动更新 Hook 如何“看见“ SDK 内部提交的 git commit

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

【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done

本篇围绕 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.md 中graphify.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
commitSDK 内部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 commitinvokes git viaspawnSync('git', [...]), so the substringgit commitnever 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/二进制中转层的偏移。

【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 18:05:37

Gin ginS 包深度解析:用全局单例 API 快速搭建默认 HTTP 服务器

Gin ginS 包深度解析:用全局单例 API 快速搭建默认 HTTP 服务器 【免费下载链接】gin Gin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks t…

作者头像 李华
网站建设 2026/9/5 18:02:14

STM32F103驱动P5全彩LED点阵屏的硬实时实现

简介:本资源是一套面向嵌入式初学者与STM32入门者的LED点阵屏驱动实践方案,聚焦HUB75接口P5全彩色LED点阵屏在STM32F103C8T6平台上的快速点亮与原理理解。区别于课堂常见的简易点阵模块,该方案针对内置行/列驱动芯片(如16路恒流IC…

作者头像 李华
网站建设 2026/9/5 17:59:47

renodx:游戏修改利器,助力DirectX游戏升级

renodx:游戏修改利器,助力DirectX游戏升级 【免费下载链接】renodx Renovation Engine for DirectX Games 项目地址: https://gitcode.com/GitHub_Trending/re/renodx 在游戏开发与修改领域,一款高效、稳定的工具至关重要。renodx&…

作者头像 李华
网站建设 2026/9/5 17:55:40

C++控制台学生成绩管理系统:内存、编码与状态机实战

简介:本资源是一套完整的C课程设计项目——控制台版学生成绩管理系统,面向计算机专业本科生及C初学者,解决课程实践环节中数据结构应用、模块化编程与小型系统开发能力训练问题。系统实现五大核心功能:成绩录入与修改、单学生查询…

作者头像 李华
网站建设 2026/9/5 17:55:24

17秒转写13分钟音频:faster-whisper语音识别加速实现

17秒转写13分钟音频:faster-whisper语音识别加速实现 【免费下载链接】faster-whisper Faster Whisper transcription with CTranslate2 项目地址: https://gitcode.com/GitHub_Trending/fa/faster-whisper faster-whisper 是基于 CTranslate2 推理引擎的语音…

作者头像 李华
网站建设 2026/9/5 17:52:43

MATLAB QPSK误码率仿真:从Eb/N0原理到工程可信度

简介:本资源是一份面向通信工程初学者与MATLAB实践者的QPSK数字调制系统误码率仿真工具包,聚焦于无线通信链路性能分析核心环节——误码率(BER)随信噪比(Eb/N0)变化关系的建模与可视化。压缩包共含2个文件&…

作者头像 李华