Claude Code Skills源码深度解析与性能优化指南

2026-06-05阅读 0热度 0
Claude

前言

某种程度上,Skills成了2025年LLM应用领域最受追捧的新概念——但要说准它到底是什么,还真没那么简单。从字面翻译看是"技能",可它承载的东西远不止于此。今天这篇,就先从历史脉络梳理LLM的几个发展阶段,再讲清楚Skills能做什么、给它下个定义,最后结合代码解析,分享一些尚不成熟的思考。

刀耕火种

2022年底,ChatGPT一夜爆火。那时候大家热情高涨,核心议题就是"怎么说才能让它听话"。Prompt怎么写、角色怎么定义、怎么把需求和指令加上约束全塞进一句话——一时间,Prompt工程师这个岗位都冒出来了。这个阶段的知识高度碎片化、难以复用,但它确实改变了程序的逻辑:从"按代码执行"变成了"靠自然语言沟通"。

开始规模化

到了2023年初,Anthropic发布Constitutional AI,OpenAI也推出了官方Prompt工程指南。大家开始把有效的prompt沉淀成模板库和系统提示规范,诸如Prompt-Engineering、awesome-chatgpt-prompts-zh这些有名的仓库就是那个时期的产物。但问题也随之暴露:模板难以维护、跨任务迁移困难、模型一更新就得大量重新调试。Prompt开始尝试"函数化",但从工程角度看,依然脆弱——Prompt的管理仍是个老大难。

有标准

2023年6月是个转折点,OpenAI正式推出Function Calling,模型第一次有了结构化调用外部系统的标准接口。到2024年,Anthropic提出MCP(Model Context Protocol),试图统一工具调用的协议层,生态开始走向标准化。当模型具备了调用外部工具的能力后,"让模型做什么"和"模型怎么做"这两件事终于可以分开了。Prompt不再需要硬编码所有逻辑,只需要描述意图,执行交给工具。思路从此打开了——从纯粹的"语言生成"迈向了"决策+调度"。

推出Skill

2025年10月中旬,Anthropic正式发布了Claude Skills。Skills本质上是一套可复用的、带有文档的能力单元。它把"如何完成某类任务的最佳实践"封装到了一起——比如怎么生成docx、怎么读取PDF——模型在需要时直接查阅并遵循,不用靠Prompt里的临时指令。带来的优势很直接:

  • 知识可维护:最佳实践集中在SKILL.md和相关文件夹里,更新一处即可;
  • 按需加载:模型判断需要时才读取内容,不随意污染上下文;
  • 人机协作:人只负责打磨Skill文档,模型负责执行;
  • 可复用:别人获取编写好的Skills,得到的结果基本一致。

可以把Skills理解成"公司规章制度"加"工具箱"的组合体。

规章制度告诉AI:遇到某类任务时该怎么做、分几步、每一步用什么工具。工具箱里装着它需要用的脚本和参考资料。

展开来说,一个Skill就是一个文件夹,里面包含三样东西:

第一,SKILL.md文件。这是"指令",用自然语言写成的。告诉AI这个Skill是干什么的、什么情况下该用、怎么用、有什么注意事项。

第二,脚本。可以是Python、Ja vaScript或其他语言写的代码,当AI需要"动手"时就执行这些脚本。

第三,资源文件。比如参考文档、模板、配置文件,AI在执行任务时可以查阅。

所以,Skills可以视作高阶Prompt与工具调用的综合体,再结合Claude Hub之类的发布平台,就有了Skills的发布、查询、安装、版本管理——之前那些麻烦事基本都解决了。

打个比方。函数调用像是给你一把锅铲、一口锅、再加一些调料,你得自己知道什么时候倒油、什么时候下菜、怎么颠勺。而Skills则像是给AI一本《中国八大菜系菜谱》加十八般工具——菜谱里不只告诉AI炒菜步骤,还写明了每个阶段该用哪个工具。AI只管照着做,做完就是一盘菜。哪怕一个从来没炒过菜的人,只要跟AI说"我要个土豆丝",得到的成品和五星级大厨做的基本一样。

所以,根据上面的叙述,Skills可以简单定义为:可被语义触发的能力包,它包含领域知识、执行步骤、输出规范与约束条件。

