Day 13 / 共 20 天 · 第 3 周 扩展能力 · 加深版

Skills 技能系统

昨天(Day 12)MCP 接的是"工具/能力";今天 Skills 接的是"流程/怎么用这些能力办成一件事"。贴真实的 SKILL.md 解析代码,讲格式/来源/发现/调用,并用真实代码坐实 Day 11 的大一统洞察——skill 其实就是一个 prompt Command。

📍 你在整门课的位置 · 第 3 周「扩展能力」(MCP 给能力,Skill 给流程,Hook 给拦截点,子代理给分身)
D11 Slash 命令 D12 MCP 客户端 D13 Skills 技能 D14 Hooks D15 子代理
L01

Skill 是什么

🤔 痛点:每次都要重复交代"该怎么做" 你团队里"审查代码"有一套固定套路(先看 diff、按 5 个维度检查、用某种格式输出)。如果没有 Skill,你每次都得在提示词里把这套流程重抄一遍,既啰嗦又容易漏,换个人做又不一样。
💡 本质:Skill = 岗位的《作业指导书》 Skill 就像工厂工位上贴的《标准作业指导书 SOP》:新人不用问老师傅,照着卡片一步步做就行。把"做某类事的最佳步骤"写成一张卡片(SKILL.md),需要时自动递到模型手边,用哪张翻哪张——而 MCP 给的是工位上的"工具",Skill 给的是"用这些工具的作业流程"。

Skill(技能)是一段沉淀好的操作说明——把"遇到某类需求该怎么一步步做、该调哪些工具"写成一个 Markdown 文件(SKILL.md)。任务匹配时,这段说明会被注入给模型,让它照着做。

对比 Day 12 的 MCP:MCP 提供"工具"(能力),Skill 提供"流程"(怎么用这些能力办成一件事)。比如一个 "code-review" skill 告诉模型"审代码时先看 diff、按这几个维度检查、用 X 格式输出"。核心代码 src/skills/

为什么需要 Skill? 有些任务有固定的"最佳做法",但你不想每次都在 prompt 里重复交代。把它写成一个 skill,需要时自动加载给模型——相当于给模型一本"操作手册",用到哪章翻哪章。这也是 gov-agents 教程里 L4 skills 的同类概念(那边是团队 SOP,这边是本地/项目技能)。你现在读的这份教程,就是我(Claude Code)用一个 skill 驱动、加上读源码写出来的。
L02

三种来源

① Bundled(内置)

src/skills/bundled/*.ts

编译进二进制的技能(如 simplify/verify/ultracode)。用 registerBundledSkill 注册。

② 目录型

.claude/skills/<name>/SKILL.md

用户/项目/托管目录下的技能文件。你自己写技能的方式。

③ MCP 提供

src/skills/mcpSkills.ts

MCP server 也能提供 skill(feature 门控)。

bundled vs 目录型:bundled 是官方内置、跟着程序发布(改不了);目录型是你放在 .claude/skills/ 里的,能自由增删——项目级(进 git 团队共享)、用户级(~/.claude/skills 个人全局)。又是 Day 09/12/16 那套"项目/用户/托管"分层。
L03

SKILL.md 格式(真实解析字段)

一个 SKILL.md = YAML frontmatter(元数据)+ Markdown 正文(给模型的指令)。解析在 parseSkillFrontmatterFieldsloadSkillsDir.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_usewhenToUse:252);frontmatter.context === 'fork'executionContext='fork':260);user-invocable 默认 true(:217)。frontmatter = 文件顶部 --- 包起来的 YAML 元数据;正文才是给模型的实际内容。
context: fork 是什么? 表示这个 skill 作为一个独立子代理运行(Day 15)——它有独立上下文,不污染主对话。inline(默认)则是"内容直接展进当前对话"。getPromptForCommand:344)会做参数替换、注入基础目录、执行内联 shell(!命令)——但 MCP 来源的技能禁止执行内联 shell(安全)。
L04

发现与去重

getSkillDirCommandsloadSkillsDir.ts:638,memoized)扫描三处目录:托管 → 用户 ~/.claude/skills → 项目(从 cwd 向上直到 home 的各级 .claude/skills)+ --add-dir 附加目录。

  • 每个 skill 必须是 目录/SKILL.md 形式(单个 .md 不行)。
  • 旧式 .claude/commands/ 仍兼容(loadedFrom: 'commands_DEPRECATED')。
  • 符号链接去重:用 realpath 算文件真实身份,同一技能通过软链出现在多处只算一次。
为什么"从 cwd 向上找各级 .claude/skills"? 这样你在子目录工作时,能同时用到项目根、中间层、当前目录各级定义的技能——近的优先。这跟 Day 16 讲的 CLAUDE.md 记忆"向上逐级查找"是同一套目录发现机制。符号链接去重防止你把一个技能软链到多处导致重复注册。
L05

条件技能:碰到相关文件才出现

paths frontmatter 的条件技能:平时"隐身",只有当模型读写到匹配的文件时才激活可见。

  • discoverSkillDirsForPaths:861):模型读写文件时,从文件路径向上找嵌套的 .claude/skills(跳过 gitignored 目录)。
  • activateConditionalSkillsForPaths:997):用 ignore 库匹配 paths glob,命中才把技能激活进 dynamicSkills
条件技能解决什么? 假设你有个"React 组件规范"技能,只在改 .tsx 时相关。如果它总是注入给模型,纯属浪费 token(改后端时用不上)。用 paths: "**/*.tsx" 让它按需出现——模型碰到 tsx 文件才加载。这是"上下文经济"的又一体现(呼应 Day 10):只在相关时才占用宝贵的上下文空间。
L06

