ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

agent-skills实战:为AI编程代理构建可测试可版本化的技能规范

agent-skills实战:为AI编程代理构建可测试可版本化的技能规范 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叫什么、版本是多少、适用于哪些agentClaude 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.2B项目用的是v1.3你根本记不住哪个项目用的哪个版本。依赖冲突skill A依赖skill B的某个输出格式但你只更新了A没更新B。测试缺失你改了一个skill的prompt但没有任何机制告诉你这个改动会不会破坏原有的行为。skillsCLI解决的就是这些问题。它的核心命令大概有这几个# 安装一个skill到当前项目 skills install test-driven-development # 安装指定版本 skills install test-driven-development1.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实验性skill4.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的promptCI会自动跑一遍测试。如果测试挂了说明改动破坏了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改改bugagent-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 keyvalue # 跑单个测试并输出详细日志 skills test my-skill --verbose --case should write test first # 对比两个版本的skill输出差异 skills diff my-skill1.2.0 my-skill1.3.0 --input keyvalueskills diff这个命令文档里没怎么写但实际很好用。它会用同一个输入分别跑两个版本的skill然后把输出并排显示。改prompt的时候用这个命令能快速看出改动有没有引入意外变化。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进