Agent 方法论框架 superpowers 多平台装配对照:一套技能如何适配不同扩展模型

2026-07-29

本文基于 superpowers 仓库 commit 44c9b2d(2026-07-27)梳理,该项目仍在持续迭代,具体行为以仓库 https://github.com/obra/superpowers 最新代码与文档为准。

把四个平台目录摆在一起读完你会发现:技能正文一个字都没变,真正在平台之间变化的只有两件事——技能目录怎么被发现,以及会话开始时那段引导文本怎么塞进模型上下文。 所有你以为的”平台差异”,最后都收敛到这两个问题上;各家扩展模型的真实区别,也就藏在它们回答这两个问题的方式里。

superpowers 是 obra 维护的一套给编码 Agent 用的技能框架与开发方法论,MIT 许可证,仓库在 https://github.com/obra/superpowers 。它在同一个仓库里同时支持十来个宿主,这让它成了一个难得的横向切片样本:同样的内容,同样的维护者,同样的一天,你能看到不同扩展模型各自逼着它做了什么妥协。

一、三层结构,只有两层是平台相关的

仓库根目录下 skills/ 里是 14 个技能目录:brainstormingwriting-plansexecuting-planstest-driven-developmentsystematic-debuggingsubagent-driven-developmentdispatching-parallel-agentsrequesting-code-reviewreceiving-code-reviewverification-before-completionfinishing-a-development-branchusing-git-worktreesusing-superpowerswriting-skills

docs/porting-to-a-new-harness.md 的 Part 1 把整套结构讲得很直白,分三个组成部分:

  1. 技能本身,与宿主无关skills/ 下的内容是唯一事实源,所有宿主逐字共享。文档里明确写了这么做的前提:技能正文只描述动作——“invoke a skill""read a file""dispatch a subagent""create a todo”——永远不写具体工具名。
  2. 工具映射,按宿主一份。把动作词汇翻译成这个宿主真实的工具名,放在 skills/using-superpowers/references/<harness>-tools.md,或者内联在引导注入器里。
  3. 引导注入,按宿主一份。每次会话开始,把 skills/using-superpowers/SKILL.md 的完整内容包在 <EXTREMELY_IMPORTANT> 标签里送进模型上下文,后面追加工具映射。

文档对第三点的措辞很重:引导注入就是集成本身。没有它,技能文件是惰性的——躺在磁盘上,永远不会被调用。

这条主线值得单独记一下,因为它决定了后面所有对照怎么读。站内 /learn/agent-xieyi-shengtai-duibi//learn/mcp-extensions-kuangjia/ 讲的是通用方法论——协议生态怎么分层、扩展框架该怎么设计;这一篇不重复那些,而是把方法论落到一个你现在就能 clone 下来打开的仓库上,逐个字段核对它到底是怎么做的、代价是什么。

二、四份清单摆在一起:字段的有无就是扩展模型的差异

先看三份 plugin.json。三个文件的 nameversionauthorhomepagerepositorylicense 几乎一样,差异全在剩下的字段上。

.claude-plugin/plugin.json 只有 namedescriptionversionauthorhomepagerepositorylicensekeywords它既没有 skills 字段,也没有 hooks 字段。 移植文档解释了原因:Claude Code 按约定自动发现 skills/hooks/hooks.json,不需要声明。

.cursor-plugin/plugin.json 多了两行:"skills": "./skills/""hooks": "./hooks/hooks-cursor.json",另外多一个 displayName。这是”什么都要显式声明”的路子。

.codex-plugin/plugin.json 最长。它有 "skills": "./skills/",也有 "hooks": {}——一个空对象。移植文档里专门点名说:这个空对象是用来抑制 Codex 对 hooks/hooks.json 的自动发现的,因为 Codex 原生就暴露技能,不跑会话开始钩子。文档还加了一句警告:做钩子形态的移植时不要照抄 Codex 这份清单。除此之外,Codex 这份清单里还有一整个 interface 块:displayNameshortDescriptionlongDescriptiondeveloperNamecategorycapabilitiesdefaultPromptwebsiteURLprivacyPolicyURLtermsOfServiceURLbrandColorcomposerIconlogoscreenshots。这不是技术能力字段,是应用商店的陈列位——它说明这个平台的扩展模型是奔着分发市场去的。

