技能系统
Claude Code 如何发现、加载、解析并执行可复用工作流:SKILL.md、frontmatter、fork 模式、条件技能与 MCP 技能。
1. 概览
在 Claude Code 里,技能(Skill)不是一段普通提示词,而是一种可发现、可复用、可版本化的工作流封装。它既可以是你仓库里的 SKILL.md 文件,也可以是编译进 CLI 的内置技能,或者来自 MCP 服务器的远程技能。
对用户来说,技能通常表现为一条斜杠命令;对系统来说,技能其实是一种受控提示注入机制,外加参数替换、工具权限、模型覆盖、子代理执行和生命周期钩子。
2. 技能生命周期
每个技能从Claude Code开始到克劳德发挥作用都会经历六个阶段。
Walk managed / user /
project / --add-dir paths.
Read dir entries.
Resolve symlinks."] L["Load
Read SKILL.md.
Parse YAML frontmatter.
Estimate token budget."] P["Parse
Extract all frontmatter
fields into Command
object. Validate hooks,
paths, effort, shell."] S["Substitute
Replace $ARGUMENTS,
$1/$name, shell !backtick,
${CLAUDE_SKILL_DIR},
${CLAUDE_SESSION_ID}."] E["Execute
Run inline (expand into
conversation) or fork
(isolated sub-agent with
own token budget)."] I["Inject
Tool result enters
conversation. Allowed
tools and model override
applied for this turn."] D --> L --> P --> S --> E --> I style D fill:#22201d,stroke:#7d9ab8,color:#b8b0a4 style L fill:#22201d,stroke:#7d9ab8,color:#b8b0a4 style P fill:#22201d,stroke:#6e9468,color:#b8b0a4 style S fill:#22201d,stroke:#c47a50,color:#b8b0a4 style E fill:#22201d,stroke:#8e82ad,color:#b8b0a4 style I fill:#22201d,stroke:#6e9468,color:#b8b0a4
Discovery
启动时 getSkillDirCommands() 并行行走四个位置并加载旧版 .claude/commands/ 目录。 符号链接通过以下方式解析 realpath() 因此,在任何技能到达克劳德之前,重复的文件(相同的内容,不同的路径)都会被删除。
Load
Each skill-name/SKILL.md 文件被读取。 令牌数为 仅根据前文估计 (名称+描述+whenToUse)——完整的正文在启动时不会被标记化。 即使有数百个技能,这也可以使技能列表保持快速。 列表本身的预算上限为 上下文窗口的 1%.
Parse
frontmatter 被解析为 Command 目的。 所有字段(描述、允许的工具、参数提示、模型覆盖、挂钩、路径、工作量、shell)均在此处进行验证。 技能与 paths 场成为 条件技能 — 它们会被存储,但不会向 Claude 展示,直到用户打开匹配的文件。
Substitute
调用时,技能体按以下顺序进行参数替换:
- 命名参数:
$foo,$bar(按位置映射argumentsfrontmatter) - 索引参数:
$ARGUMENTS[0],$0,$1 - 完整参数字符串:
$ARGUMENTS - 如果没有找到占位符并且参数存在,它们将被附加为
ARGUMENTS: ... - 外壳注入:
!`command`or```!封锁(仅限本地技能 — MCP 技能被封锁) - 特殊变量:
${CLAUDE_SKILL_DIR},${CLAUDE_SESSION_ID}
Execute
技能以以下两种模式之一运行: context: fork 在前面的内容中:
- 内联(默认): 扩展的提示作为用户消息注入到当前对话中。 克劳德在同一上下文窗口中处理它。
- Forked: 该技能在隔离的子代理中运行(
runAgent())有自己的代币预算。 父对话仅接收最终的文本输出。 独立任务的理想选择。
Inject
技能工具返回一个 ToolResult。 对于内联技能:结果携带 allowedTools 和一个可选的 model 覆盖适用于本轮后续工具调用的内容。 对于分叉技能: Done 显示署名,并且子代理的输出作为上下文反馈。
3.四种技能来源及优先顺序
技能可以源自四个不同的来源。 当两个来源定义同名技能时, 第一个加载的获胜 (托管>用户>项目>捆绑)。 重复数据删除是通过解析的文件路径进行的,而不是通过名称进行的,因此符号链接的技能可以隐藏真实的技能。
管理/政策
managed/.claude/skills/由 IT 部署的企业控制技能。 可以通过锁定以防止用户覆盖 CLAUDE_CODE_DISABLE_POLICY_SKILLS.
用户(个人)
~/.claude/skills/您的跨项目个人技能库。 在运行 Claude Code 的任何地方都可用。 通过 chokidar 观看实时变化。
Project
.claude/skills/存储库范围的技能已签入存储库。 Claude从项目根往上走,这样嵌套 .claude/skills/ 当文件打开时,目录会被动态发现。
Bundled
编译成 CLI技能如 /simplify, /loop, /remember 与二进制文件一起发布。 注册通过 registerBundledSkill() 在启动时。 有些带有功能标记。
第五个来源:MCP 技能
当 MCP 服务器暴露技能时(由 loadedFrom === 'mcp'),它们在连接时获取并添加到命令注册表中。 MCP 技能遵循相同的 frontmatter 模式,但是 shell 注入被阻止 — !backtick and ```! 永远不会对远程、不受信任的内容执行块。
Legacy: .claude/commands/
年龄较大的 /commands/ 仍然支持目录(loadedFrom: 'commands_DEPRECATED')。 它接受单 .md 文件和 skill-name/SKILL.md 目录格式。 新作品应该使用 .claude/skills/.
实时重新加载
The skillChangeDetector 模块使用 chokidar 监视技能目录。 当有任何 SKILL.md 变化、去抖动(300 毫秒)、触发 ConfigChange 挂钩,然后清除所有记忆缓存,以便下次调用看到新技能。 在 Bun 上,使用统计轮询代替 FSWatcher 以避免已知的 Bun 死锁错误。
4. SKILL.md 格式和 Frontmatter
技能文件是纯 Markdown,带有可选的 YAML frontmatter 块,由 ---。 该文件必须位于 <skill-name>/SKILL.md (目录名称成为斜杠命令名称)。
现场参考
| Field | Type | Description |
|---|---|---|
name | string | 覆盖源自目录的显示名称 |
description | string | 技能列表中显示一行摘要。 回到正文的第一段。 |
when_to_use | string | 克劳德的详细触发说明。 结合清单中的描述。 |
allowed-tools | list | 在此技能期间授予的工具权限模式。 使用最窄的模式: Bash(gh:*) not Bash. |
argument-hint | string | 在 CLI 自动完成中显示为占位符提示。 |
arguments | 字符串或列表 | 命名参数标识符。 按位置映射到 $name 体内的替代品。 |
context | fork | 作为独立的子代理运行技能。 省略内联(默认)。 |
model | string | 执行此技能时使用的模型别名。 inherit 表示使用会话默认值。 |
effort | low/medium/high/int | 运行此技能时所应用的思考预算。 |
version | string | 信息版本标签; 无运行时效果。 |
user-invocable | bool | Default true。 放 false 躲避 /skills 菜单(仅限代理技能)。 |
paths | 全局字符串 | 有条件激活——技能仅在打开匹配文件时出现。 |
hooks | object | 工具使用前/后生命周期挂钩。 在加载时使用 HooksSchema 进行验证。 |
agent | string | 分叉时使用的代理类型标识符。 例如。 code, browser. |
shell | object | Shell 解释器配置 !backtick 技能体内的执行。 |
disable-model-invocation | bool | If true,该技能无法通过技能工具调用(只能通过斜杠命令)。 |
5. 高级技能模式
条件技能(基于路径的激活)
一项技能具有 paths frontmatter 场是 条件技能。 它在启动时加载,但是 not 直到用户打开或编辑路径与 glob 模式之一匹配的文件时,Claude 才会发现这一点。
这使您可以保持特定于支付、特定于基础设施或特定于 iOS 的工作流程不可见,直到它们真正相关为止,从而避免在不相关工作的技能列表中出现噪音。
--- paths: src/payments/** description: "Stripe refund workflow" --- # Stripe Refund Steps to issue a refund via the Stripe API...
模式使用与以下相同的语法 .gitignore /CLAUDE.md 条件规则。 一个图案 ** (匹配所有)被视为 unconditional (与省略相同 paths).
条件技能也与 动态技能发现:当模型在项目树中更深入地读取文件时,Claude Code 会向上走 cwd 并且可能会发现更多 .claude/skills/ 启动时未找到目录。 跳过 Gitignored 目录。
Bundled 技能(编译到 CLI 中)
Bundled 技能包含在 Claude Code 二进制文件中,并在启动时通过以下方式注册 registerBundledSkill()。 他们遵循相同的 BundledSkillDefinition 界面作为基于文件的技能,但提供 getPromptForCommand(args, context) 函数而不是 SKILL.md 文件。
众所周知的捆绑技能包括:
/simplify— 产生三个并行审查代理(重用、质量、效率)/loop— 解析间隔 + 提示并创建 cron 作业(带有功能标记)/remember,/verify,/debug,/stuck/skillify— 采访您有关当前会话的信息并编写 SKILL.md(Anthropic-内部)
Bundled 技能还可以通过以下方式包含参考文件 files 财产。 这些在第一次调用时被提取到每个进程的随机数目录中(使用 O_EXCL | O_NOFOLLOW | 0o600 防止符号链接攻击的标志),以及 Base directory for this skill: ... 前缀添加到提示符之前,以便 Claude 可以读取/Grep 它们。
一些捆绑技能以功能标志为条件(feature('AGENT_TRIGGERS'), feature('KAIROS'))或运行时检查(isKairosCronEnabled())。 无需修改磁盘上的 SKILL.md 即可添加或删除它们。
// Registering a bundled skill
registerBundledSkill({
name: 'simplify',
description: 'Review changed code for reuse, quality, and efficiency.',
userInvocable: true,
async getPromptForCommand(args) {
return [{ type: 'text', text: SIMPLIFY_PROMPT }]
},
})
MCP 技能(通过模型上下文协议提供)
MCP 服务器除了工具之外还可以公开技能。 当 Claude Code 使用以下命令连接到 MCP 服务器时 MCP_SKILLS 功能启用,它调用 fetchMcpSkillsForClient(),它使用相同的方法解析技能的 frontmatter parseSkillFrontmatterFields() and createSkillCommand() 用于基于文件的技能的函数。
MCP 技能出现在 SkillsMenu 在他们自己的“MCP 技能”组下。 他们的名字遵循惯例 server-name:skill-name.
与本地技能的主要区别:
- 无外壳注入:
!backtickand```!块被默默地跳过。 代码注释说: “安全性:MCP 技能是远程且不受信任的 - 切勿从其 Markdown 主体执行内联 shell 命令。” - ${CLAUDE_SKILL_DIR} 没有意义: MCP 技能没有本地目录,因此不替换该变量。
- 单独注册:MCP 技能存在于
AppState.mcp.commands,而不是本地技能注册表。 技能工具通过以下方式合并它们getAllCommands()在调用时。 - 注册表桥:
mcpSkillBuilders.ts是一个依赖图叶模块,包含对createSkillCommandandparseSkillFrontmatterFields,解决之间的循环导入问题client.ts → mcpSkills.ts → loadSkillsDir.ts.
// How getAllCommands() merges MCP and local skills const mcpSkills = context.getAppState().mcp.commands .filter(cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp') const localCommands = await getCommands(getProjectRoot()) return uniqBy([...localCommands, ...mcpSkills], 'name')
技能许可和自动允许逻辑
每次技能工具调用都会经过 checkPermissions()。 该决定遵循以下瀑布:
- 拒绝规则:如果一个
deny规则与技能名称(或带有前缀:*),立即封锁。 - 远程规范技能:自动允许(仅限蚂蚁实验功能)。
- 允许规则: 如果明确
allow规则匹配,无需询问即可继续。 - 安全属性检查:如果技能仅使用“安全”属性(没有允许的工具、没有模型覆盖、没有钩子、没有路径等),则它会自动允许,而不提示用户。
- Ask:否则,系统会提示用户添加精确或前缀的建议(
:*) 允许本地设置规则。
这意味着简单的信息技能无需任何权限对话框即可运行,而获得 Bash 访问权限或覆盖模型的技能必须得到明确批准。
深度参数替换
参数解析使用 shell-quote 因此带引号的字符串可以用作单个标记:
/myskill "hello world" foo → ["hello world", "foo"]
支持三种替换模式,按顺序处理:
| Pattern | 它决心做什么 |
|---|---|
$foo, $bar | 按位置命名 arg(需要 arguments: foo bar frontmatter) |
$ARGUMENTS[0], $0 | 索引位置 arg |
$ARGUMENTS | 完整的原始参数字符串 |
如果技能体包含 no 占位符和用户提供的参数,它们会自动附加: ARGUMENTS: <args>。 这可以防止在未声明占位符的简单技能中悄悄删除参数。
6. 要点
-
📁
技能住在
<skill-name>/SKILL.md。 目录名称是斜杠命令。 YAML frontmatter 控制所有元数据; Markdown 主体是提示。 - 🔍 克劳德从四个来源发现技能,按优先顺序排列: 托管→用户→项目→捆绑。 MCP 技能在调用时单独合并。
-
⚡
技能跑 inline (默认,相同的对话上下文)或 forked (
context: fork,孤立的子代理)。 Fork 更适合独立任务; 当您需要中间过程转向时,内联会更好。 -
🔒
MCP 技能 从不执行 shell 注入。 仅本地(托管/用户/项目/捆绑)技能可以运行
!backtick块。 这是源代码中的硬安全边界。 -
🎯
pathsfrontmatter 是一种技能 conditional — 在打开匹配文件之前不可见。 仅当相关时才使用它来显示特定于域的工作流程。 - 💡 技能列表的预算上限为 上下文窗口的 1%。 Bundled 技能描述永远不会被截断; 如果列表超出预算,自定义技能描述可能会缩短。
- 🔄 技能文件是 观看了直播。 保存 SKILL.md 会触发去抖重新加载(300 毫秒)。 无需重新启动。
- ✅ 简单的技能(没有允许的工具,没有模型覆盖) auto-approved 没有任何权限提示。 更强大的技能需要明确的用户允许规则。
知识检查
context: fork 会让技能在独立子代理中运行,而不是把展开后的提示直接塞进当前对话。!`...` 或 ```! shell 片段不会真正执行?paths frontmatter 的主要作用是什么?paths 不是注释信息,而是可见性门。只有当当前打开或操作的文件命中这些模式时,这个技能才会进入可发现集合。$ARGUMENTS 占位符,但用户仍然传了参数,系统会怎样处理?