把 mattpocock 的 38 个开源 Skills 装进 DeepSeek Harness:一篇讲清那个「不报错但完全失败」的坑

把 mattpocock 的 38 个开源 Skills 装进 DeepSeek Harness

环境:Windows 10 IoT 企业版 LTSC 21H2(19044.7807)· DeepSeek Harness 0.2.1-rc.2 项目:https://github.com/mattpocock/skills(38 个技能,MIT,29.8 万 star) 结果:38 个技能全部被 DSH 识别,实测可用

mattpocock/skills 是 Matt Pocock(Total TypeScript 作者)开源的一套 Agent Skill 技能库,近 30 万 star,现在也在 DSH 社区里很热。

但它有个反直觉的地方:很多人第一反应是去 DSH 的插件市场里搜,搜不到,然后以为装不了。

其实它根本不是插件。本文把整套流程走通,并记录下那些文档里不会写、但你一定会撞上的坑。


一、先搞清楚你装的是什么

mattpocock/skills 不是 DSH 插件,也不能通过插件市场安装。

它是 Agent Skill 格式的技能库——一堆带 YAML 头的 Markdown 文件。这个格式最先是 Anthropic 为 Claude Code 定的,现在 Codex、Gemini CLI、Cursor 等一批 harness 都认。DSH 也认,但它的入口和上面那些不一样。

所以「装到 DSH」这件事,本质是:把符合格式的目录放到 DSH 会去扫的地方。没有安装向导,也不需要。

⚠️ 别在插件市场里硬塞。 如果你把 skills 目录填进 DSH 的「添加插件」对话框,会得到这样的红字:

把 skills 目录当插件安装的报错

提示是「该路径不存在或不是有效的插件包」——因为它确实不是插件包,扫描器找的是 package.json,而 skills 目录里只有 SKILL.md。这条路是死路,看到这个报错就说明方向错了。

顺带说明一下这个仓库的规模:截至本文写作时,mattpocock/skills 已有 298,777 个 star、25,090 个 fork,是当前 Agent Skill 生态里最热门的项目之一。


二、DSH 的 skill 发现机制

DSH 内核里有三个包负责这件事:

包 作用
@deepseek-ai/dsh-skill 技能注册表,只管登记
@deepseek-ai/dsh-skill-filesystem 从磁盘扫目录、解析 frontmatter、监听变更
@deepseek-ai/dsh-tool-skill 把扫到的技能渲染成模型能看到的目录 + 给你用的 / 命令

dsh-skill-filesystem 会按 rank 从小到大扫这 5 个位置:

DSH 的 5 个 skill 扫描根

选哪一格取决于你想让谁看到:

  • 只给这个项目用 → 100 或 200,跟着仓库走,可以提交进 git
  • 给这台机器的所有 DSH 会话用 → 400(~/.dsh/skills),本文选这个
  • 同时给 Codex / Claude Code 用 → 500(~/.agents/skills),跨 harness 通用

我选 400,是因为它用户级、又只属于 DSH,不会污染你其他工具的技能列表。代价是不能跟着项目走。


三、最大的坑:只认「一层深度」

这是整套流程里唯一非直觉、也最容易失败的地方。

扫描器只往下看一层:认 <根目录>/<技能名>/SKILL.md,也认平铺的 <根目录>/<技能名>.md。它刻意不支持 **/SKILL.md 这种递归查找。

而仓库的结构是三层:

skills/engineering/tdd/SKILL.md
       └─ 分类 ─┘ └技能┘

把这个目录原样搬进去,结果是 0 个技能被发现,而且不报错——因为对扫描器来说,engineering/ 里没有 SKILL.md,它只是"一个不认识的子目录",跳过。

错与对的目录结构对比

正确做法是拍平:丢掉 engineering/、productivity/ 这些分类层,让每个技能目录直接挂在一层。分类信息在仓库里只是给人看的组织方式,对 DSH 没有意义(你的 SKILL.md 里的 description 才是模型挑技能的依据)。

技能目录内部可以随便深:references/、scripts/、templates/ 放多深都行,只有 SKILL.md 本身必须在一层。

💡 这是整个安装过程里唯一一个"不报错但完全失败"的地方。 没有红色提示、没有警告,就是 38 个技能变 0 个。如果你按流程走完发现技能面板空着,先回来查这一条。


四、实操步骤

步骤 1:下载仓库

GitHub 页面 → Code → Download ZIP,解压得到 skills-main/skills-main/。

目录里真正有用的是 skills/ 这一层,四个子目录:engineering(20)、productivity(7)、misc(4)、in-progress(7)。deprecated/ 是空的(只剩一个 README),跳过。

仓库里还有个 scripts/link-skills.sh,别用它。作者在文件开头自己写明:

「NOTE: This is a dev-only script, intended for use by maintainers of this repo. It is not a supported installer. Modifications to it, or requests for modifications, will not be approved.」

