Claude Code Skill(技能)系统机制与运行原理报告

报告日期:2026-05-12 分析范围:Claude Code CLI 项目完整 skill 系统


目录

  1. 概述
  2. 核心类型系统
  3. [Skill 来源分类](#Skill 来源分类)
  4. 生命周期
  5. 动态发现与条件激活
  6. 权限模型
  7. [Skill 与工具系统的集成](#Skill 与工具系统的集成)
  8. [Prompt 预算与列表管理](#Prompt 预算与列表管理)
  9. 使用追踪
  10. 关键文件索引

1. 概述

1.1 Skill 是什么

Skill(技能)是 Claude Code 中的一种可扩展的 prompt 注入机制。每个 skill 本质上是一个 Markdown 文件(或程序化定义的 prompt 生成器),当被触发时,会将预定义的指令内容注入到当前对话上下文中,指导 Claude 模型执行特定任务。

1.2 Skill 解决什么问题

没有 skill 系统时,用户每次都需要用自然语言详细描述任务。有了 skill 系统后:

  • 标准化复杂任务:将多步骤工作流封装为可复用的 skill
  • 预授权工具权限:skill 可以预先声明需要的工具,避免反复弹出权限确认
  • 动态上下文注入:skill 可以根据运行时状态动态生成 prompt 内容
  • 条件激活:skill 可以根据文件路径自动启用,无需手动触发

1.3 实际案例:/simplify 技能

simplify 技能展示了 skill 系统的核心能力。当用户调用 /simplify 时:

复制代码
用户输入: /simplify

触发流程:
1. 解析斜杠命令 → 识别为 simplify 技能
2. 注入 SIMPLIFY_PROMPT 到对话上下文
3. 模型根据 prompt 内容执行:
   - Phase 1: git diff 获取变更
   - Phase 2: 并行启动 3 个 Agent(复用审查、质量审查、效率审查)
   - Phase 3: 汇总发现并修复问题

对应的代码实现(src/skills/bundled/simplify.ts):

复制代码
const SIMPLIFY_PROMPT = `# Simplify: Code Review and Cleanup

Review all changed files for reuse, quality, and efficiency. Fix any issues found.

## Phase 1: Identify Changes
Run \`git diff\` ...

## Phase 2: Launch Three Review Agents in Parallel
Use the Agent tool to launch all three agents concurrently...

### Agent 1: Code Reuse Review
### Agent 2: Code Quality Review  
### Agent 3: Efficiency Review

## Phase 3: Fix Issues
Wait for all three agents to complete...`

export function registerSimplifySkill(): void {
  registerBundledSkill({
    name: 'simplify',
    description: 'Review changed code for reuse, quality, and efficiency...',
    userInvocable: true,
    async getPromptForCommand(args) {
      let prompt = SIMPLIFY_PROMPT
      if (args) {
        prompt += `\n\n## Additional Focus\n\n${args}`
      }
      return [{ type: 'text', text: prompt }]
    },
  })
}

关键点 :skill 本身不执行代码,而是通过注入 prompt 指令让模型自主完成任务。getPromptForCommand 是 skill 的核心函数,负责生成最终注入到对话中的内容。


2. 核心类型系统

2.1 Command 联合类型

Skill 系统的核心类型定义在 src/types/command.ts

复制代码
// 三种 Command 变体
export type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
变体 type 用途 示例
PromptCommand 'prompt' Skill 的主要类型,生成 prompt 内容注入对话 /simplify/review
LocalCommand 'local' 懒加载的本地原生命令,执行特定逻辑 某些内部命令
LocalJSXCommand 'local-jsx' 渲染 React/Ink UI 组件的命令 /skills/help

2.2 PromptCommand 关键字段

PromptCommand 是 skill 的核心类型,定义在 src/types/command.ts:25-57

复制代码
export type PromptCommand = {
  type: 'prompt'                          // 固定值,标识这是一个 prompt 类型的命令
  progressMessage: string                 // 执行时显示的进度消息
  contentLength: number                   // 内容字符数(用于 token 估算)
  argNames?: string[]                     // 参数名列表(用于参数替换)
  allowedTools?: string[]                 // 预授权的工具列表
  model?: string                          // 模型覆盖(如 'opus'、'haiku')
  source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'  // 来源
  pluginInfo?: {                          // 插件元数据
    pluginManifest: PluginManifest
    repository: string
  }
  hooks?: HooksSettings                   // 生命周期钩子
  skillRoot?: string                      // 技能资源根目录
  context?: 'inline' | 'fork'            // 执行上下文模式
  agent?: string                          // fork 模式下的 Agent 类型
  effort?: EffortValue                    // 思考努力级别
  paths?: string[]                        // 条件激活的 glob 模式
  getPromptForCommand(                    // 核心方法:生成 prompt 内容
    args: string,
    context: ToolUseContext,
  ): Promise<ContentBlockParam[]>
}

字段详解

字段 作用 示例值
context 'inline':内容直接注入当前对话;'fork':在独立子 Agent 中执行 'fork'
allowedTools 预授权的工具,执行时不会弹出权限确认 ['Read', 'Bash(git:*)']
model 覆盖当前会话的模型选择 'opus'
effort 控制模型思考深度 'high'
paths 条件激活:只有当模型操作匹配的文件时才启用 ['src/**/*.ts']
hooks 注册生命周期钩子(PreToolUse、PostToolUse 等) { PreToolUse: [...] }

2.3 BundledSkillDefinition

内置技能使用 BundledSkillDefinition 类型定义(src/skills/bundledSkills.ts:15-41):

复制代码
export type BundledSkillDefinition = {
  name: string                           // 技能名称
  description: string                    // 描述
  aliases?: string[]                     // 别名
  whenToUse?: string                     // 何时使用的详细说明(给模型看的)
  argumentHint?: string                  // 参数提示
  allowedTools?: string[]                // 预授权工具
  model?: string                         // 模型覆盖
  disableModelInvocation?: boolean       // 禁止模型调用
  userInvocable?: boolean                // 是否允许用户通过 /name 调用
  isEnabled?: () => boolean              // 条件启用回调
  hooks?: HooksSettings                  // 生命周期钩子
  context?: 'inline' | 'fork'           // 执行上下文
  agent?: string                         // Agent 类型
  files?: Record<string, string>         // 参考文件(首次调用时提取到磁盘)
  getPromptForCommand: (                 // 核心方法
    args: string,
    context: ToolUseContext,
  ) => Promise<ContentBlockParam[]>
}

与 PromptCommand 的映射registerBundledSkill()BundledSkillDefinition 转换为 Command 对象,自动设置 source: 'bundled'loadedFrom: 'bundled' 等字段。

2.4 SKILL.md Frontmatter 完整字段表

自定义技能通过 SKILL.md 文件的 YAML frontmatter 配置(src/utils/frontmatterParser.ts:10-59):

字段 类型 默认值 说明 使用示例
description string - 技能描述 description: "代码审查工具"
allowed-tools string \ string[] [] 预授权工具
argument-hint string - 参数提示文本 argument-hint: "<file> [options]"
when_to_use string - 模型自动调用的条件说明 when_to_use: "Use when reviewing code"
version string - 版本号 version: "1.0"
model string 'inherit' 模型覆盖 model: opus
user-invocable string (boolean) true 是否允许用户 /name 调用 user-invocable: false
disable-model-invocation string (boolean) false 禁止模型调用 disable-model-invocation: true
hooks HooksSettings - 生命周期钩子 见下方 hooks 示例
effort string - 思考努力级别 effort: high
context 'inline' \ 'fork' 'inline' 执行上下文
agent string - Agent 类型(仅 fork) agent: general-purpose
paths string \ string[] - 条件激活 glob 模式
shell string 'bash' 内联命令的 shell shell: powershell
name string 目录名 显示名覆盖 name: "My Skill"
arguments string \ string[] - 参数名列表

完整 SKILL.md 示例

复制代码
---
name: my-code-reviewer
description: 智能代码审查工具,支持多种语言
allowed-tools:
  - Read
  - Grep
  - Glob
  - Bash(git:*)
when_to_use: "Use when the user wants to review code changes or PRs"
argument-hint: "[file-pattern]"
arguments:
  - file-pattern
context: inline
model: opus
effort: high
paths:
  - "src/**/*.ts"
  - "src/**/*.tsx"
hooks:
  PostToolUse:
    - matcher: Write|Edit
      hooks:
        - type: command
          command: prettier --write
user-invocable: true
---

# Code Reviewer

## Inputs
- `$file-pattern`: Glob pattern for files to review (default: all changed files)

## Goal
Perform a thorough code review and provide actionable feedback.

## Steps

### 1. Identify Changes
Run `git diff` to see what changed.

**Success criteria**: Complete diff is available.

### 2. Analyze Code Quality
Review each change for:
- Bug potential
- Performance issues
- Security concerns
- Code style

**Success criteria**: All changes reviewed.

### 3. Report Findings
Summarize findings with file/line references.

3. Skill 来源分类

3.1 四种来源概览

来源 加载方式 存储位置 安全限制 配置能力
内置 (bundled) 代码注册 编译进 CLI 二进制 完全信任 完整(程序化定义)
自定义 (file-based) 磁盘加载 .claude/skills/ 目录 中等 完整(SKILL.md frontmatter)
MCP MCP 服务器加载 外部 MCP 服务器 严格(禁用内联 shell) 受限
插件 (plugin) 插件系统加载 插件目录 中等 完整(pluginInfo 元数据)

3.2 内置 (Bundled) 技能

注册方式 :在 src/skills/bundled/index.tsinitBundledSkills() 中程序化注册:

复制代码
export function initBundledSkills(): void {
  registerUpdateConfigSkill()    // 配置 settings.json
  registerKeybindingsSkill()     // 键盘快捷键自定义
  registerVerifySkill()          // 代码验证(ANT-ONLY)
  registerDebugSkill()           // 调试会话问题
  registerLoremIpsumSkill()      // 生成占位文本(ANT-ONLY)
  registerSkillifySkill()        // 捕获会话流程为可复用 skill(ANT-ONLY)
  registerRememberSkill()        // 自动记忆管理(ANT-ONLY)
  registerSimplifySkill()        // 代码审查(复用/质量/效率)
  registerBatchSkill()           // 并行工作编排
  registerStuckSkill()           // 诊断冻结会话(ANT-ONLY)
  // ... 特性开关控制的技能
}

内置技能完整列表

技能名 文件 功能 限制
update-config updateConfig.ts 配置 settings.json、hooks、权限 -
keybindings keybindings.ts 键盘快捷键自定义 需要 feature flag
verify verify.ts 通过测试/lint 验证代码变更 ANT-ONLY
debug debug.ts 诊断会话问题 disableModelInvocation: true
lorem-ipsum loremIpsum.ts 生成占位文本 ANT-ONLY
skillify skillify.ts 将会话捕获为可复用 skill ANT-ONLY
remember remember.ts 审查/管理自动记忆 ANT-ONLY
simplify simplify.ts 3 并行 Agent 代码审查 -
batch batch.ts 5-30 worktree Agent 并行编排 disableModelInvocation: true
stuck stuck.ts 诊断冻结会话 ANT-ONLY
loop loop.ts 定时循环执行 需要 KAIROS flag
claude-api claudeApi.ts Claude API 开发 需要 BUILDING_CLAUDE_APPS flag

条件注册示例

复制代码
// src/skills/bundled/index.ts
if (feature('KAIROS') || feature('KAIROS_DREAM')) {
  const { registerDreamSkill } = require('./dream.js')
  registerDreamSkill()
}

3.3 自定义 (File-based) 技能

目录结构

复制代码
# 项目级(推荐)
.claude/skills/
  my-reviewer/
    SKILL.md              # 必需:技能内容 + YAML frontmatter
    style-guide.md        # 可选:参考文件

# 用户级
~/.claude/skills/
  my-reviewer/
    SKILL.md

# 企业/策略级
~/.managed/.claude/skills/
  my-reviewer/
    SKILL.md

# 遗留格式(兼容)
.claude/commands/
  my-command.md           # 单文件格式(旧)
  my-skill/
    SKILL.md              # 目录格式(旧)

加载层级(按优先级从高到低):

  1. Managed (策略设置):~/.managed/.claude/skills/
  2. User (个人):~/.claude/skills/
  3. Project (项目级):.claude/skills/(向上遍历到 home)
  4. Additional--add-dir):<dir>/.claude/skills/
  5. Legacy commands.claude/commands/

加载代码src/skills/loadSkillsDir.ts:638-804):

复制代码
export const getSkillDirCommands = memoize(async (cwd: string): Promise<Command[]> => {
  const userSkillsDir = join(getClaudeConfigHomeDir(), 'skills')
  const managedSkillsDir = join(getManagedFilePath(), '.claude', 'skills')
  const projectSkillsDirs = getProjectDirsUpToHome('skills', cwd)

  // 并行加载所有来源
  const [managedSkills, userSkills, projectSkillsNested, additionalSkillsNested, legacyCommands] =
    await Promise.all([
      loadSkillsFromSkillsDir(managedSkillsDir, 'policySettings'),
      loadSkillsFromSkillsDir(userSkillsDir, 'userSettings'),
      Promise.all(projectSkillsDirs.map(dir => loadSkillsFromSkillsDir(dir, 'projectSettings'))),
      Promise.all(additionalDirs.map(dir => loadSkillsFromSkillsDir(join(dir, '.claude', 'skills'), 'projectSettings'))),
      loadSkillsFromCommandsDir(cwd),
    ])

  // 按 realpath 去重(处理符号链接)
  const fileIds = await Promise.all(allSkillsWithPaths.map(({ skill, filePath }) =>
    skill.type === 'prompt' ? getFileIdentity(filePath) : Promise.resolve(null),
  ))
  // ...
})

3.4 MCP 技能

从 MCP(Model Context Protocol)服务器加载的技能:

复制代码
// src/skills/mcpSkillBuilders.ts
// MCP 技能通过 write-once 注册表加载,避免循环依赖

// 安全限制:MCP 技能永远不执行内联 shell 命令
if (loadedFrom !== 'mcp') {
  finalContent = await executeShellCommandsInPrompt(finalContent, ...)
}

安全边界:MCP 技能来自外部服务器,被视为不可信来源,因此:

  • 禁用内联 shell 命令执行(! 代码块)
  • ${CLAUDE_SKILL_DIR} 变量无意义(不适用)
  • 需要明确的权限检查

3.5 插件 (Plugin) 技能

插件可以通过 skillsPath/skillsPaths 提供技能:

复制代码
// 插件定义中的技能声明
type BuiltinPluginDefinition = {
  skills?: BundledSkillDefinition[]
  // ...
}

// 插件技能加载后设置元数据
const command: Command = {
  // ...
  source: 'plugin',
  loadedFrom: 'plugin',
  pluginInfo: {
    pluginManifest: manifest,
    repository: 'github.com/org/plugin',
  },
}

4. 生命周期

4.1 完整生命周期流程图

复制代码
┌─────────────────────────────────────────────────────────────────────┐
│                        Skill 生命周期                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │
│  │   注册阶段    │───▶│   加载阶段    │───▶│   聚合阶段    │          │
│  │  (Startup)   │    │   (Load)     │    │  (Aggregate) │          │
│  └──────────────┘    └──────────────┘    └──────────────┘          │
│         │                   │                   │                   │
│         ▼                   ▼                   ▼                   │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐          │
│  │ 触发阶段      │    │   执行阶段    │    │   完成阶段    │          │
│  │  (Trigger)   │───▶│  (Execute)   │───▶│  (Complete)  │          │
│  └──────────────┘    └──────────────┘    └──────────────┘          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4.2 注册阶段

内置技能注册(启动时):

复制代码
// src/skills/bundled/index.ts
export function initBundledSkills(): void {
  registerUpdateConfigSkill()  // 调用 registerBundledSkill(definition)
  registerKeybindingsSkill()
  registerVerifySkill()
  // ...
}

// src/skills/bundledSkills.ts
export function registerBundledSkill(definition: BundledSkillDefinition): void {
  const { files } = definition
  let skillRoot: string | undefined
  let getPromptForCommand = definition.getPromptForCommand

  // 如果有参考文件,包装 getPromptForCommand 以提取文件到磁盘
  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 for this skill: ..."
    }
  }

  // 创建 Command 对象并注册
  const command: Command = {
    type: 'prompt',
    name: definition.name,
    source: 'bundled',
    loadedFrom: 'bundled',
    // ... 其他字段映射
    getPromptForCommand,
  }
  bundledSkills.push(command)
}