Skills是如何实现的

Skill的本质,是将磁盘上一段人类可读的Markdown(SKILL.md),在调用瞬间编译成模型能消化的Prompt Blocks,然后注入对话上下文。因此可以把Skill分为两个大阶段:一个是加载阶段,一个是注入调用阶段。下面从Claude Code的源代码视角,看看这两个阶段是怎么实现的。

加载

一、启动入口:从命令行到main()

当在命令行输入claude时,下面这条流程就启动了。

Claude Code 的 skills 源码解析

关键代码位置:src/main.tsx:1918-1932

// 同步注册:必须在 getCommands 之前完成
if (process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent') {
    initBuiltinPlugins();
    initBundledSkills();
}
// 并行启动
const setupPromise = setup(preSetupCwd, ...);
const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);

这样设计的原因在于:initBundledSkills()是纯内存的数组push操作(bundledSkills.push(skill)),耗时不到1毫秒。它必须在getCommands()启动前完成,否则getBundledSkills()返回空数组,技能就丢了。

二、技能加载

当在.claude/skills/目录或项目目录等位置放置Skills时,系统会通过IO方式读取。

Claude Code 的 skills 源码解析

loadAllCommands的合并顺序(优先级从高到低):

// 如果有重复的,会进行去重处理
return [
    ...bundledSkills,                // 内嵌技能
    ...builtinPluginSkills,          // 内置插件技能
    ...skillDirCommands,             // 磁盘上的技能(用户/项目/管理)
    ...workflowCommands,             // 工作流命令
    ...pluginCommands,               // 插件命令
    ...pluginSkills,                 // 插件技能
    ...COMMANDS(),                  // 内置命令(非技能类型)
]

三、磁盘技能加载:getSkillDirCommands

关键代码位置:getSkillDirCommands。这是最核心的技能加载逻辑,负责从文件系统读取SKILL.md文件。

3.1 目录搜索范围

getSkillDirCommands(cwd)搜索以下目录:

├── Managed Skills   ←── /etc/claude/.claude/skills/ (企业策略管理)
├── User Skills      ←── ~/.claude/skills/(用户全局技能)
├── Project Skills   ←── .claude/skills/(项目级技能,沿目录树向上搜索)
├── Additional Skills ←── --add-dir 指定的目录/.claude/skills/
└── Legacy Commands  ←── .claude/commands/(旧格式,向后兼容)
3.2 并行加载策略

所有目录的加载是并行的(Promise.all),因为它们互不依赖:

const [managedSkills, userSkills, projectSkills, additionalSkills, 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'), ...))),
        loadSkillsFromCommandsDir(cwd),
    ]);
3.3 单个技能目录的加载过程
loadSkillsFromSkillsDir(basePath, source)
│
├── 1. fs.readdir(basePath)    ←── 读取目录列表
│
└── 2. 对每个 entry 并行处理:
    │
    ├── 跳过非目录项(只支持 skill-name/SKILL.md 格式)
    ├── 读取 skill-name/SKILL.md 文件内容
    ├── parseFrontmatter(content)    ←── 解析 YAML frontmatter
    │   输入: "---\ndescription: ...\n---\n# Skill body"
    │   输出: { frontmatter: {...}, content: "# Skill body" }
    ├── parseSkillFrontmatterFields(...) ←── 提取结构化字段
    │   提取: description, allowedTools, model, hooks, paths, effort...
    │
    └── createSkillCommand(...)      ←── 构建 Command 对象
        闭包捕获 markdownContent,延迟到调用时再编译
3.4 去重机制

加载完成后,使用realpath解析文件的真实路径进行去重:

// 通过 realpath 检测符号链接和重复的父目录
const fileIds = await Promise.all(allSkillsWithPaths.map(({ filePath }) => getFileIdentity(filePath)));
// 先到先得:优先级由合并顺序决定
// managed > user > project > additional > legacy
for (entry of allSkillsWithPaths) {
    if (seenFileIds.has(fileId)) continue;  // 跳过重复
    seenFileIds.set(fileId, skill.source);
    deduplicatedSkills.push(skill);
}
3.5 条件技能(Conditional Skills)

带有paths frontmatter的技能不会立即激活,而是存储在conditionalSkills Map中:

