从 0 写一个 SKILL.md:让 Claude / Codex / Gemini 共用一个 AI 技能

摘要

上一篇我们聊到 AI 编程工具的工作流复用问题,这一篇继续往下拆:所谓 skill 到底是什么?为什么很多人会说“一个 Markdown 文件就是一个 AI 技能”?这句话为什么又只对了一半?

本文不做软文式介绍,而是从一个最小可用的 SKILL.md 开始,拆解 Skill 的元信息、提示词主体、跨 Provider 兼容字段,以及 SpectrAI 在源码层面如何解析、加载并分发 Skill。最后会解释:当一个 Skill 变复杂后,为什么它往往会扩展成一个包含 scripts/templates/references/hooks/ 等目录的完整技能包。

1. 为什么要聊 Skill

如果你经常使用 Claude、Codex、Gemini 处理工程任务,会很快遇到一个问题:很多提示词不是一次性的。

例如:

  • 每次做代码审查,都希望模型按相同维度检查逻辑、性能、安全和可维护性;
  • 每次写测试,都希望模型遵守项目里固定的测试框架和断言风格;
  • 每次发版,都希望模型按固定步骤检查版本号、构建产物、发布说明和回滚方案。

这些稳定流程如果每次手写 prompt,不但低效,而且很容易遗漏步骤。Skill 的作用就是把这类“可复用的 AI 工作流”封装起来:用户只需要输入一个斜杠命令,系统就能把预设的上下文、规则、模板、参考资料甚至脚本一起交给模型。

在 SpectrAI 的用户手册 docs/manual/技能系统.md 中,Skill 被分成三类:

  1. prompt 技能:本质是一个提示词模板,最轻量;
  2. orchestration 编排技能:可以让多个 Provider 按步骤协作;
  3. native 原生技能:可以用更强的代码能力封装复杂逻辑。

这三类 Skill 的共同点是:它们都在试图把一次性的聊天输入,变成可安装、可发现、可复用的能力。

2. 为什么说“一个 Markdown 文件就是一个 AI 技能”

这句话的来源很简单:对最小可用的 Skill 来说,确实只需要一个 SKILL.md

一个极简 Skill 可以长这样:

---
name: PR 审查助手
description: 按工程质量、安全性和可维护性审查 Pull Request
slashCommand: pr-review
type: prompt
category: development
compatibleProviders: all
tags: [review, git]
---

你是一名严格但务实的代码审查助手。

请根据用户输入的 diff 或改动说明进行审查,重点关注:

1. 逻辑正确性:是否存在边界条件、异常分支或状态不一致问题;
2. 安全风险:是否引入权限、注入、敏感信息泄露等问题;
3. 可维护性:命名、职责划分、重复代码和复杂度是否合理;
4. 测试建议:指出必须补充的测试用例。

输出格式:

- 先给出总体结论;
- 再按“高风险 / 中风险 / 建议优化”分组列出问题;
- 每个问题都要说明原因和修改建议。

用户输入:
{{user_input}}

这个文件分成两部分:

  • 上面的 --- 区域是 YAML frontmatter,描述 Skill 的元信息;
  • 下面的正文是提示词主体,也就是最终要交给模型执行的任务说明。

这就是“一个 Markdown 文件就是一个 AI 技能”的合理部分:Markdown 天然适合写说明、规则、步骤和模板,而 frontmatter 又能补充机器可解析的结构化字段。对 prompt 类型 Skill 来说,这个组合已经足够完成安装、展示、调用和执行。

3. 最小 SKILL.md 里应该有哪些字段

从 SpectrAI 的解析逻辑看,一个 SKILL.md 不需要字段越多越好,关键是把“人能理解”和“系统能识别”的信息写清楚。

建议最小结构包含这些字段:

字段 作用
name 技能名称,展示给用户看
description 技能说明,帮助用户判断何时使用
slashCommand 斜杠命令,例如 /pr-review
type 技能类型,常见为 promptnativeorchestration
category 分类,例如 developmentwritinganalysis
compatibleProviders 兼容哪些 Provider,简单场景可写 all
tags 标签,方便检索和管理

其中最容易被忽略的是 compatibleProviders。在 docs/manual/技能系统.md 的说明里,大多数内置技能可以设置为 all,表示可以在 Claude Code、Codex、Gemini 等不同 Provider 上运行。

但在实际工程里,我建议遵守一个原则:

  • 如果 Skill 只是纯提示词,不依赖某个 Provider 的专有能力,可以写 all
  • 如果 Skill 依赖某个 Provider 的目录结构、插件机制或工具能力,就应该显式声明兼容范围;
  • Provider 标识要以项目内部配置为准,不要凭感觉混写。