文件技能加载(启动时 + 动态发现):

复制代码
加载流程:
1. 扫描所有来源目录(managed/user/project/additional/legacy)
2. 并行读取每个目录下的 skill-name/SKILL.md
3. 解析 YAML frontmatter → 提取元数据
4. 调用 createSkillCommand() 创建 Command 对象
5. 按 realpath 去重(处理符号链接)
6. 分离条件技能(有 paths 字段)和无条件技能
7. 返回无条件技能列表

4.3 加载与聚合

命令聚合src/commands.ts:460-517):

复制代码
// loadAllCommands 按优先级合并所有命令源
const loadAllCommands = memoize(async (cwd: string) => {
  const bundledSkills = getBundledSkills()                    // 1. 内置技能
  const builtinPluginSkills = getBuiltinPluginSkills()       // 2. 内置插件技能
  const skillDirCommands = await getSkillDirCommands(cwd)    // 3. 文件技能
  const workflowCommands = await getWorkflowCommands()        // 4. 工作流命令(feature flag)
  const pluginCommands = await getPluginCommands()            // 5. 插件命令
  const pluginSkills = await getPluginSkills()               // 6. 插件技能
  return [
    ...bundledSkills,        // 最高优先级
    ...builtinPluginSkills,
    ...skillDirCommands,
    ...workflowCommands,
    ...pluginCommands,
    ...pluginSkills,
    ...COMMANDS(),           // 最低优先级(硬编码命令)
  ]
})

