superpowers-zh 1.7.12 → 1.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +2 -2
  5. package/.opencode/INSTALL.md +82 -14
  6. package/.opencode/plugins/superpowers.js +319 -31
  7. package/GEMINI.md +3 -2
  8. package/README.md +50 -18
  9. package/README.zh-Hant.md +48 -19
  10. package/RELEASE-NOTES.zh.md +108 -0
  11. package/assets/sponsors/logo-fluxionai-icon.png +0 -0
  12. package/assets/sponsors/logo-volcengine-icon.png +0 -0
  13. package/assets/sponsors/volcengine.png +0 -0
  14. package/bin/superpowers-zh.js +21 -2
  15. package/docs/README.aider.md +2 -2
  16. package/docs/README.claw.md +1 -1
  17. package/docs/README.cline.md +3 -3
  18. package/docs/README.codearts.md +1 -1
  19. package/docs/README.codebuddy.md +1 -1
  20. package/docs/README.codex.md +26 -0
  21. package/docs/README.crush.md +1 -1
  22. package/docs/README.deepseek-harness.md +1 -1
  23. package/docs/README.deerflow.md +1 -1
  24. package/docs/README.hermes.md +1 -1
  25. package/docs/README.kilocode.md +3 -3
  26. package/docs/README.kiro.md +1 -1
  27. package/docs/README.opencode.md +128 -19
  28. package/docs/README.qoder.md +1 -1
  29. package/docs/README.qwen.md +1 -1
  30. package/docs/README.trae-cn.md +62 -0
  31. package/docs/README.vscode.md +1 -1
  32. package/docs/README.zcode.md +2 -2
  33. package/gemini-extension.json +1 -1
  34. package/hooks/session-start +6 -2
  35. package/index.js +9 -0
  36. package/package.json +2 -1
  37. package/skills/brainstorming/SKILL.md +19 -3
  38. package/skills/brainstorming/visual-companion.md +36 -33
  39. package/skills/diagnosing-superpowers/SKILL.md +102 -0
  40. package/skills/diagnosing-superpowers/prompts/analyst-common.md +32 -0
  41. package/skills/diagnosing-superpowers/prompts/cost-and-time.md +20 -0
  42. package/skills/diagnosing-superpowers/prompts/plan-adherence.md +21 -0
  43. package/skills/diagnosing-superpowers/prompts/quality-evidence.md +20 -0
  44. package/skills/diagnosing-superpowers/prompts/repeated-work.md +24 -0
  45. package/skills/diagnosing-superpowers/prompts/request-conflicts.md +14 -0
  46. package/skills/diagnosing-superpowers/prompts/scrub-audit.md +26 -0
  47. package/skills/diagnosing-superpowers/prompts/scrub.md +22 -0
  48. package/skills/diagnosing-superpowers/prompts/similar-session.md +34 -0
  49. package/skills/diagnosing-superpowers/prompts/skill-timeline.md +23 -0
  50. package/skills/diagnosing-superpowers/prompts/stumbles.md +20 -0
  51. package/skills/diagnosing-superpowers/references/context-safety.md +20 -0
  52. package/skills/diagnosing-superpowers/references/github-issues.md +46 -0
  53. package/skills/diagnosing-superpowers/references/redaction-policy.md +29 -0
  54. package/skills/diagnosing-superpowers/references/session-discovery.md +23 -0
  55. package/skills/diagnosing-superpowers/templates/bundle-README.md +65 -0
  56. package/skills/diagnosing-superpowers/templates/case.md +61 -0
  57. package/skills/diagnosing-superpowers/templates/issue.md +48 -0
  58. package/skills/diagnosing-superpowers/templates/report.md +78 -0
  59. package/skills/executing-plans/SKILL.md +176 -125
  60. package/skills/executing-plans/scripts/task-done +52 -0
  61. package/skills/executing-plans/scripts/task-start +28 -0
  62. package/skills/mcp-builder/SKILL.md +1 -1
  63. package/skills/requesting-code-review/SKILL.md +1 -1
  64. package/skills/requesting-code-review/code-reviewer.md +8 -0
  65. package/skills/subagent-driven-development/SKILL.md +17 -17
  66. package/skills/subagent-driven-development/re-review-prompt.md +1 -1
  67. package/skills/subagent-driven-development/scripts/review-package +8 -1
  68. package/skills/subagent-driven-development/scripts/sdd-workspace +44 -2
  69. package/skills/subagent-driven-development/scripts/task-brief +3 -1
  70. package/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
  71. package/skills/systematic-debugging/root-cause-tracing.md +1 -1
  72. package/skills/test-driven-development/SKILL.md +2 -0
  73. package/skills/using-superpowers/SKILL.md +19 -14
  74. package/skills/using-superpowers/references/claude-code-tools.md +11 -0
  75. package/skills/using-superpowers/references/muse-tools.md +35 -0
  76. package/skills/writing-plans/SKILL.md +34 -27
  77. package/skills/writing-skills/SKILL.md +4 -2
  78. package/skills/writing-plans/plan-document-reviewer-prompt.md +0 -49
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superpowers-zh",
3
- "description": "AI 编程超能力中文增强版:20 个 skills,支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
3
+ "description": "AI 编程超能力中文增强版:21 个 skills,支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
4
4
  "owner": {
5
5
  "name": "jnMetaCode",
6
6
  "url": "https://github.com/jnMetaCode"
@@ -8,8 +8,8 @@
8
8
  "plugins": [
9
9
  {
10
10
  "name": "superpowers-zh",
11
- "description": "AI 编程超能力中文增强版:20 个 skills(14 翻译 + 4 中国原创 + 2 上游历史保留),支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
12
- "version": "1.7.12",
11
+ "description": "AI 编程超能力中文增强版:21 个 skills(15 翻译 + 4 中国原创 + 2 上游历史保留),支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
12
+ "version": "1.7.14",
13
13
  "source": "./",
14
14
  "author": {
15
15
  "name": "jnMetaCode",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "superpowers-zh",
3
- "description": "AI 编程超能力中文增强版:20 个 skills(14 翻译 + 4 中国原创 + 2 上游历史保留),支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
4
- "version": "1.7.12",
3
+ "description": "AI 编程超能力中文增强版:21 个 skills(15 翻译 + 4 中国原创 + 2 上游历史保留),支持 Claude Code / Hermes Agent / Cursor / Claw Code / Qoder 等 26 款工具",
4
+ "version": "1.7.14",
5
5
  "author": {
6
6
  "name": "jnMetaCode",
7
7
  "url": "https://github.com/jnMetaCode"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superpowers-zh",
3
- "version": "1.7.12",
3
+ "version": "1.7.14",
4
4
  "description": "AI 编程超能力中文增强版:头脑风暴、subagent 驱动开发、规划、TDD、调试、代码审查、收尾工作流,附 4 个中文 skills 沉淀。",
5
5
  "author": {
6
6
  "name": "jnMetaCode",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "superpowers-zh",
3
3
  "displayName": "Superpowers 中文版",
4
- "description": "AI 编程超能力中文增强版:20 个 skills(14 翻译 + 4 中国原创 + 2 上游历史保留),支持 Cursor / Claude Code / Hermes Agent / Claw Code / Qoder 等 26 款工具",
5
- "version": "1.7.12",
4
+ "description": "AI 编程超能力中文增强版:21 个 skills(15 翻译 + 4 中国原创 + 2 上游历史保留),支持 Cursor / Claude Code / Hermes Agent / Claw Code / Qoder 等 26 款工具",
5
+ "version": "1.7.14",
6
6
  "author": {
7
7
  "name": "jnMetaCode",
8
8
  "url": "https://github.com/jnMetaCode"
@@ -6,7 +6,11 @@
6
6
 
7
7
  ## 安装步骤
8
8
 
9
- 在你的 `opencode.json`(全局或项目级别)中将 superpowers-zh 添加到 `plugin` 数组:
9
+ OpenCode V2 需要 2.0.4 或更高版本。
10
+
11
+ ### OpenCode V1
12
+
13
+ 使用原有的 V1 插件配置,在你的 `opencode.json`(全局或项目级别)中将 superpowers-zh 添加到 `plugin` 数组:
10
14
 
11
15
  ```json
12
16
  {
@@ -14,10 +18,24 @@
14
18
  }
15
19
  ```
16
20
 
17
- 重启 OpenCode。完成——插件会自动安装并注册所有 skills。
21
+ ### OpenCode V2(2.0.4 或更高版本)
22
+
23
+ 使用 V2 插件配置(注意键名是 `plugins`):
24
+
25
+ ```json
26
+ {
27
+ "plugins": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git"]
28
+ }
29
+ ```
30
+
31
+ 如果是 V2 的本地安装,配置的应当是包含 `index.js` 的仓库目录。OpenCode 2.0.4 和 2.0.7 会拒绝直接配置成 JavaScript 文件路径。通过自动发现找到的插件符号链接仍然受支持。
32
+
33
+ 重启 OpenCode。V2 使用 `opencode` 命令;`opencode2` 可能作为别名可用。插件会通过 OpenCode 的插件管理器安装,并注册所有 skills。
18
34
 
19
35
  通过询问来验证:"告诉我你的超能力"
20
36
 
37
+ OpenCode 使用它自己的插件安装。如果你同时在用 Claude Code、Codex 或其他运行环境,需要为每一个分别安装 Superpowers。
38
+
21
39
  ## 从旧版符号链接安装方式迁移
22
40
 
23
41
  如果你之前使用 `git clone` 和符号链接安装过 superpowers,请移除旧的配置:
@@ -41,28 +59,59 @@ rm -rf ~/.config/opencode/superpowers
41
59
 
42
60
  ```
43
61
  use skill tool to list skills
44
- use skill tool to load superpowers/brainstorming
62
+ use skill tool to load brainstorming
45
63
  ```
46
64
 
47
65
  ## 更新
48
66
 
49
- Superpowers 会在你重启 OpenCode 时自动更新。
67
+ OpenCode 通过一个基于 git 的包说明(package spec)安装 Superpowers。有些 OpenCode 和 Bun 版本会把解析出的 git 依赖锁定在 lockfile 或缓存里,所以重启不一定能拿到 Superpowers 的最新提交。如果更新没有生效,清掉 OpenCode 的包缓存,或重新安装插件。
50
68
 
51
- 要固定到特定版本:
69
+ 要固定到特定版本,在包说明后面加上标签或提交(V1 的 `plugin` 键和 V2 的 `plugins` 键写法相同):
52
70
 
53
71
  ```json
54
72
  {
55
- "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git#v1.0.0"]
73
+ "plugin": ["superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git#v1.7.13"]
56
74
  }
57
75
  ```
58
76
 
77
+ 在 V2 上,要固定到包含 V2 插件的版本(v1.7.13 之后的版本);v1.7.13 及更早的发布只能在 V1 上加载。
78
+
59
79
  ## 故障排除
60
80
 
61
81
  ### 插件未加载
62
82
 
63
- 1. 检查日志:`opencode run --print-logs "hello" 2>&1 | grep -i superpowers`
83
+ 1. 检查日志。V1:`opencode run --print-logs "hello" 2>&1 | grep -i superpowers`。
84
+ V2 在后台服务进程里加载插件,所以要加上 `--standalone`:
85
+ `opencode run --standalone --print-logs "hello" 2>&1 | grep -i superpowers`,
86
+ 或者查看 `~/.local/share/opencode/log/opencode.log`,过滤 `role=server`。
64
87
  2. 验证 `opencode.json` 中的插件配置
65
- 3. 确保你运行的是最新版本的 OpenCode
88
+ 3. 确保你运行的是较新版本的 OpenCode
89
+
90
+ ### Windows 安装问题
91
+
92
+ 部分 Windows 版 OpenCode 对基于 git 的插件说明存在上游安装器问题,包括 `git+https` URL 的缓存路径问题,以及 Bun 找不到 `git.exe`(即使它在普通终端里能用)。如果 OpenCode 装不上插件,可以试试用系统的 npm 安装,再让 OpenCode 指向本地包:
93
+
94
+ ```powershell
95
+ npm install superpowers@git+https://github.com/jnMetaCode/superpowers-zh.git --prefix "$HOME\.config\opencode"
96
+ ```
97
+
98
+ 然后按你的 OpenCode 版本,在 `opencode.json` 里使用已安装包的绝对路径。OpenCode 不展开 `~`;`~/...` 这样的条目会被当成包名,而不是本地目录。
99
+
100
+ **V1:**
101
+
102
+ ```json
103
+ {
104
+ "plugin": ["C:\\Users\\<you>\\.config\\opencode\\node_modules\\superpowers"]
105
+ }
106
+ ```
107
+
108
+ **V2(2.0.4 或更高版本):**
109
+
110
+ ```json
111
+ {
112
+ "plugins": ["C:\\Users\\<you>\\.config\\opencode\\node_modules\\superpowers"]
113
+ }
114
+ ```
66
115
 
67
116
  ### Skills 未找到
68
117
 
@@ -71,13 +120,32 @@ Superpowers 会在你重启 OpenCode 时自动更新。
71
120
 
72
121
  ### 工具映射
73
122
 
74
- 当 skills 引用 Claude Code 工具时:
75
- - `TodoWrite` → `todowrite`
76
- - `Task` 子代理 → `@mention` 语法
77
- - `Skill` 工具 → OpenCode 的原生 `skill` 工具
78
- - 文件操作 → 你的原生工具
123
+ Skill 用动作来表述(「创建一条待办」「分派一个子智能体」「读取一个文件」)。插件会按 OpenCode 的版本注入对应的映射——先确认你的 OpenCode 版本:
124
+
125
+ **V1(`opencode` 1.x):**
126
+
127
+ - 「创建一条待办」/「在待办列表里标记完成」→ `todowrite`
128
+ - `Subagent (general-purpose):` 模板 → `task` 工具,传 `subagent_type: "general"`(探索代码库时用 `"explore"`)
129
+ - 「调用一个 skill」→ OpenCode 的原生 `skill` 工具
130
+ - 「读取文件」→ `read`
131
+ - 「创建文件」/「编辑文件」/「删除文件」→ `apply_patch`
132
+ - 「运行 shell 命令」→ `bash`
133
+ - 「搜索文件内容」/「按文件名查找」→ `grep`、`glob`
134
+ - 「抓取 URL」→ `webfetch`
135
+
136
+ **V2(`opencode` 2.0.4 或更高版本;`opencode2` 可能作为别名可用):**
137
+
138
+ - 「创建一条待办」→ V2 没有待办工具;改为在一个 markdown 文件里跟踪计划
139
+ - `Subagent (general-purpose):` 模板 → `subagent` 工具,传 `agent: "general"`(或 `"explore"`);传 `sessionID` 可以继续之前的子智能体
140
+ - 「调用一个 skill」→ OpenCode 的原生 `skill` 工具
141
+ - 「读取文件」→ `read`
142
+ - 「创建、编辑或删除文件」→ 有 `patch` 时用 `patch` 并传 `patchText`;否则用 `write` 创建或覆盖文件、`edit` 做定点修改、`shell` 删除文件
143
+ - 「运行 shell 命令」→ `shell`(`command`、`workdir`、`timeout`、`background`)
144
+ - 「搜索文件内容」/「按文件名查找」→ `grep`、`glob`
145
+ - 「抓取 URL」→ `webfetch`
146
+ - 「搜索网络」→ `websearch`
79
147
 
80
148
  ## 获取帮助
81
149
 
82
150
  - 报告问题:https://github.com/jnMetaCode/superpowers-zh/issues
83
- - 完整文档:https://github.com/jnMetaCode/superpowers-zh
151
+ - 完整文档:https://github.com/jnMetaCode/superpowers-zh/blob/main/docs/README.opencode.md
@@ -1,8 +1,18 @@
1
1
  /**
2
2
  * Superpowers plugin for OpenCode.ai
3
3
  *
4
- * Injects superpowers bootstrap context via user message transform.
5
- * Auto-registers skills directory via config hook (no symlinks needed).
4
+ * Dual-compatible with OpenCode V1 and V2.
5
+ *
6
+ * V1 (opencode): loaded via named export SuperpowersPlugin — provides config
7
+ * hook for skills registration and experimental.chat.messages.transform for
8
+ * bootstrap injection.
9
+ *
10
+ * V2 (opencode2): loaded via default export { id, setup } by PluginSupervisor.
11
+ * setup() registers skills natively via ctx.skill.transform(), and injects
12
+ * bootstrap context via ctx.session.hook("context").
13
+ *
14
+ * No external dependencies — pure JavaScript works in both V1 and V2 without
15
+ * installing @opencode-ai/plugin or effect.
6
16
  */
7
17
 
8
18
  import path from 'path';
@@ -11,49 +21,113 @@ import { fileURLToPath } from 'url';
11
21
 
12
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
23
 
14
- // Simple frontmatter extraction (avoid dependency on skills-core for bootstrap)
24
+ // Skills directory shared by V1 (config hook) and V2 (setup/ctx.skill.transform)
25
+ const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
26
+
27
+ // Simple frontmatter extraction (avoid dependency on skills-core for
28
+ // bootstrap). Handles plain `key: value` lines, quoted values (including
29
+ // quotes that close on an indented continuation line), YAML block scalar
30
+ // markers (`>`, `|`) with indented continuation lines, and CRLF line
31
+ // endings. Not a full YAML parser — nested maps flatten into their parent
32
+ // key's value, which is fine for the name/description fields consumed here.
15
33
  const extractAndStripFrontmatter = (content) => {
16
- const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
34
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
17
35
  if (!match) return { frontmatter: {}, content };
18
36
 
19
37
  const frontmatterStr = match[1];
20
38
  const body = match[2];
21
39
  const frontmatter = {};
40
+ let lastKey = null;
22
41
 
23
- for (const line of frontmatterStr.split('\n')) {
42
+ for (const rawLine of frontmatterStr.split('\n')) {
43
+ const line = rawLine.replace(/\r$/, '');
24
44
  const colonIdx = line.indexOf(':');
25
- if (colonIdx > 0) {
45
+ if (colonIdx > 0 && !/^\s/.test(line)) {
26
46
  const key = line.slice(0, colonIdx).trim();
27
- const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
28
- frontmatter[key] = value;
47
+ const value = line.slice(colonIdx + 1).trim();
48
+ // Block scalar markers (>, |, optionally with +/- chomping) carry no
49
+ // value themselves; the indented lines that follow do.
50
+ frontmatter[key] = /^(>[+-]?|\|[+-]?)$/.test(value) ? '' : value;
51
+ lastKey = key;
52
+ } else if (lastKey !== null && line.trim() !== '') {
53
+ // Continuation of a multi-line value: append rather than drop so long
54
+ // descriptions survive parsing. Newlines collapse to spaces — good
55
+ // enough for the single-line name/description fields consumed here.
56
+ frontmatter[lastKey] = `${frontmatter[lastKey]} ${line.trim()}`.trim();
29
57
  }
30
58
  }
31
59
 
60
+ // A quoted value may close on a continuation line, so unquote only once
61
+ // the value is fully assembled: strip exactly one matching surrounding
62
+ // pair and leave unbalanced quotes alone.
63
+ for (const key of Object.keys(frontmatter)) {
64
+ frontmatter[key] = frontmatter[key].replace(/^(["'])([\s\S]*)\1$/, '$2');
65
+ }
66
+
32
67
  return { frontmatter, content: body };
33
68
  };
34
69
 
35
- export const SuperpowersPlugin = async ({ client, directory }) => {
36
- const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
70
+ // Tool mapping injected into the bootstrap, differentiated by host flavor.
71
+ // V1 (OpenCode 1.18.x) and V2 (OpenCode 2.0.4/2.0.7) expose different built-in
72
+ // tools, so each flavor's injection path picks its own constant below.
73
+ // Exported for tests (tests/opencode/test-bootstrap-caching.mjs).
37
74
 
38
- // Helper to generate bootstrap content
39
- const getBootstrapContent = () => {
40
- // Try to load using-superpowers skill
41
- const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
42
- if (!fs.existsSync(skillPath)) return null;
75
+ // V1 built-ins: todowrite, task (subagent_type), skill, read, apply_patch,
76
+ // bash, grep, glob, webfetch.
77
+ export const V1_MAPPING = `**Tool Mapping for OpenCode:**
78
+ When skills request actions, substitute OpenCode equivalents:
79
+ - Create or update todos → \`todowrite\`
80
+ - \`Subagent (general-purpose):\` → \`task\` with \`subagent_type: "general"\`
81
+ - Invoke a skill → OpenCode's native \`skill\` tool
82
+ - Read files → \`read\`
83
+ - Create, edit, or delete files → \`apply_patch\`
84
+ - Run shell commands → \`bash\`
85
+ - Search files → \`grep\`, \`glob\`
86
+ - Fetch a URL → \`webfetch\`
43
87
 
44
- const fullContent = fs.readFileSync(skillPath, 'utf8');
45
- const { content } = extractAndStripFrontmatter(fullContent);
88
+ Use OpenCode's native \`skill\` tool to list and load skills.`;
46
89
 
47
- const toolMapping = `**Tool Mapping for OpenCode:**
48
- When skills reference tools you don't have, substitute OpenCode equivalents:
49
- - \`TodoWrite\` → \`todowrite\`
50
- - \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
51
- - \`Skill\` tool → OpenCode's native \`skill\` tool
52
- - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
90
+ // V2 built-ins: no todo tool at all; task → subagent (agent name in 'agent',
91
+ // continuation via sessionID); apply_patch → patch (patchText, same patch
92
+ // format); bash → shell. read, write, edit, grep, glob, webfetch, websearch,
93
+ // and skill all exist under those names (verified against the 2.0.4 and 2.0.7
94
+ // host contracts).
95
+ export const V2_MAPPING = `**Tool Mapping for OpenCode:**
96
+ When skills request actions, substitute OpenCode equivalents:
97
+ - Create or update todos → OpenCode v2 has no todo tool; track the plan in a markdown file (or the harness's plan facility) instead
98
+ - \`Subagent (general-purpose):\` → \`subagent\` with \`agent: "general"\` (give it \`description\` and \`prompt\`, optionally \`background\`; pass \`sessionID\` to continue a previous subagent)
99
+ - Invoke a skill → OpenCode's native \`skill\` tool
100
+ - Read files → \`read\`
101
+ - Create, edit, or delete files → use \`patch\` with \`patchText\` when available; otherwise use \`write\` to create or overwrite files, \`edit\` for targeted changes, and \`shell\` for deletion
102
+ - Run shell commands → \`shell\` (\`command\`, \`workdir\`, \`timeout\`, \`background\`)
103
+ - Search files → \`grep\`, \`glob\`
104
+ - Fetch a URL → \`webfetch\`
105
+ - Search the web → \`websearch\`
53
106
 
54
107
  Use OpenCode's native \`skill\` tool to list and load skills.`;
55
108
 
56
- return `<EXTREMELY_IMPORTANT>
109
+ // Module-level cache for bootstrap content, keyed by tool mapping (host
110
+ // flavor). The SKILL.md file does not change during a session, so reading +
111
+ // parsing it once eliminates redundant fs.existsSync + fs.readFileSync +
112
+ // regex work on every agent step. See #1202 for the full analysis.
113
+ const _bootstrapCache = new Map(); // mapping -> bootstrap (null = file missing)
114
+
115
+ // Helper to generate bootstrap content (cached after first call per mapping)
116
+ const getBootstrapContent = (toolMapping) => {
117
+ // Return cached result on subsequent calls
118
+ if (_bootstrapCache.has(toolMapping)) return _bootstrapCache.get(toolMapping);
119
+
120
+ // Try to load using-superpowers skill
121
+ const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
122
+ if (!fs.existsSync(skillPath)) {
123
+ _bootstrapCache.set(toolMapping, null);
124
+ return null;
125
+ }
126
+
127
+ const fullContent = fs.readFileSync(skillPath, 'utf8');
128
+ const { content } = extractAndStripFrontmatter(fullContent);
129
+
130
+ _bootstrapCache.set(toolMapping, `<EXTREMELY_IMPORTANT>
57
131
  You have superpowers.
58
132
 
59
133
  **IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.**
@@ -61,15 +135,96 @@ You have superpowers.
61
135
  ${content}
62
136
 
63
137
  ${toolMapping}
64
- </EXTREMELY_IMPORTANT>`;
65
- };
138
+ </EXTREMELY_IMPORTANT>`);
139
+
140
+ return _bootstrapCache.get(toolMapping);
141
+ };
142
+
143
+ // --- Task-subagent (child session) detection --------------------------------
144
+ //
145
+ // #2160: the bootstrap drives controller workflows (brainstorming, planning,
146
+ // approval cycles). Injecting it into task subagent sessions makes workers
147
+ // restart design/approval cycles for work the parent already authorised; the
148
+ // <SUBAGENT-STOP> note inside the bootstrap relies on model compliance, which
149
+ // is not reliable. Detect child sessions structurally instead: a parentID on
150
+ // the session is the child signal on both flavors (task sessions are created
151
+ // with one; top-level sessions simply lack the field), so when the session
152
+ // carrying the message has a parentID we skip bootstrap injection. Skills
153
+ // stay registered for every session — workers keep explicit access to
154
+ // execution skills.
155
+
156
+ // sessionID -> is-child decision. parentID never changes for a session, so
157
+ // the result is cached until eviction and the injection hook (which fires on
158
+ // every agent step) pays only one client roundtrip per session. The V2
159
+ // service process is long-lived and sessions accumulate over weeks, so the
160
+ // cache is bounded: when full, drop the oldest quarter (Map iterates keys in
161
+ // insertion order). An evicted session merely pays one extra lookup if seen
162
+ // again.
163
+ const CHILD_SESSION_CACHE_MAX = 512;
164
+ const _childSessionCache = new Map();
165
+
166
+ const _cacheChildSession = (sessionID, isChild) => {
167
+ if (_childSessionCache.size >= CHILD_SESSION_CACHE_MAX) {
168
+ let toDrop = Math.ceil(CHILD_SESSION_CACHE_MAX / 4);
169
+ for (const key of _childSessionCache.keys()) {
170
+ if (toDrop-- <= 0) break;
171
+ _childSessionCache.delete(key);
172
+ }
173
+ }
174
+ _childSessionCache.set(sessionID, isChild);
175
+ };
176
+
177
+ const isChildSession = async (fetchSession, sessionID) => {
178
+ if (!sessionID) return false; // unknown session: keep current behavior
179
+ if (_childSessionCache.has(sessionID)) return _childSessionCache.get(sessionID);
180
+
181
+ let isChild = false;
182
+ try {
183
+ const result = await fetchSession(sessionID);
184
+ // V1 returns a successful SDK envelope while V2 returns a direct session
185
+ // record. Validate both shapes before classifying or caching the result;
186
+ // resolved SDK errors must follow the same fail-open path as rejections.
187
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
188
+ throw new Error('Session lookup returned no usable record');
189
+ }
190
+ if (result.error != null || result.response?.ok === false) {
191
+ throw new Error('Session lookup was unsuccessful');
192
+ }
193
+ const session = 'data' in result ? result.data : result;
194
+ if (!session || typeof session !== 'object' || Array.isArray(session) || session.id !== sessionID) {
195
+ throw new Error('Session lookup returned an invalid session identity');
196
+ }
197
+ if (session.parentID !== undefined &&
198
+ (typeof session.parentID !== 'string' || session.parentID.length === 0)) {
199
+ throw new Error('Session lookup returned an invalid parent identity');
200
+ }
201
+ isChild = session.parentID !== undefined;
202
+ } catch (err) {
203
+ // Fail open: on lookup errors keep injecting (previous behavior) and do
204
+ // not cache, so a transient failure can recover on the next step.
205
+ console.error('[superpowers] session lookup failed, treating session as top-level:', err);
206
+ return false;
207
+ }
208
+ _cacheChildSession(sessionID, isChild);
209
+ return isChild;
210
+ };
66
211
 
212
+ /**
213
+ * V1 Plugin Function (named export + default.server)
214
+ *
215
+ * Used by V1 (OpenCode 1.x): discovered via named export scanning.
216
+ * Provides: config hook (V1 skills registration) + bootstrap injection
217
+ * (experimental.chat.messages.transform).
218
+ */
219
+ export const SuperpowersPlugin = async ({ client, directory }) => {
67
220
  return {
68
221
  // Inject skills path into live config so OpenCode discovers superpowers skills
69
222
  // without requiring manual symlinks or config file edits.
70
- // This works because Config.get() returns a cached singleton — modifications
71
- // here are visible when skills are lazily discovered later.
72
223
  config: async (config) => {
224
+ // V2: skills is a flat array — skip, setup() handles V2 skill registration
225
+ if (Array.isArray(config.skills)) return;
226
+
227
+ // V1: skills is { paths: [...] }
73
228
  config.skills = config.skills || {};
74
229
  config.skills.paths = config.skills.paths || [];
75
230
  if (!config.skills.paths.includes(superpowersSkillsDir)) {
@@ -77,19 +232,152 @@ ${toolMapping}
77
232
  }
78
233
  },
79
234
 
80
- // Inject bootstrap into the first user message of each session.
235
+ // Inject bootstrap into the first user message of each top-level session.
81
236
  // Using a user message instead of a system message avoids:
82
237
  // 1. Token bloat from system messages repeated every turn (#750)
83
238
  // 2. Multiple system messages breaking Qwen and other models (#894)
239
+ //
240
+ // The hook fires on every agent step (not just every turn) because
241
+ // opencode's prompt.ts reloads messages from DB each step. Fresh message
242
+ // arrays may need injection again, so getBootstrapContent() must not do
243
+ // repeated disk work.
84
244
  'experimental.chat.messages.transform': async (_input, output) => {
85
- const bootstrap = getBootstrapContent();
245
+ const bootstrap = getBootstrapContent(V1_MAPPING);
86
246
  if (!bootstrap || !output.messages.length) return;
87
247
  const firstUser = output.messages.find(m => m.info.role === 'user');
88
248
  if (!firstUser || !firstUser.parts.length) return;
89
- // Only inject once
249
+
250
+ // Guard: skip if first user message already contains bootstrap.
90
251
  if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return;
252
+
253
+ // #2160: never restart the controller workflow inside task subagent
254
+ // (child) sessions. V1 passes no input to this hook (verified in the
255
+ // 1.18.x bundle: trigger(..., {}, {messages})), so take the sessionID
256
+ // from the message record itself.
257
+ if (client && await isChildSession(
258
+ (id) => client.session.get({ path: { id } }),
259
+ firstUser.info.sessionID,
260
+ )) return;
261
+
91
262
  const ref = firstUser.parts[0];
92
263
  firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap });
93
264
  }
94
265
  };
95
266
  };
267
+
268
+ /**
269
+ * V2 Setup Function (default.setup)
270
+ *
271
+ * Called by V2 PluginSupervisor (packages/core/src/plugin/supervisor.ts).
272
+ * Performs two things:
273
+ *
274
+ * 1. Registers every skills/<name>/SKILL.md as a native Skill.Info object
275
+ * via ctx.skill.transform((draft) => draft.add(info)).
276
+ * V2 removed the old draft.source() directory registration; the draft API
277
+ * is now { list, add, update, remove } where add() decodes plain objects
278
+ * against the host's Skill.Info schema (OpenCode 2.0.4 contract):
279
+ * { id, name, description?, autoinvoke?, path, content }. The file field
280
+ * is `path` — renamed from `location` in upstream commit 199aabe9e2,
281
+ * first released in v2.0.4.
282
+ * See packages/core/src/plugin/skill.ts and packages/schema/src/skill.ts.
283
+ * 2. Injects bootstrap context via ctx.session.hook("context"), the V2
284
+ * equivalent of V1's experimental.chat.messages.transform.
285
+ */
286
+ async function setup(ctx) {
287
+ // V1 (observed on opencode 1.18.18) also invokes default.setup, but with a
288
+ // V1-shaped ctx that lacks the skill/session domains. Detect it and return
289
+ // quietly — V1 is served entirely by the SuperpowersPlugin named export.
290
+ if (!ctx || !ctx.skill || typeof ctx.skill.transform !== 'function' || !ctx.session || typeof ctx.session.hook !== 'function') {
291
+ return;
292
+ }
293
+
294
+ // 1. Register skills (one transform; one draft.add per skill)
295
+ try {
296
+ const skills = [];
297
+ if (fs.existsSync(superpowersSkillsDir)) {
298
+ for (const entry of fs.readdirSync(superpowersSkillsDir, { withFileTypes: true })) {
299
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
300
+ const skillPath = path.join(superpowersSkillsDir, entry.name, 'SKILL.md');
301
+ if (!fs.existsSync(skillPath)) continue;
302
+ const { frontmatter, content } = extractAndStripFrontmatter(fs.readFileSync(skillPath, 'utf8'));
303
+ skills.push({
304
+ id: entry.name,
305
+ name: frontmatter.name || entry.name,
306
+ ...(frontmatter.description ? { description: frontmatter.description } : {}),
307
+ // Skill.Info renamed its required file field `location` -> `path`
308
+ // in OpenCode v2.0.4 (upstream commit 199aabe9e2).
309
+ path: skillPath,
310
+ content,
311
+ });
312
+ }
313
+ }
314
+ await ctx.skill.transform((draft) => {
315
+ // draft.add() decodes against the host's Skill.Info schema and throws
316
+ // synchronously on a mismatch. A throw escaping this callback is what
317
+ // the host escalates into an asynchronous hard-disable of the entire
318
+ // plugin ("Plugin disabled after skill.transform failed") — the
319
+ // try/catch around ctx.skill.transform never sees it, and the
320
+ // bootstrap hook is torn down as collateral. Contain failures per
321
+ // skill so one rejected payload skips that skill instead of killing
322
+ // skills AND bootstrap.
323
+ for (const skill of skills) {
324
+ try {
325
+ draft.add(skill);
326
+ } catch (err) {
327
+ console.error(`[superpowers] skill "${skill.id}" rejected by host, skipping:`, err);
328
+ }
329
+ }
330
+ });
331
+ } catch (err) {
332
+ // Never break plugin activation: one failing plugin takes down the whole
333
+ // V2 generation (including provider/catalog plugins => no models in TUI).
334
+ console.error('[superpowers] skill registration failed:', err);
335
+ }
336
+
337
+ // 2. Inject bootstrap into first user message via V2 session context hook
338
+ try {
339
+ await ctx.session.hook('context', async (event) => {
340
+ try {
341
+ const bootstrap = getBootstrapContent(V2_MAPPING);
342
+ if (!bootstrap || !event.messages || !event.messages.length) return;
343
+ const firstUser = event.messages.find(m => m.role === 'user');
344
+ if (firstUser && (!firstUser.content || !firstUser.content.length)) return;
345
+ if (firstUser?.content.some(p => p.type === 'text' && p.text && p.text.includes('EXTREMELY_IMPORTANT'))) return;
346
+
347
+ // #2160: the context event carries the sessionID directly. Skip the
348
+ // controller bootstrap when this prompt belongs to a task subagent
349
+ // (child) session. Skills registered above stay available to workers.
350
+ if (typeof ctx.session.get === 'function' && await isChildSession(
351
+ (id) => ctx.session.get({ sessionID: id }),
352
+ event.sessionID,
353
+ )) return;
354
+
355
+ // Native compaction can leave only an opaque checkpoint. Keep it
356
+ // intact and append the transient bootstrap as a user message.
357
+ if (firstUser) {
358
+ firstUser.content.unshift({ type: 'text', text: bootstrap });
359
+ } else {
360
+ event.messages.push({ role: 'user', content: [{ type: 'text', text: bootstrap }] });
361
+ }
362
+ } catch (err) {
363
+ // Never let hook callback errors break the request pipeline.
364
+ console.error('[superpowers] context hook failed:', err);
365
+ }
366
+ });
367
+ } catch (err) {
368
+ console.error('[superpowers] session hook registration failed:', err);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Default Export: { id, server, setup }
374
+ *
375
+ * V2 PluginSupervisor reads { id, setup }.
376
+ * V1 reads named export SuperpowersPlugin.
377
+ * server() is exported for V1 compatibility.
378
+ */
379
+ export default {
380
+ id: 'superpowers',
381
+ server: SuperpowersPlugin,
382
+ setup,
383
+ };
package/GEMINI.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # Superpowers-ZH 中文增强版
6
6
 
7
- 本项目已安装 superpowers-zh 技能框架(20 个 skills)。
7
+ 本项目已安装 superpowers-zh 技能框架(21 个 skills)。
8
8
 
9
9
  ## 核心规则
10
10
 
@@ -22,8 +22,9 @@ Skills 位于 `.gemini/skills/` 目录,每个 skill 有独立的 `SKILL.md`
22
22
  - **chinese-commit-conventions**: 中文 commit 与 changelog 配置参考——Conventional Commits 中文适配、commitlint/husky/commitizen 中文模板、conventional-changelog 中文配置。仅在用户显式 /chinese-commit-conventions 时调用,不要根据上下文自动触发。
23
23
  - **chinese-documentation**: 中文文档排版参考——中英文空格、全半角标点、术语保留、链接格式、中文文案排版指北约定。仅在用户显式 /chinese-documentation 时调用,不要根据上下文自动触发。
24
24
  - **chinese-git-workflow**: 国内 Git 平台配置参考——Gitee、Coding.net、极狐 GitLab、CNB 的 SSH/HTTPS/凭据/CI 接入差异与镜像同步配置。仅在用户显式 /chinese-git-workflow 时调用,不要根据上下文自动触发。
25
+ - **diagnosing-superpowers**: 当一次 superpowers 会话出了问题、你的人类伙伴想知道原因时使用——重复劳动、无视计划、磕磕绊绊、结果质量差、某个技能没触发、"太慢了"、"为什么这么贵"、"它到底在干什么"——或者想给 superpowers 维护者整理一份 bug 报告;适用于当前会话,或按 id / 路径指定的过往会话,任何工具均可
25
26
  - **dispatching-parallel-agents**: 当面对 2 个以上可以独立进行、无共享状态或顺序依赖的任务时使用
26
- - **executing-plans**: 当你有一份书面实现计划需要在单独的会话中执行,并设有审查检查点时使用
27
+ - **executing-plans**: 当你要在当前会话里亲自担任实现者执行一份实现计划时使用——你的人类伙伴选择了内联执行,或者没有可用的子智能体工具
27
28
  - **finishing-a-development-branch**: 当实现完成、所有测试通过、需要决定如何集成工作时使用——通过提供合并、PR 或清理等结构化选项来引导开发工作的收尾
28
29
  - **mcp-builder**: MCP 服务器构建方法论 — 系统化构建生产级 MCP 工具,让 AI 助手连接外部能力
29
30
  - **receiving-code-review**: 收到代码审查反馈后、实施建议之前使用,尤其当反馈不明确或技术上有疑问时——需要技术严谨性和验证,而非敷衍附和或盲目执行