目录
  1. 1. 一、插件 vs 技能 — 概念区分
  2. 2. 二、技能系统核心 (skills/loadSkillsDir.ts, 1087行)
    1. 2.1. 2.1 技能是什么
    2. 2.2. 2.2 技能加载来源
    3. 2.3. 2.3 Frontmatter 完整字段定义
    4. 2.4. 2.4 Skill 到 Command 对象的转换
    5. 2.5. 2.5 变量替换系统
    6. 2.6. 2.6 去重机制
    7. 2.7. 2.7 路径匹配 (条件化技能)
  3. 3. 三、内置技能 (skills/bundled/, 17个文件)
    1. 3.1. 3.1 注册流程
    2. 3.2. 3.2 BundledSkillDefinition 结构
    3. 3.3. 3.3 附带文件提取机制
    4. 3.4. 3.4 典型内置技能实例: /stuck
  4. 4. 四、插件系统 (plugins/builtinPlugins.ts, 160行)
    1. 4.1. 4.1 插件注册
    2. 4.2. 4.2 插件启用/禁用逻辑
    3. 4.3. 4.3 Plugin vs Bundled Skill 的区别
  5. 5. 五、MCP技能桥接 (skills/mcpSkillBuilders.ts)
  6. 6. 六、完整的技能执行流
【Claude Code源码剖析】13-插件与技能系统深度解析

源码路径: src/plugins/ (内置插件注册), src/skills/ (技能加载核心), src/skills/bundled/ (内置技能)
核心文件: loadSkillsDir.ts (1087行), bundledSkills.ts (221行), builtinPlugins.ts (160行)
本质: 一套基于 Markdown frontmatter 的声明式扩展系统


一、插件 vs 技能 — 概念区分

这两个系统容易混淆,先厘清:

维度 Plugin (插件) Skill (技能)
定义 一个可启用/禁用的功能包 一个特定任务的prompt模板
粒度 包含多个skills + hooks + MCP servers 单个markdown文件
管理 /plugin UI 开关 /skills 目录放文件即生效
来源 {name}@builtin 或 marketplace 项目/.claude/skills/ 或用户/全局
源码 src/plugins/builtinPlugins.ts src/skills/loadSkillsDir.ts

简单说:Plugin是容器,Skill是内容。 一个Plugin可以包含多个Skills。


二、技能系统核心 (skills/loadSkillsDir.ts, 1087行)

2.1 技能是什么

一个Skill就是一个 带frontmatter的Markdown文件,放在约定的目录中:

<!-- 示例: .claude/skills/fix-tests.md -->
---
description: 自动修复失败的测试
allowed-tools:
- Bash
- Edit
- Read
when_to_use: 当用户要求修复测试或CI失败时
argument-hint: "[test file or pattern]"
---

当用户要求修复测试时,按以下步骤操作:
1. 运行 `npm test` 获取失败信息
2. 分析每个失败的测试
3. 修复代码或测试
4. 重新运行确认通过

2.2 技能加载来源

// loadSkillsDir.ts 中的 getSkillsPath():
function getSkillsPath(source: SettingSource | 'plugin', dir: 'skills' | 'commands'): string {
switch (source) {
case 'policySettings': return join(getManagedFilePath(), '.claude', dir) // 管理员级
case 'userSettings': return join(getClaudeConfigHomeDir(), dir) // ~/.claude/skills/
case 'projectSettings': return '.claude/skills' // 项目级
case 'plugin': return 'plugin' // 插件提供
}
}

加载优先级(高→低):

1. 管理员技能  /etc/claude-code/.claude/skills/   (IT管理员部署)
2. 用户技能 ~/.claude/skills/ (个人全局)
3. 项目技能 .claude/skills/ (项目级, 可git提交)
4. 内置技能 编译到binary中 (Anthropic提供)
5. MCP技能 MCP服务器注册 (远程工具扩展)

