get-shit-done 的 Graphify 自动更新钩子:主分支 HEAD 前进后自动重建代码知识图谱
【免费下载链接】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
本文围绕 get-shit-done 仓库的 changeset 文档 .changeset/3347747-graphify-auto-update-hook.md(PR 3557,关闭 issue #3347)展开,讲清「知识图谱消费方始终拿到最新语义关系」这一机制的完整设计:新增的graphify.auto_update配置项、捆绑的 PostToolUse 钩子hooks/gsd-graphify-update.sh的八道触发闸门、.last-build-status.json状态文件的三态生命周期,以及gsd-planner/gsd-phase-researcher如何零改动地感知自动重建状态。读完你可以完整理解该功能的配置方式、触发条件、失败面设计,以及从钩子到状态消费端的源码级调用链。
问题背景:图谱的生产与消费之间存在静默漂移
get-shit-done 的 Graphify 子系统会把项目代码构建成知识图谱,落在.planning/graphs/graph.json,并被gsd-planner与gsd-phase-researcher在每一步的load_graph_context阶段自动消费;但在此之前,图谱的生产是手动的(每次会话最多跑一次/gsd:graphify build)。也就是说:图谱被自动消费、却被手动生产,每多一次 commit,生产者与消费者之间的差距就静默地扩大一点。
原有的stale: true标注只能告诉消费方「文件 mtime 老了」,无法区分三种状态:自动重建钩子正在跑、刚才失败了、还是根本没人跑过。changeset 文档给出的核心意图正是消除这个盲区:
new config key
graphify.auto_update(defaultfalse) and bundled PostToolUse hookhooks/gsd-graphify-update.shkeep the.planning/graphs/graph.jsonconsumed bygsd-plannerandgsd-phase-researchercurrent without manual/gsd:graphify buildruns.
设计原则是opt-in:graphify.auto_update默认false,未开启的用户升级后行为完全不变;开启后,钩子在主分支上发生 HEAD 前进的 git 操作之后,以脱离父进程的后台子进程方式执行graphify update .,钩子本身永远同步快速返回、绝不阻塞用户可见的工具调用。
配置项:graphify.enabled 与 graphify.auto_update
自动更新由两个布尔配置项联合控制,均位于项目的.planning/config.json:
| 配置键 | 类型 | 默认值 | 含义 |
|---|---|---|---|
graphify.enabled | boolean | false | 启用项目知识图谱(/gsd:graphify) |
graphify.auto_update | boolean | false | 主分支 HEAD 前进后自动重建图谱 |
两者必须同时为true钩子才会动作(见 docs/CONFIGURATION.md 中graphify.auto_update的参数说明,以及 get-shit-done/workflows/settings.md 中的配置清单)。
在交互式设置入口/gsd:settings中,graphify.auto_update以「Graph auto-update」问题呈现,且条件可见:只有当用户选择的graphify.enabled为 on 时才会出现该问题;若graphify.enabled为 off,则省略该问题并保留配置中已有的graphify.auto_update值,不做覆盖。settings 工作流原文规定:
Conditional visibility — graphify.auto_update:This question is shown only when the user's chosen
graphify.enabledvalue is on. Ifgraphify.enabledis off, omit thegraphify.auto_updatequestion and preserve the existinggraphify.auto_updatevalue in config (do not overwrite). Implementation: ask Graphify first; only ask Graph auto-update when Graphify is enabled.
配置落盘后形如:
{ "graphify": { "enabled": true, "auto_update": true } }钩子实现:hooks/gsd-graphify-update.sh 的八道触发闸门
捆绑的钩子是 hooks/gsd-graphify-update.sh,一个匹配 Bash 工具的 PostToolUse 钩子。它的文件头注释完整列出了闸门设计(按快速失败顺序排列,每一道都削减「常见不派发路径」上的工作):
- Gate 1— stdin 载荷存在且
tool_name == "Bash":钩子从 stdin 读取工具调用 JSON,用 Node 解析出tool_name与tool_input.command,非 Bash 调用直接exit 0; - Gate 2— 命令是「推进 HEAD 的 git 操作」:直接 shell 形式匹配
git commit/git merge/git pull/git rebase --continue/git cherry-pick子串,或精确的gsd-sdk query commit命令形态。之所以要匹配后者,是因为 SDK 命令内部调用 git、命令行中从未出现字面量git commit(见 issues #3653); - Gate 3—
$CI环境变量未设置或为空(CI 中抑制); - Gate 4— 当前目录在一个 git 仓库内(
git rev-parse --git-dir验证); - Gate 5— 当前分支等于默认分支:优先读
.planning/config.json的git.base_branch覆盖值,否则按main/master/trunk依次探测; - Gate 6—
.planning/config.json同时满足graphify.enabled === true && graphify.auto_update === true; - Gate 7—
graphify可执行文件在PATH上,否则静默退出; - Gate 8— 没有正在进行的重建:读取锁文件
.planning/graphs/.rebuild.lock中的 PID 并用kill -0探活;进程存活则退出,进程已死(陈旧锁)则容忍并继续。
闸门 2 与闸门 5 的关键代码节选自 gsd-graphify-update.sh:
# 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 ... CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ] || exit 0注意「rebase --continue匹配、但裸rebase不匹配」的细节:只有git rebase --continue才代表 rebase 序列真正落盘推进了 HEAD,普通git rebase启动时未必前进 HEAD。
状态文件:三态生命周期的 .last-build-status.json
当八道闸门全部通过,钩子先同步写入初始状态文件,再把真正的重建工作派发到脱离的子进程。状态文件.planning/graphs/.last-build-status.json的结构(节选自 planner-graphify-auto-update.md):
{ "ts": "2026-05-15T14:02:23Z", "status": "running", "exit_code": null, "duration_ms": null, "head_at_build": "<commit-sha>", "graphify_version": null }| 字段 | 说明 |
|---|---|
ts | UTC 时间戳,ISO 8601 |
status | running/ok/failed三态 |
exit_code | running时为null,终态时为graphify update .的退出码 |
duration_ms | 重建耗时(毫秒),running时为null |
head_at_build | 重建启动时记录的 HEAD SHA |
graphify_version | 预留字段,当前恒为null |
同步写 running,再脱离派发
gsd-graphify-update.sh 中,钩子在派发前用 Node 写出status: "running"的初始状态(携带head_at_build),这样下一次 planner 调用即使赶在重建完成之前,也能看到「正在进行中」的信号。随后它以后台作业方式启动hooks/lib/gsd-graphify-rebuild.sh:
bash "$REBUILD_SCRIPT" \ "$STATUS_FILE" \ "$LOCK_FILE" \ "$HEAD_SHA" \ "$MS_START" \ "$GRAPHIFY_BIN" \ </dev/null >/dev/null 2>&1 & REBUILD_PID=$! echo "$REBUILD_PID" > "$LOCK_FILE" disown "$REBUILD_PID" 2>/dev/null || true这里有一个刻意的并发设计:钩子把重建进程以普通后台作业启动、通过$!同步捕获 PID 并在钩子返回之前写入锁文件。源码注释解释了动机——消除一个启动竞态:如果让子进程自己写锁,观察方(比如测试清理逻辑)在「锁不存在」时无法区分「子进程还没启动」和「子进程已经结束」。锁由父进程同步落盘后,锁的存在性本身就是可靠的进行中信号。
重建执行器:gsd-graphify-rebuild.sh
脱离的 hooks/lib/gsd-graphify-rebuild.sh 负责真正的重建,流程为:
- 把自己的 PID 写入
LOCK_FILE并设置trap ... EXIT保证任何退出路径都清理锁; - 在项目根目录执行
graphify update .(cwd 继承自调用方),捕获退出码; - 仅在成功时复制产物:
graphify-out/graph.json→.planning/graphs/graph.json,graph.html与GRAPH_REPORT.md一并复制,并把新图谱另存为.planning/graphs/.last-build-snapshot.json供后续graphifyDiff拓扑对比使用。失败路径保留上一份有效图谱,绝不落盘半成品; - 计算
duration_ms,重写状态文件为ok(graphify退出码 0)或failed(携带exit_code)。
消费端如何感知:零提示词改动的 stale 折叠
changeset 文档强调,planner 与 researcher 的load_graph_context步骤现在会把自动重建状态与既有的陈旧度标注一起呈现——包括 issue 评审中认定的必备失败面场景("auto-rebuild FAILED at {ts}; context is from the prior build")。
实现位于 get-shit-done/bin/lib/graphify.cjs 的graphifyStatus():它读取.last-build-status.json,把running/failed两个状态折叠进既有的stale: true信号:
// Auto-update status (#3347) const statusPath = path.join(planningDir, 'graphs', '.last-build-status.json'); const lastBuildAutoUpdate = fs.existsSync(statusPath) ? safeReadJson(statusPath) : null; const autoUpdateStale = lastBuildAutoUpdate && (lastBuildAutoUpdate.status === 'failed' || lastBuildAutoUpdate.status === 'running'); return { ... stale: age > STALE_MS || Boolean(autoUpdateStale), ... last_build_auto_update: lastBuildAutoUpdate || null, };planner 和 researcher 的<step name="load_graph_context">块中本来就执行node ... graphify status,并且已有一条规则:
If the status response has
stale: true, note for later: "Graph is{age_hours}hold — treat semantic relationships as approximate."
因此,这条既有规则现在会额外覆盖三种情况(引自 planner-graphify-auto-update.md):
| 触发条件 | 用户看到的效果 |
|---|---|
自动重建状态 =failed | 既有「按近似对待」提示触发(因为stale: true);完整last_build_auto_update对象(退出码 / 耗时 / commit SHA)随 JSON 返回 |
自动重建状态 =running | 同上——下一次 planner 调用知道图谱正在重建中,在脱离进程完成前按近似对待 |
状态 =ok且 mtime < 24h | 标注静默——图谱新鲜且最近一次自动重建成功 |
| 状态文件缺失 | 静默(操作者未 opt-in,或开启后尚未发生过推进 HEAD 的 git 操作) |
这套设计的三个考量(见参考文档):
- 无 planner 侧提示词改动:折叠进
stale: true复用了既有规则,agents/gsd-planner.md不新增任何内容(该文件已接近 48K 的分解大小上限); - 测试钉住接缝行为:
tests/graphify-auto-update.test.cjs针对graphifyStatus在 status =failed/running/ok/ 文件缺失四种情况下的行为做回归断言; - 向后兼容:不读
last_build_auto_update的旧调用方看到的 JSON 形状不变,stale同时反映 mtime 与自动重建状态。
触发条件全景与可验证边界
把散落在 changeset、源码与测试中的行为约束汇总成一张判定表:
| 场景 | 钩子行为 |
|---|---|
| 工具调用不是 Bash(如 Read/Edit) | Gate 1 拦截,exit 0 |
Bash 命令不含 commit/merge/pull/rebase --continue/cherry-pick/gsd-sdk query commit形态 | Gate 2 拦截 |
$CI已设置 | Gate 3 拦截(CI-aware) |
| 非 git 仓库 | Gate 4 拦截 |
当前分支 ≠ 默认分支(git.base_branch覆盖,否则main/master/trunk) | Gate 5 拦截 |
graphify.enabled或graphify.auto_update为 false | Gate 6 拦截 |
graphify不在 PATH | Gate 7 拦截,静默退出 |
已有重建在跑(锁 PID 存活,kill -0探活) | Gate 8 拦截;陈旧锁(进程已死)容忍并继续 |
| 全部通过 | 同步写running状态 → 锁文件落 PID → 脱离派发graphify update . |
无论哪条路径,钩子总是返回 0,永不阻塞用户可见的工具调用——这是文件头注释明确声明的契约("Returns 0 in all cases. Never blocks the user-facing tool call.")。
验证边界可以落到 tests/graphify-auto-update.test.cjs:该测试文件在临时目录中模拟.planning/config.json与钩子执行,断言各种闸门组合下.planning/graphs/.last-build-status.json是否产生,并验证「graphify.auto_update默认必须为false(opt-in,issue #3347 验收条件)」。更完整的端到端行为矩阵(命令形态匹配、CI 抑制、分支判定、锁语义)可以沿该测试文件与 hooks/gsd-graphify-update.sh 的闸门注释逐条对照。
实战路径:从手动 build 到自动保鲜
对于已启用 Graphify 的项目,启用自动更新的最短路径是:
- 确认
graphify已安装且在 PATH 上(构建流程的graphifyBuild()会做安装检查,安装指引见 get-shit-done/bin/lib/graphify.cjs 中给出的uv pip install graphifyy && graphify install); - 运行
/gsd:settings,在 Features 分区先开启 Graphify,再回答条件出现的「Graph auto-update」问题为 on——或直接把graphify.enabled与graphify.auto_update写入项目.planning/config.json; - 此后在默认分支上的
git commit/git merge/git pull/git rebase --continue/git cherry-pick(以及gsd-sdk query commit形态的 SDK 提交)会自动触发脱离式重建; - 观察保鲜效果:
.planning/graphs/.last-build-status.json会经历running → ok/failed的状态迁移,而gsd-planner/gsd-phase-researcher的load_graph_context输出中stale与last_build_auto_update字段即为其状态面。
适用前提与限制:该机制仅在默认分支上触发(feature 分支上的提交不会触发重建);$CI环境下被抑制;graphify update .的产物只有退出码为 0 才会覆盖.planning/graphs/,失败时消费者看到的是上一份有效图谱加上failed标注(「auto-rebuild FAILED at {ts}; context is from the prior build」语义),不会出现半成品图谱。对于多分支并行、长 rebase 会话较多的团队,这正是把「图谱陈旧度」从隐性漂移变成显式可观测状态的关键一步。
【免费下载链接】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),仅供参考