ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

gsd-core 可选更新横幅(Update Banner Opt-In)机制:为不使用 statusline 的用户提供 `SessionStart` 更新提醒

2026/9/28 7:04:19 拓冰建站 浏览量
gsd-core 可选更新横幅(Update Banner Opt-In)机制:为不使用 statusline 的用户提供 `SessionStart` 更新提醒 【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载导读本文围绕 gsd-core 的「可选更新横幅update banner opt-in」特性展开讲解 GSD 安装器如何为拒绝或未安装 GSD statusline 的用户提供一个可选的SessionStart钩子使其在会话启动时借助已有的~/.cache/gsd/gsd-update-check.json缓存静默地获知新版本可用。读完本文你将掌握该特性的触发条件、缓存数据契约、横幅输出与限流逻辑、以及与gsd-statusline.js的职责划分并能基于源码定位对应实现与测试理解其「优雅降级、绝不打断会话启动」的设计原则。该特性由 hooks/gsd-update-banner.js 实现对应的变更记录见 .changeset/archived/update-banner-opt-in.md。一、特性背景为什么需要「可选」更新横幅GSDGit. Ship. Done在安装时会注册一组托管钩子managed hooks其中 hooks/gsd-statusline.js 负责在会话中渲染状态栏statusline并在其中内嵌更新提示⬆ /gsd:update与陈旧钩子警告见该文件中约第 930 行起对缓存的读取与evaluateUpdateCache的调用。也就是说安装了 statusline 的用户不需要额外的更新提醒机制——状态栏本身就会消费更新缓存。但有一类用户会在安装器询问时拒绝安装或保留非 GSD 的 statusline。对这类用户而言更新信息没有呈现入口。于是本特性提供了一条**可选的opt-in**补充通道当安装器检测到用户拒绝/保留非 GSD statusline 时会额外注册一个SessionStart横幅钩子通过systemMessage信封在会话启动时呈现更新可用信息。从 hooks/gsd-update-banner.js 头部注释可以确认三条关键设计约束注册即 Opt-inbin/install.js仅在用户拒绝安装或替换GSD statusline 时注册该钩子「SessionStart条目的存在本身就是 opt-in」不存在独立的运行时开关复用已有缓存横幅不自己联网检查而是读取gsd-check-update-worker.js写入的~/.cache/gsd/下缓存文件per-package 命名以 issue #2795 为设计依据保证更新提示在多种运行时Claude、OpenCode、Kilo、Gemini 等下的一致呈现。二、数据来源更新检查缓存update-check cache契约横幅本身是「只读消费者」真正的更新探测由后台 worker 完成。整条链路为SessionStart 主钩子→ 派生子进程 worker → 写缓存文件 →gsd-update-banner.js或 statusline读取2.1 后台检查gsd-check-update.jshooks/gsd-check-update.js 是标准SessionStart钩子每会话运行一次其职责通过detectConfigDir探测运行时配置目录依次支持CLAUDE_CONFIG_DIR环境变量覆盖以及.claude、.gemini、.config/kilo、.kilo、.config/opencode、.opencode等目录计算项目级/全局级VERSION文件路径gsd-core/VERSION项目优先、全局兜底统一将缓存目录收敛到~/.cache/gsd/os.homedir() .cache/gsd该目录在写入前用fs.mkdirSync(..., { recursive: true })确保存在通过spawn(process.execPath, [workerPath], { stdio: ignore, detached: true, windowsHide: true })派生出独立的后台 worker并通过环境变量GSD_CACHE_FILE、GSD_PROJECT_VERSION_FILE、GSD_GLOBAL_VERSION_FILE传递路径最后child.unref()分离子进程。之所以拆成独立 worker 文件而非node -e inline code源码注释明确说明是为了避免模板字符串正则转义问题并让 worker 可以独立测试。2.2 写入端gsd-check-update-worker.jshooks/gsd-check-update-worker.js 在后台完成版本比对并写缓存其产出结果对象为const result { update_available: latest isSemverNewer(latest, installed), installed, latest: latest || unknown, checked: Math.floor(Date.now() / 1000), stale_hooks: staleHooks.length 0 ? staleHooks : undefined, package_name: PACKAGE_NAME, };各字段含义字段说明update_available布尔值latest存在且isSemverNewer(latest, installed)为真时为trueinstalled当前安装版本从项目级/全局级VERSION文件读取项目优先缺省为0.0.0latest通过checkLatestVersion()查询到的最新版本非ok结果下为unknownchecked检查发生的 Unix 时间戳秒stale_hooks陈旧托管钩子数组比对每个钩子文件头部的gsd-hook-version注释与已安装版本无则为undefinedpackage_name包身份标识来自包身份缝Package Identity seam用于下游的「血统守卫」值得注意的实现细节版本来源worker 依次读取项目级与全局级VERSION文件项目目录优先本地安装优先于全局安装。陈旧钩子检测仅扫描MANAGED_HOOKS清单来自 hooks/managed-hooks-registry.cjs中列出的文件被移除特性的孤儿文件如gsd-intel-*.js不会列入以免产生永久性陈旧警告#1750。gsd-update-banner.js正是该清单中的第 42 项。原子写入为避免与并发读取者statusline、横幅、多个运行时 worker发生撕裂读worker 采用「同目录临时文件 renameSync」的原子发布策略#4091先写cacheFile .tmp- process.pid再renameSync到位失败时尽力清理临时文件。血统字段package_name由打包的 Package Identity 缝提供而不是从../package.json向上查找后者在已安装树中解析不到名字#2544/#378。2.3 缓存文件命名缓存文件名由包身份决定。updateCacheFileName由package-identity.cjs提供从 tests/gsd-statusline.test.cjs第 2343–2345 行可以确认PACKAGE_NAME为opengsd/gsd-coreupdateCacheFileName为gsd-update-check-opengsd-gsd-core.jsonper-package 命名避免多运行时/多包冲突。也就是说横幅实际读取的路径为~/.cache/gsd/gsd-update-check-opengsd-gsd-core.json与变更记录中的~/.cache/gsd/gsd-update-check.json是「通用回退名 包专属名」的关系——当运行库缺失时退回到通用文件名见下文降级设计。三、横幅实现gsd-update-banner.js 的核心逻辑hooks/gsd-update-banner.js 是可选的SessionStart钩子其运行流程可概括为三个纯函数 一个主入口readCache(cacheFile)—— 读取并解析缓存buildBannerOutput(state)—— 依据缓存状态决策「输出 or 静默」shouldSuppressFailureWarning(sentinelFile, nowSeconds)/recordFailureWarning(...)—— 失败诊断的 24h 限流main()—— 组装以上逻辑向stdout输出 JSON 信封。3.1 输出契约systemMessage信封横幅的输出格式与 statusline 的更新段保持一致的 JSON 信封{ systemMessage: GSD update available: 1.2.0 → 1.3.0. Run /gsd:update. }其中版本号取自缓存的installed/latest缺失时显示unknown。buildBannerOutput是纯函数无 I/O返回null表示「本会话静默、什么都不输出」。3.2 决策矩阵buildBannerOutput(state)的判定顺序如下缓存状态结果缓存文件存在但JSON.parse失败parseError true且未在限流窗口内输出{ systemMessage: GSD update check failed. }同上但处于限流窗口suppressFailureWarning true返回null静默缓存缺失/不可读cache null返回null静默缓存存在但package_name缺失或与PACKAGE_NAME不一致血统守卫失败返回null视为不可信缓存update_available不为真返回null静默以上均通过输出更新可用横幅要点解读更新可用时才有横幅update_available: false时完全静默符合「更新已是最新时不打扰」的定位血统守卫lineage guardpackage_name必须存在且与当前包一致!cache.package_name || cache.package_name ! PACKAGE_NAME即拒绝。缺失package_name的缓存被视为「早于血统追踪的旧记录」一律不可信——该逻辑与 statusline 中evaluateUpdateCache的守卫完全一致解析失败限流只有「文件存在但 JSON 损坏」这一类错误才会触发一次性诊断横幅且同一错误 24 小时内最多提示一次RATE_LIMIT_SECONDS 24 * 60 * 60避免坏缓存文件在每个会话都打扰用户。3.3 失败限流的实现限流通过哨兵文件~/.cache/gsd/banner-failure-warned-at实现shouldSuppressFailureWarning(sentinelFile, nowSeconds)读取哨兵文件中的时间戳若nowSeconds - last RATE_LIMIT_SECONDS则返回true文件缺失或内容非有限数字时返回false放行提示当parseError且未限流时main()先以recursive: true确保缓存目录存在覆盖「目录被清空」的首次运行场景再写入当前时间戳recordFailureWarning尽力而为失败仅意味着下个会话重新提示。四、优雅降级构建失败绝不打断会话启动横幅是「可选」的SessionStart钩子因此它对失败的态度是降级而非崩溃#3582。具体表现为gsd-update-banner.js顶部对运行库采用try/require/ensureRuntimeBuild/require/catch形状gsd-core/bin/lib/package-identity.cjs是 tsc 构建产物ADR-457在未执行npm run build:lib的裸插件市场/git-clone 安装中不存在此时PACKAGE_NAME保持nullupdateCacheFileName保持通用回退名gsd-update-check.json由于PACKAGE_NAME为nullbuildBannerOutput的血统守卫!cache.package_name || cache.package_name ! PACKAGE_NAME恒为真缓存永远被判定为不可信main()自然落入「什么都不打印」的静默路径——无需单独的分支代码读取、限流、写哨兵等环节均有 try/catch 兜底任何 I/O 失败都只影响「本会话是否提示」不会让会话启动失败。同样的降级形状也独立存在于gsd-check-update.js与gsd-check-update-worker.js中。源码注释说明这一重复是刻意的脚本 scripts/lint-hooks-runtime-build-seam.cjs 按文件逐一要求ensure-runtime-build.cjs的require/调用与编译产物require字面量同时出现在该文件自身中提取公共 helper 会让字面量 require 移出文件破坏该 lint 的文本扫描。五、与 statusline 的职责划分同一份缓存有两个消费端判定逻辑保持一致维度gsd-statusline.jsgsd-update-banner.js消费入口状态栏渲染会话持续可见可选的SessionStart一次性横幅决策函数evaluateUpdateCache(cache)buildBannerOutput(state)血统守卫package_name缺失或与PACKAGE_NAME不一致 → 视为无缓存同上返回null更新提示状态栏内嵌⬆ /gsd:update与⚠ stale hooks段systemMessage: GSD update available: X → Y. Run /gsd:update.使用前提已安装 GSD statusline用户拒绝/保留非 GSD statusline由安装器注册两者共享同一个 per-package 缓存文件~/.cache/gsd/gsd-update-check-opengsd-gsd-core.json因此不会出现「检查写入一个位置、读取另一个位置」的多运行时错位问题#607/#1421。六、测试验证与源码佐证仓库对buildBannerOutput的行为有直接的单测覆盖位于 tests/gsd-statusline.test.cjs第 2361–2409 行「buildBannerOutput lineage guard」分组外来血统被拒绝缓存package_name为get-shit-done-cc时返回nullforeign lineage must be rejected同源血统输出横幅package_name为opengsd/gsd-core且update_available: true时返回信封且systemMessage同时包含installed、latest与/gsd:update缺失血统视为不可信缓存无package_name字段时返回null。同一测试文件中还断言了包身份常量PACKAGE_NAME opengsd/gsd-core、updateCacheFileName gsd-update-check-opengsd-gsd-core.json。此外hooks/managed-hooks-registry.cjs 将gsd-update-banner.js列入托管钩子清单第 42 项worker 会将其纳入陈旧钩子扫描保证升级后横幅脚本自身也能被识别为待更新对象。七、适用前提与使用说明本特性是可选注册的其生效链路为运行安装器如npx get-shit-done-cc等时在 statusline 询问处选择拒绝安装/保留非 GSD statusline安装器因此注册gsd-update-banner.js为SessionStart钩子SessionStart条目的存在即 opt-in每会话由gsd-check-update.js派生子进程 worker 检查更新并写缓存横幅钩子读取缓存仅在「有更新」或「缓存损坏且 24h 未提示过」时输出systemMessage信封其余情况静默卸载时通过npx get-shit-done-cc --uninstall干净移除该钩子随托管钩子一并清理。需要说明的边界若已安装 GSD statusline则无需本横幅——状态栏已内嵌更新提示横幅不会重复注册横幅不主动联网只消费 worker 写入的缓存缓存缺失、损坏、血统不符或已是最新版本时均静默源码注释强调「SessionStart条目的存在本身就是 opt-in没有单独的运行时开关」因此不要试图通过环境变量或配置文件单独开关横幅——其开关完全由安装时的选择决定本文所描述的文件路径~/.cache/gsd/、哨兵文件、缓存文件名以当前仓库实现为准若想深入阅读可继续查看 hooks/gsd-update-banner.js、hooks/gsd-check-update.js、hooks/gsd-check-update-worker.js 与 tests/gsd-statusline.test.cjs。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐get-shit-done 不装默认 statusline 时如何启用 gsd-update-banner 更新提示get shit done 不装默认 statusline 时如何启用 gsd update banner 更新提示 在 get shit doneGSD中人工智能AI 应用提示工程开发工具工作流自动化AI AgentGitHub_Trending/re/review-prompts高级调试技巧解决复杂的代码问题GitHub_Trending/re/review prompts高级调试技巧解决复杂的代码问题 GitHub_Trending/re/review promFIFA 23实时编辑器完全指南新手快速掌握游戏修改技巧FIFA 23实时编辑器完全指南新手快速掌握游戏修改技巧 FIFA 23 Live Editor是一款功能强大的游戏实时编辑工具专为FIFA 23玩家设计游戏开发上一篇DeepChat窗口状态持久化electron-window-state实现原理下一篇GitHub_Trending/re/resources-learning-spring消息队列Spring集成消息中间件资源创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考