Skill系统
1. 定位
src/skills/ 这套机制的目标,不是单纯“加载一些 Markdown 文件”,而是把一段提示资产转成可运行时调度的 Command。这使 Skill 同时具有两层身份:
- 对模型来说,它是可被自动选择的能力说明。
- 对用户来说,它又可能表现为显式的 slash command。
因此,Skill 系统本质上是“提示工程资产化 + 命令建模”的结合体。
2. 三类来源
从当前源码看,Skill 至少有三类来源。
2.1 Bundled Skills
src/skills/bundledSkills.ts 定义了 BundledSkillDefinition 与注册表。bundled skill 不是从磁盘扫描出来的,而是在启动时通过代码注册:
registerBundledSkill()把定义转成CommandgetBundledSkills()返回注册后的内存列表clearBundledSkills()主要用于测试
真正的初始化入口在 src/skills/bundled/index.ts。这里会调用一组 registerXxxSkill() 方法,把内置 skill 注册进系统。当前目录 src/skills/bundled/ 下可以看到 17 个 bundled skill 相关文件,但并不代表 17 个能力都会在所有运行环境下暴露出来:
- 一部分受
feature('...')控制 - 一部分受运行时可用性判断控制,例如
shouldAutoEnableClaudeInChrome() - 个别 skill 自身还带
isEnabled()回调
因此,bundled skill 是“代码内置 + 运行时显隐”的模式,而不是静态清单。
2.2 磁盘目录 Skills
src/skills/loadSkillsDir.ts 负责从磁盘目录加载 skills。它支持多种来源:
- managed / policy 路径
- 用户目录
- 当前项目及向上层级的
.claude/skills - 通过
--add-dir注入的附加目录 - 兼容旧版
commands/目录中的 skill / command 形式
这里真正重要的不是“从哪里读文件”,而是这些来源最终都会被折叠成统一的 Command 对象,再参与后续去重和排序。
2.3 MCP Skills
Skill 系统还有一条远端来源。src/services/mcp/client.ts 和 src/services/mcp/useManageMCPConnections.ts 在 feature 打开时会拉取 MCP skills,并把它们和 MCP prompts 一起放进 mcp.commands。
随后,src/commands.ts 通过 getMcpSkillCommands() 专门筛出 loadedFrom === 'mcp' 的命令,使它们进入 Skill 视图与 SkillTool。
这意味着 Skill 并不限于本地文件系统;远端协议返回的 skill,也会被归一成同一种命令抽象。
3. 从 Markdown 到 Command
loadSkillsDir.ts 的核心价值,在于它把 skill 从“文本文件”变成“命令对象”。
3.1 frontmatter 解析
parseSkillFrontmatterFields() 会抽取一组共享字段,包括:
descriptionallowed-toolsargument-hintargumentswhen_to_usemodeldisable-model-invocationuser-invocablehookscontextagenteffortshell
这些字段决定 Skill 在运行时如何暴露、能调用什么工具、适合何时触发,以及是否允许模型直接调用。
3.2 Command 构造
createSkillCommand() 则负责把解析结果组装成 Command。这里会统一处理:
namedescriptionargNameswhenToUsesourceloadedFromhooksskillRootcontextagentpaths
同时,真正执行 getPromptForCommand() 时,还会继续做几件事:
- 参数替换
${CLAUDE_SKILL_DIR}与${CLAUDE_SESSION_ID}注入- 非 MCP skill 的内联 shell 执行
- 为磁盘型 skill 自动加上
Base directory for this skill: ...前缀
所以 Skill 不是“读取后原样返回文本”,而是一套带运行时预处理的 prompt command 构造器。
4. 动态加载能力
Skill 系统真正复杂的部分,在于它不是一次性扫描,而是会持续按上下文扩展。
4.1 多来源加载与去重
getSkillDirCommands() 会并行加载 managed、user、project、additional dirs 与 legacy commands,再按真实路径做去重。这里用 realpath 作为文件身份,目的是处理:
- 软链接
- 重叠父目录
- 同一文件被多路径访问
所以,Skill 系统不是简单“按名字覆盖”,而是先按物理文件身份消重,再进入命令层。
4.2 Conditional Skills
frontmatter 中的 paths 会把 skill 标记为 conditional skill。它不会在启动时立即暴露,而是先放进 conditionalSkills,等待文件操作触发。
activateConditionalSkillsForPaths() 会在文件路径匹配成功时,把这些 skill 激活到 dynamicSkills 中。这里用的是 gitignore 风格匹配,因此它更像“按工作区上下文自动启用的 skill”。
4.3 动态目录发现
discoverSkillDirsForPaths() 与 addSkillDirectories() 负责沿文件路径向上查找嵌套的 .claude/skills。这使 Skill 的作用域可以比项目根更细,表现为“离当前文件越近的 skill,优先级越高”。
这是一种很典型的上下文感知扩展机制。
5. Bundled Skill 的懒提取与安全写盘
bundledSkills.ts 里有一个容易被忽略但很关键的机制:bundled skill 可以携带额外参考文件,并在首次调用时懒提取到磁盘。
这部分处理包括:
- 为每个 skill 分配确定性的提取目录
- 以 Promise 方式做进程内单次提取
- 检查相对路径,阻止目录逃逸
- 使用安全写入标志和权限模式写文件
这里的目的,是让模型在运行 skill 时还能按需 Read/Grep 这些参考资产,同时尽量降低路径穿越和竞争写入风险。
6. 与 Commands、Plugin、MCP 的关系
Skill 系统虽然位于 src/skills/,但它并不自成孤岛。
6.1 与 Commands 的关系
src/commands.ts 会把以下内容一起汇总:
getBundledSkills()getSkillDirCommands()getPluginSkills()getBuiltinPluginSkillCommands()
也就是说,Skill 最终是被命令系统消费的,而不是独立执行框架。
6.2 与 Plugin 的关系
插件可以提供 skill 目录或额外的 skills 路径,因此 Plugin 是 Skill 的一个上游分发渠道。插件解决“skill 从哪里来”,Skill 系统解决“skill 怎样变成命令对象”。
6.3 与 MCP 的关系
src/skills/mcpSkillBuilders.ts 把 parseSkillFrontmatterFields() 与 createSkillCommand() 注册出来,供 MCP skill 发现逻辑复用。这一点很重要,因为它保证了:
- 本地 skill 与 MCP skill 共享相同的建模语义
- 系统不会为远端 skill 维护一套平行的命令构造逻辑
所以,Skill 系统表面上看是在“加载 Markdown”,本质上是在提供一套统一的 prompt-command 规范。