首页
/ get-shit-done 自更新链路加固:用 `--package=` 与 `--` 分隔符根治 npx 缓存陈旧与 Bash 工具令牌误路由问题

get-shit-done 自更新链路加固:用 `--package=` 与 `--` 分隔符根治 npx 缓存陈旧与 Bash 工具令牌误路由问题

2026-09-07 14:24:10作者:齐添朝

get-shit-done(GSD)通过 /gsd:update 命令与 update.md 工作流完成自更新,其核心动作是调用 npx 拉取并执行 npm 包 get-shit-done-cc。历史上裸写 npx -y get-shit-done-cc@latest 存在两类失效:一类是 npx 命中本地缓存、拉回的并非 @latest 而是一个更旧的版本;另一类是 Bash 工具包装层错误路由 @ 令牌,报出 Unknown command: "get-shit-done-cc@latest"。本文以修复记录 .changeset/fix-3130-update-npx-robust.md 为主体,结合仓库中的更新工作流、回归测试与安装器实现,完整剖析这次加固的技术细节。读完你既能理解 npx -y --package=<pkg>@latest -- <bin> 这个鲁棒调用形态为何有效,也能在自己的 Agent 自更新或“npx 执行一次性命令”场景中复用它。

一次“缺陷修复型 Changeset”究竟在讲什么

.changeset 目录存放的是按 PR 拆分、发布时统一合并进 CHANGELOG 的碎片(见 .changeset/README.md)。每个碎片头部包含 typepr frontmatter,正文一句话描述面向用户的可见变化。本文要讲的碎片即 fix-3130-update-npx-robust.md

  • type: Fixed:这是一次缺陷修复;
  • pr: 3130:对应 GitHub issue/PR #3130;
  • 正文点明:update.md 中的 npx 调用被加固,以规避**缓存陈旧(cache-stale)**与 Bash 工具令牌路由失败两类故障;三个同源调用(local、global、unknown/fallback)统一改为 npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc

这个碎片虽然只有寥寥几句,但它浓缩了一个真实的自更新可靠性问题:当“更新命令本身被写进一个由 Agent 驱动的文档工作流,并经由 Bash 工具执行”时,命令的写法必须同时对抗包管理器的缓存行为工具链的参数路由规则。下面逐层展开。

上下文:GSD 是如何“更新自己”的

在深入命令形态之前,先锚定这段命令在整条链路中的位置。

  • 用户或状态行触发斜杠命令 /gsd:update,其命令定义为 commands/gsd/update.md,负责检测本地/全局安装、比对 npm 最新版、拉取并展示 changelog、请求用户确认、执行更新并清缓存、提醒重启。
  • 命令主体路由到执行上下文 get-shit-done/workflows/update.md。该工作流是一份高度结构化、给 Agent 逐步执行的 Markdown 过程文档,包含 get_installed_versioncheck_latest_versioncompare_versionsshow_changes_and_confirmbackup_custom_filesrun_updatedisplay_resultcheck_local_patches 等步骤。
  • run_update 步骤真正执行安装。npm 包名与可执行文件来自根 package.json:包名为 get-shit-done-cc,其 bin 字段把同名命令指向 bin/install.js——也就是说 npx get-shit-done-cc <flags> 的本质是临时拉包后运行安装器,由 bin/install.js 根据 --global/--local--claude/--codex/... 等参数把文件安装进对应运行时的配置目录。

从源码结构可以推断,这是一条“Agent 读了文档 → 由 Bash 工具执行 npx → npx 跑安装器写文件”的链式调用,任何一环对参数的解析与裸写命令假设不一致,整次更新就会失败——这正是 #3130 要解决的问题。

裸 npx 调用的两个失效模式

回归测试 tests/bug-3130-update-npx-robust-invocation.test.cjs 的头部注释把旧形态 npx -y get-shit-done-cc@latest 的两个故障写得非常直白:

模式 1:缓存陈旧(cache-stale)。 裸写的 npx -y get-shit-done-cc@latest 会命中 npx 的本地缓存,可能返回一个比 @latest 更旧的版本。也就是说,用户以为在升级,实际 npx 悄悄给了缓存里那份老包,导致更新“静默失效”——版本号没变、changelog 没有、甚至可能与安装器检测到的已装版本比较后误判“已是最新”。

模式 2:令牌路由(token-routing)。 get-shit-done-cc@latest 里带 @ 的 specifier 在经由某些 Bash 工具包装层执行时会被错误路由,npm 收到的是 get-shit-done-cc@latest 这个字符串本身,于是报出:

Unknown command: "get-shit-done-cc@latest"

更新流程在这一步直接中断。注意这类错误发生在 npm 解析参数之前——是调用链把“应该作为安装目标包名的 specifier”与“可执行的命令名”混为一谈造成的。

加固形态:npx -y --package=...@latest -- <bin>

修复后的统一形态是:

npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc

两条关键语法各自的职责(依据 fix-3130-update-npx-robust.md 与测试注释):

  • --package=get-shit-done-cc@latest 强制走全新 registry 拉取。它把“要安装的包”显式声明为 --package= 参数,绕过 npx 的本地缓存命中,从而解决“拿不到 @latest”的缓存陈旧问题。-y 保持自动确认,避免交互阻塞 Agent 驱动的执行。
  • -- 分隔符明确划分“npx 自身参数”与“要运行的命令及其参数”-- 之后的第一段 get-shit-done-cc 是待执行命令、随后的内容(如 --global)才是传给该命令的参数。这样 @ 令牌就不会再被上游包装层当作“命令名的一部分”错误路由,Unknown command 不再出现。

