get-shit-done (GSD) Skill Surface 剪枝机制修复:applySurface 在集群禁用时清除 ~/.claude/skills 下遗留的 gsd-*/ 目录

get-shit-done (GSD) Skill Surface 剪枝机制修复:applySurface 在集群禁用时清除 ~/.claude/skills 下遗留的 gsd-*/ 目录
get-shit-done (GSD) Skill Surface 剪枝机制修复applySurface 在集群禁用时清除 ~/.claude/skills 下遗留的 gsd-*/ 目录【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done本文以 GSD 仓库中一份Fixed类型的 changesetPR 3766为核心完整还原一次“运行时技能面skill surface”剪枝逻辑的缺陷修复applySurface在禁用技能集群后为何会在~/.claude/skills/下遗留gsd-STEM/旧目录、根因runtimeConfigDir取错目录导致剪枝跑在不存在的路径上是如何定位的以及pruneSkillDirs()如何被抽取为技能目录删除的单一事实来源single point of truth并附带 manifest 成员资格校验以防止误删用户自建gsd-*目录的数据丢失风险。读完后你能掌握 GSD surface 状态文件.gsd-surface.json的生效集合计算公式、剪枝的所有权判定规则以及一套覆盖五类边界场景的回归测试写法。一、changeset 声明的三件事原始变更记录位于 .changeset/wise-pumas-glide.mdfrontmatter 标记为type: Fixed、关联pr: 3766正文声明了三条修复承诺applySurface现在会在集群cluster被禁用时剪除~/.claude/skills/gsd-STEM/目录——与安装/卸载install/uninstall的行为保持一致根因说明此前禁用的集群之所以会在磁盘上留下陈旧的 skill 目录是因为 surface.md 规格文档指示 AI 把 skills 子目录~/.claude/skills当作runtimeConfigDir来用而不是基础配置目录~/.claude重构说明将pruneSkillDirs()抽取为技能目录删除逻辑的“单一事实来源”single point of truth。下面逐条给出仓库内的实现证据。二、背景GSD 的 surface 机制与生效集合公式Surface 模块实现于 surface.cjsADR-0011 Phase 2Option B。它管理的是运行期启用/禁用状态独立于安装期的 profile 标记.gsd-profile状态文件为各运行时配置目录根下的.gsd-surface.json如~/.claude/.gsd-surface.json常量定义为SURFACE_FILE_NAMEsurface.cjs#L37状态结构SurfaceState见 surface.cjs#L43-L49包含四个字段{ baseProfile: full, // 字符串基础 profile如 core / standard / full disabledClusters: [...], // 数组被禁用的集群名 explicitAdds: [...], // 数组显式追加的技能 stem explicitRemoves: [...] // 数组显式移除的技能 stem }readSurface()对文件做结构性校验缺字段或类型不符一律返回nullsurface.cjs#L57-L77writeSurface()通过平台写入缝隙tmprename原子落盘surface.cjs#L85-L87。生效技能集合的解析在resolveSurface()中完成surface.cjs#L126-L192公式为模块头注释所写Effective skill set base profile ∪ explicitAdds − disabledClusters − explicitRemoves 然后经 manifest 做传递闭包transitively closed。具体步骤surface.cjs#L153-L181基础 profile 解析surface 存在时取surface.baseProfile否则回退到安装期标记readActiveProfile()再缺省为fullfull会把 manifest 中所有 stem 物化进集合移除disabledClusters展开后的技能集群名 → stem 的展开由clustersToSkills()完成explicitAdds按 manifest 的依赖关系做传递闭包队列迭代把每个新增 stem 的依赖一并加入explicitRemoves只删 stem 本身不级联最后由技能集合推导 agents 集合manifest 中_calls_agents_stem键surface.cjs#L183-L188。集群定义在 clusters.cjscore_loopnew-project、discuss-phase、plan-phase、execute-phase、help、update、audit_review、milestone、research_ideatesketch、spike、forensics、explore、graphify、ns-ideate等注释说明集群成员可以重叠一个技能可属于两个集群。面向用户的入口是/gsd:surface命令规格见 surface.md子命令为list · status · profile name · disable cluster · enable cluster · reset。三、缺陷解剖剪枝为何从未在正确目录上执行applySurface()负责把解析后的 surface 重新落盘surface.cjs#L207-L218function applySurface(runtimeConfigDir, layout, manifest, clusterMap) { if (path.resolve(runtimeConfigDir) ! path.resolve(layout.configDir)) { throw new TypeError(applySurface runtimeConfigDir must match layout.configDir); } const resolved resolveSurface(layout.configDir, manifest, clusterMap); for (const kind of layout.kinds) { const staged kind.stage(resolved); const dest path.join(layout.configDir, kind.destSubpath); _syncGsdDir(staged, dest, kind, manifest); } return resolved; }对每个 artifact kind目标目录dest path.join(layout.configDir, kind.destSubpath)。对 Claude 全局安装skills这个 kind 的destSubpath是skills。这里就是缺陷的关键正确调用runtimeConfigDir ~/.claude→dest ~/.claude/skills剪枝在真实技能目录上执行错误调用修复前 surface.md 规格所指示runtimeConfigDir ~/.claude/skills→dest path.join(~/.claude/skills, skills) ~/.claude/skills/skills——一个不存在的嵌套目录。回归测试头部的注释精确复现了这条错误链bug-3659 测试文件#L1-L30_syncGsdDir的剪枝逻辑本身是对的但因为剪枝作用在错误且不存在的目录上被禁用集群对应的gsd-*目录永远不会从~/.claude/skills/中被移除——pruneSkillDirs()的第一行就是if (!fs.existsSync(skillsDir)) return;空目录静默返回缺陷因此长期潜伏。install/uninstall 路径_removeGsdEntries本来就传~/.claude所以工作正常只有 surface 路径受影响这正是 changeset 强调“matches the install/uninstall behavior”的原因。四、修复一pruneSkillDirs() 成为单一事实来源修复后剪枝逻辑集中在pruneSkillDirs(skillsDir, retainedNames, prefix, manifest)surface.cjs#L248-L301_syncGsdDir()对 skills kind 的同步委托给它surface.cjs#L321-L346先把 staged 目录整体覆盖拷贝进目标目录保证内容最新再调用剪枝// Prune GSD-owned dirs that are no longer in the staged set. // pruneSkillDirs() is the single point of truth for this logic. pruneSkillDirs(destDir, stagedDirs, kindPrefix, manifest);该函数同时导出供测试与需要独立剪枝的调用方使用surface.cjs#L421-L430 注释明确“Exported for testing and for callers that need stand-alone pruning”。其所有权判定ownership criteria是整个函数的安全核心非空前缀路径如gsd-目录名以前缀开头是必要但不充分条件——目录名去掉前缀后的 stem 还必须存在于 manifest 的正规 stem 集合中过滤掉_calls_agents_元键才被视为 GSD 拥有。前缀匹配但不在 manifest 中的目录即用户自建、恰好以gsd-命名的技能被保留并向 stderr 输出警告process.stderr.write( [gsd] Warning: ${entry} matches GSD prefix ${prefix} but is not in the manifest — preserving (user-owned or unknown)\n );这是针对数据丢失类缺陷测试注释中称“Finding 1”的关键闸门若只靠前缀匹配就删除用户的gsd-mything/会在一次“禁用全部集群”操作中被静默抹掉。空 prefix 路径Hermes 等无前缀运行时目录名直接出现在正规 manifest 中才视为 GSD 拥有防御性保守降级manifest 不是Map实例safeManifest判定surface.cjs#L251-L259或完全缺失时无法确认所有权一个目录都不删单个目录删除失败只写 stderr 不中断遍历fs.rmSync(..., { recursive: true, force: true })包裹在 try/catch 中。五、修复二surface.md 规格纠正 runtimeConfigDir 语义surface.md 的 “runtimeConfigDir resolution” 一节明确写入了修复后的约定runtimeConfigDirforapplySurfaceis thebase Claude config directory(~/.claude), NOT the skills sub-directory (~/.claude/skills). This matchesinstallRuntimeArtifactsanduninstallRuntimeArtifacts, which also receive~/.claudeasconfigDir. The skill dirs themselves live at~/.claude/skills/gsd-*/because theclaude globallayout hasdestSubpath skills— they are derived fromconfigDir, not the root for it.并给出 shell 侧参考# Claude Code — global install RUNTIME_CONFIG_DIR${CLAUDE_CONFIG_DIR:-$HOME/.claude} SCOPEglobal # Artifact destinations are derived from runtime layout # via resolveRuntimeArtifactLayout(runtime, RUNTIME_CONFIG_DIR, SCOPE) # then applySurface(RUNTIME_CONFIG_DIR, layout, manifest, CLUSTERS)配套的两个约定也随之统一surface 状态文件落在${RUNTIME_CONFIG_DIR}/.gsd-surface.json即~/.claude/.gsd-surface.json与 install/uninstall 的标记文件位置一致所有子命令profile/disable/enable都以scopeglobal调用resolveRuntimeArtifactLayout()使 skills kind 生效surface.md#L63-L103。applySurface内部的runtimeConfigDir ! layout.configDir强校验见第三节的TypeError则从代码层面杜绝了再次传错目录的可能。六、回归测试矩阵五类场景锁定剪枝契约回归测试 bug-3659-applysurface-prune-skill-dirs.test.cjs 用临时目录模拟 Claude 全局安装布局configDir类比~/.claude其下skills/预置gsd-explore/、gsd-help/和一个用户目录my-custom-skill/见 createFixture#L55-L70随后通过真实的loadSkillsManifest()resolveRuntimeArtifactLayout(claude, configDir, global)applySurface()驱动断言五类行为用例场景断言(a) L86-L116禁用research_ideate集群属于该集群的gsd-explore/被删除属于未禁用core_loop的gsd-help/保留(b) L118-L138保留集群的成员不受波及gsd-help/存在(c) L140-L163非gsd-前缀的用户目录my-custom-skill/及其SKILL.md原样保留(d) L165-L204幂等性连续两次applySurface后readdirSync(skillsDir)完全一致被剪目录持续缺席、用户目录持续存活(e) L206-L256禁用全部集群的反向用例GSD 拥有的gsd-explore/、gsd-help/全删my-custom-skill/保留用户自建的gsd-mything/前缀匹配但不在 manifest必须保留——这是 Finding 1 数据丢失修复的回归护栏用例 (e) 尤其重要它证明“禁用所有集群”这一最极端操作也不会把用户资产连根拔掉manifest 成员资格闸门与前缀检查构成双重约束。七、行为总结与适用边界把 changeset 的声明落到可验证的仓库事实上可以归纳出修复后的剪枝契约触发时机任何改写 surface 状态后重放applySurface的操作/gsd:surface的profile、disable、enable子命令都会重新同步 skills 目录不在保留集中的 GSD 拥有目录即被fs.rmSync递归删除行为与 install/uninstall 对齐删除资格目录必须同时满足“前缀匹配”Claude 等运行时与“stem 在 manifest 中”两条空 prefix 运行时以 manifest 成员资格为唯一判据manifest 不可用时一律保守不删状态文件位置~/.claude/.gsd-surface.json与.gsd-profile同级CLAUDE_CONFIG_DIR环境变量可整体覆盖路径可观测性listSurface()surface.cjs#L386-L415返回启用/禁用清单与 token 成本估算按各技能description长度 ÷ 4 向上取整求和与审计脚本口径一致/gsd:surface status即基于此。从源码结构看pruneSkillDirs()被导出并单独命名意味着后续若新增“按 profile 收敛”之外的独立清理入口例如升级迁移时回收旧版目录可以直接复用同一函数而不复制判定逻辑——这正是 changeset 中“single point of truth”表述的工程含义所有权判定、manifest 闸门与保守降级规则只在一处维护安装、卸载、surface 三条路径共享同一行为语义。适用前提与限制以上分析基于当前仓库中 Claude 运行时的globalscope 布局destSubpathskills、prefixgsd-其他运行时如 Hermes 空 prefix 场景走同一函数的不同分支且scope与destSubpath均由resolveRuntimeArtifactLayout()runtime-artifact-layout.cjs按 runtime 解析而非硬编码。【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

最新新闻

日新闻

周新闻

月新闻