1. 从“skills”这个热词说起:它到底在解决什么问题
最近半年,不管是在技术社区还是开发者群聊里,“skills”这个词出现的频率高得离谱。你如果只看字面意思,可能会以为它说的是“技能”这个泛泛的概念,但放到当下的开发语境里,它其实指向一个非常具体的东西:给 AI 编程助手(比如 Claude Code、Codex 这类工具)安装可复用的能力模块。你可以把它理解成给一个刚入职的实习生发了一本《岗位操作手册》,手册里写清楚了遇到什么情况该怎么做、用什么工具、遵循什么规范。没有这本手册,实习生也能干活,但干出来的东西可能五花八门;有了手册,输出质量就稳定得多。
我最初接触这个概念的时候,也走过弯路。当时我以为 skills 就是普通的提示词模板,复制粘贴一段文字丢给 AI 就完事了。后来实际用下来才发现,真正的 skills 是一套有结构、有触发条件、有执行逻辑的工程化配置。它通常包含几个核心部分:触发描述(告诉 AI 什么时候该用这个 skill)、执行指令(具体怎么做)、依赖声明(需要哪些工具或插件)、以及输出规范(结果应该长什么样)。这四样东西缺一个,skill 的可用性就会打折扣。
那为什么现在大家都在聊这个?因为 AI 编程助手已经从“能写代码”进化到了“能按规范写代码”的阶段。早期的工具你问它一句它答一句,现在你可以给它装上一整套 skills,让它在你打开项目的时候自动识别技术栈、自动加载对应的编码规范、自动跑测试、自动生成符合团队要求的提交信息。这个变化带来的效率提升是实打实的,我自己的项目里,光是代码审查这一块的返工率就降了将近四成。
这篇文章适合谁看?如果你是刚接触 Claude Code 或 Codex 的新手,想搞清楚 skills 到底怎么装、怎么用、怎么自己写,那接下来的内容会给你一条完整的路径。如果你已经在用这些工具但总觉得输出不够稳定,那问题很可能就出在 skills 的配置上。我会从整体设计思路讲到具体实操,再到踩过的坑和排查方法,尽量把每个环节都说透。
2. 整体设计思路:为什么 skills 要这样组织
2.1 核心思路:把“隐性知识”变成“显性指令”
任何一个团队里,老员工和新员工的差距往往不在硬技能上,而在那些“没人明说但大家都知道”的隐性规则上。比如提交代码前要跑哪几个检查、日志格式要遵循什么约定、遇到特定报错该查哪个文档。这些知识散落在每个人的脑子里,新人只能靠试错来积累。skills 的核心价值,就是把这些隐性知识抽出来,写成 AI 能读懂的显性指令。
我见过不少团队的做法是写一份很长的 README,把所有规范都塞进去,然后指望 AI 每次都能记住。实际用下来效果很差,因为 AI 的上下文窗口是有限的,你把一堆不相关的内容塞进去,反而会稀释真正重要的指令。skills 的设计思路正好相反:按需加载,精准触发。每个 skill 只负责一个具体的场景,AI 在遇到这个场景时才去读取对应的指令,这样既节省了上下文,又提高了执行的准确率。
这个思路背后其实有一个很朴素的工程原则:关注点分离。你把“什么时候做”和“怎么做”分开,把“通用规范”和“特定场景规范”分开,整个系统就变得可维护了。我自己的项目里,skills 目录下分了四层:基础层放编码风格和提交规范,框架层放 React、Vue 这些技术栈的特定约定,工具层放测试、构建、部署相关的操作,业务层放跟具体产品逻辑相关的规则。每一层都可以独立更新,互不影响。
2.2 方案选型:为什么是 Markdown 加 YAML 的组合
你如果去看 Claude Code 或 Codex 的 skills 目录结构,会发现一个很明显的特征:指令主体用 Markdown 写,元信息用 YAML 写。这个组合不是随便选的。Markdown 的优势在于它对自然语言友好,你可以用标题、列表、代码块来组织复杂的操作步骤,AI 读起来也顺畅。YAML 的优势在于它结构化程度高,适合放那些需要被程序解析的字段,比如触发关键词、依赖的工具名、版本号这些。
我试过纯 Markdown 的方案,把触发条件也写在正文里,结果 AI 经常忽略掉那些条件,在不该用的时候也用了。后来改成 YAML front matter 来声明触发条件,准确率明显上来了。因为 YAML 部分在解析时会被单独提取出来,AI 在判断是否触发时只看这部分,不会被正文的长篇描述干扰。
还有一个细节值得说:skill 的命名。我见过有人用skill-001、skill-002这种编号,也见过用中文命名的。实测下来,用英文小写加连字符的命名方式最稳,比如react-component-style、api-error-handling。原因是这些名字会出现在文件路径和引用里,中文或特殊字符在某些环境下容易出问题。而且英文命名在 AI 解析时歧义更少,它不需要去猜这个词的边界在哪里。
2.3 避免什么问题:不要试图用一个 skill 解决所有事
新手最容易犯的错误,就是写一个“万能 skill”,把能想到的规范全塞进去。我早期就这么干过,写了一个叫coding-standards的 skill,里面从变量命名到数据库设计全都有,洋洋洒洒两千多字。结果呢?AI 在执行具体任务时,经常只记住了开头几条,后面的内容像是没看见一样。而且这个 skill 的触发条件很难写,因为它的适用范围太广了,几乎每个任务都能触发,反而失去了“精准触发”的意义。
后来我把它拆成了六个小 skill,每个只负责一个维度,比如naming-convention只管命名、error-handling只管异常处理。拆完之后,每个 skill 的正文控制在三百字以内,触发条件也清晰了,AI 的执行准确率肉眼可见地提升。这个经验告诉我:skill 的粒度应该跟“一个具体的决策点”对齐,而不是跟“一个大的知识领域”对齐。
另外要避免的是过度依赖 skill 而忽略基础提示。skills 是补充,不是替代。你仍然需要在跟 AI 对话时把当前任务的目标说清楚,skill 只是帮你在执行细节上保持一致。我见过有人装了一堆 skills 之后,跟 AI 说话就变得很简略,结果 AI 虽然按 skill 的规范执行了,但方向跑偏了。这个锅不能让 skills 来背。
3. 核心细节解析:一个 skill 文件里到底该写什么
3.1 触发条件怎么写才准
触发条件是 skill 的入口,写不好就会出现两种问题:要么该触发的时候不触发,要么不该触发的时候乱触发。我总结了一个比较稳的写法:用“场景描述 + 关键词列表”的组合。场景描述用一句话说清楚这个 skill 适用于什么情况,关键词列表放那些在用户输入里可能出现的词。
举个例子,我写了一个处理 API 错误的 skill,触发部分是这样的:
--- name: api-error-handling description: 当需要处理 HTTP 请求错误、设计错误响应格式、或编写重试逻辑时使用 triggers: - "API 错误" - "请求失败" - "错误响应" - "重试" - "error handling" ---这里有个细节:关键词要覆盖中英文两种表达。因为你在跟 AI 对话时,可能一会儿用中文一会儿用英文,如果只写中文关键词,英文提问时就触发不了。另外,关键词不要写得太泛,比如“错误”这个词太常见了,几乎每个任务都可能提到,写进去反而会导致误触发。我一般会选那些跟 skill 主题强相关的复合词,而不是单个的通用词。
还有一个经验:触发条件里不要写否定句。比如“当不涉及数据库操作时使用”,这种写法 AI 很难判断,因为它需要先确认“不涉及数据库”这个条件,而这个确认过程本身就容易出错。正确的做法是正面描述适用场景,让 AI 做正向匹配。
3.2 执行指令的层次结构
执行指令是 skill 的主体,也是最容易写乱的部分。我的建议是采用三层结构:原则层、步骤层、示例层。原则层用一两句话说明这个 skill 的核心目标是什么,步骤层用有序列表列出具体的操作流程,示例层给出一两个正例和反例。
原则层看起来有点虚,但它其实很重要。因为 AI 在执行步骤时,如果遇到步骤里没覆盖到的情况,它会回退到原则层去推断该怎么做。如果你只写步骤不写原则,AI 遇到边界情况就容易瞎猜。比如我在api-error-handling的原则层写的是:“错误处理的目标是让调用方能够明确知道发生了什么,并且能够决定是否重试。”这句话在遇到步骤里没提到的错误类型时,就能引导 AI 往“提供足够信息”的方向去处理。
步骤层我一般控制在五到七步,太多了 AI 记不住,太少了又不够具体。每一步都用动词开头,比如“检查响应状态码”、“提取错误信息”、“判断是否可重试”。这样 AI 解析时能直接映射到动作,不需要额外推理。
示例层是很多人会忽略的部分,但它的效果非常好。我给每个 skill 都配了一组对比示例:一个符合规范的写法,一个不符合的写法,然后用一句话说明为什么。AI 通过对比来学习规范,比单纯看规则要准确得多。这个技巧我是从测试驱动开发里借鉴过来的,效果立竿见影。
3.3 依赖声明和输出规范
依赖声明这部分,很多人觉得可有可无,但我实际用下来发现它挺关键的。如果你的 skill 需要调用某个外部工具,比如需要读取项目里的package.json来判断依赖版本,那你就应该在依赖声明里写清楚。这样 AI 在执行前会先确认这个文件是否存在,不存在的话它会提示你,而不是执行到一半报错。
输出规范则是告诉 AI 结果应该以什么形式呈现。比如你是希望它直接修改文件,还是只给出建议?是希望输出一段代码,还是输出一个 diff?这些如果不写清楚,AI 每次的表现可能都不一样。我一般会在输出规范里写明格式要求和确认机制。格式要求比如“输出 Markdown 表格”,确认机制比如“在修改文件前先展示将要修改的内容并等待确认”。后者在涉及重要文件时特别有用,能避免 AI 直接改坏东西。
4. 实操过程:从零开始配置一套可用的 skills
4.1 环境准备与目录结构
不管你用的是 Claude Code 还是 Codex,skills 的存放位置基本遵循一个约定:项目根目录下的.skills文件夹,或者用户主目录下的全局 skills 文件夹。我建议优先放在项目目录下,因为不同项目的规范可能不一样,放在项目里可以跟着代码一起做版本管理。
目录结构我一般这样组织:
.skills/ ├── base/ │ ├── naming-convention.md │ ├── commit-message.md │ └── code-review.md ├── framework/ │ ├── react-component.md │ └── vue-composition.md ├── tooling/ │ ├── test-runner.md │ └── build-check.md └── business/ └── order-flow.md分层的逻辑前面说过了,这里补充一点:层与层之间可以有依赖关系,但不要有循环依赖。比如business层的 skill 可以引用base层的规范,但base层不应该反过来引用business层。这个约束能保证基础规范在任何项目里都能独立使用。
创建文件的时候,我习惯先用一个模板把骨架搭好,然后再填内容。模板长这样:
--- name: your-skill-name description: 一句话说明这个 skill 的用途 triggers: - "关键词1" - "关键词2" dependencies: - "需要读取的文件或工具" --- ## 原则 这里写核心目标。 ## 步骤 1. 第一步 2. 第二步 3. 第三步 ## 示例 **正确做法:** ... **错误做法:** ...这个模板我用了大半年,基本上覆盖了大部分场景。你可以根据自己的需求调整字段,但name、description、triggers这三个是必须的,缺了任何一个都会影响 skill 的正常工作。
4.2 编写第一个 skill:以提交信息规范为例
拿一个最常用的场景来演示:规范 Git 提交信息。这个 skill 的目标是让 AI 在帮你生成提交信息时,遵循统一的格式。
先写触发条件。提交信息相关的关键词包括“提交”、“commit”、“提交信息”、“commit message”。description 写“当需要生成或检查 Git 提交信息时使用”。
然后是原则层。我写的是:“提交信息应该让阅读者在不看代码的情况下,就能理解这次变更的目的和范围。”这句话定下了基调:重点是“目的”和“范围”,而不是罗列改了哪些文件。
步骤层我列了五步:
- 查看暂存区的变更内容,识别变更类型(新增功能、修复缺陷、重构、文档更新等)
- 确定影响范围,是单个模块还是跨模块
- 按照“类型: 简述”的格式生成标题行,标题不超过 50 个字符
- 如果变更较复杂,在标题下方空一行后补充详细说明,说明变更原因和影响
- 检查是否有关联的任务编号,如果有,在末尾追加
示例层给了一组对比:
正确做法:
feat: 增加订单导出功能 支持按时间范围筛选订单并导出为 CSV 格式, 导出任务在后台异步执行,完成后通过站内信通知。 关联任务: ORD-1234错误做法:
更新了一些文件错误做法的说明我写的是:“没有说明变更类型和目的,阅读者无法判断这次提交的影响范围。”
写完这个 skill 之后,我实测了大概二十次提交,AI 生成的提交信息基本都能直接使用,偶尔需要微调但不会偏离格式。这个投入产出比是很划算的,因为写这个 skill 只花了不到半小时。
4.3 参数选择与调试过程
skill 写完之后不是就完事了,还需要调试。调试的核心是观察触发时机和执行结果,然后针对性地调整。
我一般会准备一组测试用例,覆盖三种情况:应该触发的场景、不应该触发的场景、边界场景。比如对于提交信息 skill,应该触发的是“帮我写个提交信息”,不应该触发的是“帮我写个函数”,边界场景是“帮我看看这次提交有没有问题”——这个场景既涉及提交,又涉及检查,需要判断 skill 是否适用。
调试过程中最常见的问题是触发过于敏感。我写过一个处理数据库迁移的 skill,触发词里放了“迁移”两个字,结果每次提到“数据迁移”、“代码迁移”甚至“迁移到新服务器”时都会触发,但后面这些场景跟数据库迁移完全没关系。后来我把触发词改成了“数据库迁移”、“schema 变更”、“migration 文件”这些更具体的组合,误触发就少了很多。
另一个常见问题是执行结果不稳定。同样的输入,有时候 AI 按 skill 执行了,有时候没有。排查下来发现是 skill 的正文太长,AI 在上下文里读到一半就跳过了。解决办法是把正文精简到三百字以内,把详细说明移到单独的参考文档里,skill 正文只保留核心步骤和原则。
4.4 多工具环境下的同步策略
如果你同时用 Claude Code 和 Codex,可能会遇到一个问题:两个工具的 skills 格式不完全一样。Claude Code 的 skill 文件用 YAML front matter,Codex 可能用 JSON 配置。这时候你有两个选择:要么维护两套 skills,要么写一个转换脚本。
我选的是后者。写了一个简单的 Node.js 脚本,读取.skills目录下的 Markdown 文件,解析 YAML front matter,然后生成 Codex 需要的 JSON 配置。脚本核心逻辑大概三十行:
const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); const skillsDir = path.join(__dirname, '.skills'); const output = []; function walk(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { walk(fullPath); } else if (entry.name.endsWith('.md')) { const content = fs.readFileSync(fullPath, 'utf-8'); const match = content.match(/^---\n([\s\S]*?)\n---/); if (match) { const meta = yaml.load(match[1]); output.push({ name: meta.name, description: meta.description, triggers: meta.triggers || [], path: fullPath }); } } } } walk(skillsDir); fs.writeFileSync('codex-skills.json', JSON.stringify(output, null, 2));这个脚本我放在项目的scripts目录下,每次更新 skills 之后跑一次就行。如果你用的工具更多,可以在这个基础上扩展,核心思路是一样的:以 Markdown 为单一数据源,其他格式通过转换生成。这样你只需要维护一套内容,不用操心同步问题。
5. 常见问题与排查技巧实录
5.1 触发失败:为什么我的 skill 没生效
这是被问得最多的问题。skill 没生效,通常有三个原因。第一个是文件位置不对。不同工具对 skills 目录的位置要求不一样,有的要求放在项目根目录,有的要求放在用户主目录。你先确认一下你用的工具默认读哪个位置,然后把文件放对地方。
第二个原因是YAML 格式错误。YAML 对缩进很敏感,多一个空格少一个空格都可能导致解析失败。我建议你写完 front matter 之后,用一个在线的 YAML 校验工具过一遍,确认没有语法问题。常见的错误包括:冒号后面没加空格、列表项缩进不一致、特殊字符没加引号。
第三个原因是触发词不匹配。你写的触发词是“API 错误”,但用户输入的是“接口报错”,这两个词虽然意思相近,但字面上不匹配,AI 就不会触发。解决办法是在触发词列表里多放几个同义词,覆盖不同的表达习惯。我一般会放三到五个同义词,太少覆盖不够,太多又容易误触发。
排查的时候,你可以先手动在对话里输入触发词,看 AI 有没有加载对应的 skill。如果加载了但执行不对,那是正文的问题;如果根本没加载,那就是触发条件或文件位置的问题。这个二分法能帮你快速定位问题所在。
5.2 执行偏差:AI 没有按 skill 的要求做
有时候 skill 触发了,但 AI 的执行结果跟你的预期有偏差。这种情况我遇到过的原因主要有两个。一个是指令有歧义。比如你写“输出简洁的代码”,这个“简洁”就很主观,AI 的理解可能跟你不一致。改成“函数不超过 20 行,嵌套不超过 3 层”就明确多了。写 skill 的时候要尽量用可量化、可验证的描述,避免主观形容词。
另一个原因是skill 之间有冲突。如果你装了两个 skill,一个说“错误信息要详细”,另一个说“错误信息要简洁”,AI 就不知道该听谁的。解决办法是给 skill 设定优先级,或者在冲突的 skill 里明确说明适用范围。比如详细错误信息用于开发环境,简洁错误信息用于生产环境,这样就不冲突了。
还有一个比较隐蔽的原因:AI 的上下文里已经有其他指令覆盖了 skill。比如你在对话开头说了一句“尽量用简短的代码”,这句话可能比 skill 里的规范优先级更高。遇到这种情况,你需要在对话里明确说“按照 skill 的规范来”,把优先级拉回来。
5.3 性能问题:skills 太多导致响应变慢
skills 装多了之后,你可能会发现 AI 的响应速度变慢了。这是因为每次对话时,AI 都需要扫描一遍所有的 skill 来判断是否触发。如果你的 skills 目录下有几十个文件,这个扫描过程就会消耗不少时间。
我的做法是按项目启用 skill。不是所有项目都需要所有 skill,你可以在项目配置里指定只加载哪些 skill。比如一个纯前端项目就不需要数据库迁移的 skill,把它排除掉能省不少时间。大部分工具都支持这种配置,你查一下对应工具的文档就能找到。
另一个优化点是合并相似 skill。如果你有五个 skill 都是关于代码风格的,可以考虑合并成一个,用不同的章节来区分。这样扫描时只需要读一个文件,而不是五个。合并的时候注意保持每个章节的独立性,不要让它们互相干扰。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| skill 完全不触发 | 文件位置错误 | 确认工具默认读取路径 | 移动到正确目录 |
| skill 完全不触发 | YAML 语法错误 | 用校验工具检查 front matter | 修正缩进和格式 |
| skill 偶尔触发 | 触发词覆盖不足 | 检查用户输入与触发词的匹配度 | 增加同义词 |
| 执行结果不稳定 | 正文过长 | 统计 skill 正文字数 | 精简到 300 字以内 |
| 执行结果偏差 | 指令有歧义 | 检查是否使用了主观描述 | 改为可量化描述 |
| 多个 skill 冲突 | 规范互相矛盾 | 检查 skill 之间的规则 | 设定优先级或适用范围 |
| 响应变慢 | skill 数量过多 | 统计 skills 目录文件数 | 按项目启用或合并 |
5.5 几个我踩过的坑
第一个坑是在 skill 里写死版本号。我早期写了一个 React 相关的 skill,里面写了“使用 React 18 的 API”。后来项目升级到 React 19,这个 skill 就过时了,但 AI 还是会按旧版本的建议来。后来我改成从package.json里动态读取版本号,skill 里只写“根据项目实际使用的 React 版本选择 API”。这样 skill 就不用跟着版本升级而频繁修改了。
第二个坑是skill 的触发词跟其他工具的关键词撞车。我写了一个处理“构建”的 skill,触发词里有“build”。结果每次我提到“build 一个函数”时也会触发,但这里的 build 跟项目构建完全没关系。后来我把触发词改成“项目构建”、“构建配置”、“build script”这些更具体的组合,问题就解决了。
第三个坑是忘了给 skill 写版本记录。skills 是会迭代的,你今天写的规范可能下个月就调整了。如果没有版本记录,你很难追溯某个规范是什么时候改的、为什么改。我现在每个 skill 文件末尾都会加一个简单的变更记录,格式就是日期加一句话说明。这个习惯帮我省了很多回溯的时间。
6. 进阶玩法:让 skills 真正融入工作流
6.1 用 skills 串联多个工具
skills 不只能给单个 AI 助手用,你还可以用它来串联多个工具。比如我现在的流程是:用 Claude Code 写代码,用 Codex 做代码审查,用另一个工具跑测试。这三个环节各自有对应的 skill,但它们共享同一套基础规范。这样不管在哪个环节,输出的代码风格和错误处理方式都是一致的。
实现方式是在基础 skill 里定义通用规范,然后在各个工具的配置里引用这些基础 skill。大部分工具都支持引用外部 skill 文件,你只需要在配置里写上路径就行。这样你更新基础规范时,所有工具都会同步生效,不用一个个去改。
6.2 根据项目类型动态加载
不同类型的项目需要不同的 skill 组合。Web 项目需要前端相关的 skill,CLI 工具需要命令行参数处理的 skill,库项目需要 API 设计规范的 skill。你可以写一个简单的加载脚本,根据项目里的特征文件来判断项目类型,然后自动加载对应的 skill。
判断逻辑可以很简单:有package.json且依赖里有react或vue就加载前端 skill,有Cargo.toml就加载 Rust 相关的 skill,有go.mod就加载 Go 相关的 skill。这个脚本我放在项目的scripts目录下,配合 Git hooks 在切换分支时自动执行。
6.3 团队协作中的 skills 管理
如果你在团队里推广 skills,有几个点需要注意。首先是命名规范要统一,不然每个人起的名字不一样,引用的时候容易乱。我们团队的做法是统一用领域-具体场景的格式,比如frontend-component、backend-api、devops-deploy。
其次是变更要走评审。skills 是团队共享的规范,改动了会影响所有人。我们现在的做法是 skills 的修改也走 Pull Request,至少一个人 review 之后才能合并。这样能避免有人不小心改错了规范导致大家的输出都出问题。
最后是定期清理。团队用久了之后,skills 目录下会积累很多不再使用的文件。我们每个季度会做一次清理,把过时的、重复的、没人用的 skill 删掉。清理的标准很简单:如果过去三个月没有任何项目引用过这个 skill,就标记为待删除,公示一周后没人反对就删掉。
6.4 从 skills 到自动化工作流
skills 的终极形态是跟自动化工作流结合。比如你可以配置成:每次提交代码时,自动触发代码审查 skill,检查提交信息是否符合规范、代码风格是否一致、有没有遗漏的测试。如果检查不通过,就阻止提交并给出修改建议。
这个配置需要结合 Git hooks 和 CI 流程来实现。Git hooks 负责本地检查,CI 负责远程检查。两边的 skill 是同一套,保证标准一致。我现在的项目里,本地提交时会有三个 skill 自动运行:提交信息检查、代码风格检查、敏感信息扫描。这三个检查加起来大概两秒钟,但能拦住大部分低级错误。
7. 一些个人体会
写了这么多,最后说几句实在的。skills 这个东西,刚上手的时候会觉得有点繁琐,要写 YAML、要组织 Markdown、要调试触发条件。但一旦跑通了,它带来的收益是持续的。我自己的项目里,AI 生成的代码从“需要大改”变成了“小修即可”,这个变化节省的时间远远超过写 skill 的时间。
另外,不要追求一次写完美。我最早的几个 skill 现在回头看写得很粗糙,但正是从那些粗糙的版本开始,我慢慢摸清楚了什么样的指令 AI 能准确执行、什么样的触发条件不会误触发。这个过程没有捷径,就是写、用、改、再写。你如果刚开始接触,建议先从一两个最常用的场景入手,比如提交信息规范或者代码风格检查,跑通之后再逐步扩展。
还有一个小心得:skill 的正文里多放示例,少放规则。规则是抽象的,示例是具体的。AI 从示例里学到的模式,比从规则里推导出来的更准确。我现在的每个 skill 至少配两组对比示例,效果比单纯列规则好很多。这个技巧你可以直接拿去用,不用谢。