Skill-最基础的 Skill 该怎么写
一、Skill 到底是什么
先纠正常见误区:Skill 不是一个文件,而是一个文件夹。
物理上看,它就是一个文件夹,里面放一个 SKILL.md 文件,再加上一些可选的脚本和参考资料。核心就三样东西:
- 指令(Instructions):告诉 AI 按什么步骤干活——提供经验和判断
- 脚本(Scripts):可执行代码处理确定性任务——提供能力和执行
- 上下文(Context):AI 不可能凭空知道的项目背景、团队规范、业务规则
打个比方:裸着的 AI 就像一个刚入职的新人,啥都得问;装了 Skill 之后,就像拿到了老员工整理的操作手册,照着就能干。
为什么需要 Skill? 几个真实痛点:
| 痛点 | Skill 怎么解决 |
|---|---|
| 知识分散在 TAPD、Wiki、某个人脑子里 | 全部结构化封装为标准技能包 |
| 同样的活反复做,每次都手动来 | 写成 Skill 让 AI 自动跑 |
| 张三一个样,李四另一个样 | 用 Skill 固定流程,统一标准 |
| 新人上手慢,得教半天 | Skill 本身就是最好的培训材料 |
| 核心成员离职,"部落知识"丢失 | 经验沉淀进 Skill,知识完整留存 |
二、Skill 怎么运作:渐进式加载
Anthropic 设计了一个三级加载机制,这是理解 Skill 一切设计决策的基石:
Level 1: name + description → 始终驻留在 AI 上下文(约 100 词)
Level 2: SKILL.md 正文 → Skill 被触发时加载(建议 ≤500 行)
Level 3: 脚本和参考资料 → 执行过程中按需引用(不限大小)这意味着什么?
- 你有 100 个 Skill,上下文也不会炸——AI 只在 Level 1 看到它们的 description
- SKILL.md 正文只在触发时才占用上下文——不触发不付费
- Level 3 的脚本可以直接执行而不读入上下文——真正零 token 消耗
核心原则:Level 1 越精准越好(决定触发),Level 2 越精简越好(减少 token 消耗),Level 3 放心放大(按需加载)。
三、长什么样:最简结构 → 完整结构
最简版(刚够用)
my-skill/
└── SKILL.md # 核心配置文件(唯一必需的文件)完整版(复杂场景)
my-skill/
├── SKILL.md # 导航页 + 核心工作流 + 关键经验
├── scripts/ # 可执行脚本(Python/Bash)
│ ├── pre-check.sh
│ └── transform.py
├── references/ # 参考文档(按需加载)
│ ├── api-spec.md
│ └── style-guide.md
├── examples/ # 示例(Few-Shot 用例)
│ └── sample-output.md
└── assets/ # 静态资源(模板、图片)
└── template.jsonSKILL.md 的职责是"导航页"——告诉 AI 核心工作流是什么、遇到什么情况该去读哪个子文件、运行哪个脚本。它不是一个大而全的操作手册,而是一个索引 + 核心流程 + 关键经验的组合。
四、5 分钟写出你的第一个 Skill
以"自动生成 Go 单元测试"为例:
Step 1:创建目录
mkdir -p ~/.claude/skills/go-test-gen
touch ~/.claude/skills/go-test-gen/SKILL.mdStep 2:写 SKILL.md
---
name: go-test-gen
description: 为 Go 函数自动生成表驱动的单元测试。当用户要求"写测试"、"生成测试用例"、"补充单测"、"加 test"时触发。适用于所有 Go 项目。
---
# Go 单元测试生成
## 目标
为指定的 Go 函数生成表驱动(table-driven)风格的单元测试。
## 规则
1. 使用 `testing` 标准库,不引入第三方测试框架
2. 测试函数命名为 `TestXxx`(与被测函数对应)
3. 使用 `t.Run` 子测试 + 表驱动模式
4. 覆盖:正常输入、边界值、错误输入 三类场景
## 示例
**输入函数:**
```go
func Add(a, b int) int {
return a + b
}生成的测试:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 1, 2, 3},
{"zero values", 0, 0, 0},
{"negative numbers", -1, -2, -3},
{"mixed signs", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}验证
go test ./... -v -run TestXxx
### Step 3:直接测试
在对话中输入"帮我为 `pkg/utils/math.go` 里的函数写单元测试"——AI 应该会自动触发这个 Skill。不需要重启,修改后热重载立即生效。
---
## 五、Frontmatter 字段速查
| 字段 | 必填 | 说明 |
|------|------|------|
| `name` | 是 | Skill 标识符,小写,用连字符分隔 |
| `description` | 是 | **最关键**:描述什么时候该加载这个 Skill |
| `version` | 否 | 语义化版本号 |
| `user-invocable` | 否 | 是否允许手动 `/name` 调用,默认 true |
| `argument-hint` | 否 | 参数提示,如 `<arg> [optional]` |
| `allowed-tools` | 否 | 预授权工具,减少弹窗确认 |
| `disallowed-tools` | 否 | 强制禁用的工具 |
| `model` | 否 | 覆盖模型(haiku/sonnet/opus) |
| `context` | 否 | `fork` = 在隔离子 agent 中执行 |
---
## 六、Description 是灵魂:它不是功能介绍,是路由规则
**这是写 Skill 最重要的一行。** Claude 启动时扫描所有 Skill 的 name + description,然后根据用户当前问题判断该加载哪个 Skill。所以 Description 本质上是一个**路由规则**——它回答的不是"这个 Skill 能干什么",而是"什么情况下应该加载它"。
### 三个核心原则
**1. 描述用户意图,而非罗列功能**
```yaml
# ❌ 功能介绍写法
description: PR 管理工具,帮助用户监控 PR 状态、处理 CI 问题、自动合并。
# ✅ 意图路由写法
description: 当用户说"帮我盯一下这个 PR"、"CI 又挂了"、"合并到 main"、"review 这个 MR"时触发。用于管理 GitHub PR 的状态监控、CI 排障和自动合并。2. 用第三人称,包含具体触发短语
# ❌ 太模糊
description: 处理代码迁移。
# ✅ 精准
description: 将项目中的旧版 HTTP 客户端迁移到新版统一请求库。适用于 Go 项目中使用了 old-http-client 需要替换为 unified-httpclient 的场景。包含 import 路径替换、请求参数适配和错误处理改造。3. 略微"pushy"
Claude 倾向于"不触发" Skill,所以 description 要多列触发短语、多列具体场景。宁可多触发几次,不要该触发时不触发。
一个简单检验方法
写完 Description 之后,把整个 Skill 正文删掉,只保留这一行 Description,然后问自己:模型看到用户的一句话问题之后,能不能准确判断什么时候该加载这个 Skill? 如果做不到,继续改。
七、正文写作规范
7.1 用祈使句,不要商量
# ✅ 好:直接下指令
检查 Go 版本。根据版本号选择对应方案:
- Go < 1.18 → 使用 interface{} 做泛型替代
- Go >= 1.18 → 使用原生泛型
# ❌ 差:商量的口吻
你应该检查 Go 版本,然后你需要选择合适的方案。7.2 解释"为什么",而非堆砌"MUST"
# ✅ 好:让 AI 理解原因
使用参数化查询而非字符串拼接来构建 SQL。
字符串拼接会导致 SQL 注入——攻击者可通过输入 `'; DROP TABLE users; --` 来删除整张表。
# ❌ 差:死记硬背
必须使用参数化查询!绝对不能拼接字符串!AI 理解了背后的道理,遇到你没想到的情况也能做出合理判断。
7.3 给出 Before/After 对比
这是 Skill 中最关键的部分——让 AI 清楚知道"改什么"和"改成什么":
// Before
import oldhttp "github.com/example/old-http-client"
// After
import uhttp "github.com/example/unified-httpclient"7.4 放 3-5 个 Few-Shot 示例
覆盖:最常见场景 + 稍有变化 + 边界/特殊情况。AI 看示例比读文字描述理解得更准确。
7.5 Skill 里最有价值的其实是 Gotchas
不要写 AI 已经知道的常识。真正有价值的是"老师傅经验"——那些模型根本不知道、只存在于员工脑子里的东西:
## ⚠️ 常见陷阱
- staging 环境返回 200 不代表发布成功,需进一步检查 deployment_status 表
- 这个表不能按 created_at 排序,必须用 event_time
- request_id 和 trace_id 在日志里是同一个字段,不要重复关联八、放哪里与生效范围
| 路径 | 作用范围 | 适合放什么 |
|---|---|---|
~/.claude/skills/ | 所有项目可见 | 个人偏好、通用工具 |
项目根/.claude/skills/ | 当前项目 | 团队共享的项目规范 |
九、Skill 和 Rule 的区别
新手容易搞混。一句话:Rule 是"底线",Skill 是"技能"。
| 维度 | Rule | Skill |
|---|---|---|
| 定位 | 全局约束,始终生效 | 按需触发的能力包 |
| 加载方式 | 每次对话都自动加载 | 匹配意图时才加载 |
| 典型内容 | 编码规范、安全红线 | 迁移流程、审查模板、项目初始化 |
| 长度 | 宜短(始终占上下文) | 可长(触发时才占) |
| 触发 | 无需触发,一直生效 | 依赖 description 匹配 |
选择建议:所有对话都该遵守的 → 写 Rule;特定任务才需要的 → 写 Skill。
十、总结:一个最小 Skill 的完整清单
- [ ] 创建文件夹 +
SKILL.md - [ ] YAML frontmatter 有
name和description - [ ] Description 写的是"什么时候用"而非"能干什么"
- [ ] 正文用祈使句,解释"为什么"
- [ ] 至少有一个 Before/After 示例
- [ ] 标注了关键 Gotchas
- [ ] 有验证清单或检查命令
最小 Skill = 一个文件夹,里面一个 SKILL.md,写好 name + description + 几段指令。能跑起来,然后在实际使用中慢慢打磨。好的 Skill 都是一点点改出来的,不是一次写完就封存的。