// getCommands 过滤并合并动态技能
export async function getCommands(cwd: string): Promise<Command[]> {
  const allCommands = await loadAllCommands(cwd)
  const dynamicSkills = getDynamicSkills()  // 运行时发现的技能

  // 过滤:满足可用性要求 + 已启用
  const baseCommands = allCommands.filter(
    _ => meetsAvailabilityRequirement(_) && isCommandEnabled(_),
  )

  // 去重后插入动态技能
  const baseCommandNames = new Set(baseCommands.map(c => c.name))
  const uniqueDynamicSkills = dynamicSkills.filter(
    s => !baseCommandNames.has(s.name) && meetsAvailabilityRequirement(s) && isCommandEnabled(s),
  )

  // 插入到内置命令之前
  const builtInNames = new Set(COMMANDS().map(c => c.name))
  const insertIndex = baseCommands.findIndex(c => builtInNames.has(c.name))
  // ...
}

4.4 触发机制

Skill 有两条触发路径:

路径 A:用户输入 /skill-name
复制代码
用户输入 "/simplify"
    │
    ▼
processUserInput()  // src/utils/processUserInput/processUserInput.ts
    │
    ▼
parseSlashCommand()  // src/utils/slashCommandParsing.ts
    │  解析: { commandName: "simplify", args: "", isMcp: false }
    │
    ▼