.pi/extensions/superpowers.ts 根本不是清单。它是一个 TypeScript 模块,导出一个接收 ExtensionAPI 的函数。技能目录是代码算出来的:从 import.meta.url 拿到扩展目录,resolve(extensionDir, "../..") 得到包根,再拼出 skills,然后在 resources_discover 事件里返回 { skillPaths: [skillsDir] }。声明式配置在这里换成了命令式回调。

对照维度Claude CodeCursorCodexpi依据文件
入口形态JSON 清单JSON 清单JSON 清单TypeScript 模块见右侧四列各自路径
技能目录怎么被发现不声明,按约定发现 skills/"skills": "./skills/""skills": "./skills/"代码里 resolveskills 目录,经 resources_discover 返回 skillPaths.claude-plugin/plugin.json.cursor-plugin/plugin.json.codex-plugin/plugin.json.pi/extensions/superpowers.ts
钩子怎么声明不声明,按约定读 hooks/hooks.json指向 ./hooks/hooks-cursor.json"hooks": {} 空对象,用来关掉自动发现无钩子概念同上四份 + hooks/hooks.jsonhooks/hooks-cursor.json
引导注入的触发点SessionStart,matcherstartup|clear|compactsessionStart,version: 1 的扁平结构无会话开始钩子,靠原生技能发现context 事件,配合 session_startsession_compactagent_end 三个生命周期事件翻标志位hooks/hooks.jsonhooks/hooks-cursor.json.codex-plugin/plugin.json.pi/extensions/superpowers.ts
注入内容怎么交付脚本 stdout 打印 hookSpecificOutput.additionalContext脚本 stdout 打印 additional_context不适用直接往 messages 数组里插一条 role: "user" 的消息hooks/session-start.pi/extensions/superpowers.ts
工具映射放在哪无需适配文件无需适配文件references/codex-tools.mdpiToolMapping() 内联 + references/pi-tools.mdskills/using-superpowers/references/.pi/extensions/superpowers.ts
清单里的陈列元数据只有 displayName完整 interface 块,含 brandColorcomposerIcondefaultPrompt三份 plugin.json

对你意味着什么:如果你要给自家 Agent 设计扩展点,这张表就是一份需求清单的反推。约定优于配置省字段但把规则藏进了宿主实现;显式声明啰嗦但可读;空对象这种”用配置关掉默认行为”的设计,一定会在文档里留下一句警告。这些取舍在 /learn/claude-code-skills/ 里也能看到对应的一面。

三、引导注入的三种形态:打印 JSON、改消息数组、加载上下文文件

移植文档 Part 4 把集成方式分成三种结构形态,判据只有一个:引导文本怎么到模型面前。

形态 A,shell 钩子。 宿主在会话开始跑一条 shell 命令,读它的 stdout。hooks/session-start 就是这个脚本:读 SKILL.md,用 bash 参数替换做 JSON 转义,拼出 <EXTREMELY_IMPORTANT> 包裹的字符串,然后——这里是全篇最能说明问题的一段——按环境变量分三路输出不同的 JSON 字段:

设了 CURSOR_PLUGIN_ROOT 就打印 additional_context;设了 CLAUDE_PLUGIN_ROOT 且没设 COPILOT_CLI 就打印嵌套的 hookSpecificOutput.additionalContext;其余情况打印顶层 additionalContext。脚本注释里写清了原因:Claude Code 会同时读 additional_contexthookSpecificOutput 两个字段而不做去重,所以必须只输出当前平台真正消费的那一个,否则就是双份注入。同一段内容,三个平台三种信封——这是钩子这种扩展模型最典型的税。想深入钩子机制可以看 /learn/claude-code-hooks/

