AI AgentAI 编程安全与供应链

怎么管理 Agent 技能的依赖和自动升级

第一次将他人已经写好的Agent技能集成到自己的项目中是一个相当爽的事情。不论是直接从GitHub上直接Copy/Paste一个SKILL.md文件,还是运行一个终端命令,在十几秒内就能把一个文件放到.claude/skills/或者.github/skills/目录中,马上可以在编辑器里直接使用。如果这个技能在集成进来以后再也不用改动,那么我们其实并不需要任何复杂的包管理工具。

更麻烦的是装进去两个月之后的持续维护:上游厂商更新了 SDK 的接口规范,或者公司的基础架构组补了一个安全审查技能的漏报,但你早就把这个技能复制到了十几个不同的业务仓库里。有的仓库在 .claude/ 下面,有的在 .github/ 下面,有的在 .cursor/ 下面。几个月过去,你发现十几个仓库里跑着好几个不同时期的版本,有的文件组员顺手改了两行,没人说得清哪个仓库用的是哪一版,也没人愿意挨个仓库去手动同步一遍。

第一天怎么把技能分发出去其实早就解决了,真正的难题在于装进去之后,怎么低成本、不出乱子地让各个仓库跟上上游的持续迭代。面对跨仓库的依赖同步,软件工程过去十几年最成熟的直观解法就是包管理器加上自动升级机器人。2026 年 11 月 3 日,GitHub 宣布旗下的自动依赖升级工具 Dependabot 正式支持微软推出的 Agent Package Manager。按照官方设想,只要在仓库根目录放一份 apm.yml 声明依赖,用 apm.lock.yaml 锁死版本,Dependabot 就会定时扫描上游变动,自动给你的仓库提 PR 升级技能版本。这套方案看着和我们熟悉的 npm 一模一样,但如果你真把管代码的这套流程原封不动搬过来管 Agent 技能,很快就会在好几个关键环节上卡壳。

管几个文本文件,到底难在哪里

很多读者第一次看到这段话可能会困惑:Agent 的技能不就是几个 Markdown 文本文件吗?JavaScript 和 Python 代码也是文本文件,为什么管理代码的套路直接搬到管理技能文本上就会水土不服?如果我们观察一下使用技能的实际场景,会立刻发现技能文件在项目里扮演的角色,和传统代码包有四个非常具体的不同。

首先,宿主工具没有类似代码里的模块导入机制,所以安装出来的文件往往需要直接提交到代码仓库里。前端项目对运行时来说有一个专门的 node_modules/ 目录,所以我们不用把下载的第三方代码提交到仓库上,只需要在版本库里面提交 package.json 和锁文件这两个账本就可以了。但是 Claude/Cursor/云端编程助手并不认识包管理器的缓存目录,也不会在你打开编辑器时自动运行安装命令,它们只会扫描 .claude/ 或 .github/ 等固定路径下的文件。而且团队里经常有人在网页上直接调用云端助手审代码,或者同事刚把仓库克隆下来就马上用编辑器打开,这种情况下大家也不会先跑去终端敲安装命令。为了确保每个人拉取代码之后都能立刻获得一致的上下文,微软官方推荐直接把安装展开后的目标目录提交到版本库。

第二个问题是,我们装的东西不只有提示词,而且文件落地即生效。今天的技能包除了给一段提示词以外,往往还附带一个 scripts/ 目录里的可执行脚本,自动改写客户端配置的 MCP 声明,还有塞在 .claude/settings.json 里的生命周期钩子。代码包下载下来存在硬盘里,只要你不用里面的函数,它就是死代码,留给审计的缓冲期很长。但 APM 的安全模型文档里也指出提示词供应链的特殊性:这类技能、指令和钩子文件一旦落到目标目录,正在监听文件系统的编辑器 Agent 就会立刻把它读进上下文或直接触发钩子,文件存在本身就是执行。

第三,自然语言没有传参接口,经常逼着使用者直接去改源码。我们用 axios 或 lodash 这类代码库,可以通过传参来定制超时时间或行为细节,没有人会去打开 node_modules/ 修改里面的库源码。但 SKILL.md 是一篇写给大模型看的自然语言说明书,没有预留函数参数。我们团队从外部装了一个通用的代码审查或数据库查询技能,用两天发现它老写出不符合内部规范的语句,最顺手的改法就是直接打开 .claude/skills/ 下的文件,在里面添上两行内部表名约定或框架禁令。一旦往里面塞了这两行团队私货,这份文件就同时混杂了外部公共依赖和内部私有规则,再也没法像黑盒代码包那样无脑替换了。

