首页
/ get-shit-done(GSD)更新机制深度解析:从版本检测到补丁回放的完整升级链路

get-shit-done(GSD)更新机制深度解析:从版本检测到补丁回放的完整升级链路

2026-09-09 11:54:51作者:韦蓉瑛

/gsd:update 是 get-shit-done(GSD)系统内置的自我升级命令,负责通过 npm 检测新版本、拉取并展示变更日志、征得用户确认后执行带缓存清理的干净安装。本文以 get-shit-done/workflows/update.md 为骨架,结合仓库中的命令定义、版本检查脚本、SessionStart Hook 与测试用例,完整拆解这条升级链路的八个步骤,并说明 --sync--reapply 两个扩展标志的用途,帮助读者理解 GSD 如何在多 AI 运行时(Claude Code、OpenAI Codex、Gemini CLI、Kilo、OpenCode)下安全地完成自我更新,同时保住用户的本地自定义内容。

一、命令入口:/gsd:update 的定义与标志路由

/gsd:update 的命令契约定义在 commands/gsd/update.md 中,其 frontmatter 声明了参数提示 [--sync | --reapply],并允许使用 Read、Write、Edit、Bash、Glob、Grep、AskUserQuestion 等工具。命令的 execution_context 直接指向 get-shit-done/workflows/update.md,即本文要解析的核心工作流。

命令根据 $ARGUMENTS 的第一个 token 做三路路由:

参数 行为
--sync 剥离该标志后转交 sync-skills 工作流,跨运行时根目录同步受管 GSD skills,支持 --from--to--dry-run--apply 子标志,保证多运行时用户在更新后保持一致
--reapply 转交 reapply-patches 工作流,用三方比较(原始基线、用户修改备份、新安装版本)把用户本地修改合并回新版本
(无标志) 标准更新:检查新版本 → 展示变更日志 → 执行安装

标准更新路径是本文的主角,其完整流程在 update.md<process> 中分为八个 step:

  1. get_installed_version:检测本地/全局安装并校验完整性
  2. check_latest_version:通过确定性脚本查询 npm 最新版本
  3. compare_versions:比较已装版本与最新版本
  4. show_changes_and_confirm:更新前拉取并展示变更日志,获取用户确认
  5. backup_custom_files:安装前备份 GSD 管理目录内的用户自建文件
  6. run_update:按检测到的安装类型执行干净安装并清理更新缓存
  7. display_result:格式化完成消息并提示重启运行时
  8. check_local_patches:检查安装器是否备份了被本地修改的文件

<success_criteria> 则定义了成功的硬性标准:正确读取已装版本、经 npm 检查最新版本、已是最新时跳过、在更新前展示变更日志、展示干净安装警告、获得用户确认、更新成功执行、展示重启提醒。

二、Step 1:安装检测——在多运行时与自定义 config-dir 下的精确定位

更新流程的第一步是回答三个问题:GSD 装在哪、是本地还是全局安装、属于哪个运行时。其核心是解析 execution_context 中的路径推导 PREFERRED_CONFIG_DIRPREFERRED_RUNTIME

  • 路径含 /get-shit-done/workflows/update.md 时,剥掉该后缀得到 PREFERRED_CONFIG_DIR
  • 路径含 /.codex/codex/.gemini/antigravity/antigravity/.gemini/gemini
  • 路径含 /.config/kilo//.kilo/,或 config dir 含 kilo.json/kilo.jsonckilo
  • 路径含 /.config/opencode//.opencode/,或 config dir 含 opencode.json/opencode.jsoncopencode
  • 否则默认 → claude

这样设计的意义在于:使用自定义 --config-dir 安装的用户,其配置目录不在任何默认路径下,必须优先信任 execution_context 指向的目录,而不是机械地扫默认目录。同时,PREFERRED_RUNTIME 作为第一个被检查的运行时,保证 /gsd:update 精确命中发起调用的运行时,而不是误更新同机器上其他运行时的副本。

2.1 运行时候选与优先级重排

