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”的 SkillSKILL.md 里洋洋洒洒列了十几个步骤先读 entity再生成 DTO再写 mapper再写 service最后写 controller。看起来很完整对吧但实际用的时候Claude 经常在第一步就卡住或者跳步执行或者干脆不触发这个 Skill。后来我才想明白Skill 的读者不是编译器是模型。编译器需要精确的指令序列模型需要的是意图、边界和判断依据。你写“第一步读 entity”模型会问自己“为什么要读 entity”“如果项目里没有 entity 怎么办”“读完之后判断标准是什么”。这些你没写它就只能猜猜错了就是“Skill 不好用”。正确的写法应该是反过来先告诉模型这个 Skill 解决什么问题再告诉它什么情况下该用最后才是大致怎么做。步骤是参考不是铁律。模型需要的是理解你的意图而不是执行你的脚本。2.2 SKILL.md 的三个核心字段description、when_to_use、instructions我现在写 SkillSKILL.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 写得太“官方”。比如我写过一个“代码审查”的 Skillwhen_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 的事塞进 Skill4.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 也要有 changelogSkill 是会迭代的迭代多了之后你会忘记某个版本为什么改。我现在每个 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。
阅读完成 · 觉得有帮助?