Skip to content

技能系统 (Skill System) 深度剖析

技能系统是 Claude Code 最重要的扩展框架之一,允许通过 Markdown + YAML Frontmatter 定义可复用的专业提示词,并将其注册为斜杠命令(/skill-name)或 LLM 可自动调用的工具。

系统全景

┌──────────────────────────────────────────────────┐
│                 5 大技能来源                       │
│  ┌──────────┐  ┌────────┐  ┌────────────────────┐│
│  │ Bundled  │  │ Skills │  │  Plugin / Managed  ││
│  │ (编译内置) │  │  Dir   │  │  (插件/策略管理)    ││
│  │ 10+ 技能  │  │ (磁盘)  │  │                    ││
│  └────┬─────┘  └───┬────┘  └────────┬───────────┘│
│       │            │                │             │
│  ┌────┴─────┐  ┌───┴────────────────┤             │
│  │ MCP 技能  │  │ Legacy commands/  │             │
│  │ (远程服务) │  │ (旧格式兼容)       │             │
│  └────┬─────┘  └───┬───────────────┘             │
│       │            │                              │
│       └────────┬───┘                              │
│                ▼                                  │
│   ┌────────────────────────┐                      │
│   │  getSkills() 聚合       │                      │
│   │  去重 → 排序 → Command[]│                      │
│   └──────────┬─────────────┘                      │
│              │                                    │
│    ┌─────────┴──────────┐                         │
│    ▼                    ▼                          │
│ ┌──────────────┐  ┌──────────────────┐            │
│ │ 斜杠命令面板   │  │ SkillTool        │            │
│ │ /skill-name  │  │ (LLM 自动调用)    │            │
│ │ (用户调用)    │  │                  │            │
│ └──────────────┘  └──────────────────┘            │
└──────────────────────────────────────────────────┘

核心文件

文件位置功能
bundledSkills.tssrc/skills/内置技能注册、文件提取
loadSkillsDir.tssrc/skills/磁盘技能扫描、加载、去重
mcpSkillBuilders.tssrc/skills/MCP 技能循环依赖桥接
bundled/index.tssrc/skills/bundled/所有内置技能的初始化入口
SkillTool.tssrc/tools/SkillTool/技能执行工具 (LLM 调用入口)
frontmatterParser.tssrc/utils/YAML frontmatter 解析
commands.tssrc/命令聚合与排序

核心类型定义

BundledSkillDefinition — 内置技能接口

typescript
type BundledSkillDefinition = {
  name: string
  description: string
  aliases?: string[]
  whenToUse?: string               // LLM 何时应调用此技能
  argumentHint?: string            // 参数提示
  allowedTools?: string[]          // 技能可使用的工具白名单
  model?: string                   // 指定模型
  disableModelInvocation?: boolean // true = 仅用户可调用,LLM 不可自动触发
  userInvocable?: boolean          // false = 仅 LLM 可调用
  isEnabled?: () => boolean        // 运行时可见性控制
  hooks?: HooksSettings            // 技能级 Hooks
  context?: 'inline' | 'fork'     // inline = 当前会话展开, fork = 子 agent
  agent?: string                   // 指定 agent 名称
  files?: Record<string, string>   // 附带参考文件 (相对路径 → 内容)
  getPromptForCommand: (           // 提示词生成函数
    args: string,
    context: ToolUseContext
  ) => Promise<ContentBlockParam[]>
}

FrontmatterData — 磁盘技能前端数据

typescript
type FrontmatterData = {
  'allowed-tools'?: string | string[]   // 工具白名单
  description?: string                   // 技能描述
  'argument-hint'?: string              // 参数提示
  when_to_use?: string                  // 何时使用
  version?: string                      // 版本号
  model?: 'inherit' | 'haiku' | 'sonnet' | string  // 模型控制
  'user-invocable'?: string            // 用户可调用性
  hooks?: HooksSettings                 // 技能级 Hooks
  effort?: 'low' | 'medium' | 'high' | 'max' | number  // 思考力度
  context?: 'inline' | 'fork'          // 执行上下文
  agent?: string                        // agent 名称
  paths?: string | string[]            // glob 模式,条件激活
  shell?: 'bash' | 'powershell'        // Shell 类型
  arguments?: string | string[]         // 参数名列表
}

LoadedFrom — 来源标识

typescript
type LoadedFrom =
  | 'commands_DEPRECATED'  // 旧 /commands/ 目录
  | 'skills'               // /skills/ 目录
  | 'plugin'               // 插件市场
  | 'managed'              // 企业策略管理
  | 'bundled'              // 编译内置
  | 'mcp'                  // MCP 服务器