---
description: React 组件开发助手
paths: src/components/**, src/pages/**
---

当用户操作的文件路径匹配paths模式时,技能被激活并加入动态技能列表。激活流程:activateConditionalSkillsForPaths(filePaths, cwd) → 使用ignore库做gitignore风格匹配。

四、SKILL.md 文件解析

4.1 Frontmatter 字段格式
---
# 基础信息
name: 显示名称(可选,默认取目录名)
description: 技能描述
argument-hint: <参数提示文本>
arguments: [arg1, arg2]

# 模型和行为控制
model: claude-sonnet-4-6          # 指定使用的模型
effort: high                       # low | medium | high | 整数
context: fork                      # fork = 独立子进程执行,inline = 主线程
agent: agent-name                 # 指定 agent 定义

# 权限控制
allowed-tools: [Bash, Read, Write]
user-invocable: true              # 用户是否可通过 /name 调用
disable-model-invocation: false   # 模型是否可通过 SkillTool 调用

# 条件激活
paths: src/**/*.tsx               # 匹配文件路径时自动激活

# 钩子
hooks:
  PreToolUse:
    - command: "eslint $FILE"
      matcher: "Write|Edit"

# Shell 执行环境
shell: bash

# 版本
version: "1.0"
---
4.2 解析为 Command 对象

parseSkillFrontmatterFields()将YAML映射为结构化字段,然后createSkillCommand()组装成Command对象:

{
    type: 'prompt',                // 技能都是 prompt 类型
    name: 'skill-name',             // 目录名(唯一标识)
    description: '...',             // 从 frontmatter 或正文第一行提取
    source: 'projectSettings',     // 来源:userSettings / projectSettings / policySettings
    loadedFrom: 'skills',           // 加载方式:skills / bundled / plugin / mcp
    allowedTools: ['Bash'],         // 额外允许的工具
    model: 'claude-sonnet-4-6',     // 模型覆盖
    effort: 'high',                 // 努力程度
    userInvocable: true,           // 用户可调用
    context: 'fork',                // 执行上下文
    hooks: {...},                     // 钩子配置
    paths: ['src/**/*.tsx'],       // 条件路径
    contentLength: 1234,             // SKILL.md 内容长度
    skillRoot: '/path/to/skill',    // 技能目录路径
    // 核心:延迟加载闭包
    getPromptForCommand: async (args, toolUseContext) => {...}
}

五、延迟加载机制:getPromptForCommand

技能内容在启动时只解析frontmatter,SKILL.md的正文内容通过闭包捕获,仅在用户调用/skill-name时才执行完整的"编译"过程。

Claude Code 的 skills 源码解析

这里的设计权衡是:启动时只解析frontmatter(用于技能列表展示),正文编译延迟到调用时。好处是启动速度快,同时支持动态内容——shell命令可以在每次调用时执行获取最新结果。

六、动态技能发现

除了启动时加载,系统还支持在会话过程中动态发现新技能。

6.1 文件操作触发发现

当用户读写文件时,系统会沿文件路径向上搜索.claude/skills/目录:

discoverSkillDirsForPaths(filePaths, cwd)
│
├── 对每个 filePath:
│   从文件父目录开始,向上遍历到 cwd(不含 cwd)
│   每级检查是否存在 .claude/skills/ 目录
│   记录到 dynamicSkillDirs(去重用)
│
└── 返回新发现的目录列表(按深度降序排列)
6.2 激活流程
addSkillDirectories(dirs)
│
├── 对每个目录调用 loadSkillsFromSkillsDir()
├── 深层路径覆盖浅层路径(同名技能)
├── 存入 dynamicSkills Map
└── skillsLoaded.emit() ←── 通知缓存失效
6.3 缓存失效

动态技能加载后,需要清除相关的memoization缓存:

