【免费下载链接】gsd-core
Git. Ship. Done - Core
本文讲解 gsd-core 在多个 git worktree 场景下本地安装(
--local)的一个关键修复:通过--relative-includes(或环境变量GSD_RELATIVE_INCLUDES=1),让安装产物中的@文件引用以项目相对路径写入,而不是把每个 worktree 都绑定到运行安装器的那一个 checkout。读完本文,你将理解该问题的成因、开关的用法与生效边界,以及它背后的路径前缀计算与安全回退机制,可直接在自己的多 worktree 仓库中落地使用。
一、问题背景:本地安装为何默认写入绝对路径@includes
gsd-core 向 Claude Code、OpenCode、Kilo、Codex、Cline 等运行时(runtime)安装命令(commands)、工作流(workflows)与引用(references)等构件。这些 Markdown 构件之间通过@文件引用互相指认,例如:
@.claude/gsd-core/commands/... # 项目相对形式 @/absolute/path/to/checkout/.claude/gsd-core/... # 绝对路径形式本地安装(--local)默认把这类 include 写成安装器在安装时刻解析出的绝对路径。对单个 checkout 的仓库来说,这个行为完全透明、工作正常;问题出现在同一个仓库被 git worktree 多次检出时:
- 每个 git worktree 都有自己的一份
.claude/(或对应 runtime 的本地配置目录); - 但每一份拷贝里的
@includes 都指回运行安装器的那个 checkout; - 结果是:某个 worktree 通过
git rev-parse --show-toplevel正确解析到自己的gsd-tools.cjs(引擎),却从另一个checkout 读取工作流说明文字; - 一旦更新了那一个 checkout,所有其他 worktree 会"用新指令驱动旧引擎",而且没有任何可以分阶段更新的手段。
官方 how-to 文档 install-on-your-runtime.md 的 "Local installs across several git worktrees" 一节,对这一缺陷有完整描述。修复方案即在 gsd-core 中引入--relative-includes开关(issue #4377,随 PR #4425 以type: Fixed合入,见变更集 patient-finches-rest.md)。
二、修复方案:--relative-includes与GSD_RELATIVE_INCLUDES=1
2.1 两种等效的开启方式
该开关是**双源(dual-sourced)**的,命令行 flag 与环境变量二选一即可:
# 方式一:命令行 flag npx @opengsd/gsd-core@latest --claude --local --relative-includes # 方式二:环境变量 GSD_RELATIVE_INCLUDES=1 npx @opengsd/gsd-core@latest --claude --localflag 与环境变量的二选一逻辑实现在安装器入口 bin/install.js:
const hasRelativeIncludes = args.includes('--relative-includes') || process.env.GSD_RELATIVE_INCLUDES === '1'; if (hasRelativeIncludes) process.env.GSD_RELATIVE_INCLUDES = '1';注意这里把结果回写进环境变量——这是刻意的设计:路径前缀的计算分布在安装引擎、两处重写入口、安装计划与applySurface等多个 seam,如果逐个函数传递参数,五个签名很容易失步;改由所有 seam 统一读取同一个环境变量,则不可能出现不一致。这与既有的--portable-hooks/GSD_PORTABLE_HOOKS是同一模式。
2.2 开启后的实际效果
开启后,安装产物中的每个@include 从:
@/absolute/path/to/checkout/.claude/gsd-core/...变为:
@.claude/gsd-core/...每个 worktree 从此只解析自己那一份拷贝,worktrees 之间完全独立,更新任意一个 checkout 都不会影响其他 worktree。
三、底层原理:路径前缀的计算与安全回退
3.1 开关判定:relativeIncludesEnabled
核心实现位于 runtime-artifact-conversion.cts,判定规则严格为环境变量值等于字符串'1':
function relativeIncludesEnabled(env = process.env): boolean { return env.GSD_RELATIVE_INCLUDES === '1'; }该函数作为_relativeIncludesEnabled导出,供单元测试注入自定义环境(runtime-artifact-conversion.cts)。
3.2 前缀决策链:computePathPrefix
路径前缀的计算集中在computePathPrefix(runtime-artifact-conversion.cts),决策顺序如下:
- 全局安装(
isGlobal且目标位于$HOME下、非 OpenCode):一律返回$HOME/...形式。这一形式天然与 checkout 无关,因此全局安装本身就不受本缺陷影响; - 本地安装 + 开启了项目相对模式(
!isGlobal && projectRelative):依次尝试三种推导,取第一个成功者:projectRelativePrefix(projectRelativePath)——显式指定的项目相对路径;projectRelativePrefixFromProjectRoot(projectRoot, resolvedTarget)——从项目根到解析目标的相对路径(runtime-artifact-conversion.cts);projectRelativePrefix(localDirName)——runtime 描述符中的localConfigDir值(如.claude/、.cursor/);
- 其余情况一律回退到绝对形式(
${posixTarget}/),即修复前的默认行为。
源码注释特别强调"fail safe 到绝对前缀":未开启、全局安装、目录名缺失、或configHome.kind === 'none'哨兵值,都落入回退分支。理由是"一个错误但绝对的 include 仍能解析到真实文件;而一个错误的相对 include 会静默地按读取者当时的 cwd 解析",相对形式必须足够可靠才值得采用。
3.3 防御性校验:projectRelativePrefix
projectRelativePrefix(runtime-artifact-conversion.cts)对描述符目录名做了四重防御:
- 空字符串直接拒绝;
- 等于
runtimeNamePolicy.NO_LOCAL_CONFIG_DIR_SENTINEL("无本地配置目录"哨兵)直接拒绝——若放行,会插值出字面量(no-local-config-dir)/这样的路径段; - 绝对路径(
/开头或C:风格盘符)拒绝; - 路径任意一段包含
..都拒绝,而不仅是开头——posixNormalize只归一化分隔符、不解析路径段,所以nested/../../outside这类穿越必须显式拦截,防止相对 include 逃出项目目录。
同时把尾部斜杠归一化(.cursor/与.cursor得到同一个前缀)。前缀还会经过 POSIX 归一化(反斜杠转正斜杠,#1615),因为@引用所在的 Markdown 内容统一使用 POSIX 路径,Windows 上泄漏反斜杠会破坏跨平台校验。
3.4 项目根即目标目录的 runtime:localIncludeDirName
localIncludeDirName(runtime-artifact-conversion.cts)处理另一类特殊情况:若某 runtime 声明localTargetIsProjectRoot === true(即它的本地 agent 直接装在项目根的agents/,而不是.cline/agents/之类的描述符目录),则该 runtime没有可用的项目相对描述符目录,函数返回undefined,调用方回退到绝对前缀——否则会写出指向一个从未被安装创建过的目录的相对 include。
四、使用边界与注意事项
官方文档(install-on-your-runtime.md)明确列出以下边界,务必在实际使用前确认:
- opt-in,且保持 opt-in:绝对 include 对单 checkout 用户完全可用,若把相对形式改成默认,等于为了解决大多数用户不存在的问题而改变每一个既有本地安装。需要它的人(多 worktree 用户)知道自己需要,显式打开即可;
--relative-includes搭配--global无效:该 flag 只在本地安装时被读取;- 全局安装不受影响:继续保持
$HOME相对形式,本来就与 checkout 无关; - runtime 启动器(launcher)保持绝对 fallback:定位
gsd-tools.cjs的 shell 片段探测${CLAUDE_CONFIG_DIR:-$HOME/.claude}及每个 runtime 的默认值,这些是shell 词展开而非@include,相对值会在 shell 当前目录下解析而非项目目录。启动器已优先探测$(git rev-parse --show-toplevel)/.claude,因此在触及那些绝对默认值之前就能找到当前 worktree。端到端测试专门固化了这一行为:即使传了--relative-includes,启动器 shim 中的绝对 shell 默认值也不得被改写(install.test.cjs)。
五、测试验证:从 flag 到落盘字节的端到端证明
该修复配有完整的测试矩阵,覆盖纯函数层与整机安装层:
5.1 端到端安装测试
tests/install.test.cjs 的#4377: --relative-includes emits project-relative @ includes for a local install描述块,通过两次真实安装(一次默认、一次带 flag)验证:
- 对照组:默认本地安装仍然烘焙绝对 checkout 路径(防止某次改动把绝对形式整体破坏却依然通过全部断言);
- 开启组:安装树中没有任何文件引用它被安装来源的 checkout 路径;
- 不只是"缺席":安装产物中存在
@.claude/gsd-core/形式 include——若实现只是剥掉前缀,一样能通过"无 checkout 引用"断言,但 include 会指向不存在的位置; - manifest 持久化:
.claude/gsd-file-manifest.json中写入relativeIncludePrefix: '.claude/',供后续surface应用阶段复用同一相对风格(install.test.cjs); - 项目根 runtime 回退:对 Cline(
localTargetIsProjectRoot)做真实安装,断言其 agent 中没有@.cline/gsd-core/形式 include,而存在安全的绝对前缀回退(install.test.cjs)。
测试还会从子进程环境里删除GSD_RELATIVE_INCLUDES(install.test.cjs),防止环境变量把对照组"静默打开"、令整套测试失去证明力。
5.2 单元测试:前缀纯函数
tests/install-runtime-artifacts.test.cjs 直接对_computePathPrefix、_projectRelativePrefix、_relativeIncludesEnabled做逐臂断言:
projectRelative: true, localDirName: '.cursor'→'.cursor/';projectRelative: false→ 绝对形式;- OpenCode 的
.config/opencode目标、--config-dir自定义目录(.custom/claude)均能推导出正确相对前缀; - 描述符含反斜杠(
.claude\nested)与尾部斜杠(.cursor/)都归一化; - 注入
GSD_RELATIVE_INCLUDES: '1'时开关为真,其余值均为假; - 含
..的任何路径段一律拒绝(返回空、回退绝对)。
此外,仓库的 shipped 文档引用一致性检查(shipped-reference-cites.test.cjs)与契约漂移检查(check-contract-drift.cjs)都会追踪--relative-includes这一被发布文档承诺的拼写,防止该能力从文档与实现之间漂移。
六、快速上手小结
在多 worktree 仓库中让每个 worktree 的 gsd-core 本地安装彼此独立:
# 对 Claude Code 做本地安装并开启项目相对 includes npx @opengsd/gsd-core@latest --claude --local --relative-includes验证方式:检查安装后的.claude/下构件内容,include 应以@.claude/gsd-core/开头,且整棵树不含安装来源 checkout 的绝对路径。若需手动转换(无 Node.js 环境),各 runtime 的前置字段变换细节参见 USER-GUIDE.md 的 "Manual install / no-Node.js setup" 一节,其完整链路指向安装器的convert*Frontmatter系列函数。
相关资源:
- 修复变更集:.changeset/patient-finches-rest.md(
type: Fixed,PR #4425,issue #4377) - 官方使用说明:docs/how-to/install-on-your-runtime.md
- 前缀计算实现:src/runtime-artifact-conversion.cts
- 安装器 flag 解析:bin/install.js
- 端到端测试:tests/install.test.cjs;单元测试:tests/install-runtime-artifacts.test.cjs
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
Gemini CLI Git Worktree 实战:为每个并行会话分配独立代码副本
Gemini CLI Git Worktree 实战:为每个并行会话分配独立代码副本 Gemini CLI 提供实验性的 Git Worktree 支持,让你在
人工智能AI Agent交互助手CLIMCP Clientsloop-worktree 实战指南:用 Git Worktree 为每个 AI 修复尝试建立隔离沙箱与多 Loop 防碰撞锁
loop worktree 实战指南:用 Git Worktree 为每个 AI 修复尝试建立隔离沙箱与多 Loop 防碰撞锁 导读 loop worktree
人工智能AI AgentAgent 工作流CLI研发协作AI 技能MCP 服务为什么Agent Orchestrator的每个Worker都要独立git worktree:多Agent并行开发不再撞车
为什么Agent Orchestrator的每个Worker都要独立git worktree:多Agent并行开发不再撞车 ! Agent Orchestrat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考