
上周有个同事跑到我工位盯着我屏幕上的桌面应用看了半天问“你桌面这个Skills Manager到底是什么东西”我当时正把一排技能文件拖进窗口系统提示已同步到Trae和Cursor。我说你可以把它理解成这些AI编程工具共同的“技能仓库”。现在AI编程工具多得让人眼花免费的Trae、收费的Cursor、被反复提到的Copilot还有一批从开源社区长出来的CLI工具……不少人的桌面上同时躺着三四个。工具挑到最后真正拉开体验差距的已经不是模型本身而是你给Agent预置的那套“做事手法”——也就是Agent技能。问题在于每家的技能格式都不一样Cursor用的是rulesTrae里叫自定义指令Claude Code是Skills目录Copilot是custom instructions。技能在A工具里调得再好换个工具就全废。这篇文章就聊聊我做的这个Skills Manager解决了什么问题以及搭建统一Agent技能管理的完整思路。适合同时使用多个AI编程工具、或想在团队里统一Agent行为的同学参考。1. 从技能分散到技能资产化AI编程工具爆发甜头与代价并存1.1 五十多个工具背后的“技能孤岛”现象我数过一轮目前圈子里叫得上名字的AI编程工具包括IDE插件、独立编辑器、CLI代理、代码评审机器人加起来早就超过五十四个。这个数字本身不算夸张真正让人头疼的是每家都有一套自己的“技能体系”。Cursor以.mdc规则文件为主放在.cursor/rules/目录里通过自然语言描述让Agent在对应场景下自动加载。Trae提供自定义指令功能可以按项目或者按用户级别配置指令在Agent生成代码前被注入上下文。Claude Code使用SKILL.md技能目录.claude/skills/下每个技能是一个文件夹里面包含描述、指令和示例。GitHub Copilot用custom instructions可以在仓库里放一份.github/copilot-instructions.md。Continue有commands机制~/.continue/commands/下每个文件对应一个斜杠命令。这只是主流几家。我自己试过Gemini CLI、Aider、OpenCode等开源命令行工具它们对技能的定义更随意有的就是把一段系统提示塞进启动参数里。你看看这个局面一个团队同时用Trae和CursorA同学在Trae里精心打磨了一套代码审查技能B同学在Cursor里完全用不了。A想分享给B只能把指令文本复制到聊天记录里让B自己翻译成Cursor能懂的规则文件。抄来抄去版本一多就乱最后谁也不知道当前生效的是哪一版。这就是典型的“技能孤岛”。Agent技能作为一种经验资产被锁死在各自工具的私有格式里既不能自由流转也不能统一维护。我做的Skills Manager本质上就是在这堆孤岛之间架一座桥。1.2 技能到底是什么——先别把它想得太玄很多人一听到“Agent技能”就觉得是某种高深的东西其实没那么复杂。所谓技能就是你给Agent预置的一套“做某类任务的方法论”让它在固定场景下稳定输出而不必每次从头解释。类比一下新员工入职会拿到一本岗位手册里面写了“遇到客户投诉先道歉再记录最后升级”。技能就是给Agent写岗位手册。我拆分技能时习惯分三层提示词模板层告诉Agent“怎么回答”。比如“当你看到TypeError报错时先提取堆栈中的文件和行号再根据变量类型推导可能的不匹配位置最后给出最小修复方案”。规则与约束层告诉Agent“别怎么做”。比如“不要重写整个模块”“不要给出没有根据的猜测”“不要把变量名改成与项目风格不一致的命名”。工作流与调用层让技能能联动外部工具。比如“定位问题后运行npx tsc --noEmit来验证类型错误是否消除”。一个打磨过的代码迁移技能可能包含“识别旧API调用位置—映射到新API—生成替换代码—运行单测验证”四个步骤每个步骤都有输入输出约定。这跟写一个函数没什么区别只是运行主体从计算机变成了大模型。想清楚这一点你就知道为什么技能值得被当成资产来管理——它是一支团队在特定任务上反复沉淀的经验压缩包。1.3 以前用文档管理技能为什么总是失败在没有Skills Manager之前我也试过正经方法建一个Markdown文档库把每个技能的提示词、适用场景、注意事项全写进去挂在团队Wiki上。结果呢文档很快就过时了。原因不是大家懒得写而是文档这种形态承载不了“可执行的技能”没有统一结构每个人写技能的风格不一样有人写一段话有人列十条有人放一堆示例代码。复制到不同工具时还需要手工翻译成对应格式。没有版本管理技能改了三次Wiki上只有最新版根本不知道改了什么。改坏了想回滚只能凭记忆。没有分发通道文档写好之后大家得手动去复制、手动粘贴到IDE里路径一换就忘。无法验证一个技能在Trae里好不好用只有跑过才知道。文档里写了“已测试”但换个模型版本可能就失效没有任何回归手段。说白了技能要变成资产前提是它有明确的格式、版本、接口和测试方法。做不到这四点就只是散落在聊天记录里的段子。2. 让技能变成可交换的资产统一数据格式的设计2.1 核心字段设计把技能当成一个API接口来做统一格式是整套方案的地基。我参考了Claude Skills的目录结构、Cursor rules的Markdown规范、以及OpenAPI的字段设计思路最后定下来一套YAML格式。选YAML而不是JSON是因为可读性好、支持注释、Git diff对比时干净。一个标准技能包包含这些字段字段类型作用说明namestring唯一标识推荐“团队.分类.技能名”的命名空间versionstring语义化版本行为有较大变化时升主版本号descriptionstring技能概述决定Agent在什么场景下会命中这个技能tagsstring[]标签用于搜索和分类triggerstring[]触发条件什么样的用户输入应该激活技能model_hintobject模型要求提示该技能需要的最小上下文窗口variablesobject变量声明允许用户按项目传入参数stepsarray技能步骤多步执行流程forbiddenstring[]禁止事项明确告诉Agent不要做什么examplesarray示例输入输出样例帮助Agent理解预期为什么要设计成这个样子我当时的考虑很简单技能本质上是要被“另一种程序”调用的既然它是被调用的就得有契约。契约清楚了适配器才能翻译测试才能自动跑多人协作才不会鸡同鸭讲。一个没有接口定义的技能就像没有函数签名的函数谁拿去用都得猜。2.2 多步流程与上下文变量区分“真技能”和“一段提示词”我见过很多人把技能写成一整段华丽的提示词实际上那只能算是个强一点的prompt。真正的技能需要有步骤、有变量、有条件分支否则换个输入场景就崩。拿“修复TypeError”这个技能举例。如果只是一段提示词“帮我分析这个TypeScript报错”模型可能会回答但回答质量完全随缘。拆成步骤之后执行链路就清晰了collect_context提取报错信息中的文件路径、行号、堆栈及相关变量名。locate_cause根据收集到的上下文定位类型声明与实际赋值不一致的位置。propose_fix输出最少改动的修复代码并注明修改前后的类型变化。verify列出验证方法比如运行类型检查或相关单测命令。每个步骤都是一个小的指令单元加起来才是一个完整的技能。变量也很关键比如技能里声明include_tests变量默认false用户可以在特定项目里传true让Agent额外生成测试用例。这样同一个技能在不同项目里就有不同的表现而不是每个项目各写一份。上下文窗口也值得在model_hint里标注。有些技能需要读长堆栈如果某个工具的上下文窗口只有8K技能里的步骤就很容易被截掉看起来像“失灵”了。先把这个信息写清楚后面排查兼容性问题时能省一半时间。2.3 和主流工具格式的映射统一格式是源原生格式是产物有了统一格式之后剩下的事情就是做“翻译”。很多工具的原生技能格式都有对应的能力边界适配器要做的是把标准YAML“编译”成目标工具能理解的形态而不是简单地把字段堆砌在一起。工具原生技能/指令形态常用位置统一层处理方式Cursor.mdc规则文件.cursor/rules/将标准YAML翻译为Markdown规则文本Trae自定义指令用户配置目录转换为Trae指令文件Claude CodeSKILL.md技能目录.claude/skills/从YAML的steps生成SKILL.mdGitHub Copilotcustom instructions.github/copilot-instructions.md合并为一段指令文本Continuecommands模板~/.continue/commands/翻译为command模板这里有个设计上的重要决定统一格式永远作为唯一事实来源原生格式只是生成物。你在Skills Manager里修改一次技能它会重新生成所有工具对应的原生文件反过来在某个工具里直接改了原生文件适配器也可以把它反解析回统一格式。这种“源文件构建产物”的模式做前端的人应该很熟——就像scss编译成css一样你永远只维护源文件生成的产物可以随时重建。如果哪边不同步了重新编译一次就对齐了。2.4 一个可落地的示例技能包下面是我实际在用的一个技能包用来处理TypeScript类型错误。你可以直接拿去当模板name: team-ops.typeerror-fix version: 1.2.0 description: 当用户粘贴 TypeScript/JavaScript 的 TypeError 堆栈或代码片段时定位类型不匹配的根因并生成可运行的修复代码。 tags: [debug, typescript, javascript, typeerror] trigger: - 出现 TypeError - 粘贴报错堆栈 - 提到 类型不匹配 model_hint: min_context: 16000 variables: include_tests: default: false description: 是否同时输出测试用例 steps: - name: collect_context action: 提取报错信息中的文件路径、行号、堆栈信息及相关变量名 - name: locate_cause action: 根据收集到的上下文定位类型声明与实际赋值不一致的位置 - name: propose_fix action: 输出最少改动的修复代码并注明修改前后的类型变化 - name: verify action: 列出验证方法例如运行类型检查或相关单测命令 forbidden: - 不提供无中生有的堆栈信息 - 不一次性重写整个模块 examples: - input: TypeError: Cannot read property map of undefined at line 42 output: 根因line 42 的 data 可能来自异步接口在赋值前为 undefined。修复在调用 map 前做空值兜底。验证运行 npm run typecheck。这个技能包在Skills Manager里一键导出后Trae那边会生成一条自定义指令Cursor那边会生成一个.mdc文件Claude Code那边会生成一个SKILL.md目录。改动一次全线同步。3. 桌面中枢的工程实现本地优先、跨平台与插件化3.1 为什么不做纯Web方案而要做桌面应用一开始也有人建议我做Web版塞个账号系统技能存云端打开浏览器就能用。我想了很久还是决定做桌面端核心原因有三条。第一是隐私。技能文件里往往包含团队内部的项目代码片段、目录结构、常见坑位说明这些东西很多不能往外传。如果做成云服务用户第一反应就是“我的技能会不会被拿去训练模型”。放在本地文件就是一个个YAML目录结构清清楚楚别人不拷贝就不会泄露。第二是打通本地工具链。Skills Manager的优势在于能直接读写各工具的配置文件目录比如.cursor/rules/、Trae的用户配置目录、.claude/skills/。Web应用要做到这点要么用浏览器文件系统API绕路要么让用户手动上传下载体验差一大截。桌面应用没有这个限制读本地目录跟读自己家抽屉一样顺畅。第三是离线可用。我自己的使用场景很多是蹲在火车上写代码、做技能调试没有网络也得能干活。桌面端天然满足。技术选型我最后落在Tauri(U002B)React。Tauri的安装包比Electron小一个量级内存占用也低不少后端用Rust处理文件系统和目录同步性能和安全性都够用。如果你要复刻这个方案我不建议一上来就上Electron那套东西越到后期打包和性能优化越折腾。3.2 跨平台真正难的不是UI是路径与目录约定写跨平台应用UI适配反而是最简单的真正的坑在“同一个工具在不同系统上的配置文件路径不一样”。拿Cursor举例macOS~/Library/Application Support/Cursor/User/Windows%APPDATA%\Cursor\User\Linux~/.config/Cursor/User/Trae和Claude Code也有各自的路径差异。如果你在代码里写死路径Windows用户装完直接白屏。我的做法是建一个PathManager适配层每个工具对应一个路径发现器按平台返回候选路径列表再逐个探测存在性。同时支持用户手动指定目录毕竟总有人把配置目录挪到别的地方。interface PathResolver { toolName: string; getCandidatePaths(platform: NodeJS.Platform): string[]; detectExistingPath(): string | null; }这层设计起来不复杂但非常影响体验。第一次打开Skills Manager时它能自动识别你机器上装了哪些工具、各工具的配置目录在哪比手动一个一个找高效得多。开发者如果有意向也可以把这套路径发现逻辑单独抽出来做成npm包接入新工具时直接复用。3.3 插件化适配器机制接一个新工具只需要写一个adapter统一层做扎实之后接入新工具的工作量就小很多了。我给每个AI编程工具写一个适配器适配器实现五个方法interface Adapter { id: string; // 例如 trae、cursor discover(): ToolContext; readSkills(): SkillFile[]; exportSkill(skill: UnifiedSkill): SkillFile; importSkill(file: SkillFile): UnifiedSkill; verify(toolDir: string): boolean; }discover()探测工具的安装目录和配置目录。readSkills()把工具原生技能读出来转成统一格式。exportSkill()把统一格式的技能导出成工具的原生形态。importSkill()把原生技能反向解析成统一格式。verify()检查导出结果能否被目标工具正常识别。实际维护54个适配器时我发现大部分工具都走“通用模板”就能搞定真正需要定制逻辑的其实只有头部那几个比如Cursor的mdc规则语法、Claude Code的目录结构、Trae的指令格式。剩下的小工具大多数是“读一个文本文件、写一个文本文件”的区别写一个基础适配器类继承一下就能用。这就引出一个结论统一格式是中枢适配器是翻译官。想接入新工具不需要动核心逻辑写一个适配器、跑一遍回归测试就行。我最快一次接入新工具只用了半天其中大部分时间在等模型跑测试。3.4 数据安全、版本管理与同步技能资产不能锁在工具私有目录里技能是有沉淀价值的资产所以存储方式上我坚持两个原则纯文本、Git友好。纯文本意味着什么意味着你不需要打开某个专属数据库才能读取和备份。YAML、Markdown这些文本文件可以被grep搜索、被diff对比、被纳入Git版本管理。对你来说技能库就是一个普通文件夹双击就能打开。版本管理上我采用语义化版本约定主版本技能行为或输出格式有重大变化时升级比如把“三步流程”改成“五步流程”。次版本优化提示词、增加示例、补充触发条件。补丁版本修正错别字、调整措辞。每次通过Skills Manager修改技能都会生成一条记录写明改动内容和原因。团队成员如果各自有技能库也可以把整个技能库推到一个Git仓库里共享。我没有做云端同步服务因为技能数据太敏感了放Git仓库或内网共享目录让用户自己控制反而最稳妥。4. 实测兼容性同一个技能换一个Agent就“不听话”的排查链路4.1 案例一个“日志反推根因”技能在Trae与Copilot上的表现差异理论设计再完美真刀真枪跑起来还是会出幺蛾子。我最开始拿一个“日志反推根因”技能做跨工具验证同一份YAML导出到Trae和Copilot结果差异很大。Trae那边表现很好粘贴一段运行时日志Agent能按步骤输出“错误定位→原因分析→修复建议”三段。Copilot那边就直接缩水了只输出了一段“原因分析”修复建议和验证步骤全没了而且回复末尾看起来像是被硬生生切掉的。第一反应是“Copilot模型不行”但冷静下来想更可能是技能加载过程中出了问题。于是我走了一条完整的排查链路这个链路也成了后续适配器调试的标配流程。4.2 从“半截输出”倒查五步排查链路排查思路是按嫌疑程度从大到小排的第一步查上下文截断。技能文件本身包含了steps、examples、forbidden如果导出时内容过多或者模型上下文窗口有限后半段内容会被丢进“历史遗忘区”。我怎么确认的呢把同一份技能手动粘贴到Copilot的对话框里让它复述自己的技能内容结果它只能复述前两个步骤。这说明导出的技能文本没有被完整加载不是模型能力问题是内容超出了实际生效范围。第二步查指令前缀解析差异。不同Agent对“技能指令”的解析逻辑不一样。Trae会把自定义指令当作高优先级系统提示来处理Copilot则更像是在主提示之后拼接一段辅助文本。技能里的steps在Trae里被当作行动准则在Copilot里可能被当成对话历史的一部分模型甚至会误以为那是用户输入的内容。第三步查模型版本差异。不同模型对长指令的服从度区别很大。同样一段技能文本Claude 3.5 Sonnet能严格执行步骤顺序而有些模型会在第三步开始“自由发挥”。这个只能通过切换模型实测来确认。第四步查工具内置安全策略。Copilot这类工具对代码生成有额外的安全过滤层如果技能中的examples包含了可被判定为“不安全”的代码片段过滤机制会直接把整个后半段丢弃。这里的“不安全”有可能是误判比如示例代码里有一个看起来像注入攻击的模板字符串。第五步查工具内置规则优先级。很多工具自带默认规则比如“不要输出无效代码”“永远不要泄露密钥”。自定义技能里如果出现了和内置规则冲突的表述内置规则会压过技能指令。这时候不是技能写错了而是它没有遵守工具的底层约束。我用这个五步链路排查了两个晚上最终定位到Copilot的问题是上下文截断加上指令前缀解析差异。解决方案是给Copilot适配器做两件事裁掉技能里冗余的examples、把trigger和steps合并成一段更有引导性的系统指令文本。这里也提醒你遇到技能在某个工具上失效先别骂模型先按“截断→解析→模型→安全策略→内置规则”的顺序排查。大部分问题出在前两步。4.3 三类高频兼容性问题及对策我把实测中遇到的兼容性问题归成三类做了一个处置表问题表现可能原因检测手段处理对策输出突然变短缺少后置步骤技能文件过大或上下文窗口不足让Agent复述技能内容对比是否完整精简steps把长技能拆成多个子技能技能根本没触发Agent像没看到一样指令前缀解析差异技能成了普通提示在对话中询问“你的系统指令包含哪些步骤”调整适配器改写为更明确的系统指令部分内容被过滤比如样例代码消失工具内置安全策略误判对比输入输出中被删除的段落将示例改成脱敏占位符或放到独立文件这三种问题在适配器开发过程中反复出现。我最终在Skills Manager里加了一个“兼容性检测”按钮它会调用每个已接入工具的verify()方法把导出的技能文件在目标工具配置目录里做一轮预检跑完给出风险提示。虽然不能完全模拟真实对话但至少能把“技能没被加载”这类低级问题挡在门外。4.4 回归验证建立一份“技能冒烟测试集”适配器改完之后怎么确定没改坏别的东西我的做法是建立一套“技能冒烟测试集”。每个技能对应几个固定的验收问题比如TypeError技能就对应“粘贴一段包含undefined.map的报错”测试时把同一段输入分别发给每个已接入的工具记录结果工具验收问题是否命中技能输出格式耗时Trae粘贴TypeError堆栈是根因修复验证8sCursor粘贴TypeError堆栈是根因修复验证7sCopilot粘贴TypeError堆栈否被截断只有根因12s每次更新适配器、修改统一格式、或某个工具升级了基础模型之后我都会把冒烟测试集重跑一遍。这个工作看起来很枯燥但能省掉大量被用户在群里吐槽的时间。如果工具本身支持CLI调用还可以写成脚本自动轮询但是第一版建议先手工过一遍先把“预期结果”基线建立起来后面才知道哪些才是“回归异常”。5. 从“存得住”到“用得好”技能包的生产与质量管理5.1 命名、版本和描述是技能资产的“门面”很多人把注意力放在提示词本身忽略了name、version、description这三个字段。恰恰这三个字段决定了技能能否被Agent和队友快速理解。命名上我建议采用“团队.分类.技能名”的三段式。比如team-ops.typeerror-fix一眼就能看出这是运维团队维护的、处理TypeError问题的技能。避免用error-handler这种宽泛的名字没法搜也没法排序。描述这个字段最重要。Agent的意图识别阶段会先把用户输入和你写的description做语义匹配描述写得好不好直接决定技能会不会被触发。举个例子你写“分析错误”Agent可能在任何涉及“错误”的场景都尝试用这个技能但你写“当用户粘贴TypeScript编译错误或运行时堆栈时定位类型不匹配根因并给出修复建议”Agent就能非常精确地判断适用场景。描述越具体技能命中率越高。版本管理上我强烈建议用语义化版本并且每次升级都写CHANGELOG。你有三五个技能时可能觉得无所谓等技能库积到几十个没有版本记录就完全没法维护。5.2 写技能时的常见败笔范围太宽、步骤无序、没有默认值我审过团队里不少人写的技能问题高度集中在这几个范围过宽一个技能想同时覆盖代码审查、性能优化、安全漏洞修复。结果是Agent每次触发都做一遍所有事输出又长又乱哪个任务都没做好。正确做法是一个技能只解决一个高内聚的任务拆成多个技能单独维护。步骤顺序含糊写了“先分析再做决定”但没定义分析需要的输入。比如真正有效的顺序是“先提取上下文再定位问题最后给修复”每一步都要写清楚输入是什么、输出是什么。变量没有默认值技能里声明了include_tests变量但没给默认值。用户调用时如果忘了填Agent就开始猜猜错了整段流程就飘了。所有变量都要有明确的默认值并提供填写说明。样例太少只有一个正例没有反例。Agent只能模仿样例格式遇到边界情况就懵。每个技能至少配2到3组输入输出示例覆盖正常情况和边界情况。当然还有提示词里最经典的毛病过度堆砌形容词。什么“极其优秀”“深思熟虑”“全面分析”……这些词对模型行为几乎没有约束力反而占用了宝贵的上下文空间。换成具体指令比如“输出时先给结论再给论证”“每一条修复建议都要附上涉及文件路径”效果立竿见影。5.3 回归测试集技能也得有验收标准技能写出来不是给自己看的是要在不同工具里反复跑的。不给技能设立验收标准你就永远不知道它到底是“能用”还是“碰巧跑通一次”。我给每个技能建立一张验收表字段包括验收问题、期望输出、实际结果、命中率、备注。比如一个代码审查技能会准备三段有刻意缺陷的代码分别对应逻辑错误、类型错误、性能问题期望输出必须包含“问题定位”“严重级别”“修复建议”三个部分。每次跑完如果哪个工具缺了某一部分就回到第4章的排查链路里去定位。这个习惯养成了之后技能质量会有质的飞跃因为每次修改都能立刻量化反馈。改描述、改步骤、加示例效果是变好还是变差跑一轮测试集就清楚了。你不需要靠感觉判断数据会告诉你答案。5.4 团队消费方式中心仓库加按需分发技能资产数量多了之后分发就成了核心问题。我目前采用的模式是“中心仓库加按需分发”团队有一个私有Git仓库目录结构按技能分类组织。每个技能在合并之前要走一次code review——别笑技能也要review而且比代码review更该严格因为模型会严格按照技能生成代码一个错误约束会被放大到每一次生成里。成员通过Skills Manager从仓库拉取技能列表按需勾选要激活的技能一键导入到自己的工具配置里。技能更新时仓库里的版本号变了客户端会提示哪些技能有新版本成员选择是否同步。这套流程跑顺之后团队里就不会出现“A在用自己的旧版技能B在用新版技能”的割裂情况。技能的版本、归属、生效范围全都有据可查。6. 如果你也想上手我的三个建议和一点体感6.1 建议一从小处开始先管好一个工具我几乎是这么走过来的一开始只用Skills Manager统一管理自己手头的Trae和Cursor先收编五个最高频的技能代码审查、日志反推根因、单测生成、依赖迁移、文档更新。等这五个技能跑顺了再慢慢往里加。不要一开始就追求把54个工具全部铺开管理那样大概率会让你的技能库变成一团乱麻。6.2 建议二技能描述多花十分钟后面省十小时这个投入产出比是最划算的。写技能的时候先写清楚描述和触发条件再用步骤去约束行为最后补示例和禁止事项。描述模糊的技能你会发现它经常在你不需要的时候出现真正需要的时候又“装死”。6.3 建议三不要迷信工具数量真正高频的永远只有那几款适配器收到54个之后我自己最常用的其实还是三五个主力工具。统一管理的价值不在于让所有工具都拥有一模一样的技能而在于让你在主力工具之间切换时技能行为保持一致不用为每个工具单独维护一套副本。对大多数开发者来说管好两三个工具的技能一致性已经能带来巨大的效率提升。最后说点个人体感。用Skills Manager管理技能几个月最大的变化不是“技能变多了”而是我敢改技能了。以前改一个技能会担心改坏了不知道什么时候触发、在哪个工具上失效现在改完技能跑一遍回归测试立刻就知道它在五个工具里的表现。AI编程工具一直在升级模型也一直换代但技能这件事的本质没有变它是一份会生长的经验资产给Agent保存好它比换一个更贵的模型划算多了。