技能生命周期

阶段 1:定义与注册

内置技能注册

src/skills/bundled/index.ts 在启动时同步调用 initBundledSkills()

typescript
export function initBundledSkills(): void {
  // ━━ 始终注册 ━━
  registerUpdateConfigSkill()
  registerKeybindingsSkill()
  registerVerifySkill()
  registerDebugSkill()
  registerLoremIpsumSkill()
  registerSkillifySkill()
  registerRememberSkill()
  registerSimplifySkill()
  registerBatchSkill()
  registerStuckSkill()

  // ━━ 特性门控注册 ━━
  if (feature('KAIROS') || feature('KAIROS_DREAM'))
    registerDreamSkill()
  if (feature('REVIEW_ARTIFACT'))
    registerHunterSkill()
  if (feature('AGENT_TRIGGERS'))
    registerLoopSkill()
  if (feature('AGENT_TRIGGERS_REMOTE'))
    registerScheduleRemoteAgentsSkill()
  if (feature('BUILDING_CLAUDE_APPS'))
    registerClaudeApiSkill()
  if (shouldAutoEnableClaudeInChrome())
    registerClaudeInChromeSkill()
  if (feature('RUN_SKILL_GENERATOR'))
    registerRunSkillGeneratorSkill()
}

registerBundledSkill() 将定义转为 Command 对象:

typescript
function registerBundledSkill(definition: BundledSkillDefinition): void {
  let getPromptForCommand = definition.getPromptForCommand
  let skillRoot: string | undefined

  // 如果技能附带文件 → 包装提示词函数以懒提取
  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) => {
      // 延迟提取:首次调用时解压,Promise 被 memoize
      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 command: Command = {
    type: 'prompt',
    name: definition.name,
    source: 'bundled',
    loadedFrom: 'bundled',
    skillRoot,
    context: definition.context,
    getPromptForCommand,
    // ...
  }
  bundledSkills.push(command)
}

磁盘技能加载

loadSkillsFromSkillsDir() 扫描标准目录结构 skill-name/SKILL.md

.claude/skills/
├── my-skill/
│   ├── SKILL.md           # 技能定义(frontmatter + prompt)
│   └── reference.txt      # 参考文件(可选)
├── another-skill/
│   └── SKILL.md
typescript
// 扫描流程
async function loadSkillsFromSkillsDir(
  dir: string,
  source: SettingSource,
  loadedFrom: LoadedFrom
): Promise<Command[]> {
  // 1. 列举目录下所有 SKILL.md
  const entries = await readdir(dir)

  // 2. 跳过 .gitignore 中的目录
  const gitignore = await loadGitignore(dir)

  // 3. 对每个 SKILL.md:
  for (const entry of entries) {
    const content = await readFile(join(dir, entry, 'SKILL.md'))

    // 4. 解析 YAML frontmatter
    const { frontmatter, content: body } = parseFrontmatter(content)

    // 5. 解析字段
    const fields = parseSkillFrontmatterFields(frontmatter)

    // 6. 创建 Command
    const command = createSkillCommand({
      name: entry,
      body,
      ...fields,
      source,
      loadedFrom,
      skillRoot: join(dir, entry),
    })

    commands.push(command)
  }
  return commands
}

阶段 2:聚合与去重

commands.ts 中的 getSkills() 并行加载所有来源:

typescript
async function getSkills(cwd: string) {
  const [skillDirCommands, pluginSkills] = await Promise.all([
    getSkillDirCommands(cwd),      // 磁盘技能
    getPluginSkills(),              // 插件市场技能
  ])
  const bundledSkills = getBundledSkills()             // 内置技能(同步)
  const builtinPluginSkills = getBuiltinPluginSkillCommands() // 内置插件技能
  return { skillDirCommands, pluginSkills, bundledSkills, builtinPluginSkills }
}

最终命令列表的组装顺序(优先级从高到低):

bundledSkills          ← 编译内置(最高优先)
builtinPluginSkills    ← 内置插件技能
skillDirCommands       ← 磁盘技能
workflowCommands       ← 工作流命令
pluginCommands         ← 插件命令
pluginSkills           ← 插件技能
COMMANDS()             ← 内置斜杠命令 (/help, /clear 等)

去重机制(双层)

第一层:文件身份去重

