
别再直接 Fork 别人的 Claude Skill:公开模板只是原材料,项目规则才是成品
AI Coding 系列第 05 篇 · 核心工具
我第一次批量导入公开 Skill 模板的时候,是真的以为自己走了捷径。
GitHub 上一堆 star 很高的仓库,code review、需求分析、文档编写、调研、拆任务,看起来什么都有。我当时的想法特别简单:既然别人已经把常见工作流整理好了,我直接 fork 一份,全量导入,不就能让 Claude 立刻更稳、更懂项目吗?
结果用了几天,我反而越来越不放心。
它每次都输出得很像那么回事。格式完整,措辞专业,检查项也不少。可真正让我在项目里反复吃亏的那几件事,它一次都没替我盯住。异步链里是不是又漏了 await,这次 migration 有没有回滚方案,新同学是不是又顺手写了 throw new Error(),数据库 schema 改了之后 Prisma 类型是不是也一起更新了。
它会提醒一堆"大家普遍都应该注意"的东西,却不懂"我们团队到底最怕什么"。
后来我才慢慢明白,问题不是 Skill 机制不好,而是我导入的根本不是自己的 Skill,只是别人整理好的经验。
这些经验当然有用,但它们解决的是共性问题,不会天然长成你项目里的"肌肉记忆"。
这篇文章就讲一件事:
怎么把公开模板当原材料,而不是成品;怎么从自己的项目里,提炼出一个真正会被反复复用、而且越用越准的 Skill。
如果你只想先记住一句话,那就是:
公开 Skill 模板是素材库,不是最终产品。
先给你一个判断框架
为了后面不绕,我先把最核心的判断放前面:
- 对所有任务都生效的规则,放
CLAUDE.md - 只对某一类任务生效的规则,做成
Skill - 只对这一次任务有效的约束,写进
Prompt - 只有"输入相对稳定、输出有共同模式、而且容易漏步骤"的任务,才值得沉淀成 Skill
- 第一个 Skill 不要选最关键的任务,先拿中等风险任务练手
如果你现在就卡在"这条到底该写哪",后面大部分内容其实都可以用这五条往回推。
一、为什么很多公开 Skill 模板,一开始觉得香,后来却越用越别扭
我现在反而会对"看起来很全"的公开 Skill 模板保持一点警惕。
不是因为它们没用,而是因为它们太容易制造一种错觉:好像什么都覆盖到了,但真正最重要的东西其实没进去。
公开模板最常见的问题,不是方向错,而是下面这三种。
1. 太宽泛
它什么都管一点,但什么都不够深。
它会告诉你"注意异常处理""注意性能""注意安全",这些当然没错。但这些话本身不构成你项目里的工作流。它不知道你们统一用的是 AppError,不知道你们数据库变更必须检查回滚,也不知道你们哪几个目录历史包袱最重。
2. 太嘈杂
50 行模板里,真正有价值的可能只有 5 行。
剩下的 45 行不是完全没用,而是在和那 5 行争夺 Claude 的注意力。对于 agent 来说,规则不是越多越强。很多时候,8 行写透项目约束的 Skill,比 50 行"样样都提一点"的模板更有用。
3. 太不像你的项目
这点才是最致命的。
公开模板知道"大家普遍应该注意什么",但不知道"你们团队反复死在哪些地方"。而真正有价值的 Skill,恰恰应该把那些项目特有、团队高频踩坑的东西固化下来。
说得更直白一点:你把一个新同事扔进团队,给他一份行业通用培训材料,当然比什么都不给强;但如果你不告诉他"我们团队最容易出错的是哪三件事",他依然干不好你最在意的活。
所以正确姿势不是"找一个最全的模板直接用",而是:
先借鉴,再裁剪,最后只留下真正属于你项目的那几条。
二、先把 Prompt、CLAUDE.md、Skill 这三件事分清楚
很多人不是不会写 Skill,而是一开始就把这三件事混在一起了。
判断方法其实很简单,只问一个问题的三个变体:
- 这个要求对所有任务都成立吗?如果是,放
CLAUDE.md - 这个要求只对某一类任务成立吗?如果是,做成
Skill - 这个要求只对这一次成立吗?如果是,写进
Prompt
举几个特别典型的例子:
"所有 throw 必须是 AppError"
这是全局规则。不管你是在写新功能、修 bug,还是做重构,都要遵守。它应该进 CLAUDE.md。
"代码审查时按固定顺序检查数据库、异步和错误处理"
这只在 code review 这种任务里才触发,它不是全局规则,而是任务模板,所以应该做成 Skill。
"这次先只分析原因,不要动代码"
这只对当前这次任务有效,应该写进 Prompt。
最容易搞混的是 CLAUDE.md 和 Skill。它们都能约束 Claude 的行为,但本质完全不同:
CLAUDE.md是永远生效的规则Skill是遇到对应任务才触发的模板
如果要打个比方:
CLAUDE.md是交通规则Skill是导航路线Prompt是你这次上车前临时交代的一句话
这三层一旦分清楚,后面很多混乱都会自动消失。
三、什么时候一个任务值得被沉淀成 Skill
不是所有重复任务都值得沉淀。
我现在给自己的标准其实很克制,就一句话:
同一类任务做了三次以上,而且每次都要重新给 Claude 解释背景。
反过来说,如果某个任务每次背景和目的都完全不同,就不值得沉淀。比如"写文档"这个动作本身很常见,但公司文档、API 文档、用户手册的写法完全不同,它们应该是三个不同的 Skill,而不是一个叫"写文档"的通用模板。
在真正开始写之前,我会先做三个检查。
1. 输入是否稳定
"根据 Figma 设计稿生成 React 组件"这种任务,输入格式相对稳定,比较适合沉淀。
"根据 SQL 查询结果生成图表"这种任务,每次数据格式和图表类型都可能差很多,Skill 会很难写得稳。
2. 输出是否有共同模式
"写 Pull Request 描述"很适合,因为它天然就有固定框架:改了什么、为什么改、怎么测试。
但"和 AI 讨论技术方案"这种任务,每次深度、重点、结论都不同,就不太适合硬沉淀成一个模板。
3. 有没有容易漏掉的关键步骤
最值得沉淀成 Skill 的任务,通常不是"最复杂"的任务,而是那些不特别提醒就容易漏一步的任务。
Skill 最有价值的地方,不是让 Claude 变得更聪明,而是把你每次最容易忘的检查项,固化成默认动作。
所以一个任务如果同时满足下面三点:
- 输入相对稳定
- 输出有共同模式
- 总有一两步容易漏
它就很值得沉淀成 Skill。
四、一个真正好用的 Skill,内容层通常只需要四个部分
很多人一开始会把 Skill 写得很重,像在写规范文档。但实际用起来之后你会发现,真正好用的 Skill 通常很短。
它一般只需要四个部分。
1. 触发条件
什么时候用,一句话说清楚。
text
❌ 代码审查
✅ 当我提交 PR 前,检查我的实现是否符合项目约定
2. 执行步骤
按什么顺序做,列出来。尽量不要超过五步。
text
1. 读完整个改动的 diff
2. 检查是否用了禁用的库或模式
3. 检查异步操作的错误处理
4. 检查是否有 SQL 注入的风险
5. 给出修改建议
3. 输出格式
不要写"请清晰输出"。这种话几乎没有约束力。直接给模板。
text
❌ 用清晰的格式列出所有问题
✅ 给个模板:
Found 3 issues:
🔴 Critical: ...
🟡 Warning: ...
✅ Suggestion: ...
4. 注意事项
说清楚边界。什么情况不适用,有哪些常见陷阱。
text
- 不适用于新增功能的初始实现,只适用于 PR 前的最终检查
- 不关注 UI 层细节
- 改动超过 500 行,先拆成多个 Skill 请求
Skill 不是规范手册,更不是把所有经验一次性塞进去。它本质上是一个高频任务的最小可执行模板。
五、真正落到 SKILL.md 文件层,哪些字段最值得你花心思
讲完"内容怎么提炼",还得讲"文件怎么写"。
很多人第一次写 SKILL.md 会卡在另一个地方:字段太多,不知道哪些真有用,哪些只是"看起来高级"。
一个完整的 SKILL.md,通常会长这样:
markdown
---
name: code-review
description: 提交 PR 前的代码审查
when_to_use: 当用户要求 review 代码或提交 PR 前检查时
allowed-tools:
- Read
- Grep
- Glob
- Bash(git diff *)
argument-hint: "[PR 分支名或文件路径]"
arguments:
- target
---
# Code Review
## 步骤
1. 读取 ${target} 的改动 diff
2. 检查错误处理:所有 throw 必须是 throw new AppError()
3. 检查异步操作:Promise 链是否有遗漏的 await
4. 检查数据库查询:是否有 N+1 问题
## 输出格式
🔴 Critical: ...
🟡 Warning: ...
✅ 通过: ...
这里最值得你认真写的,其实是下面几个字段。
name
名字别太抽象。要让人一眼知道它是做什么的。
description
一句话说清这个 Skill 的用途,不要写成空泛口号。
when_to_use
这是最容易被低估的字段之一。它不是装饰,它直接影响 Claude 在什么场景下会想到这个 Skill。
allowed-tools
它决定这个 Skill 具备哪些能力。这个字段后面我会在源码部分展开讲,因为它比很多人想象的更"硬"。
arguments
让 Skill 接受参数,比如目标文件、目录、分支名。${target} 会在正文里被替换成你传进去的实际值。
还有几个很好用,但不是每次都要上的字段。
argument-hint
告诉调用者这个 Skill 期待什么参数。
model: haiku
简单任务可以指定更轻量的模型,直接省成本。像格式化、重命名、简单改写这类工作,很多时候没必要上更重的模型。
paths
让 Skill 只在某些路径下激活。适合模块边界明确的项目。
context: fork
高风险操作放进独立上下文,避免污染主会话。
大多数 Skill 根本不需要把字段填满。真正实用的思路不是"功能全",而是"正好够用"。
如果一个 Skill 只是做常规代码审查,前四五个字段通常就够了。只有当你真的遇到参数化、模块隔离、上下文隔离这些需求时,再往上加。
六、如果只停在经验层,这篇其实还差半口气:我后来去翻了源码
前面这些判断,靠经验其实也能总结出来。
但我后来还是不太满足。因为有几个问题如果不看实现,心里总会悬着:
when_to_use到底是不是自动触发的关键?allowed-tools到底只是提示,还是硬限制?paths到底是真过滤,还是只是写给人看的说明?
我后来去翻了一遍源码,结论是:这些字段比我一开始以为的更"硬"。
1. Claude 只在启动时读 frontmatter,Skill 正文是懒加载的
loadSkillsDir.ts 里有一个函数 estimateSkillFrontmatterTokens,注释写得非常直接:
ts
/**
* Estimates token count for a skill based on frontmatter only
* (name, description, whenToUse) since full content is only loaded on invocation.
*/
export function estimateSkillFrontmatterTokens(skill: Command): number {
const frontmatterText = [skill.name, skill.description, skill.whenToUse]
.filter(Boolean)
.join(' ')
return roughTokenCountEstimation(frontmatterText)
}
这段代码背后的意思非常重要。
Claude Code 启动时,主要只把每个 Skill 的 name、description、when_to_use 这些 frontmatter 信息算进上下文。Skill 正文不是一开始就全量塞进去,而是在你真正触发它的时候才加载。
这直接解释了两件事。
第一,when_to_use 写得越具体,自动命中的效果就越稳定。Claude 不是先把你整篇 Skill 读完再判断要不要触发,它先看的就是前面这几行。
第二,你有十个 Skill 还是三个 Skill,对启动时上下文的占用差距没你想的那么大。真正的成本在触发时才发生。
所以 when_to_use 不能写成"代码相关任务时使用"这种空话。它要写成"当用户要求 review TypeScript 后端代码或提交 PR 前做最终检查时"这种具体到能命中的描述。
这也是为什么我现在越来越重视 frontmatter。以前我会把心思都放在正文步骤上,后来才发现,前面几行写虚了,后面写得再好都不一定有机会被用上。
2. allowed-tools 是系统层权限,不是给 Claude 的礼貌性建议
这一点是我看源码之后感受最强的一处。
Skill 执行时,getPromptForCommand 会在返回内容之前把 allowedTools 写进工具权限上下文:
ts
getAppState() {
const appState = toolUseContext.getAppState()
return {
...appState,
toolPermissionContext: {
...appState.toolPermissionContext,
alwaysAllowRules: {
...appState.toolPermissionContext.alwaysAllowRules,
command: allowedTools,
},
},
}
}
这说明 allowed-tools 不是"提醒 Claude 尽量这样做",而是权限层的强制限制。
比如一个 code review Skill 只开放 Read、Grep、Glob 和 Bash(git diff *),那它就不是"理论上不该写文件",而是从架构上根本没有写文件的能力 。Bash(git diff *) 这种写法也不是装饰,它真的只允许 git diff 开头的命令,其他 Bash 调用会被挡住。
这让我对 allowed-tools 的理解完全变了。它不是"不信任模型",而是最小权限设计。就像你给数据库只读账号只开 SELECT 权限,不是因为你怀疑这账号会作恶,而是因为这个任务本来就不该拥有写权限。
3. paths 不是文档字段,它会把 Skill 放进条件激活区
这一点也比表面上看起来更硬。
源码里,带 paths 的 Skill 在加载时会被单独分流到一个 conditionalSkills Map:
ts
// Separate conditional skills (with paths frontmatter) from unconditional ones
for (const skill of deduplicatedSkills) {
if (skill.type === 'prompt' && skill.paths && skill.paths.length > 0
&& !activatedConditionalSkillNames.has(skill.name)) {
newConditionalSkills.push(skill)
} else {
unconditionalSkills.push(skill)
}
}
// Store conditional skills for later activation when matching files are touched
for (const skill of newConditionalSkills) {
conditionalSkills.set(skill.name, skill)
}
// 最后只返回无条件的 Skill
return unconditionalSkills
这段逻辑的含义是:带 paths 的 Skill,根本不会像普通 Skill 一样直接进入启动时上下文。它会先待在一个"条件激活区"里,只有当你在会话里碰到了匹配路径的文件,它才会被真正激活。
这点对复杂项目非常有价值。
比如你给支付模块写一个 paths: src/payment/** 的 Skill,在你处理用户系统、文章系统、管理后台时,这个 Skill 对 Claude 几乎是隐身的。只有当你真的进入 src/payment/ 相关文件,它才"出现"。
这也是我现在很认同的一种团队实践:不要在根目录堆一个什么都想管的大 Skill 集合,而是让复杂模块在自己的目录附近维护自己的 Skill。
4. Skill 发现是沿目录向上找的,而且离文件越近优先级越高
还有一个很容易被忽略,但工程上非常实用的机制:Claude Code 会从当前文件所在目录一路向上寻找 .claude/skills。
源码大概是这样:
ts
// Walk up to cwd but NOT including cwd itself
while (currentDir.startsWith(resolvedCwd + pathSep)) {
const skillDir = join(currentDir, '.claude', 'skills')
// ...check if exists, then load
currentDir = dirname(currentDir)
}
// Sort by path depth (deepest first) so skills closer to the file take precedence
return newDirs.sort((a, b) => b.split(pathSep).length - a.split(pathSep).length)
这里最关键的是最后一行:deepest first。也就是说,越靠近当前文件的 Skill,优先级越高。
这意味着你放在 src/auth/.claude/skills/ 里的 Skill,可以自然覆盖根目录下更通用的同名 Skill。对 monorepo 或大仓库来说,这个机制非常好用:
packages/api/.claude/skills/可以放 API 专属 Skillpackages/web/.claude/skills/可以放前端专属 Skill- 根目录只保留真正的全局规则
如果把上面四点放在一起看,设计 Skill 的顺序其实会变得很清楚:
- 先把 frontmatter 写准,再去打磨正文步骤
- 先按最小权限收紧
allowed-tools,再考虑要不要给更多能力 - 只有模块边界明确时再上
paths,不要为了"高级"硬加 - 多目录项目优先做"离代码更近"的局部 Skill,而不是维护一个大而全的总模板
七、完整案例:把一个通用 code review 模板,提炼成你项目真正需要的 Skill
上面说了这么多抽象原则,不如走一遍完整例子。
假设你们团队每周都做后端代码审查,而且总在重复盯这几件事:
- 有人改一个功能,顺手动了三个不相关模块
- 新同学不知道项目里统一用
AppError,直接throw new Error() - Promise 链里漏了
await - 数据库查询没有索引,或者潜在 N+1 没被看出来
这就是非常典型的"该沉淀 Skill 的信号"。
第一步:先确认痛点到底是什么
这一步别着急写模板,先把"你们到底在反复出什么问题"说清楚。
很多团队的问题不是"没有 code review",而是每次 review 的注意力都被分散了。真正高频出错的点,永远是那几类项目特有的约束。
所以要沉淀的不是"代码审查"这四个字,而是你们团队在代码审查里最容易漏掉的那几类检查。
第二步:从公开模板里提取真正有用的部分
这时候公开模板就有用了,但它的用途不是直接上生产,而是当素材库。
假设你找到一个 50 行的通用 code review 模板。你真正该提取的,可能只有下面这几类东西:
- 逻辑正确性,尤其是异步操作
- 项目约定的遵守,比如
AppError、错误处理模式 - 数据库相关的风险,比如 N+1、索引、查询范围
- 改动范围是否聚焦,不要顺手改不相干文件
剩下那些跟你们项目关系不大的部分,就应该果断删掉。
第三步:把它压缩成一个真正能用的 Skill
最后落地出来的 Skill,应该更像这样:
markdown
# Code Review Skill
## When to use
在提交 PR 前,请 Claude 做最后的 code review。只用于 TypeScript 后端代码。
## Steps
1. 读 diff,确认改动是否只涉及这个 PR 的范围(不要顺手改无关文件)
2. 检查错误处理:所有 throw 都必须是 throw new AppError(),不能 throw new Error()
3. 检查异步操作:Promise 链是否有遗漏的 await,错误是否被正确 catch
4. 检查数据库查询:是否有 SELECT * 的懒惰写法,是否明显的 N+1 查询,关键查询是否 explain 过
## Output Format
Issues found (Critical → Warning → Info):
🔴 **Line 45**: Missing `.select()` in Prisma query - this will fetch unnecessary columns
🟡 **Line 67**: Potential N+1: loop inside `posts.map()` should use `Promise.all()`
✅ **No AppError violations** --- all errors properly handled
Summary: 1 critical issue to fix before merge.
## Caveats
- 不审查 UI 层代码(只关心后端逻辑)
- 不关注代码风格(那是 prettier 的事)
- 如果一个改动涉及多个不相关功能,分别提交 PR 再 review
你会发现,到这一步之后,Skill 就不再是"通用模板的中文版"了,而是你们项目真正有用的一个局部工作流。
50 行公开模板,最后可能只剩下 4 个真正属于你项目的核心关注点。但恰恰是这 4 个点,才决定它到底值不值得用。
第四步:在真实使用里继续迭代
Skill 从来不是一次写完的。
比如你用了两周之后,又发现一个常见问题:改了数据库 schema,但忘记更新 Prisma 类型。那就把它加进去:
markdown
4.5. 检查 Prisma 类型:如果改了数据库,Prisma schema 和生成的 types 是否都已更新
这时候你会发现,Skill 真正的价值不是"第一次写出来",而是在真实工作里被持续打磨。
八、Skill 的维护节奏,比第一次写出来更重要
Skill 不是写好就扔。
如果你写完之后三个月不看,它很快就会从"项目经验"重新退化成"历史遗留文档"。
我更推荐一个很轻的维护节奏:
第一个月
高频使用,快速迭代。每次用完就问自己三个问题:步骤是不是太复杂?输出是不是太啰嗦?有没有漏掉今天刚踩到的新坑?
之后每个月
回顾一次。看看最近有没有经常被遗漏的步骤,有没有新的痛点需要加入。
每个季度
系统清理一次。把已经不再是问题的注意事项删掉,把那些已经变成全局共识的规则移进 CLAUDE.md。
Skill 应该越用越精炼,而不是越写越臃肿。
九、一个特别反直觉,但很重要的经验:第一个 Skill,故意别写最重要的任务
这条我非常想单独拿出来讲。
因为很多人第一次沉淀 Skill,会本能地想挑一个最关键的任务,比如"生产环境发布前检查""数据库迁移前审查""支付流程改动 review"。
但工程上更稳的做法,其实正好相反。
大多数人写的第一个 Skill,质量都不会太高。触发条件偏模糊,步骤偏啰嗦,输出格式也不够稳定。这很正常,因为你第一次做这件事时,对"什么是好的 Skill"还没有直觉。
如果你一开始就把它用在最关键的任务上,一旦写得不够好,伤害会非常直接:要么关键场合出问题,要么你从此对这套机制失去信心。
更好的策略是:先拿一个重要程度中等、容错率比较高的任务练手。
比如:
- 写周报
- 生成 PR 描述
- 做一次常规 code review
先跑两周,迭代两三次,等你对 Skill 的节奏有感觉了,再去沉淀真正关键的流程。
第一个 Skill 的目的,不是直接解决最大的问题,而是让你学会怎么写 Skill。
十、如果你今天就想开始,可以直接做这三个动作
别先想着搭一整套系统,直接从最小动作开始:
任务一:列任务
列出你最近一个月里重复做过三次以上的任务。
任务二:做分类
用"三问判断法"确认它该放进 CLAUDE.md、Skill 还是 Prompt。
任务三:先写一个 5 到 10 行版本
先写触发条件、执行步骤、输出格式、注意事项。不要追求完美,先拿去用一次,再立刻改第一轮。
真正好的 Skill,几乎都不是第一次就写对的,而是在实际使用里慢慢长出来的。
下篇预告
第 06 篇:Sub-agents 实战------什么时候应该拆任务,怎么设计子任务边界
单个 Claude 实例有上下文上限,复杂任务拆成多个子任务让 Sub-agents 并行处理,理论上能大幅提速。但什么时候值得拆?拆错了会有什么代价?下一篇会拆开 Sub-agents 的真实适用场景,以及最常见的过度设计陷阱。
写在最后
公开 Skill 模板当然有用,但它的价值更像脚手架,而不是成品。
真正管用的 Skill,不是 star 最多的那个,也不是字段最全的那个,而是最贴近你项目真实工作流的那个。
你不需要一个很复杂的 Skill 系统。
你需要的,往往只是把团队最容易反复犯错的那几件事,提前写下来,让 Claude 每次都替你盯住。
这才是 Skill 真正应该发挥的作用。
如果你已经开始写 Skill 了,我反而建议你先检查一个问题:
你现在最卡住的,到底是"写不出规则",还是"根本没分清哪些该放 CLAUDE.md、哪些该做成 Skill"?
这两个问题看起来很像,但解法完全不同。
AI Coding 系列持续更新。用别人的 Skill 模板是起点,不是终点。真正管用的 Skill,只有你自己的项目才能提炼出来。