第四个不同是自然语言没有编译器和单元测试兜底。代码依赖升了一个小版本,GitHub Actions 跑一遍类型检查和单元测试,只要绿灯通过,基本就能说明接口没坏。可如果上游作者把 SKILL.md 里的三段提示词换了一种写法,它的 Markdown 语法永远是对的,项目里的业务代码单测也完全测不到 .claude/skills/ 目录下的文本。就算 CI 一路绿灯,合进去之后,新措辞和项目原有的系统提示词叠加在一起,第二天全组就可能发现 Agent 的输出风格全变了。

照搬 npm 加 Dependabot,会在哪三个地方翻车

理解了这四点以后,就能理解为什么这次落地的包管理和自动升级方案,直观地去照抄代码包管理器的做法会在这三个细节上翻车。代码包管理器的默认假设是仓库里只有账本,而且运行时可以通过编译和测试来验证,但这些都是对技能文件不成立的。

npm 与 APM 在 Dependabot 升级时的工作流差异

第一个翻车点是最容易踩到的,就是点了合并 PR,账本是升了,仓库里真正的技能文件却没升。传统代码包升级机器人提 PR 的时候只要改声明文件和锁文件。根据 dependabot-core 的实现记录,Dependabot 给 APM 技能提升级 PR 时,在后台调用的是 APM 的 lock 命令。按官方文档说明,apm lock 只负责拉取上游新版本并更新 apm.lock.yaml 里的版本号和校验哈希,明确跳过安全扫描,也不会把新版本的技能文件复制到 .github/、.claude/ 等宿主目录里。

这就造成了账本和实际文件的脱节。考虑到我们一般都会把展开后的 .github/skills/ 等目录提交到仓库,机器人提的 PR 就只会改动 apm.yml 和 apm.lock.yaml 这两个账本文件,不会改动仓库里真正的技能文本。如果你像合并前端依赖那样直接点击合并,主分支的锁文件虽然已经升到最新版,但编程助手每天读的仍是旧版文本。

要避免这种文件脱节,解决方法是按照官方实践在流水线里配置一道 apm audit 漂移检查门禁。这道检查会在临时目录里按新锁文件重新安装一遍技能,再和你仓库里的现有文件逐一比对。结果就是,由于 Dependabot 的 PR 只改了锁文件、没更新仓库里的技能文件,机器人提出来的每一个升级 PR 都会因为两边文件对不上而直接挂红灯,必须额外补跑一次安装才能合入。而如果为了省事不配这道门禁,就连微软官方仓库自己日常维护时,也曾在今年 6 月因为流水线配置跳过了漂移检查,导致主分支已提交的技能文件长期停留在旧版,比上游新源码少了一整个性能评审角色,而检查依然一路绿灯。

第二个坑是,只要你在配置里用到了挑子技能、改别名或配工具服务等进阶功能,自动升级就会直接抓瞎。很多开源技能库是个大合集,一个仓库里塞了二三十个技能;你往往只想挑其中两三个装进项目,或者想给引入的技能改个名字,免得和本地已有的同名技能冲突;有时候你还会在 apm.yml 里顺手声明项目要用的 MCP 工具服务。遇到这些情况,你就不能只写一行简单的仓库地址,而要在配置文件里展开写成带选项的多行配置块(比如指定挑哪几个子目录、改成什么新名字)。但翻开 GitHub 官方的 Dependabot 生态支持文档就会看到一长串限制:它目前只认识最简单的一行仓库地址写法。只要某个依赖展开写成了带选项的多行配置块,或者声明了 MCP 服务,又或者企业为了合规在配置里指定了内部私有源,解析器扫到这些条目就会静默跳过,既不报错,也不提 PR。你越是用到了包管理器的高级组合能力,自动升级就越指望不上。

第三个坑是安全漏洞库和内容安检都空着。很多组用 Dependabot 是因为它能第一时间在开源包里曝出安全漏洞时给你推 PR。但你去看 dependabot-core 并入 APM 支持的那次 commit,里面有个特别奇怪的改动:作者在前面的 review 里已经实现了对安全公告的版本解析和降级防护,在最终合并前把这段代码整个删掉了。提交里也直说 GitHub 的安全公告数据库压根就没收录 APM 包,所以触发安全更新的判断永远是 false,全是 dead code。

但更现实、更隐蔽的威胁其实藏在别的地方。2026 年出现了一个叫 Glassworm 的攻击,攻击者用肉眼完全看不见的 Unicode 变体选择符和双向控制符,把隐藏指令藏进共享规则文件里。人类在 GitHub 网页上看 PR Diff 以为只是几行普通格式微调,大模型的分词器却把隐藏指令原样读进去执行。APM 本身其实在 apm install 阶段设了一道预部署扫描来拦这种隐藏字符,偏偏 Dependabot 更新锁文件时调用的 apm lock 跳过了这道扫描。如果你单靠看眼 Dependabot 的 PR 绿灯就点合并,安全防线形同虚设。

同样在微软和 GitHub 内部,为什么同时造了两套工具