而且它链去的是 ~/.claude/skills 和 ~/.agents/skills,不是 DSH 的目录。注释里还额外说明:deprecated/ 不链(已退役),misc/ 也不链(保留但很少用、不推荐),in-progress/ 会链(公开征求意见用的)。

步骤 2:校验(先看清楚再动手)

在动手复制之前先跑一遍校验,确认每个技能的 frontmatter 都合规。原因见下一节——DSH 对格式错误的技能是静默丢弃的。

import os, re, sys, shutil

SRC = r'C:/Users/hek/Downloads/skills-main/skills-main/skills'
DST = r'C:/Users/hek/.dsh/skills'
BUCKETS = ['engineering', 'productivity', 'misc', 'in-progress']
APPLY = '--apply' in sys.argv
KEBAB = re.compile(r'^[a-z0-9]+(?:-[a-z0-9]+)*$')

def frontmatter(path):
    text = open(path, encoding='utf-8', errors='replace').read()
    if not text.startswith('---'):
        return None
    end = text.find('\n---', 3)
    if end < 0:
        return None
    data = {}
    for line in text[3:end].splitlines():
        m = re.match(r'^([A-Za-z0-9_-]+):\s*(.*)$', line)
        if m:
            data[m.group(1)] = m.group(2).strip()
    return data

rows, problems = [], []
for b in BUCKETS:
    bdir = os.path.join(SRC, b)
    if not os.path.isdir(bdir):
        continue
    for name in sorted(os.listdir(bdir)):
        sdir = os.path.join(bdir, name)
        sk = os.path.join(sdir, 'SKILL.md')
        if not os.path.isfile(sk):
            continue
        fm = frontmatter(sk)
        if fm is None:
            problems.append((b, name, 'no frontmatter')); continue
        fname = fm.get('name', '').strip('"\'')
        desc = fm.get('description', '').strip('"\'')
        if not fname:
            problems.append((b, name, 'name 缺失'))
        elif not KEBAB.match(fname):
            problems.append((b, name, 'name 非 kebab-case: ' + repr(fname)))
        if not desc:
            problems.append((b, name, 'description 缺失'))
        rows.append((b, name, fname))

names = [r[2] for r in rows if r[2]]
dupes = {n for n in names if names.count(n) > 1}
print('发现 %d 个 skill,问题 %d 个,重名 %s' % (len(rows), len(problems), dupes or '无'))
for p in problems:
    print('  ', p)

if APPLY:
    os.makedirs(DST, exist_ok=True)
    for b, n, f in rows:
        if not f or f in dupes:
            continue
        dst = os.path.join(DST, f)
        if os.path.exists(dst):
            shutil.rmtree(dst)
        shutil.copytree(os.path.join(SRC, b, n), dst)
    print('已复制 ->', DST)

不加 --apply 是只读校验,加了才写入。

这台机器的结果:

发现 38 个 skill,问题 0 个,重名 无

步骤 3:复制到 ~/.dsh/skills

python install_dsh_skills.py --apply

结果:38 个技能 / 103 个文件 / 440 KB。

我选的是复制目录而不是建符号链接:符号链接的好处是 git pull 后自动更新,坏处是源目录一挪走,技能全部失效且不报错。复制更笨但更可控,更新时重跑一次就行。

📌 上面脚本里的 SRC / DST 请按你自己的实际路径改。这台机器的下载目录是 C:\Users\hek\Downloads\skills-main\skills-main\skills。

步骤 4:用 DSH 自己的解析器复核(关键一步)

上面那个脚本用的是我自己写的简易 frontmatter 解析器,只能查"像不像"。要查"DSH 收不收",得用 DSH 运行时同一个 YAML 库。

DSH 是 Electron,app.asar 里就带着 yaml 包。把 ELECTRON_RUN_AS_NODE 打开,就能借它的 node 跑一段校验:

const fs = require('fs');
const path = require('path');
const YAML = require('C:/Users/hek/AppData/Local/Programs/DeepSeek Harness/resources/app.asar/dsh/node_modules/yaml');

const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const BOOLS = ['true','false','yes','no','on','off','1','0'];

function strictBool(v) {
  if (typeof v === 'boolean') return v;
  if (typeof v === 'string' && BOOLS.includes(v.toLowerCase()))
    return ['true','yes','on','1'].includes(v.toLowerCase());
  return undefined;
}

for (const name of fs.readdirSync('C:/Users/hek/.dsh/skills').sort()) {
  const sk = path.join('C:/Users/hek/.dsh/skills', name, 'SKILL.md');
  if (!fs.existsSync(sk)) continue;
  const text = fs.readFileSync(sk, 'utf8');
  const end = text.indexOf('\n---', 3);
  let fm;
  try { fm = YAML.parse(text.slice(3, end)); }
  catch (e) { console.log('DROP', name, 'YAML 坏了:', e.message); continue; }
  if (typeof fm.name !== 'string' || !KEBAB.test(fm.name)) { console.log('DROP', name, 'name 不合规'); continue; }
  if (typeof fm.description !== 'string' || !fm.description.trim()) { console.log('DROP', name, '缺 description'); continue; }
  const dmi = fm['disable-model-invocation'];
  if (dmi !== undefined && strictBool(dmi) === undefined) { console.log('DROP', name, '布尔值不合规'); continue; }
  console.log('OK  ', name, strictBool(dmi) === true ? '仅用户' : '模型可自动调用');
}
export ELECTRON_RUN_AS_NODE=1
"C:/Users/hek/AppData/Local/Programs/DeepSeek Harness/DeepSeek Harness.exe" validate_skills.js

