1. 从"agent-skills"这个仓库名说起:它到底在解决什么问题
第一次看到agent-skills这个名字,很多人会以为是某个AI代理的插件市场,或者是一个技能包合集。但真正翻过它的目录结构、跑过它的CLI之后,你会发现它想做的事情比"收集一堆prompt模板"要务实得多——它试图给AI coding agents建立一套可复用、可测试、可版本管理的技能规范。
说白了,现在用Claude Code、Cursor、Windsurf这类AI编程工具的人越来越多,但大部分人用得很"野":今天写一个让AI帮忙做代码审查的prompt,明天写一个让它生成单元测试的prompt,后天又写一个让它重构的prompt。这些prompt散落在各个项目的.claude/目录、CLAUDE.md文件、或者干脆就在聊天记录里。换一个项目,全部重来。换一个模型,效果又不一样。
agent-skills的核心思路就是:把"让AI做某件事"这件事本身,当成一个软件工程问题来处理。每个skill是一个独立的、有明确输入输出的单元,有测试用例,有版本号,可以通过CLI安装和分发。这个思路听起来简单,但它背后牵扯到的东西不少——skill的粒度怎么切、测试怎么写、不同agent的兼容性怎么处理、CLI怎么设计才能让人愿意用。
我花了大概两周时间把这个仓库从里到外跑了一遍,包括它的skills CLI、几个内置skill的实现、以及它和Claude Code的集成方式。下面把我踩过的坑、想明白的设计逻辑、以及实际用下来的感受,完整地分享出来。
提示:这篇文章假设你已经对AI coding agent有基本了解,至少用过Claude Code或者类似的工具。如果你完全没接触过,建议先花半小时跑通一个最简单的"让AI改一个bug"的流程,再回来看这篇。
2. skills CLI的设计哲学:为什么不是简单的文件拷贝
2.1 一个skill的目录结构长什么样
先看一个最简skill的目录长什么样。以仓库里自带的test-driven-development这个skill为例,它的结构大概是这样的:
skills/ test-driven-development/ skill.yaml # 元数据:名称、版本、适用agent、依赖 prompt.md # 核心prompt模板 examples/ # 输入输出示例 input-01.md output-01.md tests/ # 测试用例 test-01.yaml README.md这个结构里,skill.yaml是最关键的。它定义了skill的"契约"——这个skill叫什么、版本是多少、适用于哪些agent(Claude Code、Cursor、通用)、需要什么前置条件、输入输出的格式是什么。
prompt.md是实际注入给AI的指令。但注意,它不是简单的"你是一个资深工程师,请帮我..."这种。它更像是一个结构化的任务描述,包含角色定义、任务边界、输出格式要求、以及几个few-shot示例的引用。
tests/目录是我觉得最有意思的部分。它用YAML定义了一组"给定输入X,期望输出满足条件Y"的测试。比如对于TDD skill,一个测试可能是:给定一段没有测试的Python函数,期望AI输出的第一步是写一个失败的测试,而不是直接改函数实现。
2.2 为什么要有CLI而不是直接拷贝文件夹
你可能会想:不就是几个文件吗,我手动拷贝到项目的.claude/skills/目录不就行了,为什么要搞一个CLI?
我一开始也是这么想的,直到我同时维护三个项目、每个项目用了五六个skill、然后Claude Code升级了一次导致某个skill的prompt格式不兼容。手动管理的问题瞬间暴露:
- 版本漂移:A项目用的是skill v1.2,B项目用的是v1.3,你根本记不住哪个项目用的哪个版本。
- 依赖冲突:skill A依赖skill B的某个输出格式,但你只更新了A没更新B。
- 测试缺失:你改了一个skill的prompt,但没有任何机制告诉你这个改动会不会破坏原有的行为。
skillsCLI解决的就是这些问题。它的核心命令大概有这几个:
# 安装一个skill到当前项目 skills install test-driven-development # 安装指定版本 skills install test-driven-development@1.2.0 # 列出当前项目已安装的skill skills list # 运行某个skill的测试 skills test test-driven-development # 更新所有skill到最新兼容版本 skills update这个CLI是用Node.js写的,安装方式就是npm install -g @agent-skills/cli。我实测下来,在Ubuntu 22.04和macOS Sonoma上都能正常跑,Windows下用WSL也没问题。
2.3 skill.yaml里的字段到底怎么填
这是最容易踩坑的地方。我见过有人把agent字段填成claude,结果CLI不认;也有人把version写成v1.0,导致语义化版本比较出错。下面是我总结的字段填写规范:
| 字段 | 必填 | 格式 | 常见错误 |
|---|---|---|---|
| name | 是 | 小写字母+连字符 | 用下划线或大写 |
| version | 是 | 语义化版本 x.y.z | 加v前缀 |
| agents | 是 | 数组,如[claude-code, cursor] | 写成字符串 |
| inputs | 否 | 对象,描述输入参数 | 漏掉导致prompt变量未定义 |
| outputs | 否 | 对象,描述输出格式 | 格式不明确导致AI输出不稳定 |
| dependencies | 否 | 数组,依赖的其他skill | 循环依赖 |
注意:
agents字段的值必须是CLI支持的枚举值。截至我写这篇的时候,支持的值是claude-code、cursor、windsurf、generic。填错了CLI会直接报错,不会静默忽略。
3. 写一个自己的skill:从TDD场景完整走一遍
3.1 为什么选TDD作为第一个skill
仓库里自带的skill有好几个,但我建议新手从test-driven-development开始改起,原因有三个:
第一,TDD的流程足够标准化。红-绿-重构这个循环是固定的,AI很容易理解"先写失败测试"这个约束。
第二,TDD的输入输出边界清晰。输入是一段需求描述或者一个函数签名,输出是一个测试文件加一个实现文件。不像"代码审查"这种skill,输出什么完全取决于AI的心情。
第三,TDD skill的测试最好写。你可以用确定性的方式验证:AI输出的第一个代码块是不是测试?测试里有没有assert?实现代码是不是在测试之后才出现的?
3.2 prompt.md的写法:约束比描述重要
我见过太多人写skill prompt的方式是:"你是一个资深Python工程师,请帮我用TDD的方式实现以下功能..."。这种写法的问题在于,它给了AI太多自由发挥的空间。
agent-skills里TDD skill的prompt写法是这样的(我做了简化):
## 角色 你是一个严格遵循TDD流程的编程助手。 ## 硬性约束 1. 你的第一个输出必须是一个测试文件,且该测试在当前代码下必须失败。 2. 在用户确认测试失败之前,你不得输出任何实现代码。 3. 实现代码必须让测试通过,且不得修改测试本身。 4. 每次只处理一个测试用例。 ## 输出格式 第一步:输出测试文件,用```python代码块包裹。 第二步:等待用户运行测试并反馈结果。 第三步:根据反馈输出实现代码。 ## 示例 [这里放一个完整的红-绿-重构示例]关键区别在于:用"硬性约束"代替"角色描述"。AI不需要知道你是一个"资深工程师",它需要知道的是"第一步必须做什么、不能做什么"。
我实测下来,这种写法让AI遵守TDD流程的概率从大概60%提升到了90%以上。剩下的10%失败案例,基本都是因为用户在第一轮就催着要实现代码,AI"心软"了。
3.3 tests目录怎么写才能真的起作用
这是整个skill开发里最被低估的部分。很多人写完prompt就完事了,tests目录空着。但tests才是保证skill不随模型升级而退化的关键。
一个TDD skill的测试用例大概长这样:
name: "should write test first" input: task: "实现一个函数,计算斐波那契数列的第n项" expect: first_code_block_language: "python" first_code_block_contains: "def test_" first_code_block_contains_any: ["assert", "pytest.raises"] no_implementation_before_confirmation: true这个测试怎么跑?skills test命令会把input喂给配置的agent,然后检查输出是否满足expect里的条件。如果第一个代码块不是测试,测试就失败。
我建议每个skill至少写3个测试:一个正常路径、一个边界情况、一个"诱导AI偷懒"的情况。比如TDD skill的第三个测试可以是:input里明确说"我很急,直接给我实现代码",期望AI仍然先输出测试。
提示:测试的expect条件不要写得太死。比如不要检查具体的函数名或变量名,只检查结构性的特征(有没有assert、代码块顺序对不对)。否则模型稍微换个写法测试就挂了,维护成本极高。
3.4 本地调试skill的实用技巧
CLI提供了一个--dry-run模式,可以把skill的prompt渲染出来但不实际调用agent。这个在调试prompt变量替换的时候特别有用:
skills run test-driven-development --dry-run --input task="实现快速排序"它会输出最终发给agent的完整prompt。我经常用这个来检查:变量有没有正确替换?few-shot示例有没有被截断?格式有没有乱?
另一个技巧是--agent generic。当你不想消耗Claude Code的额度时,可以用generic模式把prompt输出到stdout,然后手动粘贴到任何AI工具里测试。虽然麻烦一点,但调试阶段能省不少钱。
4. 和Claude Code集成时遇到的真实问题
4.1 skill安装到哪里去了
skills install默认会把skill安装到当前项目的.claude/skills/目录下。但Claude Code读取skill的机制和你想的可能不太一样。
Claude Code并不会自动加载.claude/skills/下的所有skill。它只会在CLAUDE.md里被显式引用,或者在你用/skill命令手动调用时才会加载。这意味着:
- 如果你装了10个skill但没在
CLAUDE.md里引用,它们不会影响Claude Code的默认行为。 - 如果你想某个skill总是生效(比如代码风格检查),需要在
CLAUDE.md里加一行@skill code-style-check。 - 如果你想按需调用,用
/skill test-driven-development即可。
我一开始以为装了就会自动生效,结果发现Claude Code的行为完全没变,排查了半天才发现是没引用。
4.2 prompt长度和上下文窗口的博弈
Claude Code的上下文窗口虽然大,但不是无限的。当你同时加载多个skill时,每个skill的prompt都会占用token。我实测过一个极端情况:加载了6个skill,每个平均800 token,加上项目本身的代码上下文,直接触发了截断。
解决方案有两个:
第一,按需加载。不要把所有skill都写进CLAUDE.md,只写最常用的1-2个。其他的用/skill手动调用。
第二,精简prompt。把few-shot示例从3个减到1个,把角色描述删掉只留硬性约束。我做过对比,精简后的TDD skill prompt从1200 token降到了400 token,效果几乎没有下降。
| 加载方式 | token占用 | 适用场景 |
|---|---|---|
| CLAUDE.md引用 | 每次对话都占用 | 高频使用的核心skill |
| /skill手动调用 | 仅调用时占用 | 低频但重要的skill |
| 不加载 | 0 | 实验性skill |
4.3 不同模型下的表现差异
agent-skills设计上是模型无关的,但实际用下来,不同模型对同一个skill的遵守程度差别很大。
我用同一个TDD skill测试了三个模型(这里不点名具体版本),发现:
- 模型A:严格遵守"先测试后实现",但在写测试时经常忘记加assert。
- 模型B:前两轮遵守,第三轮开始偷懒直接给实现。
- 模型C:完全无视约束,上来就给完整实现。
这说明skill的prompt需要针对不同模型做微调。agent-skills的agents字段就是干这个的——你可以为不同agent提供不同的prompt变体:
skills/ test-driven-development/ prompt.md # 默认 prompt.claude-code.md # Claude Code专用 prompt.cursor.md # Cursor专用CLI会根据当前agent自动选择对应的prompt文件。如果专用文件不存在,就回退到默认的。
5. 把skill纳入版本管理和CI的实践
5.1 skill也应该进git
我见过有人把.claude/skills/加到.gitignore里,理由是"这是本地配置"。这是个坏习惯。skill应该和代码一样进版本管理,原因很简单:skill会影响AI生成的代码,而代码是要进git的。
如果skill不进git,就会出现:你本地用TDD skill生成的代码有一堆测试,同事拉下来用他自己的skill重新生成,测试全没了。这种不一致在code review时是灾难。
我的做法是:.claude/skills/进git,但skills.lock文件也要进。skills.lock记录了每个skill的确切版本和哈希值,保证所有人装的是同一套skill。
5.2 在CI里跑skill测试
skills test命令可以集成到CI里。我的GitHub Actions配置大概是这样:
- name: Install skills run: npx @agent-skills/cli install --from-lockfile - name: Test skills run: npx @agent-skills/cli test --all env: AGENT_API_KEY: ${{ secrets.AGENT_API_KEY }}这样每次有人改了skill的prompt,CI会自动跑一遍测试。如果测试挂了,说明改动破坏了skill的预期行为,需要修复或者更新测试。
注意:CI里跑skill测试会消耗API额度。建议只在skill文件变更时触发,不要每次push都跑。用
paths过滤器可以做到:
on: push: paths: - '.claude/skills/**'5.3 skill的版本升级策略
skill的版本号不是随便加的。我总结了一套规则:
- patch版本(x.y.Z):只改了prompt的措辞,不改变输入输出契约。比如把"请写测试"改成"你必须先写测试"。
- minor版本(x.Y.z):增加了新的可选输入参数,或者增加了新的测试用例,但旧用法仍然兼容。
- major版本(X.y.z):改变了输入输出格式,或者改变了skill的核心行为。比如TDD skill从"先测试后实现"改成"测试和实现交替进行"。
major版本升级时,一定要在README里写清楚迁移指南。我见过一个skill从v1到v2把输入参数从task改成了description,结果所有依赖它的项目全挂了。
6. 几个我踩过的坑和对应的解法
6.1 skill之间的隐式依赖
有一次我装了一个code-reviewskill,它依赖git-diffskill来获取变更。但git-diffskill没有被自动安装,导致code-review运行时报错说找不到diff输入。
agent-skills的dependencies字段可以声明依赖,但CLI默认不会自动安装依赖。你需要显式地跑skills install --with-deps。
我的建议是:尽量让skill自包含。如果code-review需要diff,就让它在prompt里自己调用git diff命令,而不是依赖另一个skill。skill之间的依赖越少,维护成本越低。
6.2 prompt里的变量替换陷阱
skill的prompt里可以用{{variable}}语法引用输入变量。但有个坑:如果变量值里包含特殊字符(比如反引号、花括号),替换后会破坏prompt结构。
比如你传入的task是"实现一个函数,返回{key: value}格式的字典",替换后prompt里就会出现未闭合的花括号,AI会懵。
解法是在skill.yaml里声明变量的类型和转义规则:
inputs: task: type: string escape: true # 自动转义特殊字符或者更简单粗暴:在prompt里用代码块包裹变量:
## 任务{{task}}
这样即使变量里有特殊字符,也不会影响prompt的其他部分。
6.3 测试的"假阳性"问题
skill测试最容易出现的问题是"假阳性"——测试通过了,但skill实际上没按预期工作。
比如TDD skill的测试只检查"第一个代码块是不是测试",但AI可以输出一个空的测试文件(只有def test_foo(): pass),测试照样通过。
解法是让expect条件更具体。除了检查结构,还要检查内容:
expect: first_code_block_contains: "assert" first_code_block_line_count_min: 5 first_code_block_has_function_call: true我一般会先用几个真实的输入跑一遍skill,把AI的实际输出拿来看,然后根据输出反推应该检查哪些特征。这样写出来的测试才有意义。
6.4 跨平台路径问题
skillsCLI在Windows上跑的时候,路径分隔符是反斜杠,但skill.yaml里写的路径是正斜杠。这会导致在某些情况下找不到文件。
CLI本身做了处理,但如果你在skill里写了自定义脚本(比如scripts/setup.sh),就需要自己注意跨平台兼容性。我的做法是:能用Node.js脚本就不用shell脚本,实在要用shell就同时提供.sh和.ps1两个版本。
7. 这套东西到底值不值得用
说实话,如果你只是偶尔用Claude Code改改bug,agent-skills这套东西是过度设计。你直接写个CLAUDE.md,把常用的指令写进去,就够了。
但如果你符合以下任何一种情况,它值得你花时间:
- 团队里多个人用AI coding agent,需要统一行为
- 你有多个项目,想复用同一套AI指令
- 你被模型升级导致的"行为漂移"坑过
- 你想把AI生成的代码纳入CI质量管控
我自己的使用感受是:前期投入大概2-3天(学习CLI、写第一个skill、配CI),之后每个新项目能省下至少半天的"调教AI"时间。而且最大的收益不是省时间,是可预测性——你知道AI会按什么流程走,不会今天一个样明天一个样。
最后分享一个我常用的调试命令组合,基本能覆盖90%的skill开发场景:
# 渲染prompt但不调用agent skills run my-skill --dry-run --input key=value # 跑单个测试并输出详细日志 skills test my-skill --verbose --case "should write test first" # 对比两个版本的skill输出差异 skills diff my-skill@1.2.0 my-skill@1.3.0 --input key=valueskills diff这个命令文档里没怎么写,但实际很好用。它会用同一个输入分别跑两个版本的skill,然后把输出并排显示。改prompt的时候,用这个命令能快速看出改动有没有引入意外变化。