写作提醒:上述关于 --package= 绕过缓存、-- 防止令牌误路由的机制说明,来自项目文档与测试注释的表述,引用时以此为准;若要在自己的脚本中依赖这些行为,建议实测你的 npx 版本与调用链。

三个“兄弟调用”:local / global / unknown 全覆盖

get-shit-done/workflows/update.mdrun_update 步骤中,工作流先根据第一步 get_installed_version 探测到的安装范围(INSTALL_SCOPE)拼出 RUNTIME_FLAG,再分别落到三种调用。三种情况统一替换为加固形态,这正是碎片所说的“all three sibling invocations”。

本地安装(--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

未知安装(fallback)——没探测到任何安装,退回 Claude Code 全局安装:

npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc --claude --global

(上述命令原文分别位于 update.md 第 520-541 行 附近。)

$RUNTIME_FLAGTARGET_RUNTIME 拼出(--$TARGET_RUNTIME),而运行时取值在 get_installed_version 步骤中推导,覆盖 claudeopencodegeminikilocodex 等;环境变量优先级(如 CODEX_HOMEGEMINI_CONFIG_DIROPENCODE_CONFIG_DIR)也各有专门处理。安装完成后,工作流还会跨 preferred/env/默认目录清理 cache/gsd-update-check.json 以及共享缓存 ~/.cache/gsd/gsd-update-check.json,避免状态行残留“⬆ /gsd:update”的陈旧提示。

补充一条手动的逃生通道:当版本检查失败(如 npm 暂不可用)时,工作流直接建议用户手动执行 npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc --global(见 update.md 第 329 行)——同一加固形态也用于人类手动升级路径。若完全不依赖 npm(发布中断或直接基于源码仓库开发),则可参考 docs/manual-update.md,用 git pull --rebase && node scripts/build-hooks.js && node bin/install.js --claude --global 的方式绕过 npx。

确定性约束:为什么包名不能“让 Agent 现场编”

加固 npx 调用并非孤立的语法修补,它与相邻的可靠性设计相互配合。check_latest_version 步骤明确规定不要直接运行 npm viewnpm search,而是调用确定性脚本 get-shit-done/bin/check-latest-version.cjs

该脚本背后的动机(#2992)与 #3130 高度同源:当“npm 查询目标包名”由 LLM 在运行时自由发挥时,模型可能拼出 @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: bool, version: string, reason: string, detail?: string }reason 使用受控枚举(ok / fail_npm_failed / fail_invalid_output),版本需通过语义化版本正则校验,并内置 15 秒超时上限,防止 registry 挂起阻塞 /gsd:update。可以看出,GSD 的自我更新设计遵循一条清晰原则:包名、命令形态这类“会影响对哪个包动手”的决策一律固化进代码/文档常量,不让 Agent 在执行时自由发挥;#3130 是把这一原则从“包名查询”延伸到了“安装调用参数路由”。

回归防线:测试如何“守住”文档形态

加固不能只靠一次改稿,仓库为此在 tests/bug-3130-update-npx-robust-invocation.test.cjs 中建立了两条文本级回归守卫,直接把工作流文档当成被测契约:

  1. 断言不存在旧形态:用 npx -y get-shit-done-cc@latest[^\n]* 扫描 update.md,匹配到任何裸写形式即失败——“一旦有人回退成 cache-stale 形态,CI 立刻报警”。
  2. 断言新形态至少出现 3 次:用 npx -y --package=get-shit-done-cc@latest -- get-shit-done-cc 统计加固形态出现次数,要求 >= 3,正好对应 local、global、unknown 三个兄弟调用,防止后续维护时漏改某个分支。

测试注释还特意说明这是“允许的文档结构契约测试”——项目对“测试直接 grep 源码”通常设限(见仓库测试目录中大量 lint-no-source-grep* 规则),但对这类“产品工作流文档的调用形态”保持白名单放行,因为这里文档本身就是运行时被 Agent 执行的指令。这一对测试构成了典型的“防回退护栏”:语法修复 + 两条文本断言 + changeset 记录,共同保证 #3130 的修复不会在未来的重构里悄悄消失。

可迁移的工程结论

从这次修复能沉淀出几条不依赖具体项目的通用经验:

  1. --package= 显式声明要安装的包,别靠“把 specifier 当命令的一部分”的裸写npx pkg@latest 形态在交互终端里顺手,但在“缓存可能陈旧 + 命令经包装层路由”的环境中,npx -y --package=pkg@latest -- bin 更能保证拉取的是真 @latest、执行的是真 bin
  2. -- 是参数路由的“防火墙”。凡是“npx 选项之后还有可执行命令 + 传给它的参数”,都应使用 -- 分隔,避免上游工具把带 @- 等特殊字符的令牌误当作命令名。
  3. 凡是“影响对哪个目标动手”的常量,都要固化成代码/文档约束(包名、命令形态),并结合结构断言测试防止回归——这是从 #2992(包名确定性)与 #3130(命令形态确定性)中一致体现出来的设计取向。
  4. 多个分支调用同步改造。同一条更新逻辑往往有 local/global/fallback 等多个出口,修复时逐一核对,并让测试断言“分支数量级”的覆盖,避免只修了最常见的路径。

关键文件索引

角色 相对路径
本次修复的 changelog 碎片 .changeset/fix-3130-update-npx-robust.md
更新工作流(三条加固命令所在) get-shit-done/workflows/update.md
/gsd:update 斜杠命令 commands/gsd/update.md
回归守卫测试 tests/bug-3130-update-npx-robust-invocation.test.cjs
确定性版本检查脚本 get-shit-done/bin/check-latest-version.cjs
安装器(npm bin 目标) bin/install.js
包名与 bin 映射 package.json
不依赖 npm 的手动更新流程 docs/manual-update.md
changeset 碎片约定 .changeset/README.md
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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