如何写好 AI Skill
我以前也把 Skill 理解成一份写给 AI 的长 Prompt:把背景、要求和几个示例放在一起,文件越完整越好。真正开始维护之后,感觉很快变了。
一个 Skill 好不好用,通常不取决于它写了多少内容,而取决于 AI 能不能在正确的任务里加载它,按步骤执行,并在出错时停下来。它更像一份可执行的流程资产,而不是一篇知识介绍。
Skill 解决什么问题
团队里的很多经验不会出现在正式文档里:某个旧接口不能直接删、某条命令上线前必须执行、某种外部输入不能直接当指令、发布后还要检查线上页面。
这些事情如果每次都重新解释,结果很容易不一致。Skill 的作用,是把其中一类重复任务固定下来,至少明确四件事:
- 什么时候应该使用
- 具体按什么顺序执行
- 什么情况下应该停止
- 完成后如何验证
一个最小的 Skill 目录可以很简单:
my-skill/
├── SKILL.md
├── scripts/
├── references/
└── assets/
SKILL.md 放触发条件、流程和边界;脚本、模板和较长的参考资料放在其他目录。不要一开始就把所有东西塞进一个文件。
先写清楚触发条件
Skill 的元数据里,description 往往比正文更早发挥作用。它至少要说清楚任务是什么、什么时候使用,以及覆盖哪些动作。
不太有用的写法:
description: 处理代码迁移
更具体的写法:
description: >
将 Go 项目中的旧版 HTTP 客户端迁移到统一请求库。
当项目仍引用 old-http-client,且用户要求迁移、替换或修复相关调用时使用。
包含 import 替换、参数适配、错误处理和测试验证。
触发条件写得太宽,Skill 会在不相关的任务里出现;写得太窄,又可能根本匹配不到。我的经验是,先把“应该触发”和“不应该触发”的例子各列几条,再回头修改描述,比凭感觉写一句总结更可靠。
把规则写成可以执行的步骤
“先检查版本,再选择方案”对人来说足够,对 AI 来说通常不够。应该把判断条件和下一步写出来:
1. 读取项目版本信息。
2. 如果 Go 版本低于 1.18,使用兼容写法。
3. 如果 Go 版本不低于 1.18,使用原生泛型。
4. 修改后运行 go test ./...。
5. 测试失败时停止,不要继续修改其他文件。
每个步骤最好能回答三个问题:
- 需要检查什么
- 根据结果采取什么动作
- 什么时候结束或暂停
尤其是最后一个问题。只写“完成迁移”而没有停止条件,AI 很容易在目标不明确时继续扩大修改范围。
给原因,也给改前改后
规则后面补一句原因,能减少机械执行。例如:
使用参数化查询,不要拼接 SQL 字符串。
原因:字符串拼接可能引入 SQL 注入风险。
对于代码改造,Before / After 通常比长篇解释更有效:
- import oldhttp "github.com/example/old-http-client"
+ import uhttp "github.com/example/unified-httpclient"
- return oldhttp.Do(req)
+ return uhttp.Do(req)
如果任务包含分类或分级输出,可以准备 3~5 个覆盖不同分支的样例。样例不用多,但要让 AI 看见边界:什么是严重问题,什么只是建议,什么情况应该跳过。
一个足够小的例子
下面这个 Skill 只负责为 Go 函数生成表驱动测试,没有把代码审查、依赖升级和发布流程也混进来:
---
name: go-tabletest
description: >
为 Go 函数生成表驱动单元测试。
当用户要求补单测、生成测试用例或提升覆盖率时触发。
适用于常见 Go 项目。
---
# Go 单元测试生成
## 目标
为指定函数生成标准 testing 风格的表驱动测试。
## 规则
1. 仅使用标准库 testing。
2. 使用 t.Run 组织子测试。
3. 覆盖正常值、边界值和异常输入。
4. 不修改生产代码,除非用户明确要求。
## 验证
```bash
go test ./... -v
```
它已经包含了触发描述、任务边界、执行规则和验证命令。对于一个小任务,这比一份几百行但没有明确流程的总手册更容易稳定执行。
什么时候应该拆分
Skill 变长并不一定是问题,但出现下面情况时,通常应该拆:
- 一个文件里混了多个互不依赖的任务
- 不同部分的更新频率差别很大
- 某些步骤可以单独复用
- 主要流程已经很难从细节中找出来
可以让主文件负责流程编排,把具体步骤放到参考文件中:
project-migration/
├── SKILL.md
└── references/
├── environment-check.md
├── dependency-update.md
└── api-migration.md
主流程每完成一个高风险步骤,就应该有一个检查点。例如依赖替换后运行:
go mod tidy
go build ./...
如果检查失败,先停下来处理,不要继续执行后面的步骤。这样定位问题会容易很多。
外部服务和密钥
需要访问数据库、浏览器、GitHub 或内部 API 时,可以使用 MCP,也可以在脚本中直接调用 HTTP。选择标准不必复杂:
| 场景 | 更合适的方式 |
|---|---|
| 已经有成熟的 MCP Server | MCP |
| 多个 Skill 都要复用同一个服务 | MCP |
| 需要统一鉴权和审计 | MCP |
| 一次性、简单的 API 调用 | HTTP 脚本 |
无论选择哪种方式,都不要把 API Key 写进 SKILL.md 或脚本源码。至少先检查环境变量:
if [ -z "$API_KEY" ]; then
echo "请先设置环境变量 API_KEY"
exit 1
fi
如果流程会改数据库、覆盖文件或删除资源,Skill 里必须写清楚备份、确认、验证和回滚。危险操作不能只用一句“谨慎执行”带过。
怎么测试一个 Skill
不要只在一次成功对话后就认为 Skill 可用了。可以准备一组小测试:
- 10 个应该触发的请求
- 10 个不应该触发的请求
重点看四件事:
| 维度 | 检查内容 |
|---|---|
| 触发准确性 | 该触发时能否触发,不相关时能否跳过 |
| 执行稳定性 | 步骤和输出是否基本一致 |
| 验证完整性 | 是否真的运行了检查命令 |
| 安全边界 | 是否会跳过确认、备份或回滚 |
不同问题对应的修正方式也不一样:匹配不到,先改路径和 description;执行跑偏,补清楚步骤;输出不稳定,增加 Before / After;不自检,给出明确的验证命令。
结合实际维护来写
对我来说,Skill 最有价值的地方,是把已经踩过的坑提前写进流程里。比如博客发布 Skill 中,下面这些规则都不是为了让文档看起来完整,而是实际维护时确实需要:
- 文章只能有一套 Front Matter
- 发布前必须检查裸 URL、代码围栏和 Markdown 表格
- 构建出现 warning 或 error 时不能继续推送
- 图片上传失败时要明确保留原路径或停止
- 外部文章抓取到登录墙时不能当成正文发布
- 推送后要核对本地和远程提交是否一致
这些内容放在普通 Prompt 里,很容易在下一次对话中被遗漏;放进 Skill 后,它们就成为每次执行都要经过的检查点。
因此,我现在会优先把以下任务写成 Skill:
- 高频重复、步骤比较固定的任务
- 容易漏检查、出错代价较高的任务
- 有明确输入、输出和验证方式的任务
- 需要遵守安全边界或项目约定的任务
反过来,一次性、目标模糊、没有可验证结果的事情,不适合急着做成 Skill。
发布前检查清单
[ ] 目标和适用范围是否明确
[ ] description 能否区分应该触发和不应该触发的请求
[ ] 每一步是否能直接执行
[ ] 是否写了停止条件
[ ] 是否提供了必要的 Before / After 或示例
[ ] 关键步骤后是否有验证命令
[ ] 密钥、删除、覆盖和数据库操作是否有安全边界
[ ] 文件过长时是否应该拆成参考资料或多个 Skill
[ ] 是否用真实任务测试过,而不是只检查 Markdown 格式
好的 Skill 不需要把所有知识都装进去。先从一个边界清楚、容易验证的小任务开始,执行几次,记录哪里出错,再把这些错误变成下一版的规则。这样写出来的 Skill 才会越来越像工作流程,而不是一篇越来越长的 Prompt。