Claude Code 插件与 Skill 系统深度分析
1. 插件系统设计
1.1 架构概览
Claude Code 的插件系统是一个三层扩展架构:
Plugin (容器)
├── Skills (斜杠命令技能)
├── Hooks (生命周期钩子)
├── MCP Servers (外部协议服务)
├── Agents (子代理)
├── Output Styles (输出风格)
└── LSP Servers (语言服务)
插件通过 PluginManifest 描述自身能力,LoadedPlugin 表示已加载的插件实例。插件来源分为三类:
| 来源 | 标识格式 | 说明 |
|---|---|---|
| Built-in | {name}@builtin | 随 CLI 发布,用户可开关 |
| Marketplace | {name}@{marketplace} | 从远程仓库安装 |
| Legacy | 本地目录 | 旧版 /commands/ 目录 |
1.2 内置插件注册机制
核心文件:src/plugins/builtinPlugins.ts
// 注册中心 — 一个简单的 Map<string, BuiltinPluginDefinition>
const BUILTIN_PLUGINS: Map<string, BuiltinPluginDefinition> = new Map()
// 注册入口
export function registerBuiltinPlugin(
definition: BuiltinPluginDefinition,
): void {
BUILTIN_PLUGINS.set(definition.name, definition)
}
BuiltinPluginDefinition 类型定义了插件的完整能力声明:
export type BuiltinPluginDefinition = {
name: string
description: string
version?: string
skills?: BundledSkillDefinition[] // 提供的技能
hooks?: HooksSettings // 提供的钩子
mcpServers?: Record<string, McpServerConfig> // 提供的 MCP 服务
isAvailable?: () => boolean // 可用性检查
defaultEnabled?: boolean // 默认开启状态
}
1.3 插件生命周期
启动阶段,initBuiltinPlugins()(位于 src/plugins/bundled/index.ts)被调用。目前该函数体为空——这是脚手架代码,预留用于将来将 bundled skill 迁移为可切换的内置插件。
启用/禁用状态通过 getSettings_DEPRECATED() 读取用户设置持久化:
// 优先级:用户设置 > 插件默认值 > true
const isEnabled =
userSetting !== undefined
? userSetting === true
: (definition.defaultEnabled ?? true)
插件中的 Skill 通过 skillDefinitionToCommand() 转换为 Command 对象,source 标记为 'bundled'(而非 'builtin'——后者保留给硬编码的斜杠命令如 /help、/clear)。
1.4 插件类型系统
PluginComponent 可辨别联合类型精确描述了插件可能失败的每一种情况(共 20+ 种错误类型),从 git 认证失败、网络错误到 MCP 配置无效、LSP 服务崩溃,每种都携带上下文数据以支持诊断。
2. Skill 系统设计
2.1 核心定义
Skill 的核心类型是 BundledSkillDefinition(src/skills/bundledSkills.ts:15):
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' // 执行上下文
agent?: string
files?: Record<string, string> // 附带的参考文件
getPromptForCommand: (args, context) => Promise<ContentBlockParam[]>
}
Skill 本质是一个 prompt 指令生成器——getPromptForCommand 函数根据用户参数和上下文动态生成 prompt 内容块,注入到模型对话中。
2.2 注册流程
// 1. 定义 Skill → registerBundledSkill()
// 2. 转换为 Command 对象,推入 bundledSkills[] 数组
// 3. CLI 启动时 initBundledSkills() 批量注册
注册时,如果 Skill 携带 files 参考文件,系统会:
- 懒提取:首次调用时才写入磁盘
- 幂等:Promise 级别去重,避免并发写入
- 安全:
O_NOFOLLOW | O_EXCL防止符号链接攻击,0o700/0o600权限限制 - 注入
Base directory for this skill: <dir>前缀,使模型可通过 Read/Grep 访问参考文件
2.3 文件系统 Skill 加载
src/skills/loadSkillsDir.ts 实现了从磁盘目录发现 Skill 的机制,支持四个来源:
加载优先级(并行):
├── managed (企业管理策略) → <managed>/.claude/skills/
├── user (用户全局) → ~/.claude/skills/
├── project (项目级) → .claude/skills/ (向上遍历到 home)
├── additional (--add-dir) → <dir>/.claude/skills/
└── legacy (旧版兼容) → .claude/commands/
每个 Skill 是一个目录,内含 SKILL.md 文件。SKILL.md 使用 YAML frontmatter 声明元数据:
---
name: my-skill
description: Does something useful
allowed-tools:
- Read
- Write
- Bash(git:*)
when_to_use: Use when the user wants to refactor code
argument-hint: "<file pattern>"
arguments:
- pattern
context: fork
model: inherit
---
# My Skill
Skill content with $pattern substitution and ${CLAUDE_SKILL_DIR} references.
关键 frontmatter 解析函数 parseSkillFrontmatterFields() 处理:
allowed-tools→ 工具白名单when_to_use→ 模型自动调用的触发条件arguments/argument-hint→ 参数替换 ($argName)context: fork→ 子代理隔离执行effort→ 工作量级别控制paths→ 条件激活(文件路径匹配时才加载)shell→ 前置 shell 命令执行
2.4 动态 Skill 发现
系统支持运行时动态发现 Skill:
// 文件操作触发 → 沿路径向上查找 .claude/skills/ → 加载 → 通知监听者
export async function discoverSkillDirsForPaths(filePaths, cwd)
export async function addSkillDirectories(dirs)
还支持条件激活——带有 paths frontmatter 的 Skill 暂存在 conditionalSkills Map 中,仅当用户操作匹配的文件路径时才激活:
export function activateConditionalSkillsForPaths(filePaths, cwd)
2.5 SkillTool — 模型调用入口
Skill 通过 SkillTool(src/tools/SkillTool/SkillTool.ts)暴露给模型。这是一个标准工具,接受两个参数:
z.object({
skill: z.string(), // Skill 名称
args: z.string().optional(), // 可选参数
})
执行模式分两种:
Inline(内联):Skill prompt 直接注入当前对话上下文,模型在同一会话中处理。适合需要用户中途干预的工作流。
Fork(分叉):在隔离的子代理中执行,拥有独立的 token 预算。适合自包含任务:
async function executeForkedSkill(command, commandName, args, context, ...) {
const agentId = createAgentId()
const { modifiedGetAppState, baseAgent, promptMessages, skillContent } =
await prepareForkedCommandContext(command, args, context)
for await (const message of runAgent({
agentDefinition,
promptMessages,
toolUseContext: { ...context, getAppState: modifiedGetAppState },
...
})) {
agentMessages.push(message)
// 进度上报...
}
}
3. Skill vs Tool vs Command 的区别
| 维度 | Skill | Tool | Command |
|---|---|---|---|
| 本质 | Prompt 指令生成器 | 函数调用 | UI 命令 |
| 调用者 | 模型(SkillTool)+ 用户(/name) | 模型 | 用户(/name) |
| 输入 | args 字符串 + 上下文 | 结构化 Zod schema | TUI 交互 |
| 输出 | ContentBlockParam[](prompt 注入) | ToolResult | React 组件 |
| 工具限制 | allowedTools 白名单 | 无(本身即工具) | N/A |
| 执行上下文 | inline / fork | inline | inline(TUI) |
| 来源 | 磁盘文件 / bundled / MCP / plugin | 代码注册 | 代码注册 |
关键区别:Skill 不直接执行操作,而是生成 prompt 让模型理解"该做什么"。模型再通过 Tool 调用来实际执行。Skill 是意图层,Tool 是执行层。
4. 内置 Skill 示例
4.1 /debug — 会话调试
src/skills/bundled/debug.ts
registerBundledSkill({
name: 'debug',
description: 'Enable debug logging for this session and help diagnose issues',
allowedTools: ['Read', 'Grep', 'Glob'],
disableModelInvocation: true, // 仅用户可调用
async getPromptForCommand(args) {
enableDebugLogging()
const tail = await tailDebugLog(debugLogPath, 20)
// 生成包含日志末尾、错误分析指令的 prompt
return [{ type: 'text', text: `# Debug Skill\n...${tail}...` }]
},
})
特点:运行时读取日志尾部注入 prompt,允许工具仅限读取类操作,禁止模型自动调用。
4.2 /remember — 记忆整理
src/skills/bundled/remember.ts
registerBundledSkill({
name: 'remember',
description: 'Review auto-memory entries and propose promotions...',
isEnabled: () => isAutoMemoryEnabled(), // 条件启用
async getPromptForCommand(args) {
return [{ type: 'text', text: SKILL_PROMPT }] // 静态 prompt
},
})
特点:通过 isEnabled 条件控制可见性,prompt 完全静态(定义为常量字符串),指导模型审查跨层级(CLAUDE.md / CLAUDE.local.md / auto-memory)的记忆条目。
4.3 /skillify — 过程捕获为 Skill
src/skills/bundled/skillify.ts
registerBundledSkill({
name: 'skillify',
description: "Capture this session's repeatable process into a skill",
allowedTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'AskUserQuestion', 'Bash(mkdir:*)'],
disableModelInvocation: true,
async getPromptForCommand(args, context) {
const sessionMemory = await getSessionMemoryContent()
const userMessages = extractUserMessages(context.messages)
// 模板替换:注入会话记忆和用户消息
const prompt = SKILLIFY_PROMPT
.replace('{{sessionMemory}}', sessionMemory)
.replace('{{userMessages}}', userMessages.join('\n\n---\n\n'))
return [{ type: 'text', text: prompt }]
},
})
特点:访问运行时上下文(会话记忆、消息历史)动态生成 prompt,引导模型通过多轮交互(AskUserQuestion)将当前会话的可重复过程捕获为新的 SKILL.md 文件。
4.4 /verify — 带参考文件的 Skill
src/skills/bundled/verify.ts
registerBundledSkill({
name: 'verify',
description: DESCRIPTION,
files: SKILL_FILES, // 附带参考文件
async getPromptForCommand(args) {
return [{ type: 'text', text: SKILL_BODY }]
},
})
特点:通过 files 字段携带额外参考文件,首次调用时提取到磁盘,prompt 中注入 Base directory for this skill: 前缀,使模型可以通过 Read/Grep 访问验证脚本。
5. MCP Skill 集成
5.1 MCP Skill 发现
MCP(Model Context Protocol)服务器可以通过两种方式暴露 Skill:
- MCP Prompts:标准 MCP prompt 协议,
loadedFrom === 'mcp' - MCP Skills:通过
mcpSkillBuilders.ts中的注册机制,复用createSkillCommand和parseSkillFrontmatterFields
5.2 循环依赖解决
MCP Skill 发现需要 createSkillCommand 和 parseSkillFrontmatterFields(来自 loadSkillsDir.ts),但 MCP 客户端模块又在依赖图的上游。为避免循环依赖,系统使用写一次注册表模式:
// mcpSkillBuilders.ts — 叶子模块,只导入类型
export type MCPSkillBuilders = {
createSkillCommand: typeof createSkillCommand
parseSkillFrontmatterFields: typeof parseSkillFrontmatterFields
}
let builders: MCPSkillBuilders | null = null
export function registerMCPSkillBuilders(b: MCPSkillBuilders): void { builders = b }
export function getMCPSkillBuilders(): MCPSkillBuilders { ... }
loadSkillsDir.ts 在模块初始化时注册构建器:
registerMCPSkillBuilders({
createSkillCommand,
parseSkillFrontmatterFields,
})
5.3 MCP Skill 在 SkillTool 中的集成
SkillTool 的 getAllCommands() 函数合并本地 Skill 和 MCP Skill:
async function getAllCommands(context: ToolUseContext): Promise<Command[]> {
const mcpSkills = context
.getAppState()
.mcp.commands.filter(
cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp',
)
if (mcpSkills.length === 0) return getCommands(getProjectRoot())
const localCommands = await getCommands(getProjectRoot())
return uniqBy([...localCommands, ...mcpSkills], 'name')
}
MCP Skill 与本地 Skill 在模型视角下完全同构——统一的 Command 类型,统一的 SkillTool 调用接口。
5.4 安全边界
MCP Skill 有额外的安全限制——禁止执行内联 shell 命令:
// Security: MCP skills are remote and untrusted
if (loadedFrom !== 'mcp') {
finalContent = await executeShellCommandsInPrompt(finalContent, ...)
}
6. 插件与 Skill 的关系
6.1 包含关系
Plugin 是容器,Skill 是 Plugin 可以提供的组件之一:
BuiltinPluginDefinition
├── skills?: BundledSkillDefinition[] ← Skill 在这里
├── hooks?: HooksSettings
└── mcpServers?: Record<string, McpServerConfig>
6.2 独立性
但 Skill 也可以完全独立于 Plugin 存在:
| Skill 来源 | 是否依赖 Plugin | 说明 |
|---|---|---|
src/skills/bundled/ | 否 | 编译进 CLI,通过 registerBundledSkill() 直接注册 |
.claude/skills/ | 否 | 文件系统发现,用户自定义 |
Plugin skills/ 目录 | 是 | 随 Plugin 安装,Plugin 卸载后消失 |
| MCP Server | 间接 | 通过 MCP 协议暴露,MCP 服务连接后可用 |
| Plugin 内置 | 是 | BuiltinPluginDefinition.skills,随 Plugin 启停 |
6.3 设计哲学
Bundled Skill(src/skills/bundled/)适用于随 CLI 发布、无需用户管理的功能。它们在 initBundledSkills() 中注册,始终可用。
Built-in Plugin 适用于需要用户显式启用/禁用的功能。通过 /plugin UI 管理,状态持久化到用户设置。注释明确指出:并非所有 bundled 功能都应该成为 built-in plugin——只有需要用户切换的才应该走 plugin 路径。
文件系统 Skill(.claude/skills/)是用户和项目级的扩展点。通过 SKILL.md 的 frontmatter 声明元数据,无需编译即可创建。/skillify 和 /init 命令都支持引导用户创建此类 Skill。
7. 关键代码路径总结
CLI 启动
├── initBundledSkills() → registerBundledSkill() × N
├── initBuiltinPlugins() → registerBuiltinPlugin() (目前为空)
└── getSkillDirCommands(cwd) → 扫描磁盘 SKILL.md 文件
用户输入 /skill-name
└── findCommand('skill-name') → Command (type: 'prompt')
└── getPromptForCommand() → ContentBlockParam[]
└── 注入对话上下文
模型调用 Skill
└── SkillTool({ skill: 'name', args: '...' })
├── getAllCommands() → 合并本地 + MCP Skill
├── validateInput() → 查找 Command, 权限检查
├── executeInlineSkill() → 注入当前对话
└── executeForkedSkill() → runAgent() 子代理