形态 B,进程内插件。 宿主加载一个 JS/TS 模块,你在回调里改消息数组。.pi/extensions/superpowers.ts 的做法是:模块作用域一个 injectBootstrap 标志,session_startsession_compact 置 true,agent_end 置 false;context 事件里先看标志、再用 messageContainsBootstrap 扫一遍现有消息里有没有 superpowers:using-superpowers bootstrap for pi 这个标记串,两道关都过了才注入。插入位置也不是简单塞到开头,而是用 firstNonCompactionSummaryIndex 跳过开头连续的 compactionSummary 消息再插进去——上下文被压缩过之后引导仍然在,并且位置正确。

.opencode/plugins/superpowers.js 是同一形态的另一种写法:config 回调把技能目录 push 进 config.skills.paths(注释说明这样就不用符号链接了),experimental.chat.messages.transform 把引导 unshift 到第一条用户消息的 parts 最前面,去重靠检查文本里有没有 EXTREMELY_IMPORTANT

两者的差别在移植文档的 Appendix B 里被点名:OpenCode 的回调每步触发,pi 的每轮触发,把一家的去重策略照抄到另一家会直接坏掉。这是同一形态内部的隐性差异,比形态之间的差异更难发现。

形态 C,上下文文件。 宿主既没有 shell 钩子也没有代码插件,只会加载一个上下文文件。gemini-extension.json"contextFileName": "GEMINI.md",而仓库根的 GEMINI.md 全文只有两行 @-include:一行指向 ./skills/using-superpowers/SKILL.md,一行指向 ./skills/using-superpowers/references/gemini-tools.md。没有拼接、没有转义、没有去重逻辑——因为你根本没有代码可以跑。

还有一种介于清单与代码之间的写法:.kimi-plugin/plugin.json"sessionStart": { "skill": "using-superpowers" } 一个字段搞定注入,工具映射则是清单里一个叫 skillInstructions 的超长字符串。配置文件里塞散文,这是清单式扩展模型被推到极限时的样子。

四、工具映射:同一句”派个子代理”在各家落到不同工具

技能正文里写的是”dispatch a subagent”,各平台真正要调的东西完全不同,映射文件就是这个落差的翻译表。

pi 的映射写在 piToolMapping() 里,内容相当克制:pi 有原生技能但不暴露 Claude Code 的 Skill 工具,所以指令说的”调用某个技能”在这里是用 read 读对应的 SKILL.md,或者由人显式敲 /skill:name;pi 的内置工具是小写的 readwriteeditbash,外加可选的 grepfindls;pi 不自带标准的子代理工具,如果装了 pi-subagents 提供的 subagent 就用它,没有就在当前会话里做完,或者直说能力缺失——不许臆造 Task 调用;pi 也不自带任务清单工具,退路是计划文件或仓库里的 TODO.mdreferences/pi-tools.md 把同样的内容又写了一遍表格版,Appendix B 里因此有一条提醒:映射在两个地方,改要一起改。

OpenCode 的映射内联在插件 JS 里:创建待办用 todowrite,子代理模板落到 tasksubagent_type: "general",读文件 read,增删改文件统一走 apply_patch,跑命令 bash,搜索 grep/glob,抓网页 webfetch,技能则交给 OpenCode 自己的 skill 工具去列和加载。顺带一个值得注意的细节:.opencode/INSTALL.md 里给这条子代理映射多写了一个只读探索用的取值,插件 JS 内的映射串里并没有——同一份映射写在文档和代码两处,漂移就是这么产生的,这恰好是移植文档 Appendix B 里”映射在两个地方,改要一起改”那条的现场版本。