2.3 Frontmatter 完整字段定义

parseSkillFrontmatterFields() 提取的完整字段列表:

// loadSkillsDir.ts 原文返回类型:
{
displayName: string | undefined // 显示名 (name frontmatter)
description: string // 描述
hasUserSpecifiedDescription: boolean // 是否有用户指定的描述
allowedTools: string[] // 允许使用的工具列表
argumentHint: string | undefined // 参数提示
argumentNames: string[] // 命名参数列表
whenToUse: string | undefined // 模型自动调用条件
version: string | undefined // 版本号
model: string | undefined // 指定模型 (或 'inherit')
disableModelInvocation: boolean // 禁止模型自动调用
userInvocable: boolean // 用户是否可通过/命令调用
hooks: HooksSettings | undefined // 生命周期钩子
executionContext: 'fork' | undefined // 执行上下文 (fork=子agent)
agent: string | undefined // 指定agent类型
effort: EffortValue | undefined // 努力程度
shell: FrontmatterShell | undefined // shell配置
}

2.4 Skill 到 Command 对象的转换

createSkillCommand() 是关键的转换函数,它将解析出的frontmatter + markdown body转换为运行时 Command 对象:

// loadSkillsDir.ts 原文 (简化):
export function createSkillCommand({ ... }): Command {
return {
type: 'prompt',
name: skillName,
description,
allowedTools,
whenToUse,
// ...

async getPromptForCommand(args, toolUseContext) {
let finalContent = baseDir
? `Base directory for this skill: ${baseDir}\n\n${markdownContent}`
: markdownContent

// 1. 替换 $ARGUMENTS
finalContent = substituteArguments(finalContent, args, true, argumentNames)

// 2. 替换 ${CLAUDE_SKILL_DIR}
if (baseDir) {
const skillDir = process.platform === 'win32'
? baseDir.replace(/\\/g, '/') : baseDir
finalContent = finalContent.replace(/\$\{CLAUDE_SKILL_DIR\}/g, skillDir)
}

// 3. 替换 ${CLAUDE_SESSION_ID}
finalContent = finalContent.replace(
/\$\{CLAUDE_SESSION_ID\}/g, getSessionId()
)

// 4. 执行内联shell命令 (!`...`)
// 安全: MCP技能(远程不可信)不执行shell命令
if (loadedFrom !== 'mcp') {
finalContent = await executeShellCommandsInPrompt(finalContent, ...)
}

return [{ type: 'text', text: finalContent }]
},
}
}

2.5 变量替换系统

技能markdown中支持的变量:

变量 来源 用途
$ARGUMENTS 用户输入 /skill-name 这里是参数
$1, $2 命名参数 frontmatter中的 arguments 字段
${CLAUDE_SKILL_DIR} 技能文件目录 引用技能附带的脚本/配置文件
${CLAUDE_SESSION_ID} 当前session 日志/跟踪
!`command` shell执行 在prompt中内联执行shell命令(仅本地技能)

2.6 去重机制

// loadSkillsDir.ts 原文:
async function getFileIdentity(filePath: string): Promise<string | null> {
try {
return await realpath(filePath) // 解析符号链接到真实路径
} catch { return null }
}

为什么需要去重: 用户可能有 ~/.claude/skills/test.md./claude/skills/test.md 指向同一个文件(通过符号链接)。realpath() 解析到规范路径后去重。

2.7 路径匹配 (条件化技能)

// loadSkillsDir.ts 原文:
function parseSkillPaths(frontmatter: FrontmatterData): string[] | undefined {
if (!frontmatter.paths) return undefined
const patterns = splitPathInFrontmatter(frontmatter.paths)
.map(pattern => pattern.endsWith('/**') ? pattern.slice(0, -3) : pattern)
.filter(p => p.length > 0)
// ** (match-all) 等同于无路径限制
if (patterns.every(p => p === '**')) return undefined
return patterns
}

