前阵子和一个做Agent应用的朋友聊天,他说了个比喻让我印象特别深:“现在的模型像是一个业务能力很强但记性很差的新员工。你跟他说什么他都能接住,但你指望他能稳定地按公司规范交付一件事,那得看运气。”这话放在AI编程和Agent工具链爆发的当下,真是再贴切不过。后来Claude、Codex这些工具陆续推出了Skills机制,我上手试了一段时间,才真正意识到这个看似简单的“文件夹+说明书”设计,其实是在解决Agent落地过程中最要命的一环:怎么让模型稳定、可重复地完成特定任务。
如果你一直在关注前端开发Skills、Agent Skills测试这些话题,或者手头已经有几个Skills安装包却不知道怎么组织、怎么必坑,这篇文章应该能帮到你。我会从第一性原理出发,把Skills是什么、内部结构长什么样、怎么从零开发一个自己的Skill、以及安装分发和排错技巧一次讲透。
1. Skills到底是什么,它解决了什么问题
1.1 从一次混乱的对话说起
我先讲个真实的翻车现场。上个月我让Claude整理一份项目的版本变更记录,任务听起来很简单:拉取最近的Git提交信息,按类型归类,输出成CHANGELOG.md。结果模型来回折腾了三轮:第一次把commit标题原封不动贴上去,分类完全不对;第二次倒是分了类,但把“docs”和“chore”混在一起;第三次格式对了,可版本号又拍脑袋给我升了一整个大版本。整个过程又慢又费token。
问题出在哪?不是模型笨,而是我给了它一个“开放式任务”,却没有给它“操作规范”。它不知道该先跑哪条Git命令、该按什么标准判断一个commit属于feat还是refactor、该用哪种模板输出。而Skills机制,恰好就是用来弥补这一环的——把这类重复性高、规范明确的任务,固化成模型能读懂的说明书和可执行的脚本。
1.2 Skills不是插件,是一次“会话内的能力挂载”
很多人第一次接触Skills,会下意识拿它跟插件(Plugin)或者MCP去对比。这个理解偏差还挺常见的。我的理解是:Skills本质上是一组带说明的静态文件(Markdown + 脚本),放在指定目录后,当对话任务匹配到某个Skill的description时,模型会主动读取这个Skill的说明文档,在本次会话内获得这套“操作规范”。
它不像MCP那样需要拉起一个常驻服务进程,也不像传统插件那样需要经过复杂的握手协议。它就是一份说明书加一套工具,模型按说明书操作,脚本负责那些容易出错、需要确定性的脏活累活。我第一次在Claude Code里写了一个自己的Skill并成功让它按规范输出后,才体会到那种“模型从碰运气变成了流水线工人”的区别。
1.3 第一性原理:模型擅长什么,不擅长什么
要理解Skills的设计逻辑,先得把大模型的能力边界理清楚。模型最擅长的是语义理解、归纳判断和自然语言生成,比如“这句话表达了什么意图”“这段commit大致属于哪个类型”。这些事它做得又快又稳。
但它天生不适合干这些事:第一,精确执行重复操作的稳定性不行,同一个指令换个说法可能输出就飘了;第二,它不会主动记住你上次约定的输出格式,除非你每次都在上下文里带一长串示例;第三,涉及读文件系统、跑命令这类操作时,它必须依赖外部工具,而每次调用的参数和结果解析都可能出错。Skills的聪明之处,就是把这两类任务拆开:判断类工作让模型做,执行和格式化类工作交给脚本,再用一份写好的SKILL.md把两者的协作流程定死。
2. Skill内部结构与设计要点
2.1 SKILL.md:一份给模型看的“新员工入职手册”
一个标准的Skill目录通常长这样:
changelog-generator/ ├── SKILL.md ├── scripts/ │ └── collect_logs.sh └── reference/ └── conventional_commits.md最核心的就是SKILL.md。它的开头是YAML格式的frontmatter,至少需要两个字段:name和description。可别小看这两个字段,尤其是description,它决定了这个Skill在什么场景下会被模型“想起来”并加载。模型的触发机制是语义匹配,不是菜单选择,所以description里必须写清楚这个技能是干什么的、适合哪些输入、在什么情况下不要用。
我一直强调,description要写得像一份“触发器清单”。比如:
--- name: changelog-generator description: 从Git提交历史生成规范CHANGELOG.md。当用户要求生成、更新或补全版本变更日志时使用;当用户提到commit、版本号、changelog等关键词时优先考虑。如果用户只是提交代码而没有要求整理日志,不要使用。 ---这短短几行里包含了三个要素:功能范围、触发场景、排除场景。排除场景尤其重要,因为模型对“何时不该用”的把握通常比“何时该用”更弱,你把负例写清楚,能少很多误触发。
2.2 正文结构:直接告诉模型“按这个流程干活”
SKILL.md的正文部分不需要长篇大论,但必须具备工作流程、关键约束、输出格式三块内容。我的经验是,正文写得越像“标准作业程序”越好。模型读文档的能力很强,但它不喜欢模棱两可的指导,你写“尽量保持格式统一”它反而会困惑,不如直接给它一个模板。
比如我会在changelog这个Skill的SKILL.md里这样写:
工作流程: 1. 运行 scripts/collect_logs.sh 获取最近一版tag以来的commit列表。 2. 对每一条commit,根据 Conventional Commits 规范判断类型: - feat: 新功能 - fix: 缺陷修复 - docs: 文档变更 - refactor: 重构(不新增功能、不修bug) - chore: 构建、依赖等杂项 3. 汇总判断本次版本属于 major/minor/patch 中的哪一档。 4. 按模板输出CHANGELOG.md,模板见 ../reference/changelog_template.md。 关键约束: - 不要修改任何源文件,只生成新的CHANGELOG.md。 - 分类不确定时默认归入chore,并在输出末尾列出待确认项。 - 版本号只允许从现有tag推导,不要凭空指定。这里每一条都是可验证的。模型执行完,你可以肉眼检查它有没有照着做。对了,SKILL.md里还能声明permissions字段,明确这个Skill需要哪些工具权限(比如Bash、Edit、Read),这能防止模型在需要执行命令时犹豫或者误用其他工具。
2.3 渐进式披露:别把说明书写成百科全书
我第一次写Skill时犯过一个典型错误:恨不得把所有的细节和示例全塞进SKILL.md里。结果文件写了三千多行,模型每次触发这个Skill都要吃掉大量上下文token,而且信息过载反而让执行效果变差。后来我学到了“渐进式披露”的思路:SKILL.md只写核心流程和关键规则,把模板、详细规范、示例这些大块内容拆到reference目录下的子文件里,在正文中只留一行引用说明。
这个设计的妙处在于,模型读SKILL.md时token开销很小,遇到需要的细节再按路径去读子文件——它本来就具备按需读文件的能力,你不用替它把饭喂到嘴边。模块分离之后,脚本、模板、规范文档各自迭代也方便,不至于改一个小地方就得动主文件。
3. 从0到1开发一个自己的Skill:完整实操
3.1 先定场景,再写代码
我在实际开发中总结出一条经验:不要为了写Skill而写Skill。最值得做的,是你自己工作中高频重复、输出格式相对固定、且每次让模型做都会出幺蛾子的事。像我刚才反复提到的changelog生成就是个典型场景。这里我用它作为完整的实操案例,带你走一遍从零到一的过程。
需求明确之后,先看目录该放哪。Claude Code默认会扫描两个位置:全局目录~/.claude/skills/,项目级目录.claude/skills/。全局放通用的、不依赖具体项目的个人技能;项目级放跟当前代码库强相关的规则,比如项目的commit规范或测试约定。这里我们做的是相对通用的changelog生成器,就放全局目录。
mkdir -p ~/.claude/skills/changelog-generator/scripts mkdir -p ~/.claude/skills/changelog-generator/reference3.2 先把“模型该做的事”定义清楚
核心设计决策是:哪些活儿给模型,哪些活儿给脚本。我的划分逻辑是这样的——拉取commit列表、提取tag、计算最新tag到当前HEAD之间的提交,这些是机械操作,由脚本完成,保证每次拿到的数据格式一致;而把每条commit归类到feat/fix/docs/refactor/chore,判断版本号升档,这些是语义判断,交给模型。
脚本我选了一个简单的Shell脚本,因为这里只需要调用git,不需要复杂的库。如果你要做文件解析、文本清洗这类活,用Python往往更顺手。原则是:用最顺手、最少依赖的方式解决数据获取问题,让模型拿到的是干净、结构化的输入。
先写脚本:
#!/usr/bin/env bash # scripts/collect_logs.sh # 收集最近一个tag以来的commit信息,输出为JSON set -euo pipefail latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -z "$latest_tag" ]; then # 仓库还没有tag,从第一个commit开始 range="HEAD" else range="$latest_tag..HEAD" fi git log "$range" --pretty=format:'{"commit":"%h","author":"%an","subject":"%s"}' --reverse输出是一行一个JSON对象,模型读起来非常清晰。脚本不算复杂,但它解决了一个实际问题:让模型不用自己去拼git命令,也就不会拼错参数或忘记加--reverse。
3.3 写SKILL.md:说人话,下明确的指令
接下来是重头戏,把这些内容串成SKILL.md。先看完整内容:
--- name: changelog-generator description: 从Git提交历史生成规范CHANGELOG.md。当用户要求生成、更新或补全版本变更日志时使用;当用户提到commit、changelog、版本记录等关键词时优先考虑。如果用户只是提交代码而没有要求整理日志,不要使用。 permissions: - Bash - Read --- # Changelog生成器 根据Git提交历史生成标准CHANGELOG.md文件,遵循Conventional Commits分类体系。 ## 工作流程 1. 在仓库根目录运行 `scripts/collect_logs.sh` 获取JSON格式的commit记录。 2. 逐条解析每条commit的subject字段,判断所属类型: - feat: 新功能 - fix: 缺陷修复 - docs: 文档变更 - refactor: 重构,不新增功能也不修bug - chore: 构建脚本、依赖更新、格式化等杂项 - breaking change: 包含破坏性变更时,在类型后加感叹号并在changelog中单独标注 3. 基于已有最新tag判断版本号: - `!` 标记 → major升位 - 存在feat → minor升位 - 只有fix/docs/chore → patch升位 4. 参考 `../reference/changelog_template.md` 中的模板输出CHANGELOG.md。 5. 如果某些commit无法判断类型,主动向用户确认,不要擅自归类。 ## 输出格式 - 文件顶部写 `# Changelog`,然后是 `## [版本号] - 日期`。 - 分类区块按 feat / fix / docs / refactor / chore 顺序排列。 - 破坏性变更放在文件最上方单独一个 `### Breaking Changes` 区块。 - 每条变更前用 `-` 列表,格式 `- type(scope): subject`。 ## 关键约束 - 不要修改.git目录或任何源文件。 - 分类不确定时不要硬猜,列到“待确认”区块。 - 如果仓库没有tag,默认版本从 0.1.0 开始。这里面的关键不是格式,而是我把“模型需要做的判断”都显式写成了规则。比如“分类不确定时不要硬猜”,这是我在前几次测试里发现模型最容易犯的毛病,规则一写,效果立刻改善。还有版本号推断逻辑,如果不写,模型真的会给你编一个2.0.0出来,写成明确的映射关系,它就只能在三步推理里做选择。
3.4 测试与迭代:先拿真实仓库跑一遍
Skill写完不测等于白写。我的测试方法是分三层走:先找一个体积小但commit历史丰富的仓库做冒烟测试,让模型加载Skill执行一遍,看流程能不能走通;然后换一个更复杂的仓库,带有多种类型commit和破坏性变更的,看分类和版本推断是否准确;最后故意给出边界条件,比如一个没有任何tag的仓库,或者全是格式混乱commit的历史,看模型会不会正确报错或者进入待确认流程。
第一轮测试的结果几乎一定不完美。我那个changelog Skill第一次跑,模型生成的CHANGELOG.md里连日期格式都写成了自定义格式,后来我又在SKILL.md的“输出格式”里补了一句“日期格式统一用YYYY-MM-DD,参考reference/changelog_template.md”,这个问题才解决。所以别指望一蹴而就,Skill开发本身就是一个“写-测-补规则”的循环,每跑一次,把模型的缕缕奇葩输出变成新规则,它就会越来越稳。
3.5 让skill自动安装依赖
如果你的技能需要Python依赖,不要指望模型自动帮你安装。一个安全且稳妥的方案是在SKILL.md里写明依赖检查步骤,或者提供一个setup脚本。但更推荐的做法是:尽量不依赖第三方库,把环境依赖的复杂度降到最低。比如我这个changelog生成器只用系统自带的git和bash,任务就干净得多。依赖越少,你分发给别人时出问题的概率就越低。
4. 安装、分发与生态
4.1 三种常见的安装方式
开发完Skill,最直接的使用方式就是放到对应目录里立即生效。Claude Code的Skills安装路径有三个层级:
- 全局个人级:
~/.claude/skills/,适合通用的个人技能,比如JSON格式化、代码审查、周报生成这些不依赖特定项目的技能。 - 项目级:
.claude/skills/,跟着项目仓库走,适合跟当前代码库强相关的规范类技能,比如“本项目的commit规范”“本项目的测试数据构造方式”。项目级的好处是能提交到git里,团队成员clone下来就有。 - Marketplace安装:社区里有各种Skills下载平台和市场,例如官方仓库和第三方marketplace。通过
/marketplace命令添加源之后,就能像装插件一样搜索和安装别人发布的技能。
我个人习惯是通用技能放全局,项目规范放项目级,涉及团队统一标准的东西才考虑走marketplace。至于热门社区里那些“skills大全”“瑞士军刀式合集”,我的建议是别贪多,一两百个技能塞进去,反而会让模型的触发匹配变得混乱,选择一个精一个才是正道。
4.2 团队协作时的分发与版本管理
Skills本身就是纯文本加脚本,天然适合用Git管理和分发。我们团队现在的做法是:把项目级的.claude/skills/目录纳入代码仓库,评审Skill的变更就像评审代码一样走PR流程。这样做的好处是,技能里的每一条规则变化都有迹可循,不会出现某个人悄悄改了描述导致全组行为漂移的情况。
另外,SKILL.md里的description改动要格外谨慎。因为是语义匹配,哪怕只改一个表述,都可能影响触发频率。我们遇到过把“当用户要求生成变更日志时使用”改成一个更长的描述后,整个小组的模型都开始莫名触发这个技能的情况。后来大家约定,改动description必须经过至少两人确认。
4.3 Skills和MCP的区别,一次性讲清楚
前面提到过,很多人把Skills和MCP搞混。我给一张对比表,看完你就不会再混了:
| 维度 | Skills | MCP(Model Context Protocol) |
|---|---|---|
| 本质 | 静态指令文件+脚本 | 动态工具调用协议 |
| 运行方式 | 模型按需读取说明书 | 客户端连接服务端,调用远程或本地工具 |
| 适用场景 | 固定流程、标准操作、规范约束 | 外部数据访问、实时信息、平台操作 |
| 依赖 | 无独立进程,只需文件目录 | 需要MCP Server进程 |
| 输出的确定性 | 高,规则写死就能稳定复现 | 取决于工具本身实现 |
| 典型例子 | changelog生成、代码审查规范、周报模板 | 查数据库、GitHub操作、浏览器自动化 |
划分建议很简单:凡是“大脑里的最佳实践”,适合做成Skill;凡是“手和眼睛需要向外伸”的,适合走MCP。两者不冲突,可以组合使用,比如一个Skill里写明“分析数据前先调用某个MCP工具拉取指标”,实现流程+数据双保险。
5. 常见问题与排查技巧实录
5.1 模型就是不调用你的Skill,怎么办
这是群里问得最多的一个问题,几乎每周都有。Skill开发好了,文件路径也对,但让模型干活的时候它完全无视,好像根本没这回事。排查思路按顺序来:
先检查description是否写清楚了触发场景。很多人的描述写得太泛,比如“这个技能用来生成文档”,模型看到根本不知道该什么时候用。你要把触发它的话术场景都列出来,最好带上用户可能说的原话,比如“把最近的提交整理一下”也是一种触发信号。
再检查名称和描述里的关键词是否和实际对话相关。另一个常见原因,是description写得太长,关键触发词被淹没。我的经验是description里前30个字必须包含最核心的功能名词和触发条件。
最后,如果你用的是Claude Code,直接在对话里打/skills可以查看当前项目加载了哪些技能。如果没加载,看看是不是路径放错了——全局目录和项目目录位置很容易配反。这一步实操排错比什么理论都管用。
5.2 Skill触发了,但执行结果与预期不符
这种情况通常是SKILL.md里的指令不够具体。我建议你用“如果……那么……”的句式把所有分支写掉。比如“如果仓库没有tag,默认版本从0.1.0开始”,这就是一个完整分支。模型不是不能处理模糊指令,而是模糊指令会让它每次随机选择一个解释,你的规则越完备,输出的方差就越小。
另外,如果脚本输出了非预期的数据格式,模型可能就会拿这些脏数据将就着往下走。这时候你需要在工作流程里加一句“如果脚本输出为空或格式异常,停止操作并向用户报告”,把失败路径堵死。
5.3 脚本权限和报错排查
新写的Skill第一次跑,脚本很可能没有执行权限。表现形式是模型运行脚本时报Permission denied,然后它可能试图去改权限或换一种脚本调用方式,最终反而把任务搞偏。我的建议是开发完顺手chmod +x scripts/collect_logs.sh,并在SKILL.md里注明“脚本可直接执行”,省得模型在权限问题上反复试探。
如果脚本报了别的错,先自己在终端跑一遍,别急着改SKILL.md。脚本本身能通,再让模型去调用。很多情况下问题是脚本里的相对路径引起的,模型的工作目录不一定在你预期的位置,所以脚本内最好用绝对路径或者先cd到项目根目录再操作。
5.4 问题排查速查表
| 症状 | 可能原因 | 处理办法 |
|---|---|---|
| 技能永远不触发 | description缺少触发词 | 重写description,明确场景和排除场景 |
| 技能频繁误触发 | 描述里的负例不清晰 | 补充“当…时不要使用” |
| 输出格式漂移 | 模板不够明确或缺少示例 | 在reference里给精确示例,SKILL.md引用它 |
| 脚本报权限错误 | 未加执行权限 | chmod +x,并在文档注明直接执行 |
| 脚本路径找不到 | 工作目录与预期不符 | 脚本内使用绝对路径或先cd |
| 模型乱猜分类 | 规则里没写“不确定怎么办” | 增加“无法判断时询问用户”的指令 |
| 技能在团队里行为不一致 | 多人改了description | 走Git评审流程,控制description变更 |
6. 最后分享一点我的实际体会
做Skills这件事,最让人上瘾的地方在于:它把一个“模型偶尔能做对”的任务,变成了“模型每次都能做对”的任务。第一次看到自己写的Skill稳定输出规范结果的时候,那种感觉跟写完一个精巧的函数差不多。而踩过几次坑之后,我发现真正决定Skill质量的下限不是脚本写得多漂亮,而是SKILL.md里的规则有没有写透。你愿意花多少时间把分支场景想清楚,模型就给你多稳的交付。
另外一个小建议:别一开始就想着做“全能型”Skill。我见过很多人试图把写文档、发周报、修bug三个功能塞进一个Skill里,结果description写得像一篇小作文,触发率和准确率双双拉垮。一个Skill只干一件事,干到极致,才是这套机制最正确的打开方式。有了第一个Skill的经验,后面再开发新技能时你就会发现,这套“说明书+脚本”的模式能复用到各种各样的场景里,越用越顺手。