Skills 技能系统
昨天(Day 12)MCP 接的是"工具/能力";今天 Skills 接的是"流程/怎么用这些能力办成一件事"。贴真实的 SKILL.md 解析代码,讲格式/来源/发现/调用,并用真实代码坐实 Day 11 的大一统洞察——skill 其实就是一个 prompt Command。
Skill 是什么
Skill(技能)是一段沉淀好的操作说明——把"遇到某类需求该怎么一步步做、该调哪些工具"写成一个 Markdown 文件(SKILL.md)。任务匹配时,这段说明会被注入给模型,让它照着做。
对比 Day 12 的 MCP:MCP 提供"工具"(能力),Skill 提供"流程"(怎么用这些能力办成一件事)。比如一个 "code-review" skill 告诉模型"审代码时先看 diff、按这几个维度检查、用 X 格式输出"。核心代码 src/skills/。
三种来源
① Bundled(内置)
编译进二进制的技能(如 simplify/verify/ultracode)。用 registerBundledSkill 注册。
② 目录型
用户/项目/托管目录下的技能文件。你自己写技能的方式。
③ MCP 提供
MCP server 也能提供 skill(feature 门控)。
.claude/skills/ 里的,能自由增删——项目级(进 git 团队共享)、用户级(~/.claude/skills 个人全局)。又是 Day 09/12/16 那套"项目/用户/托管"分层。SKILL.md 格式(真实解析字段)
一个 SKILL.md = YAML frontmatter(元数据)+ Markdown 正文(给模型的指令)。解析在 parseSkillFrontmatterFields(loadSkillsDir.ts:185),真实识别的字段(:217、:252、:260):
---
name: code-review # 显示名
description: 审查代码变更 # 缺失时从正文首段推断
when_to_use: 用户要求 review 时 # → 代码里的 whenToUse
user-invocable: true # 用户能否 /code-review 直接调(默认 true)
disable-model-invocation: false # 模型能否自己调
context: fork # → executionContext='fork':当子代理跑
allowed-tools: Read, Grep, Bash # 限制可用工具
paths: "src/**/*.ts" # 条件技能:触碰匹配文件才可见(L05)
model: inherit # 跟随主模型
---
# 审查流程
1. 先看 diff...
2. 按这几个维度检查...
frontmatter.when_to_use → whenToUse(:252);frontmatter.context === 'fork' → executionContext='fork'(:260);user-invocable 默认 true(:217)。frontmatter = 文件顶部 --- 包起来的 YAML 元数据;正文才是给模型的实际内容。inline(默认)则是"内容直接展进当前对话"。getPromptForCommand(:344)会做参数替换、注入基础目录、执行内联 shell(!命令)——但 MCP 来源的技能禁止执行内联 shell(安全)。发现与去重
getSkillDirCommands(loadSkillsDir.ts:638,memoized)扫描三处目录:托管 → 用户 ~/.claude/skills → 项目(从 cwd 向上直到 home 的各级 .claude/skills)+ --add-dir 附加目录。
- 每个 skill 必须是 目录/SKILL.md 形式(单个 .md 不行)。
- 旧式
.claude/commands/仍兼容(loadedFrom: 'commands_DEPRECATED')。 - 符号链接去重:用
realpath算文件真实身份,同一技能通过软链出现在多处只算一次。
条件技能:碰到相关文件才出现
带 paths frontmatter 的条件技能:平时"隐身",只有当模型读写到匹配的文件时才激活可见。
discoverSkillDirsForPaths(:861):模型读写文件时,从文件路径向上找嵌套的.claude/skills(跳过 gitignored 目录)。activateConditionalSkillsForPaths(:997):用ignore库匹配pathsglob,命中才把技能激活进dynamicSkills。
.tsx 时相关。如果它总是注入给模型,纯属浪费 token(改后端时用不上)。用 paths: "**/*.tsx" 让它按需出现——模型碰到 tsx 文件才加载。这是"上下文经济"的又一体现(呼应 Day 10):只在相关时才占用宝贵的上下文空间。两条调用路径
Skill 有两种被触发的方式:
用户手敲 /skill-name args
走 Day 11 的斜杠命令 prompt 分支。若 user-invocable: false,用户直接调会被拒("只能由 Claude 调用")。
模型自己调(通过 Skill 工具)
getSkillToolCommands(commands.ts:650)把符合条件的技能暴露给模型,模型判断需要时用 Skill 工具(Day 08 那个)调用。
user-invocable 控制"你能不能主动敲 /xxx 用它",disable-model-invocation 控制"模型能不能自己决定用它"。有的技能只给模型用、有的只给用户用、有的都行。模型调用时若技能自带 hooks(Day 14),还会 registerSkillHooks 注册,并 addInvokedSkill 记录以便压缩时保留(Day 10)。坐实大一统:skill 就是 prompt Command(真实代码)
Day 11 说"skill 本质是 type:'prompt' 的 Command"。这是真实代码——createSkillCommand(loadSkillsDir.ts:270)把一个 SKILL.md 包成的东西(:317):
function createSkillCommand({ skillName, description, whenToUse, userInvocable,
executionContext, hooks, paths, ... }): Command {
return {
type: 'prompt', // ← ★ 就是一个 prompt 命令!
name: skillName,
description,
whenToUse,
userInvocable,
context: executionContext, // inline / fork
disableModelInvocation,
paths, // 条件技能
isHidden: !userInvocable,
async getPromptForCommand(args, toolUseContext) { ... }, // 展开成给模型的内容
}
}
.claude/skills/hello/SKILL.md,frontmatter 写 name: hello、正文写"用中文说你好" → 启动时被 createSkillCommand 包成 type:'prompt' 的 Command → 你敲 /hello(走 Day 11 的 prompt 分支)→ getPromptForCommand 把正文"用中文说你好"作为 user 消息发给模型 → 模型回"你好!"。type: 'prompt'——一个 SKILL.md 最终就是被造成一个 type:'prompt' 的 Command(Day 11 L02 的三态之一)。所以它复用 Day 11 整套解析/派发管线,getPromptForCommand 就是"把技能正文展开成给模型的内容"。Command;Day 12 MCP 工具是 CoreTool;Day 13 skill 是 type:'prompt' 的 Command。Claude Code 的可扩展性不是靠堆砌 N 套插件机制,而是把一切归约到少数几个统一抽象(Tool / Command),再让各种来源(内置/MCP/skill/plugin)都实现这些抽象。学会看穿"表面多样、底层统一",你读任何大项目都会更快。Day 15 你会看到最后一块拼图——多代理只是递归 query。今日小结 + 动手
🧠 今天你应该能回答
- Skill 和 MCP 的区别?(流程 SOP vs 工具能力)
- 三种来源、SKILL.md 的关键 frontmatter 字段?
- 条件技能(paths)解决什么?(上下文经济)
- 两条调用路径 + user-invocable/disable-model-invocation 分工?
- 为什么说 skill 本质是 type:'prompt' 的 Command?(createSkillCommand 真实代码)
✋ 动手:对着真实代码读一遍
# 1. frontmatter 解析字段(L03)
sed -n '185,270p' src/skills/loadSkillsDir.ts | grep -n "when_to_use\|user-invocable\|context\|whenToUse\|executionContext"
# 2. skill = prompt Command(L07,本页精华)
sed -n '317,345p' src/skills/loadSkillsDir.ts
# 3. 发现与条件技能(L04/L05)
sed -n '861,900p' src/skills/loadSkillsDir.ts
# 4. 内置 skill 长什么样
ls src/skills/bundled/ && sed -n '1,20p' src/skills/bundled/simplify.ts
# 5. 自己写一个 skill 试试
mkdir -p .claude/skills/hello && printf -- '---\nname: hello\ndescription: 打招呼\n---\n说你好' > .claude/skills/hello/SKILL.md