processPromptSlashCommand()  // src/utils/processUserInput/processSlashCommand.tsx
    │
    ├── 查找命令: findCommand("simplify", commands)
    │
    ├── 获取 prompt: command.getPromptForCommand(args, context)
    │   │
    │   ├── 参数替换: $argName → 实际值
    │   ├── 变量替换: ${CLAUDE_SKILL_DIR} → 技能目录路径
    │   ├── 变量替换: ${CLAUDE_SESSION_ID} → 当前会话 ID
    │   └── 执行内联 shell: !`cmd` → 实际输出
    │
    ├── 注册 hooks: registerSkillHooks()
    │
    ├── 记录使用: recordSkillUsage()
    │
    └── 返回消息注入对话
路径 B:模型调用 SkillTool
复制代码
模型判断用户请求匹配某个 skill
    │
    ▼
调用 Skill 工具: { skill: "simplify", args: "" }
    │
    ▼
SkillTool.validateInput()  // src/tools/SkillTool/SkillTool.ts:354
    │
    ├── 标准化名称: 去除前导 "/"
    ├── 查找命令: getAllCommands() → findCommand()
    ├── 检查: 是否存在?是否 prompt 类型?是否禁用模型调用?
    │
    ▼
SkillTool.checkPermissions()  // SkillTool.ts:432
    │
    ├── 检查 deny 规则
    ├── 检查 allow 规则
    ├── 检查 safe auto-allow(仅安全属性的技能)
    │
    ▼
SkillTool.call()  // SkillTool.ts:580
    │
    ├── 记录使用: recordSkillUsage(commandName)
    │
    ├── 检查执行模式:
    │   ├── context === 'fork' → executeForkedSkill()
    │   └── 默认 → processPromptSlashCommand()(inline 模式)
    │
    └── 返回: { data, newMessages, contextModifier }

4.5 执行流程:inline vs forked

Inline 模式(默认)

技能内容直接注入当前对话上下文:

复制代码
// SkillTool.ts:635-643
const { processPromptSlashCommand } = await import(
  'src/utils/processUserInput/processSlashCommand.js'
)
const processedCommand = await processPromptSlashCommand(
  commandName,
  args || '',
  commands,
  context,
)

// 返回 contextModifier 修改后续工具权限
return {
  data: { success: true, commandName, allowedTools, model },
  newMessages,  // 注入到对话中的消息
  contextModifier(ctx) {
    // 修改 allowedTools、model、effort
    // ...
  },
}

contextModifier 的作用

复制代码
contextModifier(ctx) {
  let modifiedContext = ctx

  // 1. 修改工具权限
  if (allowedTools.length > 0) {
    modifiedContext = {
      ...modifiedContext,
      getAppState() {
        const appState = modifiedContext.getAppState()
        return {
          ...appState,
          toolPermissionContext: {
            ...appState.toolPermissionContext,
            alwaysAllowRules: {
              ...appState.toolPermissionContext.alwaysAllowRules,
              command: [
                ...new Set([
                  ...(appState.toolPermissionContext.alwaysAllowRules.command || []),
                  ...allowedTools,
                ]),
              ],
            },
          },
        }
      },
    }
  }

  // 2. 修改模型选择
  if (model) {
    modifiedContext = { ...modifiedContext, model }
  }

  return modifiedContext
}
Forked 模式

在独立子 Agent 中执行,有自己的 token 预算:

复制代码
// SkillTool.ts:122-150
async function executeForkedSkill(
  command: Command & { type: 'prompt' },
  commandName: string,
  args: string | undefined,
  context: ToolUseContext,
  canUseTool: CanUseToolFn,
  parentMessage: AssistantMessage,
  onProgress?: ToolCallProgress<Progress>,
): Promise<ToolResult<Output>> {
  const agentId = createAgentId()
  // ...
  const result = await runAgent({
    // 独立的上下文和 token 预算
    agentId,
    prompt: command.getPromptForCommand(args, context),
    // ...
  })
  // ...
}

5. 动态发现与条件激活

5.1 动态技能发现