到这里你也许好奇,既然搞了个中央 apm.yml + apm.lock.yaml 和实际提交的文件总是打架,有没有更轻量级的思路呢?有意思的是,实际上在今年微软/GitHub 内部同时出现了两种设计哲学的工具。

2026 年 4 月,GitHub 命令行团队发布了一个原生的子命令 gh skill。它没有采用独立的清单和锁文件,而是采取了一个单文件自包含的方案:安装一个技能并指定固定版本时,它会把上游仓库的地址、固定的版本号和当前技能目录的目录树哈希值都写到下载的 SKILL.md 文件的顶部。我们可以把这两套工具放在一起做个直观对比,看看它们分别是怎么记账的:

对比维度 单文件内嵌锁(gh skill) 构建期依赖编译器(apm + Dependabot)
核心账本位置 直接写进每个 SKILL.md 顶部的 YAML 头信息 仓库根目录独立的 apm.yml 与 apm.lock.yaml
账本与实际文件是否会脱节 不会,文件自己就带着锁,改文件与改锁是同一个动作 容易脱节,Dependabot 只改锁文件,不改已入库的技能文件
跨仓库复制粘贴溯源 复制单文件到新仓库依然保留上游出处与哈希校验 只复制生成后的 .claude/ 文件会丢失 apm.lock.yaml 溯源
多宿主编译与传递依赖 仅安装单个技能,不解析传递依赖 支持跨仓库子路径拼装、传递依赖解析与多客户端目录分发
适用团队规模 个人开发者,或只需引入几个外部独立技能的中小项目 多编辑器混用、有内部规范库与 CI 策略门禁的大型工程团队

相比之下,我们就能一眼看出这两套工具的分工。gh skill 把每一个技能文件都当作一个自包含的交付物,锁的信息直接写在文件内部。因此哪怕一个同事直接把这份 SKILL.md 文件复制到另一个仓库里,他执行更新命令的时候也会根据文件头部的哈希值来检查上游是否有更新,从而从根本上避免了锁文件升级了但实际文件没升级的双态脑裂。APM 的作者 Daniel Meppiel 也在社区 战略定位讨论 中把 APM 比作智能体领域的编排治理层,它的核心价值在于帮大型团队从不同的代码平台抽取出规则片段、解析多层传递依赖、再统一编译到不同的编辑器目录中去。

真正能跑通的技能维护方案

回到最初的问题:如果我们今天想在团队的多个仓库中有效利用外部和跨团队的 Agent 技能,并且想要跟上上游的持续迭代而不陷入上述陷阱,应该怎么办呢?结合现有工具的实际限制,我们可以立刻采取以下三条工程化策略。

Agent 技能持续同步的分层治理方案

第一、根据项目规模来分流工具使用,不是所有情况都要用重型包管理器。如果你的项目只是想加入几个外部技能(比如某个前端框架或者云服务的官方技能),成员日常也只在一两种工具上开发,直接用 gh skill install --pin 把技能装进仓库就好。锁的信息直接写在 SKILL.md 头部,需要升级的时候运行更新命令,代码审查里直接看到的是技能的正文变更,不用额外维护独立的锁文件和工作树同步。

第二条是,如果团队真的是多编辑器混用、跨仓库组合规则的重度场景,决定用APM+Dependabot,那一定要在自动化流水线里把最后一步补上。不能指望升级机器人改完 apm.lock.yaml 就万事大吉。要在仓库的流水线里配一个伴随任务:只要它检测到机器人发起修改依赖清单或锁文件的请求,流水线就要在分支上自动执行 apm install,把新版本技能文本真的刷到 .github/、.claude/ 等目录并追加提交到该分支上,紧接着强制跑一遍 apm audit --ci 门禁。这样你在审阅合并请求时不仅能触发内置隐藏字符安检,还能在文件对比里亲眼看到上游改了哪几句提示词,避免对着一串哈希值盲目点合并。

第三条是,严守公私上下文的物理隔离,把外部技能当只读黑盒。我们之前提到过,技能难升级的一个重要原因是大家习惯于直接打开安装好的 SKILL.md,在里面塞入团队自己的业务规则。要想让技能能享受自动化升级,就一定要有一条明确的规定:所有由工具安装进来的外部技能文件一律视为只读产物,禁止手动修改。对于第三方框架文档、通用代码规范这类无状态的公共能力,交给 apm.yml 或 gh skill 自动跟进上游;而团队内部特有的数据库表名、审批流约定和架构例外,则单独写在项目自己的 CLAUDE.md 或一个独立的旁路规则文件里,通过宿主的分层加载机制叠加生效。这样,把归上游管的公共机制和归自己管的私有上下文拆开在两个文件里,上游更新就不会冲掉你的本地规则,自动升级才能真正跑得通。

鸭哥每日手记

日更的深度AI新闻和分析