这样可以避免“Claude 能用、Codex 不加载”或者“Gemini 只吃到一半说明”的问题。

4. SpectrAI 是如何解析 SKILL.md 的

src/main/skill/skillParser.ts,SpectrAI 对 Skill 的解析可以概括成三层。

第一层是解析 Markdown 或 JSON。

parseSkillContent 会判断内容格式:如果像 JSON,就尝试按 JSON Skill 解析;否则按 Markdown 解析。Markdown 会先拆 frontmatter,再把正文转换成 promptTemplate。如果用户没有显式写 slashCommand,系统还能从文件名推断命令名。

第二层是标准化元信息。

解析器会处理字段别名,例如 slashCommandslash_commandslash-commandcommand 都可以归一到斜杠命令。compatibleProviders 如果没有写,默认会被理解成 alltype 如果没有写成合法值,则默认走 prompt

第三层是目录化 Skill。

parseDirectorySkill 要求目录中必须有 SKILL.md。在读取这个文件之外,它还会继续扫描同级目录:

my-skill/
  SKILL.md
  scripts/
  templates/
  references/

也就是说,SpectrAI 并不只把 Skill 当成一个单文件 prompt。源码注释里明确写到支持“完整 Skill 包(SKILL.md + scripts/templates/references 等目录)”。解析结果里也保留了 scriptstemplatesreferences 这些资源内容。

这点很关键:SKILL.md 是入口,但不一定是全部。

5. 一个 Skill 如何被 Claude / Codex / Gemini 共用

要让一个 Skill 被多个 Provider 共用,不能只看 SKILL.md 本身,还要看加载路径。

5.1 通用入口:compatibleProviders

最简单的方式是把纯提示词 Skill 写成:

compatibleProviders: all

这表示它没有绑定某个 Provider 的专有运行时,理论上可以被多个模型复用。

如果需要精确控制,也可以把兼容 Provider 写成数组。这里要注意:仓库里不同模块出现过面向用户的名称和内部适配器名称,实际值应以当前项目的 Provider 配置和加载逻辑为准。

5.2 Claude:作为本地 plugin 注入

src/main/adapter/ClaudeSdkAdapter.ts 中,loadPlugins 会从 Skill 仓库里取出已启用的包,并筛选兼容 Claude 的包,然后把它们作为本地 plugin 路径传给 Claude SDK。

这说明对 Claude 来说,Skill 不只是聊天输入里的文本,它也可以以本地 plugin 的形式挂载到会话能力里。

5.3 Codex:合并到临时 CODEX_HOME

src/main/agent/MCPConfigGenerator.ts 中,generateForCodex 会为每个会话生成隔离的临时 CODEX_HOME。随后 mergeSkillPackagesIntoCodexHome 会把兼容 Codex 的 Skill 包合并进去。

代码里能看到两类路径:

plugin/codex/<name>/  -> tempHome/skills/<name>/
plugin/commands/*.md  -> tempHome/prompts/*.md

也就是说,Codex 侧会把 Skill 包转换成它能识别的 skills/prompts/ 结构。

5.4 Gemini:把 SKILL.md 追加到 GEMINI.md

Gemini 侧也在 MCPConfigGenerator.ts 中处理。generateForGemini 会创建隔离的 GEMINI_CLI_HOME,复制必要的 Gemini 配置,然后调用 injectSkillPackagesIntoGeminiMd

这个函数的核心行为是:筛选启用且兼容 Gemini 的 Skill 包,把其中的 SKILL.md 内容追加到临时 home 下的 GEMINI.md

它读取的来源包括:

plugin/skills/*/SKILL.md
SKILL.md

所以,Gemini 不是直接“安装同一个插件目录”,而是把 Skill 的 Markdown 指令转成 Gemini CLI 会读取的上下文文件。

5.5 共用的本质:同一份能力,不同的适配层

把三者放在一起看,会发现跨 Provider 共用并不是“所有 Provider 都原样读取同一个文件”。更准确的说法是:

Skill 用一套中立的元信息和目录约定描述能力,再由不同 Adapter 转换成各 Provider 能理解的加载形态。

Claude 可能走 plugin,Codex 可能走 CODEX_HOME 下的 skills/prompts/,Gemini 可能走 GEMINI.md 注入。对用户来说是“同一个 Skill”,对实现来说是“三套适配路径”。

6. 为什么“一个 Markdown 文件就是一个 AI 技能”只对了一半

现在回到开头那句话。

它对的地方在于:

  • 最小 Skill 可以只有一个 SKILL.md
  • prompt 类型 Skill 的主要逻辑确实可以写在 Markdown 正文中;
  • frontmatter 足够表达名称、命令、类型、分类、兼容 Provider 等基础信息。