当模型读写文件时,系统会自动发现相关的 .claude/skills/ 目录:

复制代码
// src/skills/loadSkillsDir.ts:861-915
export async function discoverSkillDirsForPaths(
  filePaths: string[],
  cwd: string,
): Promise<string[]> {
  const newDirs: string[] = []

  for (const filePath of filePaths) {
    let currentDir = dirname(filePath)

    // 从文件目录向上遍历到 cwd
    while (currentDir.startsWith(resolvedCwd + pathSep)) {
      const skillDir = join(currentDir, '.claude', 'skills')

      // 跳过已检查的路径(缓存)
      if (!dynamicSkillDirs.has(skillDir)) {
        dynamicSkillDirs.add(skillDir)
        try {
          await fs.stat(skillDir)
          // 检查是否被 gitignore 忽略
          if (await isPathGitignored(currentDir, resolvedCwd)) {
            continue
          }
          newDirs.push(skillDir)
        } catch {
          // 目录不存在,跳过
        }
      }
      currentDir = dirname(currentDir)
    }
  }

  // 按深度排序(最深的优先)
  return newDirs.sort(
    (a, b) => b.split(pathSep).length - a.split(pathSep).length,
  )
}

5.2 条件激活(Path-Filtered Skills)

带有 paths frontmatter 的技能只在匹配的文件被操作时激活:

复制代码
// src/skills/loadSkillsDir.ts:771-796
// 分离条件技能和无条件技能
const unconditionalSkills: Command[] = []
const newConditionalSkills: Command[] = []
for (const skill of deduplicatedSkills) {
  if (
    skill.type === 'prompt' &&
    skill.paths &&
    skill.paths.length > 0 &&
    !activatedConditionalSkillNames.has(skill.name)
  ) {
    newConditionalSkills.push(skill)  // 条件技能,暂存
  } else {
    unconditionalSkills.push(skill)   // 无条件技能,立即可用
  }
}

// 存储条件技能,等待匹配文件时激活
for (const skill of newConditionalSkills) {
  conditionalSkills.set(skill.name, skill)
}

激活条件 :当模型操作的文件匹配 paths glob 模式时:

复制代码
// src/skills/loadSkillsDir.ts (activateConditionalSkillsForPaths)
// 使用 ignore 库进行 gitignore 风格的路径匹配

示例

复制代码
# 某个技能只在 TypeScript 文件被操作时启用
paths:
  - "src/**/*.ts"
  - "src/**/*.tsx"

5.3 热重载机制

使用 chokidar 监听技能文件变化:

复制代码
// src/utils/skills/skillChangeDetector.ts

// 时间常量
const FILE_STABILITY_THRESHOLD_MS = 1000    // 文件稳定性阈值
const FILE_STABILITY_POLL_INTERVAL_MS = 500 // 轮询间隔
const RELOAD_DEBOUNCE_MS = 300              // 重载防抖时间

// 初始化监听
export async function initialize(): Promise<void> {
  const paths = await getWatchablePaths()
  watcher = chokidar.watch(paths, {
    persistent: true,
    ignoreInitial: true,
    depth: 2,  // skill-name/SKILL.md 格式
    awaitWriteFinish: {
      stabilityThreshold: FILE_STABILITY_THRESHOLD_MS,
      pollInterval: FILE_STABILITY_POLL_INTERVAL_MS,
    },
    usePolling: USE_POLLING,  // Bun 环境使用轮询(避免死锁)
  })

  watcher.on('add', handleChange)
  watcher.on('change', handleChange)
  watcher.on('unlink', handleChange)
}

// 防抖处理
function scheduleReload(changedPath: string): void {
  pendingChangedPaths.add(changedPath)
  if (reloadTimer) clearTimeout(reloadTimer)
  reloadTimer = setTimeout(async () => {
    const paths = [...pendingChangedPaths]
    pendingChangedPaths.clear()

    // 触发 ConfigChange hooks
    const results = await executeConfigChangeHooks('skills', paths[0]!)
    if (hasBlockingResult(results)) return  // hook 阻止重载

    // 清除缓存并通知
    clearSkillCaches()
    clearCommandsCache()
    resetSentSkillNames()
    skillsChanged.emit()
  }, RELOAD_DEBOUNCE_MS)
}

重载流程

  1. chokidar 检测到文件变化(add/change/unlink)
  2. 300ms 防抖合并多次变化
  3. 触发 ConfigChange hooks(可阻止重载)
  4. 清除所有技能缓存
  5. 重置已发送的技能名称
  6. 发出 skillsChanged 信号
  7. UI 更新技能列表

6. 权限模型

6.1 分层权限检查

SkillTool 实现了 4 层权限检查(src/tools/SkillTool/SkillTool.ts:432-578):

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    权限检查流程                                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  第 1 层:Deny 规则                                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 检查是否有 deny 规则匹配技能名称                          │    │
│  │ 匹配方式:精确匹配 或 前缀匹配("review:*" 匹配 "review-pr")│    │
│  │ 结果:deny → 阻止执行,返回错误                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                           │                                      │
│                           ▼                                      │
│  第 2 层:远程 Canonical 技能(实验性)                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 如果是 _canonical_ 前缀的远程技能,自动授权                │    │
│  │ (远程技能经过审核,视为可信)                              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                           │                                      │
│                           ▼                                      │
│  第 3 层:Allow 规则                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 检查是否有 allow 规则匹配技能名称                         │    │
│  │ 匹配方式:精确匹配 或 前缀匹配                            │    │
│  │ 结果:allow → 授权执行                                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                           │                                      │
│                           ▼                                      │
│  第 4 层:Safe Auto-Allow                                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 检查技能是否只有 "安全" 属性                               │    │
│  │ 安全属性列表:SAFE_SKILL_PROPERTIES(约 30 个属性)         │    │
│  │ 如果有 hooks 等非安全属性 → 需要用户确认                   │    │
│  │ 结果:safe → 自动授权;unsafe → 询问用户                   │    │
│  └─────────────────────────────────────────────────────────┘    │
│                           │                                      │
│                           ▼                                      │
│  第 5 层:用户交互确认                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ 显示 SkillPermissionRequest UI                           │    │
│  │ 选项:Yes / Yes + don't ask again (exact) /              │    │
│  │        Yes + don't ask again (prefix) / No               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