typescript
// 通过 realpath 解决 symlink 去重
async function getFileIdentity(filePath: string): Promise<string | null> {
  try {
    return await realpath(filePath)  // 解析 symlink 到规范路径
  } catch { return null }
}

// 并行计算所有技能文件的 realpath
const fileIds = await Promise.all(
  allSkillsWithPaths.map(({ skill, filePath }) =>
    skill.type === 'prompt' ? getFileIdentity(filePath) : null
  )
)

// 第一个加载的获胜 (managed > user > project)
const seenFileIds = new Map<string, SettingSource>()
for (let i = 0; i < allSkillsWithPaths.length; i++) {
  const fileId = fileIds[i]
  if (seenFileIds.has(fileId)) {
    logForDebugging(`Skipping duplicate skill (same file)`)
    continue
  }
  seenFileIds.set(fileId, skill.source)
  deduplicatedSkills.push(skill)
}

第二层:名称去重

typescript
// getCommands() 中通过 uniqBy('name') 再次处理
const commands = uniqBy(allCommands, 'name')

去重设计要点:

  • 使用 realpath() 而非 inode(避免 NFS/ExFAT/容器环境的不可靠性)
  • 优先级:managed > user > project > additional > legacy

阶段 3:模型发现(上下文提示词注入)

getSkillToolCommands() 过滤出 LLM 可调用的技能:

typescript
function getSkillToolCommands(): Command[] {
  return allCommands.filter(cmd =>
    !cmd.disableModelInvocation &&
    cmd.source !== 'builtin'
  )
}

预算感知的技能列表

技能列表注入 system prompt 时有严格的 Token 预算控制:

typescript
const SKILL_BUDGET_CONTEXT_PERCENT = 0.01  // 上下文窗口的 1%
const MAX_LISTING_DESC_CHARS = 250

function formatCommandsWithinBudget(commands: Command[]): string {
  // 优先级截断策略:
  // 1. 内置技能: 永不截断,完整展示描述
  // 2. 非内置技能: 按预算均分描述长度
  // 3. 极端情况: 非内置技能只展示名称
}

阶段 4:执行

SkillTool.ts 是 LLM 调用技能的入口:

LLM → 调用 Skill 工具

        ├── 1. 命令查找: getAllCommands() 合并本地 + MCP 技能

        ├── 2. 上下文决策:
        │       ├── context === 'fork' → executeForkedSkill()
        │       │                        (隔离子 agent, 独立工具集)
        │       └── context === 'inline' → 直接展开到当前会话

        ├── 3. 提示词生成: command.getPromptForCommand(args, ctx)

        ├── 4. 变量替换:
        │       ├── ${CLAUDE_SKILL_DIR} → 技能目录路径
        │       ├── ${CLAUDE_SESSION_ID} → 当前会话 ID
        │       └── $arg_name → 用户参数

        └── 5. Shell 命令执行 (非 MCP 技能):
                executeShellCommandsInPrompt()
                处理 !`...` 和 ```! ... ``` 块

多层级目录搜索

getSkillDirCommands() 按优先级搜索 5 个层级:

1. managed    → {MANAGED_FILE_PATH}/.claude/skills/     策略管理(不可禁用)
2. user       → ~/.claude/skills/                        用户级全局
3. project    → {projectDirs}/.claude/skills/            项目级(cwd 向上至 home)
4. additional → {--add-dir}/.claude/skills/              CLI --add-dir 指定
5. legacy     → {projectDirs}/commands/                  旧格式兼容

WARNING

--bare 模式下跳过除 --add-dir 外的所有自动发现。

动态技能发现

当 LLM 读写文件时,沿文件路径向上查找 .claude/skills/ 目录:

typescript
async function discoverSkillDirsForPaths(
  filePaths: string[],
  cwd: string
): Promise<string[]> {
  // 从文件的父目录向上走到 cwd
  // 跳过 .gitignore 中的目录
  // 已检查的路径缓存在 dynamicSkillDirs Set 中
}

条件技能(paths 激活)

技能可通过 paths frontmatter 字段声明 glob 模式,仅当 LLM 操作匹配的文件时才激活:

yaml
---
description: React 组件专家
paths:
  - "src/components/**/*.tsx"
  - "src/hooks/**/*.ts"
---

你是 React 组件开发专家...

运行时管理:

typescript
// 全局条件技能注册表
const conditionalSkills = new Map<string, Command>()
const activatedConditionalSkillNames = new Set<string>()