它不完整的地方在于:复杂 Skill 往往不止一个 Markdown 文件。

比如一个“自动发版审查” Skill,可能需要:

release-check/
  SKILL.md
  references/
    release-policy.md
    rollback-checklist.md
  templates/
    release-note.md
  scripts/
    collect-changelog.ts
    verify-version.ts

其中:

  • SKILL.md 负责说明任务目标、触发方式和总体流程;
  • references/ 放长期稳定的规范、检查清单、领域知识;
  • templates/ 放输出模板,保证每次结果格式一致;
  • scripts/ 放可执行辅助逻辑,例如收集 diff、读取版本号、生成 changelog;
  • hooks/ 或插件结构可以在会话开始、工具调用前后等时机挂载自动化动作。

src/main/skill/SkillNpmInstaller.ts 里也能看到类似思想:它会识别 skills/hooks/hooks.jsoncommands/codex/plugin.json.claude-plugin 等能力标识,并据此判断一个包是否是更完整的 Skill Package。

这也解释了为什么“一个 SKILL.md 不足以描述复杂 skill / plugin”这个判断是成立的。Markdown 适合描述意图和流程,但复杂工程能力还需要资源、脚本、模板、钩子和 Provider 适配层。

7. 从 0 写一个可复用 Skill 的建议步骤

如果你想从 0 写一个能被 Claude / Codex / Gemini 共用的 Skill,可以按这个顺序来:

第一步:先写纯提示词版本

先不要急着加脚本和 hooks,写一个最小 SKILL.md,把任务边界说清楚:

  • 这个 Skill 解决什么问题;
  • 用户需要输入什么;
  • 模型应该按什么步骤处理;
  • 输出格式是什么;
  • 哪些事情不能做。

第二步:补全元信息

保证 frontmatter 至少包含:

name: xxx
description: xxx
slashCommand: xxx
type: prompt
category: development
compatibleProviders: all

如果已经知道它只能在某些 Provider 下运行,再把 compatibleProviders 改成明确列表。

第三步:在多个 Provider 下跑同一批样例

不要只在一个模型里验证。准备 3 到 5 个代表性输入,用 Claude、Codex、Gemini 分别跑一遍,看输出是否一致。

重点检查:

  • 是否都理解了任务边界;
  • 是否都遵守输出格式;
  • 是否有 Provider 因上下文注入方式不同而遗漏信息;
  • 是否有字段命名导致某一侧未加载。

第四步:把稳定知识移到 references

如果 SKILL.md 越写越长,先不要继续堆内容。可以把长期稳定的资料拆到 references/

my-skill/
  SKILL.md
  references/
    rules.md
    examples.md

这样 SKILL.md 保持“入口说明”的清晰度,详细规则则作为资源被加载。

第五步:再引入 templates、scripts 和 hooks

当输出格式固定时,加 templates/;当需要读取项目状态或做确定性计算时,加 scripts/;当需要在会话生命周期或工具调用前后自动触发逻辑时,再考虑 hooks/

不要一开始就把 Skill 做成复杂插件。Skill 的演进路线最好是:

单个 SKILL.md -> 目录化 Skill -> 带资源的 Skill 包 -> 带 hooks / provider adapter 的复杂插件

8. 总结

Skill 的本质不是“神秘插件”,而是一种把 AI 工作流产品化的封装方式。

对最小场景来说,一个 SKILL.md 就够了:frontmatter 负责机器识别,Markdown 正文负责模型执行。SpectrAI 的 skillParser.ts 也确实支持直接解析 Markdown,并把正文转换成 prompt 模板。

但从工程实现看,SKILL.md 更像入口文件。复杂 Skill 还可能包含 scripts/templates/references/hooks/、Provider 专用目录和插件元信息。SpectrAI 通过不同 Adapter 把同一套 Skill 能力分发给 Claude、Codex、Gemini:Claude 走本地 plugin,Codex 合并到临时 CODEX_HOME,Gemini 追加到临时 GEMINI.md

所以更准确的说法应该是:

一个 Markdown 文件可以成为最小 AI 技能;一个成熟 AI 技能,往往是以 SKILL.md 为入口的完整技能包。

如果你正在把自己的高频 AI 工作流沉淀下来,不妨先从一个最小 SKILL.md 开始。等它真的被频繁复用,再逐步拆出 references、templates、scripts 和 hooks。

评论区可以聊聊:你最想封装成 Skill 的 AI 工作流是什么?代码审查、写测试、发版检查,还是内容发布流程?

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