
【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载本文围绕 gsd-core 归档变更片段 fix-3056-worktree-path-assertion.md 展开它记录了 PR #3117 对 worktree prune 回归测试的一项关键加固——测试不再直接比较原始路径字符串而是解析git worktree list --porcelain的结构化输出并对规范化后的路径做断言从而消除跨平台路径分隔符/与\引发的误报同时保持“非破坏性 prune”的安全语义。读完本文你将理解 porcelain 输出的解析方法、gsd-core 的路径规范化工具链、prune 的“只清元数据、不删工作区目录”保证以及这些机制如何被真实 git 仓库测试与健康诊断规则W017/W020/W027共同验证。背景prune 回归测试为何会因路径分隔符误报git worktree的元数据 prune 是 gsd-core 工作树安全策略src/worktree-safety.cts的核心操作之一它会清理已被删除工作区目录留下的“幽灵”管理条目。为了守护这一行为回归测试需要在删除目录之后断言git worktree list不再列出该路径。问题出在“列出”与“断言”两个环节之间的路径表示差异git worktree list --porcelain输出的是 git 自己规范化过的路径在 Windows 上为/分隔或带盘符的绝对路径测试夹具fixture用path.join/path.resolve构造路径在 POSIX 上得到/repo/wt-x在 Windows 上则得到C:\repo\wt-x若测试拿原始字符串直接做set.has(path)或比较两种分隔符表示同一个目录却无法匹配就会产生跨平台误报path-separator false negatives——修复本身是正确的但断言在 Windows 上失败测试便不可移植。PR #3117 的修复正是针对这一点让 pruning safety 测试“parsegit worktree list --porcelain并断言结构化、规范化后的路径”而不是比较原始字符串。修复落点--porcelain的结构化解析git worktree list --porcelain是 git 面向脚本的稳定输出格式每条记录是一个以空行分隔的“块”每块以worktree path开头随后是HEAD sha、branch refs/heads/name、可选的detached以及从较新 git 版本起表示 git 判定该条目可 prune 的prunable [reason]行。gsd-core 的解析器位于 src/worktree-safety.cts 的parseWorktreeEntriesfunction parseWorktreeEntries(porcelain: string): WorktreeEntry[] { const entries: WorktreeEntry[] []; const blocks String(porcelain || ).split(\n\n).filter(Boolean); for (const block of blocks) { const lines block.split(\n); const worktreeLine lines.find((l) l.startsWith(worktree )); if (!worktreeLine) continue; const worktreePath worktreeLine.slice(worktree .length).trim(); if (!worktreePath) continue; const branchLine lines.find((l) l.startsWith(branch refs/heads/)); const branch branchLine ? branchLine.slice(branch refs/heads/.length).trim() : null; // prunable 行裸 prunable 或带原因保留原因文本以便运维 const prunableLine lines.find((l) l prunable || l.startsWith(prunable )); const prunable prunableLine ? (prunableLine.slice(prunable.length).trim() || prunable) : null; entries.push({ path: worktreePath, branch, prunable }); } return entries; }要点按空行切块、按worktree前缀取路径不依赖行号或平台分隔符假设对detached条目保留branch: null而parseWorktreePorcelain同文件 L82-L87会过滤掉branch null的条目只返回带分支的条目供 prune 决策使用从源码结构看prunable字段把 git 的“该条目已失效”判定作为事实记录含原因文本但注释明确说明它不是删除测试——目录被移除 vs 不可读如父目录权限 000最终由statSync的 errno 判定。回归测试对解析器的直接验证在 tests/worktree-safety.test.cjstest(skips detached HEAD entries, () { const porcelain [ worktree /repo/main, HEAD deadbeef, branch refs/heads/main, , worktree /repo/wt-detached, HEAD cafe1234, detached, , worktree /repo/wt-feature, HEAD f00dbabe, branch refs/heads/feature-x, , ].join(\n); const parsed parseWorktreePorcelain(porcelain); assert.deepStrictEqual(parsed, [ { path: /repo/main, branch: main }, { path: /repo/wt-feature, branch: feature-x }, ]); });这正是 changeset 中所说的“assert structured normalized paths”测试断言的是结构化对象{ path, branch }而不是脆弱的字符串拼接。路径规范化工具链从posixNormalize到toComparablePathKey“规范化路径”并非单一动作gsd-core 在 src/shell-command-projection.cts 中集中了三个互补的工具供 worktree 模块与健康诊断规则共享工具位置行为posixNormalize(p)src/shell-command-projection.cts#L55-L57无条件把全部反斜杠替换为正斜杠与运行平台无关。用于把路径投送到 POSIX/bash 目标比如在 Linux runner 上生成 Windows 配置或解析分隔符不可预测的输入toPosixPath(p)src/shell-command-projection.cts#L31-L33按当前运行平台的path.sep切分再以/连接用于本机文件系统路径toComparablePathKey(p, platform)src/shell-command-projection.cts#L69-L72先path.resolve再posixNormalize去掉尾部/仅当 platform 为win32时折叠大小写不区分大小写的文件系统POSIX 保持大小写敏感不同大小写是不同目录worktree-safety.cts在创建 worktree 时就用posixNormalize规整返回给调用方的路径src/worktree-safety.cts#L2202 的const normalizedPath posixNormalize(worktreePath);保证executeWorktreeCreatePlan的worktree_path与cwd永不包含反斜杠。对应测试在 tests/worktree-safety.test.cjs#L1639-L1649test(Windows path: a worktreePath with backslashes is posix-normalized in the result (worktree_path and cwd), () { const winFields { ...okFields, worktreePath: C:\\repo\\.claude\\worktrees\\agent-a1 }; const result executeWorktreeCreatePlan(plan, C:\\repo\\main, { execGit }); assert.equal(result.cwd, C:/repo/.claude/worktrees/agent-a1); assert.equal(result.worktree_path, C:/repo/.claude/worktrees/agent-a1); assert.ok(!result.cwd.includes(\\), cwd must contain no backslashes); });同一工具链也被 health 诊断规则用于“活动 worktree 排除”比较isActiveWorktreePathsrc/health-diagnostic-rules/worktree-health.cts#L158-L166通过toComparablePathKey同时规整process.cwd()派生路径与git worktree list派生路径避免“把当前活动 worktree 误判为 stale”的跨平台误报issue #3663。该函数的用例矩阵覆盖 win32 大小写折叠、POSIX 大小写严格、分隔符/尾斜杠归一化、段边界wt-alpha不得前缀匹配wt-alphabeta等见 tests/health-diagnostic-rules/worktree-health.test.cjs。真实 git 仓库测试非破坏性 prune 保证的实证归档 changeset 特别强调修复“preserving non-destructive prune guarantees”。在 gsd-core 中这一保证的双重落点是决策层planWorktreePrune在默认情况下allowDestructive: false永远只生成action: metadata_prune_only计划且区分no_worktrees/worktrees_present/parse_failed三种 reason解析失败与“确实为空”绝不混同见 src/worktree-safety.cts#L268-L304 及对应测试 tests/worktree-safety.test.cjs#L400-L497。执行层executeWorktreePrunePlan对metadata_prune_only只调用一次git worktree prune不执行任何git worktree removesrc/worktree-safety.cts#L314-L353测试精确断言了这次调用assert.deepStrictEqual(calls, [{ cwd: /repo/main, args: [worktree, prune] }])tests/worktree-safety.test.cjs#L501-L515。架构决策也落在 ADR 中docs/adr/0004-worktree-workstream-seam-module.md 明确记录——“Worktree metadata cleanup remains non-destructive by default:pruneOrphanedWorktreesrunsgit worktree pruneonly and does not remove linked worktree directories.”为了真正验证“非破坏 清幽灵元数据”tests/prune-orphaned-worktrees.test.cjs 使用**真实临时 git 仓库无 mock**跑通四个场景每个场景都依赖规范化路径比较已合并分支的 worktreeprune 后目录与git worktree list条目都必须保留未合并提交的 worktree不得被删位于process.cwd()的 worktree永不删除已被手动删除的目录幽灵条目prune 后git worktree list不再引用它。其中第 4 个测试正是跨平台路径断言的关键战场tests/prune-orphaned-worktrees.test.cjs#L34-L42 先把 porcelain 中每条worktree路径经canonicalPathrealpathSync.native失败时回退path.resolve归一化后再比较function listedWorktreePaths(repoDir) { const out gitOrThrow([worktree, list, --porcelain], { cwd: repoDir, timeoutMs: GIT_TIMEOUT_MS }); return new Set( out .split(\n) .filter((line) line.startsWith(worktree )) .map((line) canonicalPath(line.slice(worktree .length).trim())) ); }测试注释tests/prune-orphaned-worktrees.test.cjs#L176-L183解释了为何必须 canonical 化git 在管理文件中存储的是长格式路径而 Windows CI 的临时目录常含 8.3 短名如RUNNER~1vsrunneradmin且分隔符方向也可能不同——对原始路径做子串匹配必然失败。这正是 changeset 所述“path-separator false negatives across platforms”的完整形态。超时降级另一条“防误报”路径路径规范化解决的是“断言不匹配”而同一批回归加固还处理了“断言不该跑”的情况当git子进程超时例如.git/index.lock或挂起的 git 进程prune 决策必须降级而不是误判。在 src/shell-command-projection.cts#L631-L633超时谓词被收敛为单一事实源export function isSpawnTimeout(result: { error?: unknown }): boolean { return (result.error as NodeJS.ErrnoException | null | undefined)?.code ETIMEDOUT; }execGitsrc/shell-command-projection.cts#L654-L673通过spawnSync的timeout默认 10 秒与_spawnResult的timedOut字段把它暴露给所有调用方worktree-safety.cts的execGitDefault再把默认超时DEFAULT_GIT_TIMEOUT_MS 10000src/worktree-safety.cts#L20透传进去。由此readWorktreeListsrc/worktree-safety.cts#L147-L179在超时时返回reason: git_timed_out与“非 git 仓库”not_a_git_repo和“列表失败”git_list_failed三者严格区分planWorktreePrune则将其映射为action: skip绝不在读不到数据的情况下做出 prune 决定。测试矩阵覆盖 POSIX 形态SIGTERMerror.code ETIMEDOUT与 Windows 形态无 signal、仅error.code ETIMEDOUT两种超时并反证“外部投递的 SIGTERM 且无 error”不算超时见 tests/worktree-safety.test.cjs#L197-L243。集成视角健康诊断规则如何消费同样的规范化语义路径规范化不止服务于 prune 回归测试还贯穿 worktree 健康诊断。inspectWorktreeHealthsrc/worktree-safety.cts#L392-L437基于snapshotWorktreeInventory的三态存在性present/absent/unverified产出三种 finding映射到三条诊断规则src/health-diagnostic-rules/worktree-health.cts#L194-L216W017orphan——路径已不存在于磁盘remedy 即git worktree prunesrc/health-diagnostic-rules/worktree-health.cts#L120-L132W020扫描本身降级——git worktree list超时或失败remedy 指引用git worktree list --porcelain自行诊断src/health-diagnostic-rules/worktree-health.cts#L69-L112W027stale——按 mtime 判定且必须排除活动 worktree排除比较经由toComparablePathKey完成平台感知的规范化src/health-diagnostic-rules/worktree-health.cts#L168-L190。reapOrphanWorktreessrc/worktree-safety.cts#L2485-L2701则展示了同一原则的“管理文件侧”应用它读取.git/worktrees/name/gitdir指针解析实际 worktree 路径并建立canonical 路径 → git 已知路径的索引fs.realpathSync.nativesrc/worktree-safety.cts#L2551-L2567确保git worktree unlock/remove用的是 git 自己认识的拼写对 porcelain 输出的 CRLF\r\n也做统一归一化后再切块src/worktree-safety.cts#L2555。孤儿回收始终以git worktree prune收尾清理残留元数据src/worktree-safety.cts#L2697-L2698。变更的溯源与发布定位本文所依据的 fix-3056-worktree-path-assertion.md 位于.changeset/archived/目录属于归档 changeset 片段。按 .changeset/archived/README.md 的说明这些片段描述已随gsd-core ≤ 1.3.1发布的内容用户可见说明已手工整理进 CHANGELOG.md保留在此仅作溯源用途render工具不会递归读取该子目录因此不会重复渲染。从该片段的 frontmattertype: Fixed、pr: 3117与上述源码、测试证据看这一加固的目标可以概括为测试侧pruning safety 测试统一解析git worktree list --porcelain并断言结构化、规范化后的路径实现侧路径规范化posixNormalize/toComparablePathKey/canonicalPath形成共享工具链供 worktree 模块与健康诊断规则复用语义侧无论在哪一平台git worktree prune依旧只清理元数据、绝不擅自删除工作区目录——非破坏性保证在跨平台路径断言之下依然成立。小结把“路径相等”变成平台无关的判定fix-3056-worktree-path-assertion的价值不在于修复某个业务逻辑缺陷而在于修复测试自身的可移植性缺陷当断言依赖“字符串长相一致”时同一目录在/与\两种拼写下就会产生假阴性。gsd-core 给出的答案是三层递进——解析结构化输出--porcelain、统一规范化工具posixNormalize与toComparablePathKey、真实 git 仓库中的 canonical 路径比较realpathSync.native折叠 8.3 短名与分隔符差异。这三层分别对应 src/worktree-safety.cts、src/shell-command-projection.cts 与 tests/prune-orphaned-worktrees.test.cjs并继续被 W017/W020/W027 三条健康诊断规则消费形成从测试断言、实现决策到运维诊断的完整闭环。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐OmO worktree-sweep 路径归一化实战修复 Windows CI 下 git worktree 清扫的分隔符陷阱OmO worktree sweep 路径归一化实战修复 Windows CI 下 git worktree 清扫的分隔符陷阱 本篇文章基于 oh my op人工智能AI Agent代码智能体多智能体MCP ClientsAgent 编排gsd-core 代码修复 Agent 的同分支 worktree 隔离机制git worktree add -b 与事务化清理的工程实践gsd core 代码修复 Agent 的同分支 worktree 隔离机制 git worktree add b 与事务化清理的工程实践 导读 本文以 gFriend Windows 桌面端多 Worktree 并行开发基于 Git Worktree 的独立开发实例隔离方案Friend Windows 桌面端多 Worktree 并行开发基于 Git Worktree 的独立开发实例隔离方案 本篇技术指南围绕 Friend 仓库人工智能AI 应用语音移动开发后端桌面应用智能硬件MCP 服务上一篇FinGenius社区贡献指南加入AI金融开源项目的完整流程下一篇Obsidian-Skills调试技巧如何诊断和修复技能问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考