Kimi 那份 skillInstructions 更细,细到规定了交互形态:技能里说”问用户”就调 AskUserQuestion,给 1 个问题 2 到 4 个选项,推荐项排第一并在标签后缀 (Recommended);TodoWrite 换成 TodoList;子代理走 Agent 工具,实现和评审用 subagent_type: "coder",只读探索用 "explore",只读规划用 "plan",并且明确不许把 general-purposesubagent_type 传进去。

Codex 的映射文件开头是一条前置条件:子代理派发需要在 ~/.codex/config.toml 里打开 [features] multi_agent = true,才会有 spawn_agentwait_agentclose_agent。文件里还给了何时关闭评审子代理、何时保留实现子代理的具体规则。

Cursor 和 Copilot CLI 在移植文档的索引表里都标着”不需要适配文件”——它们的工具面与 Claude Code 兼容。

这一节的可迁移结论很简单:把动作词汇和工具名解耦,是让一套提示词活过平台更迭的唯一办法。 你自己写团队规则时也一样,规则里写”检索代码库”,不要写死某个工具名。

五、边界与代价:它放弃了什么,又明确不管什么

硬门槛卡在最前面。 移植文档 Part 2 写得毫不含糊:宿主必须能在每次会话开始自动注入文本,不需要人每次手动开启。如果唯一的办法是让人粘贴提示词、敲一条命令、开一个模式,这个宿主”无法被正确支持”,Part 3 的验收测试会失败,PR 会被关掉。文档说这是”移植不算真移植”的最常见原因。这意味着一大类只提供斜杠命令、不提供会话级注入点的工具,直接被排除在外——这是设计上主动放弃的覆盖面。

每加一个平台的边际成本是实打实的。 一个新宿主要配齐:清单或入口模块、引导注入器、工具映射、按宿主一份的测试目录(tests/hooks/tests/pi/tests/opencode/tests/kimi/tests/codex/ 等)、以及在 .version-bump.json 里登记版本字段——那个文件里现在列着七处需要同步版本号的位置。Codex 的分发还额外需要 scripts/sync-to-codex-plugin.shscripts/package-codex-plugin.sh 两个脚本,把不该进插件包的目录一条条排除掉。

有几件事它明确不管。 一是绝不改用户的文件:移植文档的第二条规则写着,引导、技能、映射都必须随宿主自己的安装机制交付,不许伸手去改用户的全局配置——安装机制真的带不动,那是要如实上报的限制,不是动手改用户配置的许可。二是绝不为适配某个宿主而修改技能正文,该改的永远是工具映射;仓库的 CLAUDE.md 把技能内容当成经过调校的行为塑形代码,为”兼容”而改写会被直接拒。三是能力缺失就降级:子代理、待办清单、联网抓取都属于可降级项,技能正文里已经写好了退路措辞。

方法论本身的代价也得说清楚。 README 描述的流程是:不直接写代码,先反问你到底要做什么,谈出规格,分段给你确认,再产出实现计划,然后进入子代理驱动的实现过程。这套流程对一个改错别字、调一行超时时间的任务是彻头彻尾的过度设计——你会被反问三轮才开始动手。它也会让 Agent 明显更啰嗦:引导文本每次会话都要占掉一块上下文,skills/using-superpowers/SKILL.md 全文加工具映射不是小数目。OpenCode 插件里那段注释就是这个成本的直接证据:选择注入用户消息而不是系统消息,原因之一正是系统消息每轮重复带来的 token 膨胀。这类账怎么算,可以对照 /learn/agent-kuangjia-token-xiaolv/ 一起看。

还有一层现实成本:仓库的 CLAUDE.md 开篇直接对 AI Agent 喊停,说绝大多数被关掉的 PR 都是没读规则的 Agent 提交的,并且要求提 PR 的人披露自己用的模型、宿主、宿主版本和所有已装插件,要求人类过一遍完整 diff,要求先把已有的开关 PR 都搜一遍,还要求所有 PR 提向 dev 分支而非 main。想给它加一个平台支持,门槛不在代码。

