1. 项目概述:为什么我会去折腾一个“技能管理器”
我先交代一下背景。我日常主力工具是Cursor + Claude Code + Continue这三个,偶尔还要用Windsurf和Zed应付不同项目。过去大半年我一直在做同一件事:把同一套编码规范、代码审查规则、重构流程、甚至提示词片段,在每一个工具的配置目录里各写一份。Cursor要.rules,Claude Code要SKILL.md,Continue要rules文件,Zed有自己的一套agent体系。每次改一条规则,我就要在五个工具里分别改一遍,改完还要担心哪个工具的语法格式写错了导致规则静默失效。
最让我崩溃的一次,是我给一个Python项目写了一条“禁止使用全局变量存储配置”的审查规则。在Claude Code里写完整套skill后,我忘记同步到Cursor的规则文件里。结果Cursor那边帮我重构代码时,直接给另一个模块也安了全局配置对象,两边的架构风格马上分叉,之后花了一整天才把两边代码重新对齐。
所以这个标题里提到的“Skills Manager”,其实就是要解决我上面描述的这一类问题:统一管理散落在多个AI编程工具里的Agent技能,让同一份技能定义能被不同工具识别、加载和执行。所谓“54+”,指的是这套系统覆盖的工具生态数量——截止到我写这篇文章时,市面上叫得出名字的、带Agent能力或自定义技能机制的编程工具,我数了一下主流加小众的加起来确实超过54个,其中我实际接入和验证过的大概有30来个。这个数量不是营销噱头,是我在维护这个管理器时真实跑过的适配列表。
这个项目适合谁看?如果你手里同时用两个以上AI编程工具,而且已经遇到了“同一个项目在不同工具里的表现不一样”“规则改一处要改五处”“某个工具不认我写的规则文件”这类问题,那这篇内容值得你完整读一遍。如果你是那种只用一个工具、从没碰过自定义规则的人,也可以看,因为你迟早会遇到“工具升级后我的规则失效了”这种坑,这篇文章里会给到排查思路。
2. 核心认知拆解:先搞清楚各家工具的“技能”到底是个什么东西
要统一管理技能,第一步不是急着写代码,而是先摸清楚各家AI编程工具的技能机制长什么样。我给一个比较通用的定义:Agent技能 = 一套能被AI工具读取、理解、并在合适场景下自动调用的规则/配置文件集合。但每个工具对它的叫法、目录结构、加载机制、语法格式都不一样,这里我梳理一下主流工具的技能形态。
2.1 主流AI编程工具的技能机制对比
我先用一个表格把我知道的整理出来,这个表格是我在实际适配过程中一点点积累的,准确性比网上很多流传的版本要高:
| 工具名称 | 技能机制名称 | 存放位置 | 配置格式 | 自动加载时机 |
|---|---|---|---|---|
| Claude Code | Skills | 项目根目录 .claude/skills 或全局 ~/.claude/skills | Markdown + YAML frontmatter | 会话启动时扫描并注入上下文 |
| Cursor | Rules | 项目根目录 .cursor/rules | 纯Markdown或.mdc文件 | 按glob匹配自动附加到上下文 |
| Continue | Rules | 项目根目录 .continuerules | Markdown | 文本相似度匹配后加载 |
| Windsurf | Global Rules / Memories | 全局配置目录 + 项目.windsurf/rules | Markdown + 特定语法标记 | 会话启动时整体注入 |
| Zed | Agent Skills | 项目根目录 .zed/skills 或 .agent/skills | Markdown + JSON frontmatter | 按需由agent检索加载 |
| JetBrains AI | Custom Prompts | 工具内配置 | XML格式的prompt模板 | 手动或按指令触发 |
从这个表能看出来,虽然大家都叫“技能”,但底层机制差异很大。Claude Code的SKILL.md是带YAML frontmatter的,metadata里可以写name、description、allowed-tools这些字段,AI会读description来决定什么时候用这个技能。Cursor的.mdc则更接近“规则注入”,它支持glob patterns来指定哪些文件的会话中加载对应规则。Continue那个rules文件更特殊,它依赖向量相似度匹配,也就是说你得把规则写得足够精确,AI才可能在对话中主动召唤它。
2.2 技能的“原子性”比你想的更重要
我在拆解各家机制时发现,判断一个技能设计得好不好,最核心的标准是它的原子性。什么意思?一个技能应该只做一件事,并且可以被独立描述、独立测试、独立复用。
举个例子,我见过很多人把整个项目的架构规范、代码风格、测试要求、部署流程全部写进一个巨大的SKILL.md里,动辄七八百行。这样看起来“很全”,但实际运行中问题很大:
- AI每次对话都要把这个大文件全量塞进上下文窗口,白白浪费token。
- 某个具体任务需要的是“代码审查规则”,结果被几千行架构文档稀释了指令权重。
- 你想让另一个工具复用其中“commit message规范”这条技能,根本没法单独抽取。
我后来每个技能都拆到极小,比如下面这样:
python-review/ # Python代码审查技能 SKILL.md # 主描述文件 hooks/ pre-commit.py # 审查前的自动化检查脚本 examples/ bad.py # 反例 good.py # 正例这也引出了我设计Skills Manager时的一个核心原则:统一的是技能的“容器格式”,而不是技能的“内容粒度”。每个技能内容依然由你按原子性原则编写,管理器只负责保证这些技能在不同工具之间能无损转换和分发。
3. 架构设计思路:统一格式、场景路由、双向同步
现在到了这个项目的核心部分。Skills Manager本质上是一个跨平台的桌面应用,它在本地运行,负责管理一个统一的技能仓库,然后把每个技能分发到各AI工具的对应目录里。整体架构我拆成三层:统一格式层、路由分发层、同步调度层。
3.1 统一格式层:为什么需要一套“中间表示”
要让不同工具都能识别同一个技能,直接做法是让一个工具去兼容所有工具的格式,这显然不现实。反过来想,我定义一套通用的中间表示(Intermediate Representation),所有技能先写成这套格式,再由管理器转换成各个工具的实际格式。这套中间格式长这样:
{ "id": "python-review", "name": "Python代码审查", "version": "1.2.0", "description": "审查Python代码中的全局变量、类型注解缺失和异常吞噬问题", "triggers": ["*.py", "pyproject.toml", "requirements.txt"], "body": "skill-content.md", "attachments": ["hooks/pre-commit.py"], "targets": { "claude-code": { "path": ".claude/skills/python-review/SKILL.md", "transform": "claude-v1" }, "cursor": { "path": ".cursor/rules/python-review.mdc", "transform": "cursor-mdc" }, "continue": { "path": ".continuerules", "transform": "append" } } }这个JSON里最关键的是targets字段。它声明了这个技能要分发到哪几个工具、写到什么路径、用什么转换器。我这里强调一点:转换器不是简单地把Markdown拷过去。Cursor的.mdc格式对描述和规则正文是有区分要求的,Claude Code的SKILL.md则要求frontmatter里必须有name和description且不能乱写allowed-tools的值。这些细节在转换时必须逐项处理。
3.2 路由分发层:按项目和场景决定技能去哪里
技能仓库建好之后,下一个问题是怎么决定一个技能该分发到哪些工具。这里面有个很重要的概念,我叫它“场景路由”。
一个项目通常是按技术栈来区分环境的:Python后端项目涉及到的技能,和React前端项目涉及到的技能,交集很少。如果我把所有技能全部分发给所有工具,那技能仓库体积会爆炸,AI上下文也会被无效信息塞满。所以我设计了路由策略:
- 项目路由:基于项目根目录的特征文件(比如
pyproject.toml、package.json、go.mod),自动推断该项目所属的技术栈分类,只分发对应类别的技能。 - 路径路由:基于技能里配置的
triggers字段,当某个文件路径匹配时,才激活对应的技能。比如只有打开.py文件时,才把python-review注入到当前会话。 - 手动路由:我也支持手动指定,比如团队里只有一个成员需要某个技能,那这个技能只分发到他的本地配置里。
从实际体验看,路由策略是这套管理器最值得花时间调优的部分,因为它直接决定了AI工具的性能表现。分类太粗,无用技能多;分类太细,很多技能永远触发不了。我目前采用的是“项目级粗筛 + 文件级细筛”的二级路由,90%以上的场景够用。
3.3 同步调度层:解决“双向同步”这个魔鬼问题
分发之后还有反向的需求——你在某个工具里手动改了技能,希望能同步回管理器。这里有个天然的不对称性:
- 管理器到工具:格式是可控的,我可以保证转换后一定能被工具读取。
- 工具到管理器:工具各自的配置文件可能被AI或用户修改过、添加过工具特有的语法,直接逆向解析容易出错。
所以我放弃了“全自动双向同步”这个诱惑,而是采用单向主从 + 冲突预览的机制。管理器作为唯一事实源(source of truth),对外分发。当你手动改了某个工具的技能文件后,管理器会检测到差异,弹窗列出“以管理器为准”还是“以该工具为准”,让你做一次确认。这个设计牺牲了一点自动化程度,但避免了灾难性的覆盖。
4. 实操过程:从零搭建一个可用的Skills Manager
讲完理论,直接上实操。我用的技术栈是Tauri 2.0 + Rust + React + TypeScript,选了Tauri是因为它打包体积小、内存占用低,而且我只需要访问本地文件系统,不需要太多系统权限。如果你更熟悉Electron,完全可以用它替代,架构逻辑是一样的。
4.1 技术选型:为什么我选了Tauri而不是Electron
在桌面端,Tauri和Electron是最常见的两个选择。当时我列了一个对比清单:
| 维度 | Tauri 2.0 | Electron |
|---|---|---|
| 安装包体积 | 约5-10MB | 约80-150MB |
| 内存占用 | 约200-400MB | 约600MB-1GB |
| 开发语言 | Rust(后端)+ Web前端 | Node.js + Web前端 |
| 文件系统操作 | 需要Rust侧写入API | 直接Node.js API |
| 跨平台支持 | Windows/macOS/Linux | Windows/macOS/Linux |
选Tauri的核心原因其实是跨平台编译的产物一致性。Electron在不同系统上对文件路径的处理有些微差异,而我在Rust侧用std::path::PathBuf处理路径,可以确保Windows的C:\project\xxx和macOS的/Users/xxx/project在拼接规则时行为完全一致。这对这种以路径操作为核心的工具来说非常关键。
4.2 目录结构与核心模块设计
项目根目录我按模块拆分,不搞单文件堆砌:
skill-hub/ src/ main.rs # Tauri入口,注册命令 commands/ sync.rs # 同步逻辑:分发/回采 convert.rs # 格式转换器注册表 router.rs # 场景路由判定 watcher.rs # 文件变更监听 store/ manifest.json # 技能清单元数据 skills/ # 技能源文件(中间格式) frontend/ src/ # React界面 targets/ # 各工具的适配器定义 claude-code/ cursor/ continue/ ... config/ settings.json # 用户级配置manifest.json是核心,它记录着所有技能的元数据和分发状态。每次启动时,管理器会先加载这个文件,然后遍历skills/目录读取所有技能,再根据配置文件里的路由规则生成分发计划。
4.3 关键转换器实现:以Claude Code和Cursor为例
转换逻辑是这项目里最不好写的部分。我拿两个典型例子说一下。
Claude Code的SKILL.md格式,要求在文件开头用YAML frontmatter定义name和description:
--- name: python-review description: 审查Python代码中的全局变量、类型注解缺失和异常吞噬问题。当用户请求代码审查或打开.py文件时使用。 allowed-tools: - Read - Edit - Grep ---这里有个坑:description写不好,AI永远召唤不出这个技能。Claude Code会基于description的语义相似度来判断是否调用技能。“审查一下这个文件”和“帮我看看这个文件有什么问题”在语义上接近,但如果description里只写了“审查”两个字,前者可能触发不了,后者就更难说。我的中间格式里描述字段写得比较详细,转换器拿到description后会自动扩展成Claude Code偏好的“场景 + 触发条件 + 执行动作”三段式结构,实测触发率比简略描述高很多。
Cursor的.mdc格式则完全不同。它对“描述”和“规则正文”做了明确区分,描述部分用于告诉模型何时引用规则,正文才是真正注入上下文的规则内容:
--- description: Python代码审查规则(全局变量、类型注解、异常吞噬) globs: ["*.py", "pyproject.toml"] --- - 禁止使用全局变量存储配置 - 所有公开函数必须带有类型注解 - 禁止except Exception后静默pass我的转换器会读取中间格式里的triggers字段,自动填充为globs。同时,中间格式的body部分是规范化的规则列表,转换时每行自动加上-前缀,防止AI把多行规则当成一段文字导致权重稀释。
4.4 具体部署步骤:第一次拿到管理器怎么跑起来
如果你不想从我这份源码开始,而是想直接用类似的机制,我建议你按以下流程部署:
第一步:初始化技能仓库建一个skills/目录,每个子目录代表一个技能。新建技能时,不要直接写配置文件,先创建一个SKILL.md草稿,用你最习惯的自然语言描述技能目的、触发场景和具体规则。把这个草稿视作“母本”。
第二步:声明目标工具在每个技能的manifest片段中,用targets字段声明要分发到哪些工具。初次配置时先只选两个工具跑通流程,别一上来就全平台分发。
第三步:执行首次同步运行同步命令,管理器会读取母本、调用对应转换器、把生成的文件写入工具指定的配置路径。同步完成后,重启AI工具,在新会话中测试技能是否被正确触发。这一步我建议用小技巧验证:在工具里输入“输出当前加载的所有技能名称”,如果工具支持,可以直接看到技能是否注入。
第四步:配置自动监听开启watcher,监听skills/目录和工具配置目录的变动。以后你改技能母本,管理器自动分发;你手动改工具配置,管理器弹冲突提示,让选择是否回采。
4.5 一个完整的技能实例:Python代码审查技能
我手写一个完整的python-review技能配置,参考一下中间格式的完整度:
--- id: python-review name: Python代码审查 version: 1.2.0 description: 审查Python代码中的全局变量、类型注解缺失和异常吞噬。适用于代码审查会话或打开.py文件时。 triggers: - "*.py" - "pyproject.toml" - "requirements.txt" body: | 1. 禁止使用全局变量存储配置或状态,所有可变状态必须显式传入函数或封装为类。 2. 所有公开函数和方法必须带有完整类型注解,包括参数和返回值。 3. 禁止捕获Exception后静默pass。如果必须吞异常,必须写日志说明原因。 4. 优先使用pathlib.Path代替os.path拼接路径。 targets: claude-code: path: ".claude/skills/python-review/SKILL.md" cursor: path: ".cursor/rules/python-review.mdc" continue: path: ".continuerules" append: true分发后,Claude Code那边会生成带frontmatter的SKILL.md,Cursor那边会生成带globs的.mdc,Continue那边则会把规则追加到.continuerules文件末尾。这就是“母本一次,多端生效”的完整流程。
5. 实操常见问题与排查技巧
我跑这个管理器跑了四五个月,踩了不少坑,下面把这些经验整理成速查表,供参考。
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 技能文件存在但AI始终不触发 | description/描述写得过于模糊,或触发条件不匹配 | 重启会话,精炼描述中的触发场景词,检查triggers是否覆盖实际操作路径 |
| Cursor规则总是被系统规则截断 | 单文件过大或跨多个会话累积注入 | 拆分为更小的.mdc文件,用globs精确控制加载范围 |
| Claude Code报“无法解析SKILL.md” | frontmatter字段格式错误或存在多余字段 | 检查name和description是否齐全,allowed-tools不要写工具不支持的名称 |
| 同步后工具配置目录出现奇怪权限错误 | 多数是macOS的沙盒机制或Windows文件权限问题 | 检查工具是否有写入目标目录的权限,Tauri配置里申请必要的fs权限 |
| Continue的rules文件越长,匹配越差 | 文本相似度匹配对超长文件效果差 | 尽量每条规则独立成短文文件,避免堆成一大坨 |
| 双向同步时管理器把工具侧手动配置覆盖了 | 冲突处理策略配置为“以管理器为准” | 建议改为“冲突预览”,在每次覆盖前先人工确认 |
5.1 排查“规则没生效”的三个由浅入深的步骤
遇到“我明明配置了,AI就是不按规则执行”,我的排查顺序是:
先看文件是否真的被加载:大部分工具的日志里会打印加载了哪些规则文件。Cursor可以在开发者工具里输入Cursor.rules查当前上下文规则,Claude Code可以用/skills命令查看已加载的技能列表。这一步能区分“配置没写好”和“配置写了但没读到”两种不同情况。
再看配置文件有没有被转换器正确生成:很多用户直接在管理器里改母本,然后发现目标工具里的文件是旧版本。原因是watcher没监听目标工具目录,管理器不知情。这时候要主动触发一次“强制同步”。
最后怀疑触发条件:有一种很诡异的情况是技能文件本身没问题,但工具的glob匹配规则跟你预期不一致。比如Cursor的glob只匹配文件路径的末尾,如果你在triggers里写了"src/**/utils/*.py",它可能在打开src/app/utils/helper.py时反而匹配不上,因为cursor内部会把glob转成正则再匹配。这种问题只能靠打日志或逐个文件测试来定位。
5.2 跨平台路径差异性:最先踩到的坑
这个项目跨平台开发和使用的过程中,路径分隔符和路径拼接规则是个很容易被忽略的大坑。我在Windows上开发时遇到过一个现象:用path.join("src", "skills")拼接的路径,在macOS上没问题,在Windows上会被自动翻译成src\skills,这算正常。但坑在于工具侧的配置期望路径是src/skills这种正斜杠格式,如果用反斜杠写进配置文件的path字段,工具可能识别不了。
解决办法很简单统一:所有存进配置文件、manifest、和路由规则里的路径,一律用正斜杠/。在代码内部转换时再根据操作系统转换为本地格式。我的转换器里专门写了一个normalize_path函数,在任何系统上统一输出Linux风格路径。
5.3 关于“54+工具”的真实坦白
趁这个机会说实话:54+这个数字,指的是清单上列出来的、理论上可以接入的工具数量,不是我完整适配过的数量。我完整适配、跑通过自动化测试的,大概在三四十个左右。剩下那些要么是相对小众的IDE插件,要么是API文档本身不完整,适配成本过高。所以我设计时留了个“适配器注册表”,每个工具对应一个转换器,接入模式是插拔式的。谁要适配一个新的工具,只需要写一个targets/<工具名>/convert.js,实现parse和generate两个函数就行。
6. 写在最后:我对这整套方案的体会
技能管理的本质,是给AI编程工具建立一套可控的、可迁移的、有版本概念的“长期记忆”。我之前所有的纠结——规则散落、改一处漏一处、工具升级后规则失效——本质是因为长期记忆被锁死在了各个工具各自的实现里。Skills Manager做的事情很简单,就是把记忆抽离出来,放到一个独立的、中立的层。
实际操作中,我个人最大的收获不在管理器的代码本身,而在于建立了一套强制性的技能设计规范:每个技能必须有明确触发场景、必须原子化、必须写清反例。这一点对AI编程工具的提效影响巨大。以前随手写一个规则文件就完事了,现在每写一个技能都像在写文档,虽然慢,但积累起来的效果很值。
如果以后要扩展,我觉得有两个方向值得尝试:一是给同一个技能做A/B版本对比,让AI分别用两版技能方案处理同一批任务,自动评估哪版产出更好;二是做技能依赖解析,让一个技能自动引用另一个技能,而不必把重复内容拷贝来拷贝去。这两个方向都需要一定的提示工程和评测工程基础,但方向上我是比较确定的。
最后分享一个小技巧:如果你现在不打算写任何代码,只是想把多工具的规则管理起来,可以先从统一目录结构入手——在你本地建立一个skill-hub/目录,把所有规则以Markdown格式集中存放,再手动复制到各工具配置目录。这个原始操作虽然费点功夫,但只要坚持下来,后面迁移到自动化方案会非常平滑,因为你的母本就是现成的。