一、听说很久,今天才第一次动手
Skill(技能)这个词我听过很多次,一直没实践。今天因为一件很具体的小事,终于把它跑通了——把「发博客」这个动作固定下来,以后我只要说一句「发博客」,AI 就能自动完成整套流程。
这篇文章就用这个真实例子,讲清楚三件事:
- Skill 到底是什么(和提示词、说明文件有什么不同)
- 文件放哪里、怎么写
- 我踩的两个真实的坑
二、问题的起点:每次发博客都要重新交代一遍
我的博客(WordPress + 一套自己写的 Node 发布脚本)发布流程其实已经固化了:
flowchart LR
A[写 Markdown] --> B[放 posts/年份/ 目录]
B --> C[写 front matter<br/>title/slug/categories/status]
C --> D[node publish.js 执行]
D --> E[按 slug 查重<br/>有则更新 无则创建]
E --> F[发布成功后脚本回写 id]
流程是固定的,但**「固定」只存在于我脑子里**。每次让 AI 帮我发,我都要重复说一遍:
- 发布器在哪个目录
- front matter 怎么写
- 命令是什么
- 发完要去验证
一次两次没关系,做十次就是纯浪费。这正是 Skill 要解决的问题。
三、Skill 到底是什么
一句话说清:
Skill = 把「一套固定流程 + 其中的规矩和雷区」写成一个文件,让 AI 在需要时自己加载执行。
它和常见的几种自定义机制的区别:
| 机制 | 本质 | 什么时候生效 |
|---|---|---|
| 全局说明(instructions) | 一直挂在上下文里的”家规” | 每次对话都生效 |
| 提示词(prompt) | 一段一次性的任务模板 | 你手动触发那一次 |
| 技能(skill) | 带流程和资产的完整工作流 | 按需自动加载(描述命中才读) |
| 自定义 Agent | 隔离上下文的子代理 | 需要时派发 |
Skill 最妙的一点是渐进式加载:
- 平时只读它的
name和description(几十个 token,几乎不占上下文) - 只有当你的话和
description对上了,才把整个SKILL.md正文读进来 - 正文里引用的脚本、模板、参考资料,再等真正用到时才加载
所以你可以装很多个 Skill,它们平时几乎不消耗任何成本。
四、落地:文件放哪儿
这一步我一开始就猜错了。有个关键区别:
| 位置 | 能放什么 | 生效范围 |
|---|---|---|
项目/.github/skills/<名字>/SKILL.md |
Skill | 只有这个项目 |
个人主目录/.copilot/skills/<名字>/SKILL.md |
Skill(个人级) | 所有项目都生效 |
用户配置目录的 prompts/ |
只支持 .prompt.md / .instructions.md / .agent.md |
所有项目 |
注意第三行:用户级的 prompts 目录不支持放 Skill(我本来打算放那儿,查了官方参考文档才发现)。想要”走到哪个项目都能用”,必须放在个人主目录下的 .copilot/skills/(另有 .agents/skills/、.claude/skills/ 两个等价位置)。
我实际创建的两个文件:
C:\Users\<用户名>\.copilot\skills\blog-publish\SKILL.md ← 正主(Skill)
C:\Users\<用户名>\AppData\Roaming\Code\User\prompts\
blog-publish.prompt.md ← 兜底(斜杠命令)
第二个是保险:万一某次 Skill 没被扫描到,还能用 / 斜杠命令手动触发。
五、SKILL.md 怎么写
头部是一小段 YAML 元信息,后面是正文:
---
name: blog-publish # 必须和文件夹名一致
description: '把 Markdown 发布到 haoyelaiga.com。当用户说"发博客"、"发到博客"、
"把这条发到博客"时使用。负责建文件、执行发布脚本、用 REST 验证并回报结果。'
argument-hint: '要发布的内容,或"上一条回答"'
---
# 发博客
## 先读这些事实,不要靠猜
- 发布器目录:<绝对路径,不在任何项目内>
- 凭据在 .env → 绝不读取、绝不打印
- 命令:node publish.js "<md 绝对路径>"
## 流程
1. 建文件 posts/<年>/<名>.md
2. 写 front matter(注意:不要写空的 id 行)
3. 执行发布
4. 用 REST 二次验证
5. 回报 ID / 状态 / 链接
## 不要做
- 不要修改 publish.js
- 不要把凭据读进上下文
有两条经验值得单独说:
第一,description 就是”发现界面”。 官方文档里那句话非常关键:如果你的触发词没写在 description 里,AI 就找不到这个技能。 所以我把「发博客」「发到博客」「发布博客」「把这条发到博客」全部写了进去——写不全就等于用不上。
第二,一定要写”不要做”这一节。 AI 很容易”过度发挥”:你让它发博客,它可能顺手去”优化”你的发布脚本。所以我明确写了两条硬约束:不要改 publish.js、不要读取 .env 凭据。事实证明这一节后来真的救了我一次(见下一节)。
六、怎么用
两种方式:
方式一:直接说人话(推荐)
发博客:把上一条回答发上去
因为触发词写在 description 里,AI 会自动加载这个 Skill 并按流程执行。
方式二:/ 斜杠命令(手动指定)
- 打开 Chat 面板,确认在 Agent 模式
- 在输入框最开头敲
/(必须是第一个字符,打在中间不触发) - 继续打字过滤,比如
blog - 上下键选中 → 回车
- 在提示后面补上内容 → 回车发送
新建的 Skill 或 prompt 文件有时需要 Developer: Reload Window 重载一次才会被扫描到。
七、我踩的两个坑
坑一:终端吞输出,误判”发布失败”
我的 PowerShell 终端有个老毛病:命令明明执行了,却什么回显都没有,甚至会提示”命令无输出”。
第一次发博客就中招了——终端一片空白,我以为失败了。结果去查线上:文章早就发出去了。
教训:不要靠终端回显判断成败,要去查真实结果。所以我把这条写进了 Skill:发布后必须调 WordPress REST 接口确认 id / status / link。
坑二:我差点”修好”一个没坏的脚本
这是今天最有价值的一次翻车。
现象:文章成功发布(ID 1346),但 Markdown 的 front matter 里 id: 还是空的,没被回写。
我的判断:脚本的写回函数有 bug。我的”证据”看起来很硬——那个正则用了 \s*,而 \s 是包含换行符的,配上多行模式的 $ 锚点,它会把换行吃掉,于是”id 这一行”实际匹配到了下一行,把 title: ... 当成了 id 的值;值非空 → 脚本判定”已有 ID,跳过写入”。
推理没错,我随即改了这个正则,还顺手把回写逻辑从”仅新建时执行”扩到”更新时也执行”。
然后作者看了以后说:
原本的逻辑是新建 MD 时第一行没有 ID 字段,发布成功后才加上 ID 并写回值。所以你刚才改的要删掉,保留我原有的发布逻辑。
原来问题根本不在脚本,在我——我按自己的模板多写了一行空的 id:,正好踩进那个分支。按作者的约定(新建文件不放 id 字段),原脚本工作得好好的。
于是我 git checkout 把脚本原样回退,只保留那条”不要写空 id 行”的约定。
三条教训:
- 动手改别人的代码前,先确认对方的设计约定——bug 可能只是”你用了错误的用法”
- 改完要能干净回退(这次靠
git diff先确认改动范围,再整体还原,很踏实) - 踩过的坑要写进 Skill——所以我专门在 front matter 那一节加了警告:不要写空的
id:行,附上原因和症状。这才是 Skill 相对”临时交代一句”的真正价值。
八、小结
- Skill 的本质:把重复解释的流程,从”我脑子里的约定”变成”文件里的规则”
- 它按需加载,装很多也不占上下文
- 放对位置(个人级是
~/.copilot/skills/,不是prompts/),否则怎么点都没反应 - 描述里写全触发词,正文里写清”不要做”
- 把踩过的坑写进去——坑只踩一次的秘诀,就是把它写成规则
顺带一个有意思的闭环:这篇文章本身,就是用刚做好的 blog-publish Skill 发布的。
本文章永久链接: 什么是 Skill?我用「发博客」这个例子跑通了一遍