两条调用路径

Skill 有两种被触发的方式:

1

用户手敲 /skill-name args

走 Day 11 的斜杠命令 prompt 分支。若 user-invocable: false,用户直接调会被拒("只能由 Claude 调用")。

2

模型自己调(通过 Skill 工具)

getSkillToolCommandscommands.ts:650)把符合条件的技能暴露给模型,模型判断需要时用 Skill 工具(Day 08 那个)调用。

两条路径的分工user-invocable 控制"你能不能主动敲 /xxx 用它",disable-model-invocation 控制"模型能不能自己决定用它"。有的技能只给模型用、有的只给用户用、有的都行。模型调用时若技能自带 hooks(Day 14),还会 registerSkillHooks 注册,并 addInvokedSkill 记录以便压缩时保留(Day 10)。
L07

坐实大一统:skill 就是 prompt Command(真实代码)

Day 11 说"skill 本质是 type:'prompt' 的 Command"。这是真实代码——createSkillCommandloadSkillsDir.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) { ... },  // 展开成给模型的内容
  }
}
SKILL.mdfrontmatter+正文 createSkillCommand造成 type:'prompt' Command Day 11 同一套解析/派发管线和 /help /model /init 走一样的路getPromptForCommand → 喂给模型 MCP prompt / 插件命令 / bundled skill —— 同理,全是 prompt Command,只是 source 不同
图注:一个 Markdown 文件如何变成一个和斜杠命令共用管线的 Command。
📝 举个例子:写一个技能到被调用 你建 .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 就是"把技能正文展开成给模型的内容"。
把 Day 11/12/13 彻底串起来:Day 11 命令是 Command;Day 12 MCP 工具是 CoreTool;Day 13 skill 是 type:'prompt'CommandClaude Code 的可扩展性不是靠堆砌 N 套插件机制,而是把一切归约到少数几个统一抽象(Tool / Command),再让各种来源(内置/MCP/skill/plugin)都实现这些抽象。学会看穿"表面多样、底层统一",你读任何大项目都会更快。Day 15 你会看到最后一块拼图——多代理只是递归 query。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 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
明天预告 · Day 14:命令/MCP/Skill 是"加能力",Hooks 是"在生命周期节点插入自定义逻辑"。Day 14 讲事件 Hooks——27 种事件、settings.json 怎么配、退出码怎么拦工具(并区分它和 React UI hooks)。

← Day 12 MCP Day 14 · 事件 Hooks 系统 →