⚠️ 学习声明 :本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。
本质 : 一套基于 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 - Readwhen_to_ use: 当用户要求修复测试或CI失败时 argument-hint: "[test file or pattern]" --- 当用户要求修复测试时,按以下步骤操作: 1. 运行 `npm test` 获取失败信息2. 分析每个失败的测试3. 修复代码或测试 4. 重新运行确认通过
2.2 技能加载来源 function getSkillsPath (source : SettingSource | 'plugin' , dir : 'skills' | 'commands' ): string { switch (source) { case 'policySettings' : return join (getManagedFilePath (), '.claude' , dir) case 'userSettings' : return join (getClaudeConfigHomeDir (), dir) 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() 提取的完整字段列表:
{ displayName : string | undefined description : string hasUserSpecifiedDescription : boolean allowedTools : string [] argumentHint : string | undefined argumentNames : string [] whenToUse : string | undefined version : string | undefined model : string | undefined disableModelInvocation : boolean userInvocable : boolean hooks : HooksSettings | undefined executionContext : 'fork' | undefined agent : string | undefined effort : EffortValue | undefined shell : FrontmatterShell | undefined }
2.4 Skill 到 Command 对象的转换 createSkillCommand() 是关键的转换函数,它将解析出的frontmatter + markdown body转换为运行时 Command 对象:
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 finalContent = substituteArguments (finalContent, args, true , argumentNames) if (baseDir) { const skillDir = process.platform === 'win32' ? baseDir.replace (/\\/g , '/' ) : baseDir finalContent = finalContent.replace (/\$\{CLAUDE_SKILL_DIR\}/g , skillDir) } finalContent = finalContent.replace ( /\$\{CLAUDE_SESSION_ID\}/g , getSessionId () ) 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 去重机制 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 路径匹配 (条件化技能) 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 ) if (patterns.every (p => p === '**' )) return undefined return patterns }
这使得技能可以声明”只在特定目录下生效”,例如:
三、内置技能 (skills/bundled/, 17个文件) 3.1 注册流程 export function initBundledSkills ( ): void { registerUpdateConfigSkill () registerKeybindingsSkill () registerVerifySkill () registerDebugSkill () registerLoremIpsumSkill () registerSkillifySkill () registerRememberSkill () registerSimplifySkill () registerBatchSkill () registerStuckSkill () if (feature ('KAIROS' ) || feature ('KAIROS_DREAM' )) registerDreamSkill () if (feature ('AGENT_TRIGGERS' )) registerLoopSkill () if (feature ('BUILDING_CLAUDE_APPS' )) registerClaudeApiSkill () if (shouldAutoEnableClaudeInChrome ()) registerClaudeInChromeSkill () }
3.2 BundledSkillDefinition 结构 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 : string , context : ToolUseContext ) => Promise <ContentBlockParam []> }
3.3 附带文件提取机制 某些内置技能需要引用额外文件(如脚本、配置):
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) } } }
安全写入:
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 ) try { await fh.writeFile (content, 'utf8' ) } finally { await fh.close () } }
3.4 典型内置技能实例: /stuck
四、插件系统 (plugins/builtinPlugins.ts, 160行) 4.1 插件注册 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 插件启用/禁用逻辑 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} ` 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 的区别
五、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 └─────────────────────────┘
七、插件 vs 技能 vs MCP — 架构边界 7.1 三种扩展机制对比
维度
插件 (Plugin)
技能 (Skills)
MCP
扩展内容
新工具 + 新命令 + Hooks + MCP
新知识/指令(Prompt 模板)
新工具(外部服务进程)
实现方式
TypeScript 函数(编译进 binary)
Markdown 文件(运行时加载)
外部进程(任意语言)
加载时机
CC 启动时(同步)
CC 启动时(异步扫描目录)
Session 开始时(spawn 子进程)
复杂度
高(需写代码 + 编译)
低(纯文本,即写即用)
中(需部署服务进程)
隔离性
进程内(共享事件循环)
进程内(prompt 注入)
进程外(stdio/socket 隔离)
用户可控
可禁用(/plugin UI)
删除文件即移除
断开连接即移除
适用场景
深度集成(自定义工具逻辑)
项目知识/规范/工作流
第三方 API 集成
安全风险
高(可执行任意 TypeScript)
低(只影响 Prompt)
中(进程级隔离但有工具权限)
7.2 三种机制的分工边界 ┌──────────────────────────────────┐ │ Plugin(容器) │ │ ┌────────────┐ ┌────────────┐ │ │ │ Skills │ │ MCP │ │ │ │ (prompt) │ │ Servers │ │ │ └────────────┘ └────────────┘ │ │ ┌────────────┐ ┌────────────┐ │ │ │ Hooks │ │ Custom │ │ │ │ (lifecyle)│ │ Tools │ │ │ └────────────┘ └────────────┘ │ └──────────────────────────────────┘ 选择策略: 需要新知识/规范 → Skill(Markdown,最简单) 需要新外部工具 → MCP Server(进程隔离,最安全) 需要深度集成/生命周期控制 → Plugin(最强大,但需要编译) 三者可组合:Plugin 内包含 Skills + MCP + Hooks
7.3 技能的 RAG 本质与限制 技能(Skills)本质上是一种简化的 RAG(Retrieval-Augmented Generation) ,但其设计哲学与传统 RAG 有本质区别:
维度
传统 RAG
Skill 系统
检索时机
查询时(动态)
启动时(静态预加载)
检索方式
Embedding + 向量相似度
目录扫描 + Frontmatter 匹配
选择性
Top-K 相关文档
whenToUse 字段条件匹配
延迟
100-500ms(嵌入计算)
0ms(已预加载到 System Prompt)
精确度
召回率-精确率权衡
精确匹配(用户主动调用或 whenToUse 触发)
Skill 的预加载模式避免了 RAG 的检索不确定性,但代价是增加了 System Prompt 长度。这是一个用 Token 换确定性 的经典权衡。
八、动态加载沙箱与安全隔离 8.1 为什么需要沙箱? Plugin 系统面临的核心安全挑战:第三方 TypeScript 代码可以直接访问文件系统、网络、环境变量 。
Plugin 的能力(进程内,无沙箱): ✅ 读取用户文件(可能泄露 ~/.ssh/id_rsa) ✅ 发起网络请求(可能外传数据) ✅ 执行 shell 命令(通过 child_process) ✅ 修改环境变量(影响其他 Plugin) ❌ 不能:操作系统级破坏(受限于 Node.js 权限)
8.2 CC 的分层安全策略 Layer 1 — 来源可信度分级 Built-in Plugin(Anthropic 签名) → 完全信任 Marketplace Plugin(社区审核) → 有限信任 Local Plugin(用户手写) → 完全信任(用户自己写的) MCP Server(外部进程) → 进程隔离 + 工具权限控制 Layer 2 — 权限最小化 Skill: allowedTools 字段限制可调用的工具 Plugin: onCreate/onDestroy hook 中声明的资源访问 MCP: toolPermissions 白名单控制 Layer 3 — 运行时监控 FileRead hook 可注入文件访问审计 Bash hook 可拦截危险命令 Stop hook 记录 Plugin 的副作用
8.3 文件提取的安全写入 内置技能需要将附属文件写入磁盘时,使用 O_NOFOLLOW | O_EXCL 标志防止符号链接攻击:
const SAFE_WRITE_FLAGS = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW ; async function safeWriteFile (p : string , content : string ): Promise <void > { const fh = await open (p, SAFE_WRITE_FLAGS , 0o600 ); try { await fh.writeFile (content, 'utf8' ); } finally { await fh.close (); } }
8.4 MCP 技能的安全限制 从 MCP 服务器加载的 Skill 不执行内联 shell 命令 :
if (loadedFrom !== 'mcp' ) { finalContent = await executeShellCommandsInPrompt (finalContent, ...) }
这个设计防止了以下攻击场景:
攻击场景: 恶意 MCP 服务器声明了 prompt template: "删除所有临时文件: !`rm -rf /tmp/*`" 如果执行这个内联命令 → 任意代码执行 CC 通过 loadedFrom === 'mcp' 检查拦截
九、版本兼容与依赖管理 9.1 语义化版本的强制执行 CC 的 Plugin 系统要求版本号(通过 manifest version 字段),但不强制使用 semver ——这让社区 Plugin 的兼容性管理变得困难:
type PluginManifest = { name : string ; version : string ; claudeCodeVersion ?: string ; dependencies ?: Record <string , string >; };
9.2 CC 版本兼容性检测 function checkPluginCompatibility ( manifest : PluginManifest , ccVersion : string ): CompatibilityResult { if (!manifest.claudeCodeVersion ) { return { compatible : true , warning : 'No CC version requirement specified' }; } if (!semver.satisfies (ccVersion, manifest.claudeCodeVersion )) { return { compatible : false , reason : `Plugin requires CC ${manifest.claudeCodeVersion} , current: ${ccVersion} ` }; } return { compatible : true }; }
9.3 加载失败时的降级策略 Plugin 加载失败的分级处理: 致命失败(阻止启动): └─ 核心 Plugin(如 tool permissions plugin)失败 降级运行(警告,继续): ├─ 社区 Plugin 不兼容 → 跳过该 Plugin ├─ Plugin LSP server 启动失败 → 跳过 LSP 功能 └─ Hooks 注册失败 → 跳过该 hook 静默忽略(不打扰用户): └─ 可选依赖缺失
async function loadPluginsWithFallback (plugins : Plugin [] ): Promise <LoadedPlugin []> { const results : LoadedPlugin [] = []; for (const plugin of plugins) { try { const loaded = await loadPlugin (plugin); results.push (loaded); } catch (err) { if (plugin.isBuiltin ) { throw new Error (`Built-in plugin ${plugin.name} failed to load: ${err.message} ` ); } else { console .warn (`Plugin ${plugin.name} skipped: ${err.message} ` ); } } } return results; }
十、插件发现与市场模型 10.1 三层插件来源 1. 内置 Plugin(Built-in) 来源:编译进 CC binary(src/plugins/builtinPlugins.ts) 标识:{name}@builtin 可禁用但不能卸载 2. 本地 Plugin(Local) 来源:项目 .claude/plugins/ 目录 开发模式:直接引用 TypeScript 源码 作用域:仅当前项目 3. Marketplace Plugin(社区) 来源:远程 Registry(npm/GitHub) 安装:claude plugin install {name} 缓存:~/.claude/plugins/cache/
10.2 插件发现流程 启动时: ├─ 1. 扫描 builtin registry → 获取内置 Plugin 列表 ├─ 2. 读取 user settings → 获取已安装的社区 Plugin ├─ 3. 扫描 project .claude/plugins/ → 获取本地 Plugin ├─ 4. 合并 Plugin 列表(后加载覆盖先加载的配置) └─ 5. 每个 Plugin 调用 getPluginLspServers() 等方法收集能力
10.3 与 VSCode Extension 系统的对比
维度
CC Plugin
VSCode Extension
扩展点
Tools, Commands, Skills, Hooks, MCP, LSP
Commands, Views, Languages, Debug Adapters
UI 贡献
无(终端 UI)
WebView, TreeView, StatusBar 等
激活事件
启动时全量加载
onLanguage:typescript 等懒激活
隔离
进程内(无隔离)
进程内 + Extension Host 分离
分发
无正式 Marketplace
内置 Marketplace
代码签名
无
可选(Verified Publishers)
自动更新
无
默认开启
CC Plugin 系统相比 VSCode 的最大差距在于懒激活 和隔离性 ——所有 Plugin 在启动时同步加载,VSCode 可以延迟到需要时才激活特定 Extension。对 CC 这种追求快速启动的工具,这是需要未来优化的方向。
十一、插件生命周期与 Hook 系统集成 11.1 完整的生命周期 Plugin 生命周期: [注册] → registerBuiltinPlugin(definition) ↓ [加载] → loadPlugin(pluginId) ├─ 读取 manifest ├─ 检查版本兼容性 ├─ 执行 onInit() hook(如定义) └─ 注册 tools/commands/skills/mcp/hooks ↓ [运行] → Plugin 提供的工具和命令生效 ↓ [卸载] → user disables plugin ├─ 注销 tools/commands ├─ 断开 MCP 连接 ├─ 执行 onShutdown() hook └─ 清理临时文件
11.2 Hook 的注入时机 Plugin 通过 Hooks 可以介入 Agent 循环的关键节点:
PostToolUse → 工具执行后,结果返回前 └─ 用途:审计文件修改、注入 LSP 诊断 Stop → Agent 决定停止时 └─ 用途:检查是否有未完成的工作 PreToolUse → 工具执行前 └─ 用途:阻止危险操作 UserPromptSubmit → 用户输入提交时 └─ 用途:预处理用户输入 Notification → 系统通知时 └─ 用途:转发通知到外部系统(Slack等)
11.3 内置技能完整清单
技能
文件
作用
触发条件
stuck
bundled/stuck.ts
诊断卡死会话
Agent 检测到循环
security-policy
bundled/security.ts
注入安全约束
始终加载
debug
bundled/debug.ts
调试辅助
用户调用 /debug
verify
bundled/verify.ts
验证代码变更
ant-only
dream
bundled/dream.ts
自动思考模式
KAIROS feature gate
loop
bundled/loop.ts
循环执行
AGENT_TRIGGERS gate
remember
bundled/remember.ts
记忆管理
始终加载
skillify
bundled/skillify.ts
转化为 skill
用户调用 /skillify
batch
bundled/batch.ts
批处理
用户调用 /batch
涉及源文件
builtinPlugins.ts
bundledSkills.ts
loadSkillsDir.ts
src/plugins/
src/skills/
src/skills/bundled/