clearCommandMemoizationCaches() {
    loadAllCommands.cache?.clear?.()
    getSkillToolCommands.cache?.clear?.()
    getSlashCommandToolSkills.cache?.clear?.()
    clearSkillIndexCache?.()        // 技能搜索索引

getCommands()本身不被缓存(因为需要每次重新检查a vailability和isEnabled),但它内部的loadAllCommands被memoize了,所以清除内层缓存即可。

七、技能优先级总览

优先级从高到低:
1. managed skills       ←── 企业策略目录 /etc/claude/.claude/skills/
2. user skills          ←── 用户全局 ~/.claude/skills/
3. project skills       ←── 项目目录 .claude/skills/(最近的优先)
4. additional skills    ←── --add-dir 指定目录
5. legacy commands      ←── 旧格式 .claude/commands/
6. bundled skills       ←── 代码内嵌技能
7. builtin plugin skills ←── 内置插件技能
8. plugin skills        ←── 第三方插件技能

同名技能:先注册者胜出(由合并顺序决定)
文件去重:realpath 相同的文件只保留第一个

八、关键数据流图

Claude Code 的 skills 源码解析

调用

加载阶段将所有Skills加载到Command[]数组中等待调用。梳理Claude Code的源码来看,用户总共有9个入口可以调用Skills:

#入口触发方式执行模式
1用户斜杠命令用户输入 /skill-nameinline / fork
2立即命令查询进行中输入 /configlocal-jsx 直接执行
3SkillTool.call()模型调用 Skill工具inline / fork / remote
4MCP Skill通过 SkillTool 或 /server:skillfork(强制)
5Cron/定时任务scheduled_tasks.json/loop队列 → processUserInput
7Agent 预加载Agent 定义中的 skills:字段预注入初始消息
8Ultraplan 关键字输入包含魔法关键字重写为 /ultraplan
9初始 prompt-p "/skill ..."或 agent initialPromptonSubmit → processSlashCommand

限于篇幅,9种调用方式都讲一遍显然不现实,今天主要讲第一种——用户用斜杠命令行来调用Skills。

代码的调用流程图

Claude Code 的 skills 源码解析

详细代码调用流程

第1层 REPL.onSubmit() — 入口把关

详细代码位置:REPL.tsx:3142

当在Claude Code中输入/commit并按下回车,PromptInput组件调用onSubmit回调。这是整条链路的入口。

// 1. 检测是否是 immediate 命令(immediate: true 的 local-jsx 命令)
// 这些命令可以在 AI 正在处理时立即执行,不用排队
if (!speculationAccept && input.trim().startsWith('/')) {
    const commandName = /* 从 input 提取命令名 */
    const matchingCommand = commands.find(...)
    const shouldTreatAsImmediate = queryGuard.isActive && (matchingCommand?.immediate || options?.fromKeybinding)
    if (matchingCommand && shouldTreatAsImmediate && matchingCommand.type === 'local-jsx') {
        // 直接执行,跳过队列 — return early
    }
}

对于大多数/skill-name(type为prompt),不会命中immediate快速通道,而是继续往下走。后续处理包括:清空输入框、加入历史记录、调用handlePromptSubmit()。

第2层:handlePromptSubmit() — 队列化

详细代码位置:handlePromptSubmit.ts:120

这个模块的核心职责是决定输入是立即执行还是排队等待。

// 如果 AI 正在处理中(queryGuard.isActive),新的输入进入队列
if (queryGuard.isActive || isExternalLoading) {
    enqueue({ value: finalInput.trim(), mode, pastedContents })
    return  // 不执行,等 AI 空闲后 dequeue
}
// 否则立即执行
await executeUserInput({ queuedCommands: [cmd], ... })

executeUserInput是实际执行的核心函数,其内部调用processUserInput()。

第3层:processUserInput() — 模式路由

详细代码位置:processUserInput.ts:533

这个函数是一个大型路由器,根据输入模式分发给不同处理器:

// Bash 模式 → processBashCommand()
if (mode === 'bash') { ... }
// Slash command → processSlashCommand()
if (inputString !== null && !effectiveSkipSlash && inputString.startsWith('/')) {
    const { processSlashCommand } = await import('./processSlashCommand.js')
    const slashResult = await processSlashCommand(inputString, ...)
    return slashResult
}
// 普通文本 → processTextPrompt()
// ...

/commit/开头,自然命中slash command分支。

第4层:processSlashCommand() — 命令解析与分发

详细代码位置:processSlashCommand.tsx:309

这是整条链路中最关键的分发层。

Step 4.1:解析命令名

const parsed = parseSlashCommand(inputString)
// 如果输入的是 /commit fix: 修复bug
// 那么经过 parseSlashCommand 函数转化成: { commandName: 'commit', args: 'fix: 修复bug', isMcp: false }

parseSlashCommand函数的解析规则:去掉/前缀,第一个空格前为命令名,同时支持MCP命令格式(/mcp:tool (MCP) args)。

Step 4.2:查找命令注册表

if (!hasCommand(commandName, context.options.commands)) {
    // 命令不存在 → 判断是文件路径还是未知命令
    if (looksLikeCommand(commandName) && !isFilePath) {
        return { messages: [...], shouldQuery: false, resultText: 'Unknown skill: xxx' }
    }
    // 可能是文件路径(如 /var/log)→ 当普通文本发给模型
    return { messages: [...], shouldQuery: true }
}

Step 4.3:按type分发

Claude Code有三种命令类型,不同的命令执行不同动作:

type行为示例
prompt展开为文本,发送给AI模型/commit, skill 类命令
local本地执行,返回文本结果/compact
local-jsx渲染交互式UI组件/config, /model

对于/skill-name(type = prompt),进入getMessagesForPromptSlashCommand()。

第5层:getMessagesForPromptSlashCommand() — 技能内容加载

processSlashCommand.tsx:827

这是Skill的核心——将SKILL.md的内容加载为prompt。

Step 5.1:检查context === 'fork'

if (command.context === 'fork') {
    return await executeForkedSlashCommand(...)
    // 在独立子 agent 中执行,有自己的上下文和 token 预算, 默认不是 fork
}

Step 5.2:加载技能内容

const result = await command.getPromptForCommand(args, context)

getPromptForCommand在技能注册时定义(loadSkillsDir.ts:344),它做了以下事情:

  • 变量替换:将$ARGUMENTS${CLAUDE_SKILL_DIR}${CLAUDE_SESSION_ID}替换为实际值;
  • Shell执行:如果SKILL.md中有!command格式的shell注入,会先执行;
  • 返回ContentBlockParam[]:包含最终展开后的文本内容。

Step 5.3:构造消息列表

const messages = [
    createUserMessage({ content: metadata }),            // 命令元数据:名称、参数
    createUserMessage({ content: skillContent, isMeta: true }), // SKILL.md 内容(对用户隐藏)
    ...attachmentMessages,                                   // 附件消息
    createAttachmentMessage({                                // 权限声明:allowedTools
        type: 'command_permissions',
        allowedTools: additionalAllowedTools,
    }),
]
return {
    messages,
    shouldQuery: true,              // ← 关键:告诉上层需要调用 AI 模型
    allowedTools: additionalAllowedTools,
    model: command.model,
    effort: command.effort,
    command,
}
第6层:onQuery() — 发送给AI

详细代码位置:handlePromptSubmit.ts:560

await onQuery(
    newMessages,                    // 包含 skill 内容的消息列表
    abortController,
    shouldQuery,                    // true → 需要调用模型
    allowedTools ?? [],              // skill 声明的额外工具权限
    model ?? mainLoopModel,
    onBeforeQuery,
    primaryInput,
    effort,
)

onQuery将这些消息追加到对话历史,然后调用Claude API。此时SKILL.md的全部内容作为一条user message发送给模型,模型根据技能指令执行相应操作。

不成熟的思考

一、Skill 在解决什么问题?

LLM目前的三个缺陷,构成了Skill系统存在的全部理由:

缺陷本质Skill 的对抗手段
输出不一致性同一输入产生不同输出Prompt 模板 + 参数化注入 → 固定行为边界
结构漂移长对话中偏离初始意图Frontmatter 约束 + hooks 校验 → 结构护栏
瞎猜问题缺乏上下文时产生幻觉when_to_use + paths 条件激活 → 精确触发域

但这只是表层观察。深层的问题是:为什么Prompt模板能收敛概率模型?

答案在代码里。看createSkillCommandgetPromptForCommand闭包:

async getPromptForCommand(args, toolUseContext) {
    let finalContent = baseDir
        ? `Base directory for this skill: ${baseDir}\n\n${markdownContent}`
        : markdownContent
    finalContent = substituteArguments(finalContent, args, true, argumentNames)
    finalContent = finalContent.replace(/${CLAUDE_SKILL_DIR}/g, skillDir)
    finalContent = finalContent.replace(/${CLAUDE_SESSION_ID}/g, getSessionId())
    finalContent = await executeShellCommandsInPrompt(finalContent, ...)
    return [{ type: 'text', text: finalContent }]
}

这不是简单的"给LLM一个模板"。这是一套编译管线——把声明式的Markdown编译成确定性的运行时上下文。每一层转换都在缩小LLM的决策空间:

  1. Base directory注入:锚定文件系统上下文,消除路径猜测;
  2. 参数替换:将用户输入映射到预定义槽位,限制输入域;
  3. 环境变量注入:运行时状态确定性绑定;
  4. Shell命令执行:动态注入实时数据,避免LLM凭记忆猜测;
  5. 返回ContentBlockParam[]:结构化输出,消除格式不确定性。

所以,Skill的本质不是"模板",而是一个Prompt编译器——把高熵的人的意图,通过多层转换,编译成低熵的结构化指令。

二、它不是一个,而是三个系统

从代码中可以识别出三个正交的子系统,各有不同的设计目标和权衡:

2.1 声明层:SKILL.md 作为 DSL

SKILL.md不是Markdown文档,而是一个领域特定语言(DSL):

---
name: ...             # 标识符
description: ...      # 语义描述(用于模型路由)
when_to_use: ...       # 触发条件(用于模型自主调用)
paths: ...             # 文件路径守卫(用于条件激活)
allowed-tools: ...     # 权限边界
model: ...             # 计算资源分配
effort: ...            # 推理深度控制
context: fork           # 隔离级别
hooks: ...             # 生命周期钩子
shell: ...             # 执行环境
---

每一行frontmatter都在回答一个问题:这个Skill需要什么样的运行时保证?

字段回答的问题设计意图
paths何时激活?延迟加载,减少上下文污染
allowed-tools能做什么?最小权限原则
model用什么脑子?成本-质量权衡
effort想多深?推理预算控制
context: fork隔离吗?故障爆炸半径
hooks谁来校验?外部护栏注入

不过梳理时也发现一个问题:目前的DSL缺少一个depends_oncomposes字段。Skill之间的组合目前只能通过SkillTool在Prompt层面隐式实现,没有声明式的依赖关系。这意味着Skill的组合是"调用时发现"而非"设计时保证"。当然,如果设计了depends_on,也可能变成类似node_modules那样可怕的依赖地狱。而且从Skills开放以来,各种带"毒"的Skills层出不穷,如果加上依赖,这些"毒"会藏得更深。

2.2 编译层:getPromptForCommand 作为编译管线

延迟编译是一个精妙的设计。启动时只解析frontmatter(estimateSkillFrontmatterTokens只估算元信息的token),正文编译推迟到调用时。这意味着:

  • 启动速度不受Skill内容大小影响;
  • Shell命令每次调用获取最新结果(而非启动时的快照);
  • 代价是每次调用的首次延迟(需执行编译管线)。

这是经典的延迟求值(Lazy Evaluation)策略在Prompt工程中的应用,很有意思——pi在pi-mono中也用了同样的设计。

2.3 运行时层:Command 对象作为运行时表示

createSkillCommand返回的Command对象是一个闭包——捕获了markdownContent和所有frontmatter字段,但不执行任何计算。直到getPromptForCommand被调用时,编译管线才启动。

这个设计有一个重要的推论:Skill的内容在内存中只有一份拷贝(闭包捕获引用),但每次调用会产生新的编译结果。这意味着:

  • 内存效率:N个Skill只占用N份Markdown的空间;
  • CPU效率:只有被调用的Skill才消耗编译时间;
  • 一致性代价:同一个Skill的两次调用,如果中间Shell命令结果变了,输出会不同(这个可以测试一下,挺有意思的)。

三、Skill 系统解决不了什么?

3.1 组合爆炸问题

当前Skill的组合是隐式的——一个Skill可以通过SkillTool调用另一个Skill,但:

  • 没有声明式的组合关系(A composes B, C);
  • 没有组合后的token预算管理;
  • 没有组合冲突检测(两个Skill对同一文件给出矛盾指令);
  • 没有DAG调度(Skill A的输出作为Skill B的输入)。

如果引入声明式组合,Skill系统会从一个"Prompt模板库"进化为一个"Prompt计算图"。每个Skill是一个节点,composes定义边,运行时按拓扑序执行,每层的输出作为下一层的输入。这能解决:

  • Token预算:DAG调度可以精确计算每层预算;
  • 冲突检测:编译期静态分析DAG的输出冲突;
  • 可观测性:每层的输入输出可独立审查。

但复杂度也随之上升,孰好孰坏,还是要看这个痛点对用户来说是否足够痛。

3.2 验证闭环缺失

从代码看,Skill的执行路径是单向的:

SKILL.md → parseFrontmatter → createSkillCommand → getPromptForCommand → LLM → 输出

没有反馈回路。如果LLM的输出偏离了Skill的预期,系统无法:

  • 自动检测偏离;
  • 回退到上一个检查点;
  • 动态调整Prompt参数。

hooks是一个部分解决方案——PostToolUse可以校验工具调用的输出,但它只能拦截工具调用,不能拦截LLM的纯文本输出。

理想的结构是:

SKILL.md → 编译 → 执行 → 校验 → { 通过 → 输出 | 失败 → 回退 + 重新编译 }

这需要为Skill引入output_schemavalidation字段,定义期望的输出结构。否则,我们写Skill没法写用例去测试,都只能边写边调,结果也没法量化。

3.3 版本和演进问题

Frontmatter有version字段,但代码中并不用它做任何版本控制逻辑。它只是一个标签。

当Skill A依赖Skill B的v1行为,而B升级到v2时,没有机制保证兼容性。这在团队协作中尤其危险——一个人的Skill升级可能悄悄破坏另一个人的工作流。

四、从产研流程视角重新审视 Skill

4.1 当前的现实

Skill目前自动化的都是原子操作:/commit/review/test等。这些是产研流程中的"叶节点"——它们不依赖其他操作的输出。

需求 → 技术方案 → 编码 → 测试 → 上线
│     │        │    │     │
│     │        ├─/commit   ├─/deploy
│     │        ├─/review   │
│     │        └─/test      │
│     │                     │
└─────────────── 尚未有 Skill 介入 ─┘

4.2 Skill 能覆盖的范围(理论最大值)

如果把产研流程的每个阶段映射到Skill的能力:

阶段当前状态Skill 理论上能做的需要的额外基础设施
需求分析从PRD提取关键需求,识别模糊点,生成问题清单需求文档的结构化输入
技术方案趋势(/ultraplan等)架构建议、依赖分析、风险评估、方案对比项目依赖图、历史方案库
编码/commit, /review, TDD skill按方案自动编码、增量校验方案到代码的映射规则
测试部分覆盖自动生成测试、覆盖率分析、边界探测测试策略模板、覆盖率基础设施
上线变更影响分析、回滚方案、灰度策略CI/CD 集成、部署配置

所以在Skills上的探索,可以以各个节点为基准点,通过领域内的专家编写Skills,然后通过自动化或人工的方式,走通整个产研流程。

4.3 关键约束:Skill 不等于自动化

正如上面产研覆盖范围的分析,用Skills嵌入产研流程时,一定要放弃惯性思维——整个过程可能并不一定是提效,而是更好地提高整个研发的质量。更深入一点说,Skill的核心价值不在于自动化(用机器替代人),而在于与AI交互的标准化——让不同的人产出相同质量的工作。

标准化带来的是:

  1. 降低随机性:没有Skill时,代码审查质量取决于审查者的经验和状态;有Skill时,审查流程由Skill保证最低质量;
  2. 知识传递:高级工程师的方法论可以编码为Skill,而非仅存在于他们的脑子里;
  3. 可审计性:Skill的执行路径是确定性的,可以回溯和审查。

但这有一个隐含假设:Skill编写者的方法论是正确的。如果方法论本身有缺陷,Skill会以工业化的速度传播这个缺陷。所以,归根结底,不在于要不要AI贯穿整个产研流程,也不在于要不要使用Skill,而在于团队或编写Skill的人的方法论是正确的、适合团队和个人的——否则方向会越走越偏。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策