1. 从“写了50个”到“前30个白写”:一个Skill作者的认知转折
我大概是在去年年底开始密集写 Claude Code Skill 的。那会儿刚把 Claude Code 装进日常工作流,发现它能直接读写文件、跑终端命令、按 SKILL.md 的约定加载能力,整个人处于一种“这东西能干的活太多了”的兴奋状态。于是我开始疯狂地写:给项目写一个代码规范检查的 Skill,给文档写一个自动摘要的 Skill,给数据库写一个表结构导出的 Skill,甚至给周报写了一个自动汇总的 Skill。前后加起来,五十个是有的。
但真正让我停下来反思的,是某天我打开~/.claude/skills目录,发现自己居然想不起来其中一半的 Skill 是干什么用的。更尴尬的是,有几个 Skill 我写完之后一次都没触发过——不是不好用,是 Claude 压根没在合适的时机调用它们。那一刻我才意识到:写 Skill 这件事,数量从来不是目标,被正确触发才是。
前三十个 Skill 之所以“白写”,核心原因就三个字:没边界。我把 Skill 当成了“功能清单”,而不是“能力契约”。一个 Skill 如果描述得太宽泛,Claude 不知道什么时候该用它;如果描述得太窄,又会在稍微变形的场景下失效。这个度,我用了三十个废案才摸清楚。
这篇文章不是教程,是我踩完坑之后重新梳理的一套方法论。如果你正在写 Skill,或者写了一批发现效果不理想,下面这些内容应该能帮你少走三十个 Skill 的弯路。我会从 Skill 的本质讲起,聊清楚 SKILL.md 到底该怎么写、触发机制是怎么运作的、和 MCP 的边界在哪里,最后给一套我自己现在在用的模板和检查清单。
2. Skill 不是函数,是给模型看的“能力说明书”
2.1 大多数人写 Skill 的第一个误区:把它当代码写
我最初写 Skill 的思路,和写一个 Python 函数几乎一样:定义输入、定义输出、写清楚每一步做什么。比如我写过一个“生成 Spring Boot Controller”的 Skill,SKILL.md 里洋洋洒洒列了十几个步骤:先读 entity,再生成 DTO,再写 mapper,再写 service,最后写 controller。看起来很完整对吧?但实际用的时候,Claude 经常在第一步就卡住,或者跳步执行,或者干脆不触发这个 Skill。
后来我才想明白:Skill 的读者不是编译器,是模型。编译器需要精确的指令序列,模型需要的是意图、边界和判断依据。你写“第一步读 entity”,模型会问自己“为什么要读 entity”“如果项目里没有 entity 怎么办”“读完之后判断标准是什么”。这些你没写,它就只能猜,猜错了就是“Skill 不好用”。
正确的写法应该是反过来:先告诉模型这个 Skill 解决什么问题,再告诉它什么情况下该用,最后才是大致怎么做。步骤是参考,不是铁律。模型需要的是理解你的意图,而不是执行你的脚本。
2.2 SKILL.md 的三个核心字段:description、when_to_use、instructions
我现在写 Skill,SKILL.md 的 frontmatter 里至少会写清楚三样东西:
--- name: spring-boot-controller-generator description: 根据已有的 Entity 和 Service 接口,生成符合项目规范的 Spring Boot Controller 层代码 when_to_use: 当用户要求新增 REST 接口、补充 Controller 层、或者提到"给某个实体加 CRUD 接口"时使用 ---这三个字段的分工非常明确:
- description回答“这是什么”。要具体到技术栈和产出物,不要写“帮助处理代码”这种废话。
- when_to_use回答“什么时候用”。这是触发率的关键,必须包含用户可能说的原话、同义表达和典型场景。
- instructions回答“怎么做”。放在正文里,用自然语言描述流程、约束和判断标准。
我做过一个粗略统计:把 when_to_use 写清楚之后,Skill 的触发准确率大概能从三成提到七成以上。剩下的三成,靠的是 description 的措辞和正文里的边界说明。
2.3 一个反直觉的结论:Skill 写得越“聪明”,越容易失效
我早期有个毛病,喜欢在 Skill 里塞各种条件分支:“如果项目用 MyBatis 就这样,如果用 JPA 就那样”“如果检测到 Lombok 就省略 getter”。写的时候觉得自己很周全,实际用的时候模型经常在分支判断上出错——因为它没有你脑子里的项目上下文,它只能根据当前对话里出现的信息判断。
后来我改成一个原则:一个 Skill 只做一件事,分支交给模型自己判断。比如“生成 Controller”这个 Skill,我只写清楚“生成符合 RESTful 规范的 Controller,遵循项目现有的命名和注解风格”,至于用 MyBatis 还是 JPA,模型读一下项目里的其他 Controller 就知道了,不需要我在 Skill 里写 if-else。
这个原则的代价是 Skill 数量会变多,但每个 Skill 的可靠性和可维护性都上了一个台阶。五十个里真正在用的那二十个,基本都是单一职责的。
3. 触发机制:为什么你的 Skill 总是“叫不醒”
3.1 Claude Code 加载 Skill 的实际逻辑
要理解触发问题,得先知道 Claude Code 大概是怎么处理 Skill 的。根据我自己的观察和社区里的讨论,流程大致是这样的:启动时扫描 skills 目录,读取每个 SKILL.md 的 frontmatter,把 name、description、when_to_use 这些元信息注入到模型的上下文里。当用户输入一句话时,模型会先判断“当前任务是否匹配某个 Skill 的描述”,匹配上了才会去读完整的 instructions 并执行。
这里有个关键点:模型看到的是元信息,不是完整内容。也就是说,你的 Skill 能不能被触发,几乎完全取决于 frontmatter 里那几行字写得好不好。正文写得再精彩,元信息没写对,模型根本不会翻到那一页。
我踩过的最典型的坑,是 when_to_use 写得太“官方”。比如我写过一个“代码审查”的 Skill,when_to_use 写的是“当用户需要进行代码质量审查时使用”。结果用户说“帮我看看这段代码有没有问题”,模型没触发;用户说“这个函数写得怎么样”,模型也没触发。后来我改成“当用户要求 review 代码、检查代码问题、询问某段代码是否合理、或者粘贴代码后问‘这样写行不行’时使用”,触发率立刻上来了。
3.2 触发失败的四种典型症状与对应修法
我把触发失败归纳成四类,每类的修法不一样:
| 症状 | 根本原因 | 修法 |
|---|---|---|
| 完全不触发 | when_to_use 太抽象,没有覆盖用户的实际表达 | 把用户可能说的原话、口语化表达、同义词都列进去 |
| 偶尔触发 | description 和相邻 Skill 的职责重叠 | 明确边界,在 description 里写清楚“不负责什么” |
| 触发后不执行 | instructions 太模糊,模型不知道从哪下手 | 给出明确的起点动作,比如“先读取项目根目录的 pom.xml” |
| 触发后执行错 | 缺少前置条件检查 | 在 instructions 开头加一段“执行前确认” |
其中“偶尔触发”是最难排查的,因为它的原因往往不在这个 Skill 本身,而在它和别的 Skill 的职责划分上。我有两个 Skill 曾经长期互相抢触发:一个是“生成单元测试”,一个是“补充测试覆盖率”。用户说“给这个类加点测试”,两个都想触发,结果模型随机选一个。后来我把前者改成“从零生成测试类”,后者改成“在已有测试类里补充缺失的用例”,边界清晰了,触发就稳定了。
3.3 用“触发词清单”反向设计 when_to_use
我现在写 when_to_use 有个固定动作:先不写,而是去翻最近一周的对话记录,把用户可能触发这个 Skill 的原话摘出来,列成一个清单,然后再从这个清单里提炼 when_to_use。
比如我要写一个“数据库迁移脚本生成”的 Skill,我会先列:
- “帮我写个 migration”
- “加个字段,需要改表”
- “这个表结构要调整一下”
- “生成一个 alter table 的脚本”
- “数据库要加个索引”
然后 when_to_use 就写成:“当用户要求生成数据库迁移脚本、修改表结构、添加字段或索引、或者提到 migration、alter table、DDL 等关键词时使用。”
这个方法的本质是:用真实语料驱动描述,而不是用想象驱动描述。你想象的用户表达,和用户实际说的话,往往差得很远。
4. Skill 与 MCP 的边界:别把该用 MCP 的事塞进 Skill
4.1 一个常见的混淆:Skill 和 MCP 到底谁干什么
社区里经常有人问“这个功能该写成 Skill 还是 MCP”。我自己的判断标准很简单:
- Skill 是“知识和流程”:它告诉模型“遇到这类任务,应该按什么思路、什么规范、什么步骤来做”。它不直接连接外部系统,靠的是模型自身的推理能力和文件读写能力。
- MCP 是“能力和连接”:它给模型提供它本身没有的能力,比如查数据库、调 API、操作浏览器、读 Figma 设计稿。MCP 是工具,Skill 是用法。
举个例子:你要让 Claude 帮你写一个 Spring Boot 的接口。“怎么写这个接口”是 Skill(遵循什么分层、用什么注解、命名规范是什么);“项目里有哪些现成的类可以参考”是 MCP(通过 MCP 连接代码库索引或数据库)。两者配合,效果最好。
我早期犯的错,是把一些本该用 MCP 做的事硬塞进 Skill。比如我写过一个“查询数据库表结构”的 Skill,让模型去读 SQL 文件然后推断表结构。这玩意儿又慢又不准,后来换成 MCP 直接连数据库查 information_schema,一秒钟出结果。Skill 里只需要写“调用 MCP 获取表结构后,按以下规范生成 Entity”。
4.2 什么时候必须上 MCP:三个信号
判断要不要上 MCP,我看三个信号:
- 需要实时数据:数据库当前状态、API 返回结果、文件系统实时内容。这些 Skill 做不了,必须 MCP。
- 需要外部系统交互:操作浏览器、调用第三方服务、读写特定格式的文件(如 Figma、蓝湖)。这些也是 MCP 的活。
- 需要确定性执行:某些操作必须精确执行、不能靠模型推理,比如跑一个特定的构建命令、执行一个固定的部署脚本。这种用 MCP 封装成工具,比让模型自己拼命令可靠得多。
反过来,如果一件事只是“按某种规范组织代码”“按某个模板写文档”“按某个流程做检查”,那它就是 Skill 的活,不需要 MCP。
4.3 一个实际案例:Spring Boot 项目里的 Skill + MCP 组合
我现在的 Spring Boot 项目里,有一套组合是这样的:
- MCP 层:一个连数据库的 MCP,能查表结构、查索引、查外键;一个连代码库的 MCP,能按类名搜索、按注解搜索。
- Skill 层:一个“生成 Entity”的 Skill,一个“生成 Mapper”的 Skill,一个“生成 Service”的 Skill,一个“生成 Controller”的 Skill。
工作流是:用户说“给订单表加个查询接口”,模型先通过 MCP 查到订单表结构,然后触发“生成 Entity”Skill 生成实体类,再触发“生成 Mapper”Skill 生成数据访问层,依次往上。每个 Skill 只负责自己那一层,层与层之间的衔接靠模型根据项目上下文判断。
这套组合跑下来,比早期那种“一个大 Skill 包办所有”的方案稳定得多。核心原因就是职责分离:MCP 管数据,Skill 管规范,模型管编排。
5. 我现在写 Skill 的固定流程:从需求到上线
5.1 第一步:先问“这个 Skill 会被谁在什么场景下触发”
我现在写任何 Skill 之前,会先花五分钟回答三个问题:
- 这个 Skill 解决的具体问题是什么?用一句话说清楚,说不清楚就别写。
- 用户在什么场景下会需要它?把场景描述出来,越具体越好。
- 它和现有 Skill 的边界在哪里?会不会和已有的 Skill 抢触发?
这三个问题答不上来,说明这个 Skill 还不该写。我前三十个废案里,至少有一半是死在这一步——需求本身就不清晰,写出来的 Skill 自然也不清晰。
5.2 第二步:写一个“最小可触发版本”
确定要写之后,我不会一上来就写完整流程。我会先写一个最小版本:frontmatter 写清楚,instructions 只写核心的三五步,然后立刻拿去用。
这个最小版本的目的不是好用,是验证触发。如果连触发都触发不了,写再多 instructions 也没用。触发验证通过之后,再逐步补充 instructions 里的细节、边界、异常处理。
我现在的习惯是:一个 Skill 从最小版本到稳定版本,大概要经过三到五轮迭代。每轮迭代的依据都是实际使用中遇到的问题,而不是我坐在那里想“可能还需要什么”。
5.3 第三步:用“反例测试”验证边界
Skill 写完之后,我会做一组反例测试:故意说一些不该触发这个 Skill 的话,看它会不会误触发。
比如我写了一个“生成单元测试”的 Skill,反例测试会包括:
- “这个测试为什么失败了”(这是排查问题,不该触发)
- “帮我跑一下测试”(这是执行命令,不该触发)
- “测试覆盖率是多少”(这是查询,不该触发)
如果这些反例触发了 Skill,说明 when_to_use 写得太宽,需要收窄。反例测试是控制误触发最有效的手段,比正例测试还重要。
5.4 第四步:给 Skill 加“退出条件”
这是我最近才加的一个习惯:在 instructions 里写清楚什么情况下应该停止执行这个 Skill。
比如“生成 Controller”的 Skill,我会写:“如果项目中没有对应的 Service 接口,停止执行并提示用户先创建 Service 层。”这个退出条件能避免模型在信息不全的情况下硬编,生成一堆用不了的代码。
退出条件的本质是承认 Skill 的能力边界。一个 Skill 不可能处理所有情况,与其让它硬撑,不如让它在该停的时候停下来,把问题交回给用户。
6. 那些让我少走弯路的实操细节
6.1 命名:用“动词+对象+场景”而不是“功能名”
我早期 Skill 的命名很随意,比如code-helper、doc-tool、db-util。这种名字的问题是模型看到之后不知道它具体干什么,触发全靠 description。后来我改成“动词+对象+场景”的格式:
generate-spring-boot-controllerreview-java-code-styleexport-database-schemasummarize-meeting-notes
这种命名方式的好处是,即使模型没读 description,光看名字也能大致判断这个 Skill 是干什么的,触发准确率会高一些。
6.2 版本管理:Skill 也要有 changelog
Skill 是会迭代的,迭代多了之后,你会忘记某个版本为什么改。我现在每个 Skill 目录下都会放一个CHANGELOG.md,记录每次修改的原因和效果。
比如:
## 2024-11-15 - 修改 when_to_use,增加"加个接口"等口语化表达 - 效果:触发率从 40% 提升到 75% ## 2024-11-20 - 在 instructions 开头增加前置检查:确认 Service 层存在 - 效果:减少了 80% 的无效生成这个 changelog 看起来麻烦,但它是我判断“这个 Skill 到底有没有变好”的唯一依据。没有它,改来改去都是凭感觉。
6.3 目录结构:一个 Skill 一个目录,别偷懒
Claude Code 加载 Skill 是按目录扫描的。我见过有人把所有 Skill 塞在一个大文件里,或者用奇怪的嵌套结构,结果加载不稳定。我现在固定用这个结构:
~/.claude/skills/ generate-spring-boot-controller/ SKILL.md CHANGELOG.md examples/ example-input.md example-output.md review-java-code-style/ SKILL.md CHANGELOG.mdexamples目录是可选的,但对于复杂的 Skill 很有用。放一两个输入输出的例子,模型在 instructions 不够明确的时候可以参考。
6.4 一个容易被忽略的点:Skill 的加载顺序
Claude Code 加载 Skill 的顺序,会影响模型在多个 Skill 都能触发时的选择。我观察到的一个规律是:后加载的 Skill 在冲突时更容易被选中。所以如果你有两个职责相近的 Skill,把更常用的那个放在目录里靠后的位置(按字母序或修改时间),可能会影响触发结果。
这个规律不是官方文档写的,是我自己反复测试观察到的,不一定对所有版本都成立。但如果你遇到两个 Skill 抢触发的问题,可以试试调整它们的相对位置。
7. 从“白写三十个”里提炼出的检查清单
7.1 写之前:三个必须回答的问题
- 这个 Skill 解决的具体问题是什么?一句话说不清楚就别写。
- 用户在什么场景下会触发它?把用户可能说的原话列出来。
- 它和现有 Skill 的边界在哪里?会不会抢触发?
7.2 写的时候:frontmatter 的硬性要求
name用“动词+对象+场景”格式,不要用泛化的功能名。description写清楚技术栈和产出物,不要写“帮助处理”这种废话。when_to_use必须包含用户的实际表达、同义表达和典型场景。
7.3 写完之后:三项验证
- 正例测试:用你列出的触发词逐个测试,看是否都能触发。
- 反例测试:用不该触发的话测试,看是否误触发。
- 边界测试:在信息不全、条件不满足的情况下测试,看是否正确退出。
7.4 上线之后:持续迭代
- 每次修改都记 changelog,写清楚改了什么、效果如何。
- 定期清理长期不触发或触发后效果差的 Skill。
- 关注 Skill 之间的触发冲突,及时调整边界。
8. 关于 Skill 这件事,我现在的真实看法
写了五十个 Skill,废了三十个,剩下的二十个里真正高频使用的也就七八个。这个比例听起来很低,但我觉得很正常。Skill 这东西,本质上是在给模型“补课”——补的是它对你项目、你团队、你个人工作习惯的了解。补课的内容不可能一次到位,必然要经过反复调整。
我现在对 Skill 的态度,从早期的“多写点总有用”变成了“少写点、写准点”。一个触发稳定、边界清晰的 Skill,价值远大于十个模棱两可的 Skill。如果你刚开始写,我的建议是:先写三个,用两周,把这三个打磨到触发率九成以上,再考虑写第四个。这个节奏比一口气写三十个然后全部推倒重来,要快得多。
另外,别把 Skill 当成孤立的工具。它和 MCP、和项目上下文、和你的使用习惯是连在一起的。一个 Skill 好不好用,往往不取决于 Skill 本身,而取决于它有没有被放在正确的工作流里。我现在的做法是:每写一个 Skill,都先想清楚它在整个工作流里的位置,以及它和上下游怎么衔接。这个思考过程,比写 SKILL.md 本身更重要。
最后分享一个我最近在用的技巧:给每个 Skill 写一句“一句话定位”,放在 SKILL.md 的最开头。这句话不参与触发,只是给我自己看的。当我打开 skills 目录,看到每个 Skill 的第一行都是清晰的一句话定位时,我就知道哪些该留、哪些该删了。这个习惯帮我砍掉了不少“食之无味弃之可惜”的 Skill。