六、上手与避坑清单

一、以为装一次就全平台生效。 README 的安装小节和 .opencode/INSTALL.md 都专门写了一句:安装方式因宿主而异,你用几个宿主就要装几次。会踩是因为技能内容是共享的,给人一种”装在仓库里”的错觉,实际每个宿主的加载路径完全独立。避法:每装一个宿主就单独验一次,别推断。

二、形态 A 的 JSON 字段写错。 后果不是报错,是静默失败或者双份注入——Appendix B 把这条列在第二位。会踩是因为三个平台字段名极像:additional_contextadditionalContexthookSpecificOutput.additionalContext。避法:照 hooks/session-start 里那三个分支去对,先确认你的宿主消费哪一个,再确认它会不会像 Claude Code 那样重复读取。

三、钩子脚本带了 .sh 扩展名。 hooks/run-hook.cmd 的注释写明了原因:钩子脚本用无扩展名文件名(是 session-start 不是 session-start.sh),这样 Claude Code 在 Windows 上的自动检测——它会给任何含 .sh 的命令前面加 bash——才不会插一脚。避法:保持无扩展名,由 run-hook.cmd 这个 polyglot 包装器统一去找 bash。

四、Windows 上”装上了但没生效”。 run-hook.cmd 在找不到 bash 时的行为是静默 exit 0,注释里写的理由是”插件仍然可用,只是没有会话开始的上下文注入”。这个设计对用户友好,对排障不友好:你不会看到任何报错,只会觉得 Agent 好像没吃到那套方法论。避法:装完主动做一次冒烟检查,移植文档给的办法是新开一个会话直接问模型能不能描述自己的 superpowers;OpenCode 那边给的是 opencode run --print-logs "hello" 2>&1 | grep -i superpowers,注意 2>&1 不能省,日志走的是 stderr。

五、把一家的去重策略照抄到另一家。 前面说过,OpenCode 每步触发、pi 每轮触发。会踩是因为两段代码看起来在做同一件事,而回调频率这个关键前提不写在代码里。避法:先测清楚你的宿主回调多久触发一次,再决定用标志位还是扫消息内容。Appendix B 还有相邻的一条:两家的消息对象结构互不兼容,别把参考实现里的对象字面量搬过来。

六、新清单忘了登进 .version-bump.json 后果是这个平台的版本号发布即过期。会踩是因为版本号分散在七个文件里,新增的那个不会自动被发现。避法:加清单的同时改 .version-bump.json

七、想靠改技能正文来适配。 这条在文档里被反复强调,并且写进了不接受的 PR 类型。会踩是因为改正文看起来最快。避法:改映射,不改正文。

结尾:三个问题的自检

把这套东西用到你自己的 Agent 工程上,只需要回答三个问题:

  1. 你的宿主能不能在每次会话开始自动注入文本,不需要人手动触发?答不上来就先做一个唯一标记测试——塞一串没有意义的 token 进去,新开会话看它到底在不在上下文里。
  2. 你的技能/规则正文里,还有多少处写死了具体工具名?每一处都是一道未来的迁移债。
  3. 你的引导内容每次会话占多少上下文,值不值这个价?

想继续往下看,顺序建议是:先读 docs/porting-to-a-new-harness.md 的 Part 1 到 Part 4,把三层结构和三种形态搞清楚;再读 hooks/session-start 那段三分支的输出逻辑,它是全仓库信息密度最高的几十行;然后对着 .pi/extensions/superpowers.ts.opencode/plugins/superpowers.js 做一次同形态对读,体会回调频率差异带来的实现分歧;最后回头扫一遍 Appendix B 的踩坑清单,你会发现前面读到的每一处古怪写法,在那里都有对应的一行解释。

本文属于 superpowers 方法论专题(共 30 篇,含三篇与其它开源 Agent 项目的对照)。想看把资产铺满的另一种取向,见 ECC 开源 Agent 套件专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。