工作流用数组存储 <runtime>:<config-dir> 候选(用数组而非空格分隔字符串,是为了在 bash 与 zsh 下都正确迭代,修复了 #1173):

RUNTIME_DIRS=( "claude:.claude" "opencode:.config/opencode" "opencode:.opencode" "antigravity:.gemini/antigravity" "gemini:.gemini" "kilo:.config/kilo" "kilo:.kilo" "codex:.codex" )

如果 PREFERRED_RUNTIME 尚未推导出来,则依次探测各运行时的环境变量:CODEX_HOMEANTIGRAVITY_CONFIG_DIRGEMINI_CONFIG_DIRKILO_CONFIG_DIR/KILO_CONFIGOPENCODE_CONFIG_DIR/OPENCODE_CONFIGCLAUDE_CONFIG_DIR,全部缺失时回退 claudeKILO_CONFIG_DIRdirname(KILO_CONFIG)XDG_CONFIG_HOME/kilo~/.config/kilo 的优先级必须与安装器保持一致。

随后,preferred runtime 的条目被提到数组最前(ORDERED_RUNTIME_DIRS/ORDERED_ENV_RUNTIME_DIRS),保证优先检查目标运行时,其余运行时按固定顺序兜底。

2.2 快路径:execution_context 已指向有效安装

如果 PREFERRED_CONFIG_DIR 下存在 get-shit-done/VERSIONget-shit-done/workflows/update.md 标记文件,直接信任该路径并快速返回,覆盖了不在默认目录下的自定义 --config-dir 安装。这段逻辑里有一个值得注意的细节:Windows(Git Bash)下 pwd 返回 POSIX 风格 /c/Users/...,而 PREFERRED_CONFIG_DIR 可能携带 C:/Users/...,因此先用 normalize_path() 把盘符路径统一转成 POSIX 形式再比较,避免本地/全局误判。快路径必须输出四行结果(4-line output contract,源自 #2993 代码评审),否则下游 check_latest_version 会把安装误判为 UNKNOWN

2.3 慢路径:扫描本地与全局

当没有可信任的 execution_context 路径时,工作流按序扫描:

  1. 本地优先:遍历 ORDERED_RUNTIME_DIRS,检查 ./$dir/get-shit-done/VERSIONupdate.md 标记;
  2. 全局(环境变量派生):遍历 ENV_RUNTIME_DIRS(由 CLAUDE_CONFIG_DIRGEMINI_CONFIG_DIRKILO_CONFIG_DIR/KILO_CONFIG/XDG_CONFIG_HOMEOPENCODE_CONFIG_DIR/OPENCODE_CONFIG/XDG_CONFIG_HOMECODEX_HOME 构造的绝对路径候选);
  3. 全局(默认位置):若上一步未命中,再扫 $HOME/$dir 默认目录;
  4. 本地/全局判定:仅当本地与全局解析出的目录不同(防止 CWD=$HOME 时误判)且 VERSION 文件通过 ^[0-9]+\.[0-9]+\.[0-9]+ 正则校验时才认定 IS_LOCAL=true

最终输出四行契约:

行 1:已装版本(0.0.0 表示未知版本)
行 2:安装范围(LOCAL / GLOBAL / UNKNOWN)
行 3:目标运行时(claude / opencode / gemini / kilo / codex)
行 4:解析出的 GSD 配置目录(如 /Users/me/.claude),UNKNOWN 时为空;记为 GSD_DIR 传给后续步骤

若同时检测到多个运行时安装且无法从 execution_context 判定发起者,工作流要求先询问用户更新哪个运行时,而不是擅自挑选。若 VERSION 文件缺失,则按版本 0.0.0 对待,直接进入全新安装流程,并提示用户“你的安装不包含版本跟踪”。

三、Step 2:确定性 npm 版本检查——包名是常量,不是自由选择

查询最新版本必须走仓库自带的确定性脚本 get-shit-done/bin/check-latest-version.cjs严禁直接运行 npm viewnpm search。这条禁令的由来记录在脚本头注释与测试 tests/bug-2992-check-latest-version.test.cjs 中:此前工作流以自然语言指示 LLM“运行 npm view get-shit-done-cc version”,执行模型可能偷懒或臆造出 @get-shit-done/cliget-shit-done-cligsd 等错误包名,这些查询要么 404,要么更糟——命中无关的抢注(typosquat)包。因此:

// Hardcoded. Do not parameterise — the whole point of this script is that
// the package name is not a runtime choice for the caller.
const PACKAGE_NAME = 'get-shit-done-cc';

脚本的返回值是结构化 JSON { ok, version, reason, detail? }reason 取自有穷枚举 CHECK_REASONokfail_npm_failedfail_invalid_output。它通过 shell 投影层 execNpm(['view', PACKAGE_NAME, 'version'], { timeout: 15_000 }) 执行 npm,15 秒上限防止注册表挂起阻塞整个 /gsd:update(#2993 代码评审要求),并区分三种失败形态:

  • 超时(status 为 null、signal 被设置、stderr 为空)→ detail: npm timed out (signal: SIGTERM)
  • npm 真实报错(stderr 非空)→ detail 取裁剪后的 stderr
  • 其他非零退出detail: npm exited non-zero

SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/ 严格校验输出必须是合法 semver(含预发布版本),否则返回 fail_invalid_output。测试用例用可注入的 spawn 伪造 npm 输出,验证了成功路径、404 路径、超时路径、HTML 页面误作 stdout 的路径以及预发布版本 1.40.0-rc.1 的接受逻辑,且断言 PACKAGE_NAME 恒等于 get-shit-done-cc、没有任何调用方能覆盖它。

工作流侧通过 node "$GSD_DIR/get-shit-done/bin/check-latest-version.cjs" --json 调用并仅在脚本真正执行时用 jq 解析;当 GSD_DIR 为空(范围 UNKNOWN)时跳过检查,用无操作值种子化 LATEST_OK=falseLATEST_REASON="no_install_detected",避免下游把未设置的 LATEST_RESULT 误当成网络检查失败(#2993 反馈)。当 LATEST_OK 不为 true 或退出码非零时,工作流打印 Couldn't check for updates (reason: ...) 并提供手动更新命令 npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc --global 后退出。

四、Step 3:版本比较——三种分支三种结局

拿到已装版本与最新版本后,compare_versions 处理三种情况:

已装 == 最新:直接输出 You're already on the latest version. 并退出,不打扰用户。

已装 > 最新:判定为开发版安装。工作流会打印“你领先于最新发布——这看起来是开发版安装”,并特别给出状态栏出现 ⚠ dev install — re-run installer to sync hooks 警告时的修复指引:这代表 hook 文件比 VERSION 文件旧,应从开发分支重新运行 node bin/install.js --global --claude明确警告此时不要用 /gsd:update——它只会安装 npm 发布版(A.B.C)并把开发版降级。

已装 < 最新:进入 Step 4,先展示“有什么新东西”再安装。

五、Step 4:变更日志展示与用户确认——先看再装

更新可用时,工作流先从 GitHub raw URL 拉取 changelog,提取已装版本与最新版本之间的条目,在更新之前展示预览,同时给出干净安装的明确警告:

## GSD Update Available

**Installed:** 1.5.10
**Latest:** 1.5.15

### What's New
────────────────────────────────────────────────────────────

## [1.5.15] - 2026-01-20
### Added
- Feature X

## [1.5.14] - 2026-01-18
### Fixed
- Bug fix Y
────────────────────────────────────────────────────────────

⚠️  Note: The installer performs a clean install of GSD folders:
- `commands/gsd/` will be wiped and replaced
- `get-shit-done/` will be wiped and replaced
- `agents/gsd-*` files will be replaced

警告明确列出会被清空替换的目录(相对检测到的运行时安装位置),同时强调其他位置的用户自定义内容会被保留:不在 commands/gsd/ 下的自定义命令、不以 gsd- 前缀命名的自定义 agent、自定义 hooks、用户的 CLAUDE.md 文件。被直接修改过的 GSD 文件则自动备份到 gsd-local-patches/,更新后可用 /gsd:update --reapply 重新应用。

交互确认环节使用 AskUserQuestion(问题“Proceed with update?”,选项“Yes, update now”/“No, cancel”)。文本模式(配置中 workflow.text_mode: true 或命令行 --text 标志,即 $ARGUMENTS--text 或 init JSON 的 text_mode 为 true)下,每个 AskUserQuestion 被替换为带编号的纯文本列表,让用户输入数字选择——这是为非 Claude 运行时(OpenAI Codex、Gemini CLI 等不支持 AskUserQuestion 的运行时)准备的必要降级方案。用户取消则直接退出。

六、Step 5:自定义文件备份——安装器不认识的文件先救出来

干净安装会清空 GSD 管理目录,但用户自行添加、安装器不知道的文件会被一并删除。因此安装前需要检测这类文件:存在于磁盘但不在 gsd-file-manifest.json 清单中的文件

工作流特别强调:不要用 bash 路径裁剪(${filepath#$RUNTIME_DIR/})或内联 node -e require()——当 $RUNTIME_DIR 未设置时这些写法会失败,裁剪出的相对路径也可能与 manifest 键格式不匹配,导致即使存在自定义文件也统计出 CUSTOM_COUNT=0(bug #1997)。正确做法是优先用 gsd-sdk query detect-custom-files,否则用随附的 gsd-tools.cjs detect-custom-files,二者都通过 Node.js path.relative() 可靠解析路径。

流程为:先按 INSTALL_SCOPE 确定 RUNTIME_DIR(LOCAL 用 LOCAL_DIR,GLOBAL 用 GLOBAL_DIR,否则置空并跳过本步);再执行 detect-custom-files 拿到 {custom_files, custom_count} JSON;若 CUSTOM_COUNT > 0,把每个文件复制到 $RUNTIME_DIR/gsd-user-files-backup/(保留相对路径层级,单文件复制失败仅告警不中断):

BACKUP_DIR="$RUNTIME_DIR/gsd-user-files-backup"
mkdir -p "$BACKUP_DIR"

完成后提示用户:

⚠️  Found N custom file(s) inside GSD-managed directories.
    These have been backed up to gsd-user-files-backup/ before the update.
    Restore them after the update if needed.

七、Step 6:执行更新——按安装类型路由并清理更新缓存

安装命令由 Step 1 检测出的目标运行时与安装范围共同决定,RUNTIME_FLAG="--$TARGET_RUNTIME"

安装范围 执行命令
LOCAL npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc "$RUNTIME_FLAG" --local
GLOBAL npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc "$RUNTIME_FLAG" --global
UNKNOWN npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc --claude --global

安装失败则展示错误并退出。成功后进入缓存清理环节——这是很多人忽略但至关重要的一步:SessionStart Hook hooks/gsd-check-update.js 每次会话在后台检查更新并把结果写入缓存,状态栏据此显示 ⬆ /gsd:update 指示器;如果更新后不清理缓存,这个过期的“有新版本”指示会一直赖着不走。

清理策略是宁可多清不可漏清:依次遍历 PREFERRED_CONFIG_DIRCLAUDE_CONFIG_DIRGEMINI_CONFIG_DIR、Kilo 与 OpenCode 的环境变量/XDG 路径、CODEX_HOME 构造的目录集合,删除各目录下 cache/gsd-update-check.json;再兜底清理全部默认运行时目录(.claude.config/opencode.opencode.gemini/antigravity.gemini.config/kilo.kilo.codex)的本地与 $HOME 全局两份缓存。最后还要清理 hook 写入的共享工具无关缓存 ~/.cache/gsd/gsd-update-check.json(#2784):gsd-check-update.js 使用这个跨运行时统一的缓存目录,正是为了避免多运行时解析不一致导致 check 写一处、statusline 读另一处(#1421)。

hooks/gsd-check-update-worker.js 可以看到后台检查的实现:父 hook 以 detached: true 派生 worker 子进程(Windows 上必须 detached 才能正确脱离),通过环境变量传入缓存文件与本地/全局 VERSION 文件路径;worker 用 isNewer() 做三段式 semver 比较(剥离 -beta.1 等预发布后缀防止 Number() 产生 NaN),顺带检查 13 个受管 hook 的版本头(// gsd-hook-version: ... 注释,兼容 JS 与 bash 两种注释风格)以报告 stale hooks,最后通过 npm view get-shit-done-cc version(Windows 下 shell: true 经 cmd.exe 解析 npm.cmd,POSIX 保持无 shell 派生)把 {update_available, installed, latest, checked, stale_hooks} 写入缓存文件。

八、Step 7:结果展示——完成消息与重启提醒

安装完成后输出格式化完成框,由于 changelog 已在确认环节展示,这里不再重复:

╔═══════════════════════════════════════════════════════════╗
║  GSD Updated: v1.5.10 → v1.5.15                           ║
╚═══════════════════════════════════════════════════════════╝

⚠️  Restart your runtime to pick up the new commands.

[View full changelog](https://github.com/gsd-build/get-shit-done/blob/main/CHANGELOG.md)

重启提醒是硬性要求:新命令、新 hook 只有重启运行时才会被加载。仓库内实际的版本发布记录可对照 CHANGELOG.md 中的历次条目,例如 /gsd:update 会始终安装最新包版本、尊重本地/全局安装位置、先展示 changelog 再征询确认,以及状态栏在存在新版本时显示 ⬆ /gsd:update 指示器。

九、Step 8:本地补丁检查与 --reapply 回放

更新完成后,工作流检查配置目录中是否存在 gsd-local-patches/backup-meta.json。若安装器在更新前检测并备份了被本地修改的文件,则提示:

Local patches were backed up before the update.
Run `/gsd:update --reapply` to merge your modifications into the new version.

--reapply 分支对应 get-shit-done/workflows/reapply-patches.md,其核心是三方比较

  • 原始基线(pristine baseline):更新前 GSD 发布版中的原始文件
  • 用户版本:备份在 gsd-local-patches/ 中的修改后文件
  • 新版本:更新后新装的文件

合并规则清晰:仅用户改过的区块 → 采用用户版本;仅上游改过的区块 → 接受上游版本;双方都改 → 标记 CONFLICT 展示双方内容请用户裁决;都没改 → 使用新版本。

基线来源按优先级选择:最可靠的是 backup-meta.json 中记录的 pristine_hashes(文件在更新前 GSD 发布版中的 SHA-256)配合 git 历史定位正确基线提交——直接用 git log --diff-filter=A 找“首次添加该文件”的提交在经历过多次更新循环的仓库里是错误的基线;其次是安装时保存的 gsd-pristine/ 快照目录;两者皆无时降级为强化启发式的两路比较。工作流还设定了不可违背的不变量:凡是进入 gsd-local-patches/ 的文件,必然被安装器的哈希比较判定为已修改,因此“无自定义内容”永远不是合法结论,拿不准时必须归类为 CONFLICT 而非 SKIP。

合并后还有两道验证闸门:5a 是绑定闸门——运行确定性验证脚本 verify-reapply-patches 对应的 get-shit-done/bin/verify-reapply-patches.cjs,结构化对比用户新增行是否真实存在于合并结果,任何缺失即非零退出并阻止后续清理(bug #2969 曾抓到自由文本“verified: yes”表格在未做实际内容检查时反复误报);5b 是咨询闸门——人工复核 Step 4 产出的 Hunk Verification Table,防止脚本回归或基线缺失时 verified: no 的行悄悄漏过。两者构成纵深防御,宁可误报中断(可恢复)也不容忍静默丢失内容(不可恢复)。此外还处理 gsd-pristine/ 快照与 backup-meta.json 哈希不一致的漂移场景(#3657),漂移时必须停下并向用户提供三种解决路径。

十、测试保障:升级链路的行为契约

这条链路的可靠性由专门测试锁定。除 tests/bug-2992-check-latest-version.test.cjs 外,仓库还有 tests/bug-3516-reapply-patches-gsd-update-filter.test.cjs(验证 reapply 时正确过滤 gsd:update 相关提交)、tests/enh-2937-statusline-context-position.test.cjs(状态栏上下文定位)与 tests/feat-2795-update-banner.test.cjs(更新横幅)。结合 hooks/ 目录下 14 个受管 hook 文件(gsd-check-update.jsgsd-statusline.jsgsd-update-banner.jsgsd-session-state.sh 等)与安装器 bin/install.js,可以完整印证:检查是后台异步的、提示是状态栏/横幅的、执行是确认后原子的、回滚是补丁回放的。

结语

GSD 的 /gsd:update 并非一条“装完就走”的简单命令,而是一条精心设计的自我维护闭环:多运行时与自定义目录的精确探测、包名常量化的防注入版本检查、先展示后确认的用户交互、清单外自定义文件的预先抢救、跨运行时缓存清理、以及基于三方比较与双重验证闸门的本地补丁回放。理解这条链路,不仅能让你在升级 GSD 时从容应对各类提示与告警,也为设计“自带安全升级机制”的 CLI 工具提供了可直接借鉴的工程范式——尤其是“LLM 驱动的流程必须用确定性脚本锁住关键常量”和“宁可多次备份也不让用户内容静默丢失”这两条设计取舍。

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

项目优选

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