6.2 规则匹配逻辑

复制代码
// SkillTool.ts:451-467
const ruleMatches = (ruleContent: string): boolean => {
  // 标准化:去除前导斜杠
  const normalizedRule = ruleContent.startsWith('/')
    ? ruleContent.substring(1)
    : ruleContent

  // 精确匹配
  if (normalizedRule === commandName) {
    return true
  }

  // 前缀匹配(如 "review:*" 匹配 "review-pr 123")
  if (normalizedRule.endsWith(':*')) {
    const prefix = normalizedRule.slice(0, -2)
    return commandName.startsWith(prefix)
  }

  return false
}

6.3 Safe Auto-Allow 判定

复制代码
// SkillTool.ts:875-933
const SAFE_SKILL_PROPERTIES = new Set([
  // PromptCommand 属性
  'type', 'progressMessage', 'contentLength', 'argNames',
  'model', 'effort', 'source', 'pluginInfo', 'disableNonInteractive',
  'skillRoot', 'context', 'agent', 'getPromptForCommand', 'frontmatterKeys',
  // CommandBase 属性
  'name', 'description', 'hasUserSpecifiedDescription', 'isEnabled',
  'isHidden', 'aliases', 'isMcp', 'argumentHint', 'whenToUse', 'paths',
  'version', 'disableModelInvocation', 'userInvocable', 'loadedFrom',
  'immediate', 'userFacingName',
])

function skillHasOnlySafeProperties(command: Command): boolean {
  for (const key of Object.keys(command)) {
    if (SAFE_SKILL_PROPERTIES.has(key)) continue

    // 检查是否有有意义的值
    const value = (command as Record<string, unknown>)[key]
    if (value === undefined || value === null) continue
    if (Array.isArray(value) && value.length === 0) continue
    if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) continue

    return false  // 有非安全属性且有值,需要权限
  }
  return true
}

示例

复制代码
// 纯文本技能(只有安全属性)→ 自动授权
const safeSkill = {
  type: 'prompt',
  name: 'my-skill',
  description: 'A simple skill',
  // ... 只有 SAFE_SKILL_PROPERTIES 中的属性
}
// skillHasOnlySafeProperties(safeSkill) → true → 自动授权

// 带 hooks 的技能(非安全属性)→ 需要用户确认
const unsafeSkill = {
  type: 'prompt',
  name: 'my-skill',
  hooks: {  // hooks 不在 SAFE_SKILL_PROPERTIES 中
    PostToolUse: [{ matcher: 'Write', hooks: [{ type: 'command', command: 'prettier' }] }]
  },
}
// skillHasOnlySafeProperties(unsafeSkill) → false → 需要用户确认

6.4 用户交互确认 UI

当需要用户确认时,显示 SkillPermissionRequest 组件:

复制代码
// src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx
// 提供四个选项:
// 1. Yes - 允许本次执行
// 2. Yes + don't ask again (exact) - 添加精确匹配的 allow 规则
// 3. Yes + don't ask again (prefix) - 添加前缀匹配的 allow 规则(如 "skill:*")
// 4. No - 拒绝执行

7. Skill 与工具系统的集成

7.1 allowedTools 预授权

技能可以通过 allowedTools 预先声明需要的工具权限:

复制代码
# SKILL.md frontmatter
allowed-tools:
  - Read
  - Grep
  - Glob
  - Bash(git:*)
  - Bash(npm:*)

// 执行时,allowedTools 被合并到工具权限上下文
contextModifier(ctx) {
  const appState = ctx.getAppState()
  return {
    ...ctx,
    getAppState() {
      return {
        ...appState,
        toolPermissionContext: {
          ...appState.toolPermissionContext,
          alwaysAllowRules: {
            ...appState.toolPermissionContext.alwaysAllowRules,
            command: [
              ...new Set([
                ...(appState.toolPermissionContext.alwaysAllowRules.command || []),
                ...allowedTools,
              ]),
            ],
          },
        },
      }
    },
  }
}

7.2 ToolUseContext 上下文

getPromptForCommand 被调用时,它接收 ToolUseContext 参数:

复制代码
// src/Tool.ts
type ToolUseContext = {
  messages: Message[]              // 当前对话消息历史
  getAppState: () => AppState      // 获取应用状态
  options: {
    tools: Tool[]                  // 可用工具列表
    // ...
  }
  // ...
}

技能可以通过这个上下文:

  • 读取会话记忆和消息历史
  • 访问应用状态(MCP 连接、设置等)
  • 获取可用工具列表

7.3 Hooks 系统

技能可以在 frontmatter 中声明生命周期钩子:

复制代码
hooks:
  PreToolUse:
    - matcher: Write|Edit
      hooks:
        - type: command
          command: "echo 'About to write file'"
  PostToolUse:
    - matcher: Write|Edit
      hooks:
        - type: command
          command: prettier --write $CLAUDE_FILE_PATH
  Stop:
    - hooks:
        - type: command
          command: "echo 'Skill execution stopped'"

钩子通过 registerSkillHooks() 注册为会话级钩子,支持 once: true 一次性执行。

7.4 实际案例:simplify 的多 Agent 协作

复制代码
// src/skills/bundled/simplify.ts
const SIMPLIFY_PROMPT = `# Simplify: Code Review and Cleanup

## Phase 2: Launch Three Review Agents in Parallel

Use the Agent tool to launch all three agents concurrently in a single message.

### Agent 1: Code Reuse Review
Search for existing utilities that could replace newly written code...

### Agent 2: Code Quality Review
Review for hacky patterns: redundant state, parameter sprawl...

### Agent 3: Efficiency Review
Review for efficiency: unnecessary work, missed concurrency...

## Phase 3: Fix Issues
Wait for all three agents to complete. Aggregate their findings...`