// 当 LLM 操作文件时检查
function checkConditionalSkillActivation(filePath: string): void {
  for (const [name, skill] of conditionalSkills) {
    if (matchesGlob(filePath, skill.paths)) {
      activatedConditionalSkillNames.add(name)
    }
  }
}

Frontmatter 解析

typescript
const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)---\s*\n?/

function parseFrontmatter(
  markdown: string,
  sourcePath?: string
): ParsedMarkdown {
  const match = markdown.match(FRONTMATTER_REGEX)
  if (!match) return { frontmatter: {}, content: markdown }

  const frontmatterText = match[1]
  const content = markdown.slice(match[0].length)

  try {
    const parsed = parseYaml(frontmatterText)
    return { frontmatter: parsed, content }
  } catch {
    // 容错: 自动引用有问题的 YAML 值
    const quotedText = quoteProblematicValues(frontmatterText)
    const parsed = parseYaml(quotedText)
    return { frontmatter: parsed, content }
  }
}

parseSkillFrontmatterFields() 统一解析所有 frontmatter 字段,被文件加载和 MCP 加载两条路径共用。


技能磁盘格式

标准 SKILL.md 文件

markdown
---
description: 自动生成 API 文档
when_to_use: 当用户要求生成文档时
argument-hint: <api-endpoint>
allowed-tools:
  - Read
  - Grep
  - Write
model: sonnet
context: fork
effort: high
paths:
  - "src/api/**"
shell: bash
arguments:
  - endpoint
  - format
---

你是 API 文档生成专家。

## 任务
`$endpoint` 端点生成 `$format` 格式的文档。

## 参考
!`cat ${CLAUDE_SKILL_DIR}/templates/api-doc.md`

Shell 命令内联

技能体中可嵌入动态 Shell 命令(非 MCP 来源才执行):

markdown
当前项目结构:
!`find src -name "*.ts" | head -20`

最近 Git 日志:
!`git log --oneline -5`