这使得技能可以声明”只在特定目录下生效”,例如:

paths: src/api/**

三、内置技能 (skills/bundled/, 17个文件)

3.1 注册流程

// skills/bundled/index.ts 原文:
export function initBundledSkills(): void {
registerUpdateConfigSkill() // 更新配置
registerKeybindingsSkill() // 快捷键帮助
registerVerifySkill() // 验证代码变更 (ant-only)
registerDebugSkill() // 调试
registerLoremIpsumSkill() // 生成测试数据
registerSkillifySkill() // 将操作转化为skill
registerRememberSkill() // 记忆
registerSimplifySkill() // 简化代码
registerBatchSkill() // 批处理
registerStuckSkill() // 诊断卡住的session (ant-only)

// 条件注册 (feature gate):
if (feature('KAIROS') || feature('KAIROS_DREAM'))
registerDreamSkill()
if (feature('AGENT_TRIGGERS'))
registerLoopSkill() // 循环执行
if (feature('BUILDING_CLAUDE_APPS'))
registerClaudeApiSkill() // Claude API使用技能
if (shouldAutoEnableClaudeInChrome())
registerClaudeInChromeSkill() // Chrome集成
}

3.2 BundledSkillDefinition 结构

// bundledSkills.ts 原文:
export type BundledSkillDefinition = {
name: string
description: string
aliases?: string[]
whenToUse?: string // 模型何时自动调用
argumentHint?: string
allowedTools?: string[]
model?: string
disableModelInvocation?: boolean
userInvocable?: boolean
isEnabled?: () => boolean // 运行时可见性检查
hooks?: HooksSettings
context?: 'inline' | 'fork' // fork = 在子agent中执行
agent?: string
files?: Record<string, string> // 附带的引用文件
getPromptForCommand: (args: string, context: ToolUseContext) => Promise<ContentBlockParam[]>
}

3.3 附带文件提取机制

某些内置技能需要引用额外文件(如脚本、配置):

// bundledSkills.ts 原文:
export function registerBundledSkill(definition: BundledSkillDefinition): void {
const { files } = definition

if (files && Object.keys(files).length > 0) {
skillRoot = getBundledSkillExtractDir(definition.name)
// 延迟提取: 首次调用时将文件写入磁盘
let extractionPromise: Promise<string | null> | undefined
const inner = definition.getPromptForCommand
getPromptForCommand = async (args, ctx) => {
extractionPromise ??= extractBundledSkillFiles(definition.name, files)
const extractedDir = await extractionPromise
const blocks = await inner(args, ctx)
if (extractedDir === null) return blocks
return prependBaseDir(blocks, extractedDir) // 添加 "Base directory: ..." 前缀
}
}
}

安全写入:

// bundledSkills.ts 原文:
// O_NOFOLLOW|O_EXCL 防止符号链接攻击
const SAFE_WRITE_FLAGS = process.platform === 'win32'
? 'wx'
: fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | O_NOFOLLOW

async function safeWriteFile(p: string, content: string): Promise<void> {
const fh = await open(p, SAFE_WRITE_FLAGS, 0o600) // 600 = owner-only
try { await fh.writeFile(content, 'utf8') }
finally { await fh.close() }
}

3.4 典型内置技能实例: /stuck

// skills/bundled/stuck.ts (ant-only, 完整prompt):
// 功能: 诊断本机上卡死/缓慢的Claude Code会话
//
// 调查步骤:
// 1. 列出所有Claude Code进程 (ps -axo pid=,pcpu=,rss=,...)
// 2. 检查可疑进程:
// - CPU ≥90% 持续 → 可能死循环
// - 状态 D (不可中断睡眠) → I/O挂起
// - 状态 T (停止) → 用户误按 Ctrl+Z
// - 状态 Z (僵尸) → 父进程未回收
// - RSS ≥4GB → 内存泄漏
// - 子进程卡死 (如 git)
// 3. 如发现问题 → 通过Slack MCP发送到 #claude-code-feedback
//
// 安全: 只诊断,不kill任何进程

四、插件系统 (plugins/builtinPlugins.ts, 160行)

4.1 插件注册

// builtinPlugins.ts 原文:
const BUILTIN_PLUGINS: Map<string, BuiltinPluginDefinition> = new Map()
export const BUILTIN_MARKETPLACE_NAME = 'builtin'

export function registerBuiltinPlugin(definition: BuiltinPluginDefinition): void {
BUILTIN_PLUGINS.set(definition.name, definition)
}

4.2 插件启用/禁用逻辑

// builtinPlugins.ts 原文:
export function getBuiltinPlugins(): { enabled: LoadedPlugin[]; disabled: LoadedPlugin[] } {
const settings = getSettings_DEPRECATED()
const enabled: LoadedPlugin[] = []
const disabled: LoadedPlugin[] = []

for (const [name, definition] of BUILTIN_PLUGINS) {
// 可用性检查 (某些插件可能依赖特定环境)
if (definition.isAvailable && !definition.isAvailable()) continue

const pluginId = `${name}@${BUILTIN_MARKETPLACE_NAME}`

// 优先级: 用户设置 > 插件默认值 > true
const userSetting = settings?.enabledPlugins?.[pluginId]
const isEnabled = userSetting !== undefined
? userSetting === true
: (definition.defaultEnabled ?? true)

const plugin: LoadedPlugin = {
name,
manifest: { name, description: definition.description, version: definition.version },
path: BUILTIN_MARKETPLACE_NAME, // 哨兵值,无文件路径
source: pluginId,
repository: pluginId,
enabled: isEnabled,
isBuiltin: true,
hooksConfig: definition.hooks,
mcpServers: definition.mcpServers,
}

if (isEnabled) enabled.push(plugin)
else disabled.push(plugin)
}
return { enabled, disabled }
}

4.3 Plugin vs Bundled Skill 的区别

// builtinPlugins.ts 原文注释:
/**
* Built-in plugins differ from bundled skills (src/skills/bundled/) in that:
* - They appear in the /plugin UI under a "Built-in" section
* - Users can enable/disable them (persisted to user settings)
* - They can provide multiple components (skills, hooks, MCP servers)
*/

