【免费下载链接】gsd-core
Git. Ship. Done - Core
本文基于 gsd-core 仓库的变更记录(.changeset/archived/witty-hawks-jump.md,对应 PR #2973)展开,讲解开发者画像工具/gsd:profile-user --refresh在 v1.39.0 后的产物落盘路径变更:dev-preferences.md不再写入遗留的commands/gsd/目录,而是写入~/.claude/skills/gsd-dev-preferences/SKILL.md;同时由安装器负责把已保留的遗留文件迁移到新位置。读者将掌握该变更的动机、新路径解析规则、安装器迁移的完整流程与安全约束,以及如何验证迁移结果。
一、变更背景:为什么 dev-preferences 要搬家
gsd-core 在 v1.39.0 引入了"技能优先(skills-only)"的安装模型:GSD 自带的命令文件从每个运行时的commands/gsd/子目录,逐步迁移到skills/<skill>/SKILL.md布局。绝大多数命令文件由 PR #1540 一次性完成迁移,但dev-preferences.md是个特例——它不是随包发布的静态文件,而是由/gsd:profile-user在运行时生成的用户产物(user artifact),因此在最初的批量迁移中被遗漏。
其结果正如源码注释所记载:
#2973: v1.39.0's skills-only migration removed the legacy
commands/gsdsubdirectory in favor ofskills/<skill>/SKILL.mdunder the runtime config dir. This writer was missed in the migration (PR #1540 targeted GSD-shipped command files; dev-preferences is a runtime-generated user artifact). Default now points at the skills/ location so/gsd:profile-user --refreshstops re-creating the legacy directory.
(见 src/profile-output.cts)
也就是说,在修复之前,每次执行/gsd:profile-user --refresh都会重新创建已经被移除的遗留commands/gsd/目录,造成"一边清理、一边复活"的拉锯。本次变更(PR #2973)解决了这个不一致:
- 写入端:
cmdGenerateDevPreferences的默认输出路径从遗留目录改为<configDir>/skills/gsd-dev-preferences/SKILL.md; - 迁移端:安装器新增
migrateLegacyDevPreferencesToSkill,把用户此前保留在遗留位置的dev-preferences.md内容搬到新位置。
二、写入端:SKILL.md 的新落盘路径解析
/gsd:profile-user --refresh的产物由 src/profile-output.cts 中的cmdGenerateDevPreferences负责生成。它读取分析 JSON(--analysis <path>,必填),填充dev-preferences.md模板后落盘。默认输出路径的解析逻辑如下(src/profile-output.cts):
- 解析有效运行时(effective runtime):依次读取环境变量
GSD_RUNTIME、配置文件中的runtime字段,缺省回落为claude。环境变量优先于配置文件,与全仓库统一的运行时裁决策略一致。 - 解析技能目录:调用
getGlobalSkillDir(effectiveRuntime, 'gsd-dev-preferences')得到技能目录;若该运行时没有 skills 目录布局(例如某些旧版 cline),则报错并提示改用--output显式指定路径。 - 拼接最终路径:
path.join(skillDir, 'SKILL.md'),即<configDir>/skills/gsd-dev-preferences/SKILL.md。
默认情况(Claude 运行时)下,最终写入路径为:
~/.claude/skills/gsd-dev-preferences/SKILL.md注意两点实现细节:
- 路径通过
path.join而非字符串字面量拼接,这是为了让cline-install leaked-path这类 ESLint 规则不误报泄漏路径; - 生成器把结果通过
command_path字段回传,同时返回command_name(即/gsd:dev-preferences的斜杠命令形式),供上层展示。
生成的内容模板
落盘的 SKILL.md 内容来自模板 gsd-core/templates/dev-preferences.md,包含 frontmatter 与两大区块:
--- description: Load developer preferences into this session --- # Developer Preferences > Generated by GSD on {{generated_at}} from {{data_source}}. > Run `/gsd:profile-user --refresh` to regenerate. ## Behavioral Directives Follow these directives when working with this developer. Higher confidence directives should be applied directly. Lower confidence directives should be tried with hedging ("Based on your profile, I'll try X -- let me know if that's off"). {{behavioral_directives}} ## Stack Preferences {{stack_preferences}}模板中的占位符由cmdGenerateDevPreferences逐项替换:
{{behavioral_directives}}:按 8 个画像维度(communication_style、decision_speed、explanation_depth、debugging_approach、ux_philosophy、vendor_philosophy、frustration_triggers、learning_style)生成的行为指令块,每条指令附带置信度,例如### Communication\nKeep responses concise and action-oriented...(MEDIUM confidence);{{stack_preferences}}:来自会话分析的技术栈偏好;若画像来源是问卷(data_source === 'questionnaire'),则替换为提示文案"Stack preferences not available (questionnaire-only profile). Run/gsd:profile-user --refreshwith session data to populate.",引导用户补跑会话分析;{{generated_at}}与{{data_source}}:生成时间与数据来源。
行为指令本身来自CLAUDE_INSTRUCTIONS映射表(src/profile-output.cts):每个维度有若干 rating,每个 rating 对应一条给 Claude 的指令。例如 frustration_triggers 维度下,scope-creep对应的指令是"Do exactly what is asked -- nothing more..."。若分析 JSON 中某维度缺少claude_instruction,生成器会先从CLAUDE_INSTRUCTIONS按 rating 反查,仍查不到则回退为通用模板句。
运行时感知:不同宿主各有归处
新路径并非只有 Claude 一种形态。getGlobalSkillDir与getProjectInstructionFile(见 src/runtime-homes.cts、src/runtime-name-policy.cts)共同保证:在不同 AI 编码工具(Codex、Cline、OpenCode 等)下,SKILL.md 写入该工具自己的配置目录,而不是硬编码 Claude 的~/.claude。这一设计同时作用于 USER-PROFILE.md 与 CLAUDE.md/AGENTS.md 节区(源码中 #1114、#2565、#3163 等多条注释反复强调了"运行时感知"的必要性,避免 Codex 安装把产物写到 Claude 目录导致互相找不到)。
例如测试 tests/profile-output.test.cjs 断言:
- Codex 运行时(skills home 覆盖到
$HOME/.agents):输出codexHome/.agents/skills/gsd-dev-preferences/SKILL.md; - Cline(v3.48.0 以上具备 skills 能力):输出
clineHome/skills/gsd-dev-preferences/SKILL.md。
三、迁移端:安装器如何搬走遗留文件
迁移目标解析
安装器侧的核心函数是migrateLegacyDevPreferencesToSkill(src/install-engine.cts),配套的路径解析辅助函数_resolveDevPreferencesSkillTarget(src/install-engine.cts)负责计算目标文件:
- 扁平 skills 布局(绝大多数运行时,技能目录前缀为
gsd-):目标为<installRoot>/skills/gsd-dev-preferences/SKILL.md; - 嵌套 skills 布局(如 Hermes 的
skills/gsd/<stem>/,前缀为空字符串):目标为<installRoot>/skills/gsd/dev-preferences/SKILL.md; - 无 skills 布局的运行时/作用域(例如 cline local):返回
null,迁移跳过。
同时,解析会尊重 skills kind 声明的home覆盖(如 Codex 把技能目录放到$HOME/.agents),保证迁移落点与安装器表面应用(surface-apply)使用同一棵目录树,避免"写错树"。
迁移流程与安全约束
migrateLegacyDevPreferencesToSkill的执行语义:
- 前置检查:仅当调用方传入的
saved映射中存在dev-preferences.md(即用户在遗留位置保留了文件)时才继续;若新位置已存在真实文件,则视为"已迁移",跳过且不覆盖——因为用户可能已直接编辑过新文件,不应被旧内容覆盖。 - 符号链接防护(安全修复,#2875/#2393 相关):对目标 SKILL.md 做
tryLstat(不跟随符号链接),区分"真实文件已存在"(跳过)与"此处被植入了符号链接"(拒绝写入并抛错,防止通过符号链接把内容写到攻击者选定的目标,如~/.ssh/authorized_keys);同时检查 installRoot 到 skillDir 之间的路径组件是否存在会重定向写入的符号链接,仅当显式设置GSD_ALLOW_SYMLINKED_DEST=1时才允许有意为之的用户符号链接布局。 - 写盘:
mkdirSync(skillDir, { recursive: true })后把saved.get('dev-preferences.md')内容写入 SKILL.md,成功返回true供上层打印一行迁移确认日志。
该迁移的运行时机在破坏性清理(wipe)之前:安装器先把遗留commands/gsd/中的用户文件通过userArtifactStaging.stageUserArtifacts(legacyCommandsGsd, ['dev-preferences.md'], stagingRoot)暂存(见 src/user-artifact-staging.cts),执行清理后再从暂存内容回填迁移目标。dev-preferences.md同时被列入"用户自有产物"清单(src/installer-migrations/000-first-time-baseline.cts 中的USER_OWNED_PATHS,同时包含gsd-core/USER-PROFILE.md与skills/gsd-dev-preferences/SKILL.md),首次安装基线会将其标记为baseline-preserve-user,确保后续破坏性迁移不会误删用户数据。
四、画像产物全景:一次 profile-user 到底生成什么
/gsd:profile-user的完整流程由 gsd-core/workflows/profile-user.md 编排(命令入口见 commands/gsd/profile-user.md),其产物不止 dev-preferences 一项。cmdGenerateDevPreferences所在的 src/profile-output.cts 还导出四个相关命令:
| 命令 | 功能 | 默认输出 |
|---|---|---|
write-profile | 从分析 JSON 渲染 USER-PROFILE.md | <configDir>/gsd-core/USER-PROFILE.md(运行时感知,#1114) |
profile-questionnaire | 无会话数据时的问卷兜底路径 | 交互输出问题 JSON,或按逗号分隔答案生成分析 JSON |
generate-dev-preferences | 生成 dev-preferences 命令产物(本文主角) | <configDir>/skills/gsd-dev-preferences/SKILL.md |
generate-claude-profile | 在指令文件中写入/更新 Developer Profile 节区 | .claude/CLAUDE.md(或按运行时/配置解析,见 #2565) |
generate-claude-md | 组装受管节区(Project/Stack/Conventions/Architecture/Skills/Workflow) | .claude/CLAUDE.md(可用--force覆盖手写文件,#1098) |
几个值得注意的行为:
- 写 USER-PROFILE.md 时的敏感信息脱敏:
cmdWriteProfile内置正则清单(sk-...、Bearer ...、password=、ghp_、xoxb-、绝对路径/Users/...、/home/...等,见 src/profile-output.cts),把证据引文中的密钥替换为[REDACTED]并计数输出; - 问卷置信度规则:问卷答案默认记为
MEDIUM置信度,其中"混合/视任务而定"类答案(如 communication_style 选 d)会被判为LOW; - CLAUDE.md 受管节区:若目标指令文件不含 GSD 标记(
<!-- GSD:xxx-start -->),且未传--force,则视为手工维护文件,跳过写入(#1098);--auto模式下还会检测用户对手工编辑过的节区做跳过而非覆盖。
五、验证与回归:如何确认迁移生效
仓库用测试锁定了本次变更的契约,最直接的验证点在 tests/profile-output.test.cjs:
command_path断言:对 Codex、Cline 等运行时分别断言生成器输出.../skills/gsd-dev-preferences/SKILL.md,防止路径回退到遗留位置;- 安装相关测试(tests/install-runtime-artifacts.test.cjs、tests/install-regressions.test.cjs)覆盖
migrateLegacyDevPreferencesToSkill的迁移、跳过与符号链接拒绝分支; - tests/install-write-confinement.test.cjs 与 tests/live-config-guard.test.cjs 校验安装器/守护进程对该路径的写入约束与配置漂移监控。
作为使用者,可以用以下方式自检:
# 1. 触发画像重建(默认运行时为 claude) /gsd:profile-user --refresh # 2. 确认新位置存在 SKILL.md ls -l "$HOME/.claude/skills/gsd-dev-preferences/SKILL.md" # 3. 确认遗留目录不再被重新创建 [ -d "$HOME/.claude/commands/gsd" ] && echo "LEGACY STILL EXISTS" || echo "LEGACY GONE" # 4. 若此前在遗留位置保留过旧文件,升级安装后应看到其内容已出现在新 SKILL.md 中 head -20 "$HOME/.claude/skills/gsd-dev-preferences/SKILL.md"对于非 Claude 运行时,可用环境变量GSD_RUNTIME=<runtime>指定(例如GSD_RUNTIME=codex),产物将落到对应工具的配置目录;若某运行时没有 skills 布局,生成器会提示显式传--output <path>。
六、小结
PR #2973 修复的并非一次简单搬文件:它同时调整了"生成器默认落盘路径"与"安装器遗留迁移"两条链路,并把目标位置统一到运行时感知的skills/gsd-dev-preferences/SKILL.md。由此带来的收益是明确的——/gsd:profile-user --refresh不再复活已废弃的commands/gsd/目录,用户画像产物能够被各宿主工具以标准 skill 形式发现(包括 Claude 的 skill 自动加载机制),而安装器对用户文件的暂存、基线保护与符号链接防御则保证了迁移过程不丢数据、不越权写入。相关实现与测试均可直接在本仓库 src/profile-output.cts、src/install-engine.cts、tests/profile-output.test.cjs 中继续深入阅读。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
gsd-core 的 ADR-457 构建即发布实践:commands 与 state 枢纽模块的 TypeScript 迁移(Batch 14)
gsd core 的 ADR 457 构建即发布实践:commands 与 state 枢纽模块的 TypeScript 迁移(Batch 14) 本文以 gs
GSD-Core 子代理派发机制详解:`/gsd-debug` Session Manager 为何从 `Task()` 迁移到 `Agent()`
GSD Core 子代理派发机制详解: /gsd debug Session Manager 为何从 Task 迁移到 Agent 导读 本文基于 gsd co
GSD Core 技能安装命名空间规范化:SKILL.md 正文 `/gsd:<cmd>` → `gsd-<cmd>` 的转换修复(3583)
GSD Core 技能安装命名空间规范化:SKILL.md 正文 /gsd:<cmd → gsd <cmd 的转换修复( 3583) 本文围绕 gsd core
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考