这台机器的结果:

被丢弃: 0
可被 DSH 识别: 38

💡 这一步值得单独花时间。 脚本里那个 strictBool 不是多余的——disable-model-invocation 的值如果是 'yes'、'on'、'1' 这些字符串,DSH 会不会当成 true 解析?自己写的简易正则不一定答得上来。借 DSH 自己的 YAML 库跑一遍,才是真的对齐了运行时的判定标准。

步骤 5:确认 Agent preset 是「标准」

装完没反应,九成是卡在这一步。

DSH 的 skill 能力不是全局开关,是挂在 Agent preset 上的。同一个 App 切到不同 preset,就是完全不同的一套插件:

4 个 preset 的 skill 能力对照

极简(minimal) 只有 4 个插件——persona + 文件读写 + 压缩 + 常驻 shell,根本没有 skill 插件。在极简模式下,~/.dsh/skills 里放再多东西也没有任何东西去读它。

怎么改:新任务页面的 preset 选择器(或设置页的 preset 卡片)→ 选「标准」。

注意两点:

  1. 新会话才生效,已有会话保留它创建时的模式——切完要开一个新会话再试
  2. 怎么确认切成功了:打开 C:\Users\hek\.dsh\profiles\desktop\cordis.patch.yml,看这一行

yaml - id: agent-preset-registry name: "@deepseek-ai/dsh-agent-preset-registry" config: default: standard selectedDefault: standard # ← 这里是 standard 才对

步骤 6:验证

不用重启 App(DSH 对这个目录挂了文件监视),但要开新会话。然后点输入框上方的「技能」,应该看到:

DSH 技能面板

列表按名称字母序排列,第一屏就能看到 ask-matt、chief-of-staff、claude-handoff、code-review、codebase-design、diagnosing-bugs。

带「仅用户」徽章的,是模型不会自己用的那批。全部 38 个都在列表里,就算成功。

📌 顺便一提,这 38 个技能按「谁能唤起」正好劈成两半:23 个仅用户调用、15 个模型可自动调用。这个划分不是随意的——仅用户的技能负责编排(管流程、分任务、决定下一步),模型可调的负责纪律(可复用的做事方法)。想深入了解每个技能怎么用,可以看配套的《使用技巧》。


五、排错表

现象 原因 怎么办
技能面板整个不存在 Agent preset 是「极简」 切到「标准」
面板在、列表几乎为空 目录没拍平(还是三级结构) 看第三节,~/.dsh/skills/<名>/SKILL.md
少了几个特定的技能 frontmatter 不合规被静默丢弃 用步骤 4 的脚本定位具体是哪个、坏在哪
改了技能内容没变化 改了 SKILL.md 正文但没改 frontmatter 正文是每次加载时重读的,但如果会话已经加载过就该技能,要开新会话
复制进去后被删了 用了符号链接且源目录被移动 改用复制
切了 preset 还是看不到 切的是已有会话 开新会话
插件市场里搜不到 它本来就不是插件 回到第一节

六、更新与卸载

更新(仓库有新版时):

# 重新下载解压,然后
python install_dsh_skills.py --apply

脚本会先删同名目录再复制,等于整体覆盖。

💡 也可以用 npx skills@latest add mattpocock/skills 走通用安装器——作者在 README 里给「其他 agent 或需要可编辑文件」的场景推荐的就是这条。但那条命令装的是 ~/.agents/skills(rank 500),不是 DSH 优先扫的 400 目录,DSH 里可能会出现两份。所以本文还是坚持自己拍平到 ~/.dsh/skills。

卸载单个:直接删 C:\Users\hek\.dsh\skills\<技能名>\,不用重启。

全部卸载:

rm -rf C:/Users/hek/.dsh/skills

给别的 harness 也装一份:把同样的内容复制到 C:\Users\hek\.agents\skills\(那格是跨 harness 通用的,rank 500,DSH 也会扫,优先级低于 ~/.dsh/skills)。


七、一句话总结

DSH 装 skill 没有"安装"这个动作,只有"放到它扫的地方"。 三个词决定成败:找对目录(400 或 500)、拍平一层、preset 是标准。


本文所有结论都来自实测:dsh-skill-filesystem 的官方中文文档、DSH 合成配置的实际内容、以及 38 个技能的逐个校验结果。文中引用的仓库技能数(38)、分类分布(20/7/4/7)、以及「仅用户 23 个 / 模型可调 15 个」的划分,均已按 2026-10-21 的仓库状态重新核对。