13 KiB
DSH(DeepSeek Harness)集成
本文是 rev-skills 与 DSH(DeepSeek Harness)集成的长期参考:DSH 在本库语境里是什么、两条集成路线的差别、DSH 强制执行的兼容规则、安装与卸载命令、预设选择方式、验证方法、排错表,以及维护时必须成对同步的文件。
命令行实现见 bin/dsh.mjs 与 bin/install.mjs;bundle 的补丁本体是仓库根的 dsh.patch.yml。
1. DSH 在本库语境里是什么
DSH 是一个宿主(host),它按 Agent Skills 风格扫描技能目录:每个被扫描根目录下识别 <root>/<name>/SKILL.md 或 <root>/<name>.md。本库的 .claude/skills/ 就是这样一个目录,因此内容不需要转换即可被 DSH 读取——需要处理的只是「放到哪个根目录」与「用哪个预设看到它」。
两条能力需要分开理解:
- 技能发现(
@deepseek-ai/dsh-skill-filesystem):扫描根目录 → 解析 frontmatter → 把技能注册进当前预设的可用技能列表。 - 预设(agent preset):
@deepseek-ai/dsh-agent-preset的一行配置,决定该预设挂载哪些插件、看到哪些技能根。预设把「技能目录」和「agent 的工具与提示词」绑定在一起。
DSH 的默认技能根与优先级(数字小者先匹配,同名技能以先命中者为准):
| 优先级 | 根目录 | 说明 |
|---|---|---|
| 100 | <projectRoot>/.dsh/skills |
项目级 DSH 技能 |
| 200 | <projectRoot>/.agents/skills |
项目级通用 agent 技能 |
| 300 | customSkillDirs |
由预设显式声明的目录(本库预设用这一档) |
| 400 | <dshHome>/skills |
全局 DSH 技能(Windows 默认 C:\Users\<用户>\.dsh\skills) |
| 500 | <agentsHome>/skills |
全局通用 agent 技能 |
| 600 | bundledSkillDir |
DSH 自带技能 |
本机可用 $env:DSH_HOME(Windows)确认第 400 档的实际位置。
2. 两条集成路线
两条路线不互斥,取哪条取决于「要不要一个只带逆向技能层的固定 agent 配置」。
路线一:装进 DSH 技能根(所有预设可见)
把技能拷到第 100 / 400 档,任何预设都能看到这 122 个技能,不需要 bundle:
npx rev-skills install --target dsh # 交互式选择 global / project
npx rev-skills install --target dsh --global # → <dshHome>/skills
npx rev-skills install --target dsh --project # → .dsh/skills
npx rev-skills install --target dsh --dry-run # 只看计划
npx rev-skills uninstall --target dsh --global
适用场景:只想让技能在任意会话里可用,不需要独立的 agent 配置。代价是技能与预设的工具集、上下文预算混在一起。
路线二:独立预设(自带技能层)
本仓库的根包自身即 bundle(包名 rev-skills):根 package.json 里的 dsh.bundle.patch 指向仓库根 dsh.patch.yml,该补丁插入一行预设 preset-rev-skills。不需要第二个包,也就没有第二处版本号要同步。
- 预设显示 id(
config.id)为rev-skills,显示名「逆向工程(rev-skills)」,order为 40; - 该预设自带的
skill-filesystem实例用includeDefaultRoots: false,即该实例自身不声明第 100–600 档的根,技能根完全由customSkillDirs指定;但 DSH 读取技能时会把这个预设层与全局层合并,所以项目/用户技能仍与会话可见(同名条目以更近的预设层为准),其他预设不受影响; - bundle 自身不带技能副本,技能始终从仓库的
.claude/skills/读取,因此技能增删改不需要重新发布 bundle。
customSkillDirs 的解析顺序(取第一个通过校验的候选;校验 = 目录存在且其中至少有一个 re- 开头的条目):
| 顺序 | 候选 | 说明 |
|---|---|---|
| 1 | REV_SKILLS_DIR |
环境变量,优先级最高;取值是仓库根目录时解析为 <dir>/.claude/skills,取值是技能目录本身时原样使用 |
| 2 | <DSH_HOME>/skills |
默认 ~/.dsh/skills(Windows %USERPROFILE%\.dsh\skills)——即「路线一」装进去的位置 |
| 3 | <rev-skills 包目录>/.claude/skills |
按包名解析;本包即 bundle,装进 profile 的 node_modules 时命中 |
候选都不可用时回落到第 2 项;目录暂缺不报错(提供方会持续探测)。
按 DSH skills 服务的实际契约,预设作用域的提供方与全局层是合并关系而非替换:同名技能以更近的层胜出。因此该预设是把 rev-skills 技能根作为自己的一层加入,用户项目与家目录下的技能仍可见;includeDefaultRoots: false 只影响这个预设实例自身的根列表。
前置要求:宿主需提供两个包——@deepseek-ai/dsh-agent-preset(agent preset 声明能力)与 @deepseek-ai/dsh-skill-filesystem(本地技能目录发现能力),官方发行版自带。bundle 自身零运行时依赖,不需要为它单独跑安装。
node bin/dsh.mjs check [--dir <skillsRoot>] [--json]
node bin/dsh.mjs preset spec
node bin/dsh.mjs preset status [--profile-dir <dir>] [--json]
node bin/dsh.mjs preset install [--profile-dir <dir>] [--force] [--apply]
node bin/dsh.mjs preset uninstall [--profile-dir <dir>] [--apply]
preset install / preset uninstall 默认是 dry-run,打印将要做的编辑;加 --apply 才写文件(写入前备份,卸载时存在备份则从备份还原)。
安装这条路线最省事的方式是交给 DSH 自带的包管理器——它的目标参数既可以是 npm 包名(从 registry 装已发布版本),也可以是本地路径(link: / file: 规格):
dsh plugin --profile <profile> add rev-skills # registry 上的已发布版本
dsh plugin --profile <profile> add link:/path/to/rev-skills # 本地工作区(改补丁立即生效)
成功后它会自动把 rev-skills 追加进该 profile 的 dsh.profile.bundles(实测:从空 bundles 开始,add 完成后 bundles 变为 [..., "rev-skills"]);失败则还原 package.json、pnpm-lock.yaml 与 node_modules,不留半截状态。GUI 插件管理器走同一套机制。
手工等价写法是把下面两处同时写进 profile 的 package.json:
{
"dependencies": { "rev-skills": "link:/path/to/rev-skills" },
"dsh": { "profile": { "bundles": [ "...", "rev-skills" ] } }
}
两处必须同时存在:bundle 行在 dsh.profile.bundles 里,且依赖能解析到该包。profile 的 package.json 位于 $env:DSH_PROFILE_DIR(Windows 默认 C:\Users\<用户>\.dsh\profiles\<profile>\package.json)。
卸载走对称的一条命令,它会同时摘掉依赖与 dsh.profile.bundles 里的那一行(实测),随后重启 DSH;已存在的会话保留创建时的插件组合,需新开会话:
dsh plugin --profile <profile> remove rev-skills
node bin/dsh.mjs preset uninstall --apply # 仓库自带 CLI 的等价路径(有备份时从备份还原)
发布面的一个硬约束:bundle 的补丁文件必须出现在该包 package.json 的 files 列表里。pnpm 处理 file: 依赖时按 files 复制内容,漏列时 add 会报 failed to read overlay …/node_modules/rev-skills/dsh.patch.yml: ENOENT 并整体回滚(测试里有对应回归断言)。
适用场景:需要一个只见得到本库技能、且带固定工具与提示词的独立 agent 预设。
3. DSH 强制的兼容规则
本库 122 个技能已全部满足下列规则;新增或修改技能时按此自检。
- 目录布局(一层):
<root>/<name>/SKILL.md或<root>/<name>.md。扫描只深入一层——嵌套的**/SKILL.md不会被发现,例如.claude/skills/re-analyze/references/analysis-contract.md不构成独立技能。 - frontmatter 分界行必须精确:开头的行就是
---(前后无空格、无 BOM),结尾是同样精确的一行---;闭合行之后才有正文。 - frontmatter 必须是合法 YAML 且解析为 mapping:键值映射,块标量与内联 list 都在 YAML 子集内。解析失败时 DSH 记一条告警并静默跳过该技能(技能看起来"消失",不报错中断)。
name语法:必须匹配/^[a-z0-9]+(?:-[a-z0-9]+)*$/(小写字母、数字、单连字符分段),并且与目录名一致。description非空:缺失或空串即不注册。本库另有更严的约束——description必须同时含 CJK 与拉丁字母(validate.mjs强制,保证中英触发词都可命中)。- 可选键:
whenToUse、metadata、disable-model-invocation、user-invocable;本库技能不需要它们即可被发现。 - 工具结果裁剪:标准预设会裁剪超过 8192 字符的工具结果。本库有 32 个
SKILL.md的文件字符数超过该阈值(CRLF 检出下为 34;node bin/dsh.mjs check会逐条列出字符数与正文行数,最长re-kernel,约 19979 字符),这些技能被加载时正文可能被截断——需要完整内容时直接读文件,或把该技能拆分为「正文 +references/」。
本机自检 frontmatter 的最短路径:
node validate.mjs # 结构校验(frontmatter / 命名 / 能力标签 / 死链 / guard / 章节)
node bin/dsh.mjs check # 按 DSH 发现规则校验技能根(一层布局 / 分界行 / name 文法 / 裁剪阈值)
YAML 本身按换行解析,DSH 允许分界行尾带 CR;本库随库脚本(validate.mjs / lib/frontmatter.mjs)按 LF 解析,因此检出的工作区行尾须与仓库声明一致(.gitattributes 与 core.autocrlf 的取向见仓库根)。
4. 验证
node validate.mjs输出OK: 122 skills validated——内容层通过。node bin/dsh.mjs check——按 DSH 发现规则校验技能根,通过时输出OK: N skills DSH-compatible;--dir <skillsRoot>换根、--json逐条给出检查项与告警。node bin/dsh.mjs preset spec——确认解析到的技能根(REV_SKILLS_DIR→<DSH_HOME>/skills→ 包目录,见第 2 节的顺序表)。- 新开会话,在预设列表里选中「逆向工程(rev-skills)」:
skill工具目录中应出现入口技能re-analyze与其余 121 个re-*技能;加载re-analyze能返回正文即技能根解析正确。 - 对照检查:切回
standard预设的会话,re-*技能不再由预设层提供——若该会话仍能看到re-*,说明技能同时装进了被扫描的全局技能根(路线一),那属于预期结果而非故障。
5. 排错
| 现象 | 常见原因 | 处理 |
|---|---|---|
| 技能全部看不到 | 装错根目录(--project 装到了 .claude/skills 而非 .dsh/skills) |
重跑 --target dsh --global 或 --project,用 node bin/dsh.mjs check 核对根 |
| 只有个别技能看不到 | frontmatter 首尾不是精确 ---,或 YAML 不合法 |
DSH 日志会有告警并跳过该条;跑 node validate.mjs 定位;修正分界行/YAML |
| 技能名不合法 | name 含大写、下划线、连续连字符或与目录名不一致 |
改为 <root>/<name>/SKILL.md 且 name 匹配 /^[a-z0-9]+(?:-[a-z0-9]+)*$/ |
技能列表里没有 references/ 里的文件 |
扫描只深入一层,嵌套 **/SKILL.md 不发现 |
属预期行为;把需要独立触发的内容提升为顶层技能 |
| 预设不出现 | bundle 名不在 profile 的 dsh.profile.bundles,或依赖未安装 |
跑 node bin/dsh.mjs preset status;补 bundle 行与依赖后重装 profile |
| 预设改完没生效 | profile 未重新安装/未应用 | node bin/dsh.mjs preset install --apply,或从 GUI 插件管理器重新安装 bundle |
| 旧会话看不到新预设 | 已存在的会话保留创建时的插件组合 | 新开一个会话(旧会话不会热切预设) |
| 技能根解析到了别处 | REV_SKILLS_DIR 或 <DSH_HOME>/skills 先命中,且其中存在 re- 条目 |
用 node bin/dsh.mjs preset spec 看命中项,清掉不该命中的目录或变量 |
| 改了技能正文但会话里没变 | 正文按需加载,编辑正文不需重启 | 重新触发该技能即可 |
| 改了技能 frontmatter 但会话里没变 | frontmatter 在启动/加载时解析并缓存 | 重启 DSH 会话(或重载技能根)后生效 |
| 技能正文被截断 | 正文超过 8192 字符被预设裁剪 | 直接读文件,或把长技能拆分为正文 + references/ |
6. 维护同步点
改 DSH 相关文件时,下列配对必须同时更新,否则出现"命令能跑但预设装不上"这类不一致:
| 项 | 一处 | 另一处 |
|---|---|---|
| 补丁文件 | 根 package.json → dsh.bundle.patch |
dsh.patch.yml 实际路径,且该路径必须出现在根 package.json 的 files 里 |
| bundle 名 | 根 package.json → name(rev-skills) |
profile package.json → dsh.profile.bundles 的对应行 + 同名依赖;本文档与 README 的安装示例 |
| 预设 id | dsh.patch.yml 的 config.id(rev-skills) |
本文档的说明 |
| 技能根 | dsh.patch.yml 的 customSkillDirs 解析顺序 |
REV_SKILLS_DIR 覆盖逻辑、bin/dsh.mjs 的 resolveLocalSkillRoot 与本文档第 2 节的候选表 |
版本号不再有第二处:bundle 就是根包,根 package.json 的 version 与 .claude-plugin/marketplace.json 成对即可。
发布流程与其他宿主一致:npm test 通过 → git tag → npm publish → 同步 package.json 与 .claude-plugin/marketplace.json 的版本号。