// getPromptForCommand 返回这个 prompt
// 模型读取后会自主调用 Agent 工具启动 3 个并行子 Agent
// 每个 Agent 独立审查代码的不同方面
// 最后汇总结果并修复问题

执行流程

复制代码
/simplify 触发
    │
    ▼
注入 SIMPLIFY_PROMPT 到对话
    │
    ▼
模型读取 prompt 内容
    │
    ▼
模型调用 Agent 工具 3 次(并行)
    │
    ├── Agent 1: 代码复用审查
    ├── Agent 2: 代码质量审查
    └── Agent 3: 效率审查
    │
    ▼
等待所有 Agent 完成
    │
    ▼
模型汇总发现并修复问题

8. Prompt 预算与列表管理

8.1 预算计算

技能列表占用上下文窗口的 1%(src/tools/SkillTool/prompt.ts:21-41):

复制代码
export const SKILL_BUDGET_CONTEXT_PERCENT = 0.01
export const CHARS_PER_TOKEN = 4
export const DEFAULT_CHAR_BUDGET = 8_000  // 回退值:200k × 1% × 4

export function getCharBudget(contextWindowTokens?: number): number {
  // 允许环境变量覆盖
  if (Number(process.env.SLASH_COMMAND_TOOL_CHAR_BUDGET)) {
    return Number(process.env.SLASH_COMMAND_TOOL_CHAR_BUDGET)
  }
  if (contextWindowTokens) {
    return Math.floor(
      contextWindowTokens * CHARS_PER_TOKEN * SKILL_BUDGET_CONTEXT_PERCENT,
    )
  }
  return DEFAULT_CHAR_BUDGET
}

计算示例

上下文窗口 字符预算 可容纳技能数(平均 200 字符/技能)
200k tokens 8,000 chars ~40 个技能
100k tokens 4,000 chars ~20 个技能

8.2 描述截断策略

复制代码
export const MAX_LISTING_DESC_CHARS = 250

function formatCommandsWithinBudget(
  commands: Command[],
  contextWindowTokens?: number,
): string {
  const budget = getCharBudget(contextWindowTokens)

  // 尝试完整描述
  const fullEntries = commands.map(cmd => ({
    cmd,
    full: formatCommandDescription(cmd),
  }))
  const fullTotal = fullEntries.reduce((sum, e) => sum + stringWidth(e.full), 0)
    + (fullEntries.length - 1)

  if (fullTotal <= budget) {
    return fullEntries.map(e => e.full).join('\n')
  }

  // 预算不足时,分层截断:
  // 1. Bundled 技能永远保留完整描述
  // 2. 非 bundled 技能按预算截断
  // 3. 极端情况:非 bundled 只显示名称

  // ...
}

截断逻辑

  1. 完整描述:如果总字符数在预算内,显示所有完整描述
  2. Bundled 优先:内置技能保留完整描述,其他技能截断
  3. 名称-only:极端情况下,非 bundled 技能只显示名称

8.3 技能列表生成

复制代码
// prompt.ts:173-196
export const getPrompt = memoize(async (_cwd: string): Promise<string> => {
  return `Execute a skill within the main conversation

When users ask you to perform tasks, check if any of the available skills match.

How to invoke:
- Use this tool with the skill name and optional arguments
- Examples:
  - \`skill: "pdf"\` - invoke the pdf skill
  - \`skill: "commit", args: "-m 'Fix bug'"\` - invoke with arguments

Important:
- Available skills are listed in system-reminder messages
- When a skill matches, this is a BLOCKING REQUIREMENT: invoke BEFORE generating any other response
- NEVER mention a skill without actually calling this tool
- Do not invoke a skill that is already running
`
})

9. 使用追踪

9.1 指数衰减评分算法

复制代码
// src/utils/suggestions/skillUsageTracking.ts

export function getSkillUsageScore(skillName: string): number {
  const config = getGlobalConfig()
  const usage = config.skillUsage?.[skillName]
  if (!usage) return 0

  // 时间衰减:每 7 天分数减半
  const daysSinceUse = (Date.now() - usage.lastUsedAt) / (1000 * 60 * 60 * 24)
  const recencyFactor = Math.pow(0.5, daysSinceUse / 7)

  // 最小衰减因子 0.1,避免旧但高频使用的技能完全消失
  return usage.usageCount * Math.max(recencyFactor, 0.1)
}

评分示例

使用次数 最后使用时间 衰减因子 最终得分
10 今天 1.0 10.0
10 7 天前 0.5 5.0
10 14 天前 0.25 2.5
10 30 天前 0.1(最小值) 1.0
1 今天 1.0 1.0

9.2 数据存储结构

复制代码
{
  "skillUsage": {
    "simplify": {
      "usageCount": 15,
      "lastUsedAt": 1715500000000
    },
    "review": {
      "usageCount": 8,
      "lastUsedAt": 1715400000000
    }
  }
}

9.3 防抖写入

复制代码
const SKILL_USAGE_DEBOUNCE_MS = 60_000  // 1 分钟防抖

export function recordSkillUsage(skillName: string): void {
  const now = Date.now()
  const lastWrite = lastWriteBySkill.get(skillName)

  // 1 分钟内不重复写入(分数算法使用 7 天半衰期,分钟级精度无关紧要)
  if (lastWrite !== undefined && now - lastWrite < SKILL_USAGE_DEBOUNCE_MS) {
    return
  }

  lastWriteBySkill.set(skillName, now)
  saveGlobalConfig(current => {
    const existing = current.skillUsage?.[skillName]
    return {
      ...current,
      skillUsage: {
        ...current.skillUsage,
        [skillName]: {
          usageCount: (existing?.usageCount ?? 0) + 1,
          lastUsedAt: now,
        },
      },
    }
  })
}