五、MCP技能桥接 (skills/mcpSkillBuilders.ts)

MCP服务器可以通过 prompts/list 端点暴露prompt模板,Claude Code 将它们转换为 Skill:

MCP服务器声明:
prompts/list → [{ name: "analyze-code", description: "...", arguments: [...] }]

Claude Code转换:
MCP prompt → createSkillCommand({
skillName: "mcp-server-name/analyze-code",
loadedFrom: 'mcp',
source: 'mcp',
// 注意: MCP技能不执行内联shell命令 (安全)
})

六、完整的技能执行流

用户输入: /fix-tests src/api/
或模型判断 whenToUse 匹配自动调用


┌─────────────────────────┐
│ 查找 Command by name │
│ (skills + bundled + │
│ managed + mcp) │
└──────────┬──────────────┘

┌──────────┴──────────────┐
│ getPromptForCommand() │
│ 1. substituteArguments │ ← $ARGUMENTS = "src/api/"
│ 2. ${CLAUDE_SKILL_DIR} │ ← 替换为技能文件目录
│ 3. ${CLAUDE_SESSION_ID} │ ← 替换为session ID
│ 4. executeShellCommands │ ← 执行 !`...` 内联命令
│ 5. return ContentBlock │
└──────────┬──────────────┘

┌──────────┴──────────────┐
│ context == 'fork'? │
│ yes → runForkedAgent() │ ← 在子agent中执行
│ no → 直接注入主循环 │ ← 作为user message
└─────────────────────────┘
打赏
  • 微信
  • 支付宝

评论