行内 !`command` 和代码块 ```! 两种格式均支持。


文件提取安全模型

内置技能的 files 字段允许捆绑参考文件,这些文件在首次调用时懒提取到磁盘。

路径遍历防护

typescript
function resolveSkillFilePath(baseDir: string, relPath: string): string {
  const normalized = normalize(relPath)
  if (
    isAbsolute(normalized) ||
    normalized.split(pathSep).includes('..') ||
    normalized.split('/').includes('..')
  ) {
    throw new Error(`bundled skill file path escapes skill dir: ${relPath}`)
  }
  return join(baseDir, normalized)
}
typescript
// 1. 每进程随机 nonce 路径
//    /tmp/claude-{uid}/bundled-skills/{VERSION}/{hex-nonce}/
//    → 攻击者无法预测提取目录

// 2. 安全写入标志
const SAFE_WRITE_FLAGS =
  process.platform === 'win32'
    ? 'wx'  // Windows: 独占创建
    : fsConstants.O_WRONLY | fsConstants.O_CREAT
      | fsConstants.O_EXCL    // 文件已存在则失败
      | O_NOFOLLOW            // 拒绝 symlink 跟随

// 3. 严格权限
// 目录: 0o700 (owner rwx)
// 文件: 0o600 (owner rw)

// 4. 不自动重试
// EEXIST 时不 unlink+重建(防止中间路径 symlink 替换攻击)

MCP 技能安全沙箱

typescript
// MCP 应来远程不可信 — 禁止执行嵌入的 Shell 命令
if (loadedFrom !== 'mcp') {
  finalContent = await executeShellCommandsInPrompt(body, skillRoot, shell)
}
// MCP 来源的 !`...` 块不会被执行,仅作为纯文本传递

MCP 技能集成

循环依赖解耦

mcpSkillBuilders.ts 作为无依赖的叶节点模块,通过写一次注册打破 import 环:

mcpClient.ts ──import──→ mcpSkills.ts ──import──→ loadSkillsDir.ts

                                            (循环! mcpClient.ts ←──×)

mcpSkillBuilders.ts ←── registerMCPSkillBuilders() ─────┘

       └── getMCPSkillBuilders() ──→ mcpSkills.ts (运行时获取)
typescript
type MCPSkillBuilders = {
  createSkillCommand: typeof createSkillCommand
  parseSkillFrontmatterFields: typeof parseSkillFrontmatterFields
}

let builders: MCPSkillBuilders | null = null

// loadSkillsDir.ts 模块初始化时注册
function registerMCPSkillBuilders(b: MCPSkillBuilders): void {
  builders = b
}

// mcpSkills.ts 运行时获取
function getMCPSkillBuilders(): MCPSkillBuilders {
  if (!builders) throw new Error('MCP skill builders not registered')
  return builders
}

MCP 技能特殊处理

  • 名称格式: <server>:<skill>
  • Shell 命令禁止执行
  • ${CLAUDE_SKILL_DIR} 无意义(远程技能无本地目录)
  • loadedFrom: 'mcp' 标记

内置技能详解

始终注册的技能

技能名用户可调LLM可调限制核心功能
update-config修改 settings.json/hooks 配置
keybindingsisKeybindingCustomizationEnabled()自定义快捷键配置
verifyANT-ONLY运行应用并验证代码变更
debug启用调试日志,tails debug log
lorem-ipsumToken 对齐测试工具
skillifyANT-ONLY从当前会话提取可复用技能
rememberANT-ONLY + autoMem审查自动记忆,分类/提升
simplify代码审查、清理、简化
batch并行 worktree agent 批量变更
stuckANT-ONLY诊断卡住的会话

特性门控技能

技能名Feature Flag说明
dreamKAIROS / KAIROS_DREAM后台思考与规划
hunterREVIEW_ARTIFACT审查工件、深度代码审查
loopAGENT_TRIGGERS循环执行 cron 计划任务
schedule-remote-agentsAGENT_TRIGGERS_REMOTE远程 agent 定时调度
claude-apiBUILDING_CLAUDE_APPSClaude API/SDK 开发辅助
claude-in-chrome动态检测Chrome 浏览器集成
run-skill-generatorRUN_SKILL_GENERATOR技能生成器

batch 技能工作流

/batch "为所有 API 端点添加错误处理"

1. Research Phase
   │  扫描代码库,理解现有错误处理模式

2. Decompose Phase
   │  拆分为 5-30 个独立子任务

3. Spawn Workers Phase
   │  为每个子任务:
   │  ├── 创建 git worktree (隔离分支)
   │  ├── 启动独立 Agent
   │  └── 每个 Worker 独立提交代码

4. Track & Merge Phase
      监控各 Worker 状态
      处理失败重试
      创建 PR

remember 技能工作流

/remember

1. 扫描 ~/.claude/memories/ 目录
2. 对每条自动记忆进行分类:
   ├── 保留  (有价值但不提升)
   ├── 删除  (过时或冗余)
   └── 提升  (重要 → CLAUDE.md 或 CLAUDE.local.md)
3. 执行操作
4. 清理冗余文件

debug 技能工作流

/debug

1. 设置 CLAUDE_CODE_DEBUG=1 环境变量
2. 启动 tail -f 监听 debug log
3. 创建 Claude Code Guide agent
4. 引导用户复现问题

技能 Token 估算

typescript
function estimateSkillFrontmatterTokens(skill: SkillDefinition): number {
  // 估算各字段的 token 消耗:
  // name + description + whenToUse + argumentHint
  // 用于系统提示词的预算分配
  return nameTokens + descTokens + whenToUseTokens + hintTokens
}

/skills 命令 UI

typescript
// commands/skills/index.ts
const skills: Command = {
  type: 'local-jsx',
  name: 'skills',
  description: 'List available skills',
  load: () => import('./skills.js'),
}

// 渲染 SkillsMenu 组件
// 展示所有已加载的技能,包括来源、状态、描述

与插件系统的区别

方面技能 (Skills)内置插件 (Builtin Plugins)
粒度单个能力单元技能 + Hooks + MCP 的容器
开关无独立开关/plugin UI 中可启用/禁用
ID 格式技能名{name}@builtin
持久化无状态用户偏好持久化到 settings
Command.source'bundled' / source'bundled'(保持兼容)
可包含提示词 + 参考文件技能 + hooks + MCP 服务器

架构决策总结

决策原因
延迟文件提取bundled skill 的 files 仅首次调用时解压,Promise memoize 防竞态
MCP 桥接解耦mcpSkillBuilders.ts 无依赖叶节点,写一次注册打破 import 环
realpath 去重避免 inode 在 NFS/ExFAT/容器环境的不可靠性
安全分层nonce 路径 + O_NOFOLLOW + O_EXCL + 0o600 权限 + MCP shell 禁执行
条件激活paths frontmatter + discoverSkillDirsForPaths() 按需加载
预算感知列表内置技能描述永不截断,非内置按 1% 上下文窗口均分
来源优先级managed > user > project 确保企业策略覆盖用户设置