10. 关键文件索引

10.1 类型定义

文件 职责 关键行数
src/types/command.ts Command 联合类型、PromptCommand、CommandBase 定义 1-217
src/skills/bundledSkills.ts BundledSkillDefinition 类型、内置技能注册表 1-221
src/utils/frontmatterParser.ts FrontmatterData 类型、YAML frontmatter 解析 1-371

10.2 注册与加载

文件 职责 关键行数
src/skills/bundled/index.ts 内置技能初始化入口 1-80
src/skills/loadSkillsDir.ts 文件技能加载、frontmatter 解析、动态发现 1-930+
src/skills/mcpSkillBuilders.ts MCP 技能加载桥接 -
src/commands.ts 命令聚合、优先级排序、过滤 1-580+

10.3 触发与执行

文件 职责 关键行数
src/tools/SkillTool/SkillTool.ts Skill 工具定义、验证、权限、执行 1-950+
src/tools/SkillTool/prompt.ts 技能列表生成、Prompt 预算管理 1-242
src/utils/processUserInput/processSlashCommand.tsx 斜杠命令处理、inline/fork 执行 -
src/utils/processUserInput/processUserInput.ts 用户输入入口、斜杠命令检测 -
src/utils/slashCommandParsing.ts 斜杠命令解析 -

10.4 权限管理

文件 职责 关键行数
src/tools/SkillTool/SkillTool.ts checkPermissions、SAFE_SKILL_PROPERTIES 432-933
src/components/permissions/SkillPermissionRequest/SkillPermissionRequest.tsx 权限确认 UI -

10.5 动态发现与热重载

文件 职责 关键行数
src/skills/loadSkillsDir.ts discoverSkillDirsForPaths、条件激活 830-930+
src/utils/skills/skillChangeDetector.ts chokidar 文件监听、防抖重载 1-312

10.6 使用追踪

文件 职责 关键行数
src/utils/suggestions/skillUsageTracking.ts 指数衰减评分、防抖写入 1-56

10.7 UI 组件

文件 职责 关键行数
src/components/skills/SkillsMenu.tsx /skills 对话框、技能列表展示 1-50+
src/commands/skills/index.ts /skills 命令定义(type: local-jsx) -

10.8 内置技能实现

文件 技能 关键行数
src/skills/bundled/simplify.ts simplify(3 并行 Agent 审查) 1-70
src/skills/bundled/updateConfig.ts update-config(配置 settings.json) -
src/skills/bundled/keybindings.ts keybindings(键盘快捷键) -
src/skills/bundled/verify.ts verify(代码验证) -
src/skills/bundled/debug.ts debug(调试诊断) -
src/skills/bundled/batch.ts batch(并行工作编排) -
src/skills/bundled/claudeApi.ts claude-api(API 开发) -

附录 A:技能执行完整时序图

复制代码
用户输入 /skill-name
    │
    ▼
processUserInput()
    │
    ▼
parseSlashCommand() → { commandName, args }
    │
    ▼
processPromptSlashCommand()
    │
    ├── findCommand() → Command 对象
    │
    ├── command.getPromptForCommand(args, context)
    │   │
    │   ├── substituteArguments() → 参数替换
    │   ├── ${CLAUDE_SKILL_DIR} → 技能目录
    │   ├── ${CLAUDE_SESSION_ID} → 会话 ID
    │   └── executeShellCommandsInPrompt() → 内联命令
    │
    ├── registerSkillHooks() → 注册钩子
    │
    ├── recordSkillUsage() → 记录使用
    │
    └── 返回 { messages, shouldQuery, allowedTools, model }
        │
        ▼
    注入消息到对话上下文
        │
        ▼
    模型读取 skill 内容并执行
        │
        ├── 调用工具(Read、Bash、Agent 等)
        │
        └── 完成任务

附录 B:SKILL.md 编写最佳实践

  1. 明确目标 :在 ## Goal 部分清晰描述成功标准
  2. 分步骤:将复杂任务分解为可执行的步骤
  3. 成功标准 :每个步骤后添加 **Success criteria**
  4. 参数化 :使用 arguments$argName 支持动态输入
  5. 工具预授权 :通过 allowed-tools 避免反复权限确认
  6. 条件激活 :使用 paths 只在相关文件被操作时启用
  7. 安全优先 :避免使用 hooks(会导致需要权限确认),除非必要
相关推荐
东方佑1 小时前
观测的连续性:从波粒二象性诠释生成式 AI 中音视频与图像的表征范式
人工智能·音视频
小程故事多_801 小时前
从零复刻Claude Code,深度拆解Agent Harness工程化落地全逻辑
人工智能
AIGC包拥它1 小时前
RAG 项目实战进阶:基于 FastAPI + Vue3 前后端架构全面重构 LangChain 0.3 集成 Milvus 2.5 构建大模型智能应用
人工智能·python·重构·vue·fastapi·milvus·ai-native
Cosolar1 小时前
AI Agent 记忆机制全景对比:OpenClaw vs QwenPaw vs Hermes vs HiClaw
人工智能·深度学习·语言模型·chatgpt·面试
禾刀围玉1 小时前
基于FPGA的卷积神经网络实现-Step2 卷积模块设计
人工智能·fpga开发·cnn
资源分享助手1 小时前
超级改图P图改字无限制版教程(安卓)AI改图软件、图片改字软件、安卓修图APP、智能消除工具、图片拼接APP、超级改图下载
android·人工智能
二哈赛车手2 小时前
新人笔记---简易版AI实现以图搜图功能
java·人工智能·笔记·spring·ai
sno_guo2 小时前
直播抠图技术100谈之25---调色中曲线是最优解
人工智能·算法·机器学习·直播·内容运营·obs抠图·直播技术
zhangxingchao2 小时前
AI应用开发二:Embedding与向量数据库
前端·人工智能·后端