Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • Claude Code 插件与 Skill 系统深度分析

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 的区别

维度SkillToolCommand
本质Prompt 指令生成器函数调用UI 命令
调用者模型(SkillTool)+ 用户(/name)模型用户(/name)
输入args 字符串 + 上下文结构化 Zod schemaTUI 交互
输出ContentBlockParam[](prompt 注入)ToolResultReact 组件
工具限制allowedTools 白名单无(本身即工具)N/A
执行上下文inline / forkinlineinline(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:

  1. MCP Prompts:标准 MCP prompt 协议,loadedFrom === 'mcp'
  2. 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() 子代理