@jspg-ai/coding-bb 0.0.3-beta.3 → 0.0.3-beta.30

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 (140) hide show
  1. package/cbb/dev-standards/rules/cbb-ai-behavior.md +135 -116
  2. package/cbb/lib/install/agents-nav.js +92 -0
  3. package/cbb/lib/install/claude-code.js +7 -2
  4. package/cbb/lib/install/command-skill.js +105 -0
  5. package/cbb/lib/install/init.js +435 -164
  6. package/cbb/lib/install/minimax.js +60 -0
  7. package/cbb/lib/install/opencode.js +3 -5
  8. package/cbb/lib/install/qoder.js +1 -3
  9. package/cbb/lib/install/rule-rewrite.js +27 -0
  10. package/cbb/lib/install/setup-decision.js +48 -0
  11. package/cbb/lib/install/tool-selection.js +45 -0
  12. package/cbb/lib/install/trae.js +43 -0
  13. package/cbb/lib/install/workbuddy.js +36 -0
  14. package/cbb/lib/install/workspaces.js +21 -17
  15. package/cbb/lib/openspec/index.js +337 -554
  16. package/cbb/lib/superpowers/index.js +246 -265
  17. package/cbb/lib/utils/check-update.js +17 -2
  18. package/cbb/lib/utils/githook.js +226 -0
  19. package/cbb/lib/utils/gitignore.js +16 -3
  20. package/cbb/lib/utils/plugin-hook.js +92 -0
  21. package/cbb/lib/utils/settings.js +17 -4
  22. package/cbb/lib/utils/tar.js +92 -0
  23. package/cbb/lib/utils/upstream.js +90 -0
  24. package/cbb/worktrees/_shared/scripts/check-local-state.js +114 -0
  25. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +148 -96
  26. package/cbb/worktrees/_shared/scripts/find-workspace-root.js +7 -7
  27. package/cbb/worktrees/_shared/scripts/install-git-hook.js +70 -0
  28. package/cbb/worktrees/_shared/scripts/junction-ai-config.js +127 -0
  29. package/cbb/worktrees/_shared/scripts/start-local.js +180 -0
  30. package/cbb/worktrees/skills/cbb-local-mode/SKILL.md +158 -0
  31. package/cbb/worktrees/skills/cbb-local-mode/scripts/check-local-state.js +114 -0
  32. package/cbb/worktrees/skills/cbb-local-mode/scripts/install-git-hook.js +70 -0
  33. package/cbb/worktrees/skills/cbb-local-mode/scripts/silence-popup.js +23 -0
  34. package/cbb/worktrees/skills/cbb-local-mode/scripts/start-local.js +180 -0
  35. package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +60 -43
  36. package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +3 -3
  37. package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-unarchived.js +75 -48
  38. package/cbb/worktrees/skills/cbb-worktree-close/scripts/delete-branches.js +1 -1
  39. package/cbb/worktrees/skills/cbb-worktree-close/scripts/discover-apps.js +14 -5
  40. package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-target-worktree.js +148 -96
  41. package/cbb/worktrees/skills/cbb-worktree-close/scripts/find-workspace-root.js +7 -7
  42. package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +13 -4
  43. package/cbb/worktrees/skills/cbb-worktree-close/scripts/safety-check.js +11 -2
  44. package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +138 -129
  45. package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env.js +1 -1
  46. package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-repos.js +2 -2
  47. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-branches.js +33 -8
  48. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +60 -46
  49. package/cbb/worktrees/skills/cbb-worktree-init/scripts/find-workspace-root.js +7 -7
  50. package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-git-hook.js +70 -0
  51. package/cbb/worktrees/skills/cbb-worktree-init/scripts/junction-ai-config.js +127 -0
  52. package/cbb/worktrees/skills/cbb-worktree-init/scripts/parse-config.js +7 -7
  53. package/cbb/worktrees/skills/cbb-worktree-init/scripts/push-branches.js +3 -3
  54. package/cbb/worktrees/skills/cbb-worktree-init/scripts/sync-repos.js +11 -7
  55. package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +37 -9
  56. package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +113 -36
  57. package/cbb/worktrees/skills/cbb-worktree-push/scripts/commit-worktrees.js +70 -12
  58. package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-target-worktree.js +148 -96
  59. package/cbb/worktrees/skills/cbb-worktree-push/scripts/find-workspace-root.js +7 -7
  60. package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
  61. package/cbb/worktrees/skills/cbb-worktree-push/scripts/push-worktrees.js +67 -20
  62. package/config/{workspace-config.sample.json → CBB-WORKSPACE.sample.json} +1 -1
  63. package/config/openspec/schemas/spec-driven/schema.yaml +40 -30
  64. package/config/openspec/schemas/spec-driven/templates/design.md +0 -18
  65. package/config/openspec/schemas/spec-driven/templates/proposal.md +4 -3
  66. package/config/upstream-mirrors.json +12 -0
  67. package/config/workspace-agents.sample.md +4 -33
  68. package/openspec/.version +2 -3
  69. package/openspec/commands/apply.md +189 -175
  70. package/openspec/commands/archive.md +237 -216
  71. package/openspec/commands/bulk-archive.md +355 -327
  72. package/openspec/commands/continue.md +116 -105
  73. package/openspec/commands/explore.md +230 -199
  74. package/openspec/commands/ff.md +115 -104
  75. package/openspec/commands/new.md +74 -63
  76. package/openspec/commands/onboard.md +557 -548
  77. package/openspec/commands/propose.md +161 -150
  78. package/openspec/commands/sync.md +277 -249
  79. package/openspec/commands/update.md +92 -80
  80. package/openspec/commands/verify.md +175 -162
  81. package/openspec/skills/openspec-apply-change/SKILL.md +20 -5
  82. package/openspec/skills/openspec-archive-change/SKILL.md +30 -8
  83. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +36 -6
  84. package/openspec/skills/openspec-continue-change/SKILL.md +14 -2
  85. package/openspec/skills/openspec-explore/SKILL.md +21 -9
  86. package/openspec/skills/openspec-ff-change/SKILL.md +14 -2
  87. package/openspec/skills/openspec-new-change/SKILL.md +13 -1
  88. package/openspec/skills/openspec-onboard/SKILL.md +49 -39
  89. package/openspec/skills/openspec-propose/SKILL.md +15 -3
  90. package/openspec/skills/openspec-sync-specs/SKILL.md +31 -2
  91. package/openspec/skills/openspec-update-change/SKILL.md +27 -14
  92. package/openspec/skills/openspec-verify-change/SKILL.md +17 -3
  93. package/package.json +2 -2
  94. package/superpowers/.version +4 -4
  95. package/superpowers/skills/brainstorming/SKILL.md +47 -12
  96. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -213
  97. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -723
  98. package/superpowers/skills/brainstorming/visual-companion.md +6 -6
  99. package/superpowers/skills/diagnosing-superpowers/SKILL.md +120 -0
  100. package/superpowers/skills/diagnosing-superpowers/prompts/analyst-common.md +38 -0
  101. package/superpowers/skills/diagnosing-superpowers/prompts/cost-and-time.md +28 -0
  102. package/superpowers/skills/diagnosing-superpowers/prompts/plan-adherence.md +29 -0
  103. package/superpowers/skills/diagnosing-superpowers/prompts/quality-evidence.md +26 -0
  104. package/superpowers/skills/diagnosing-superpowers/prompts/repeated-work.md +30 -0
  105. package/superpowers/skills/diagnosing-superpowers/prompts/request-conflicts.md +20 -0
  106. package/superpowers/skills/diagnosing-superpowers/prompts/scrub-audit.md +33 -0
  107. package/superpowers/skills/diagnosing-superpowers/prompts/scrub.md +29 -0
  108. package/superpowers/skills/diagnosing-superpowers/prompts/similar-session.md +38 -0
  109. package/superpowers/skills/diagnosing-superpowers/prompts/skill-timeline.md +30 -0
  110. package/superpowers/skills/diagnosing-superpowers/prompts/stumbles.md +28 -0
  111. package/superpowers/skills/diagnosing-superpowers/references/context-safety.md +22 -0
  112. package/superpowers/skills/diagnosing-superpowers/references/github-issues.md +47 -0
  113. package/superpowers/skills/diagnosing-superpowers/references/redaction-policy.md +34 -0
  114. package/superpowers/skills/diagnosing-superpowers/references/session-discovery.md +31 -0
  115. package/superpowers/skills/diagnosing-superpowers/templates/bundle-README.md +77 -0
  116. package/superpowers/skills/diagnosing-superpowers/templates/case.md +64 -0
  117. package/superpowers/skills/diagnosing-superpowers/templates/issue.md +51 -0
  118. package/superpowers/skills/diagnosing-superpowers/templates/report.md +82 -0
  119. package/superpowers/skills/executing-plans/SKILL.md +350 -41
  120. package/superpowers/skills/executing-plans/scripts/task-done +52 -0
  121. package/superpowers/skills/executing-plans/scripts/task-start +28 -0
  122. package/superpowers/skills/requesting-code-review/SKILL.md +1 -1
  123. package/superpowers/skills/requesting-code-review/code-reviewer.md +17 -0
  124. package/superpowers/skills/subagent-driven-development/SKILL.md +18 -18
  125. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +1 -1
  126. package/superpowers/skills/subagent-driven-development/scripts/review-package +53 -46
  127. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +82 -40
  128. package/superpowers/skills/subagent-driven-development/scripts/task-brief +43 -41
  129. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
  130. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +1 -1
  131. package/superpowers/skills/test-driven-development/SKILL.md +10 -0
  132. package/superpowers/skills/using-superpowers/SKILL.md +2 -0
  133. package/superpowers/skills/using-superpowers/references/claude-code-tools.md +29 -0
  134. package/superpowers/skills/using-superpowers/references/muse-tools.md +35 -0
  135. package/superpowers/skills/writing-plans/SKILL.md +30 -9
  136. package/superpowers/skills/writing-skills/SKILL.md +4 -2
  137. package/superpowers/skills/writing-skills/graphviz-conventions.dot +171 -171
  138. package/cbb/worktrees/commands/worktree-close.md +0 -64
  139. package/cbb/worktrees/commands/worktree-init.md +0 -51
  140. package/cbb/worktrees/commands/worktree-push.md +0 -42
@@ -1,265 +1,246 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Superpowers Skills 管理模块
5
- *
6
- * 负责从 GitHub (obra/superpowers) 下载 skills 并部署到本项目。
7
- * 提供 cbbm update --upstream-superpowers 选项,以及 init 时的自动部署能力。
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const https = require('https');
13
- const os = require('os');
14
- const { execSync } = require('child_process');
15
-
16
- const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
17
-
18
- // Superpowers 本地存储目录
19
- const SUPERPOWERS_DIR = path.join(SHARED_STANDARDS_DIR, 'superpowers');
20
- const SUPERPOWERS_SKILLS_DIR = path.join(SUPERPOWERS_DIR, 'skills');
21
- const VERSION_FILE = path.join(SUPERPOWERS_DIR, '.version');
22
-
23
- // GitHub 配置
24
- const GITHUB_REPO = 'obra/superpowers';
25
- const GITHUB_BRANCH = 'main';
26
- const GITHUB_TARBALL_URL = `https://api.github.com/repos/${GITHUB_REPO}/tarball/${GITHUB_BRANCH}`;
27
- const GITHUB_API_COMMIT_URL = `https://api.github.com/repos/${GITHUB_REPO}/commits/${GITHUB_BRANCH}`;
28
-
29
- // 预期的 skill 列表(14 个)
30
- const EXPECTED_SKILLS = [
31
- 'brainstorming',
32
- 'dispatching-parallel-agents',
33
- 'executing-plans',
34
- 'finishing-a-development-branch',
35
- 'receiving-code-review',
36
- 'requesting-code-review',
37
- 'subagent-driven-development',
38
- 'systematic-debugging',
39
- 'test-driven-development',
40
- 'using-git-worktrees',
41
- 'using-superpowers',
42
- 'verification-before-completion',
43
- 'writing-plans',
44
- 'writing-skills',
45
- ];
46
-
47
- const { log } = require('../utils/output');
48
-
49
- // ── HTTP 工具 ──────────────────────────────────────────────────
50
-
51
- /**
52
- * 发起 HTTPS GET 请求,跟随重定向(最多 5 次)。
53
- * @param {string} url
54
- * @param {object} [headers]
55
- * @returns {Promise<{statusCode: number, headers: object, body: Buffer}>}
56
- */
57
- function httpsGet(url, headers = {}, redirectCount = 0) {
58
- if (redirectCount > 5) throw new Error('Too many redirects');
59
-
60
- return new Promise((resolve, reject) => {
61
- const defaultHeaders = { 'User-Agent': 'cbb-cli' };
62
- https.get(url, { headers: { ...defaultHeaders, ...headers } }, (res) => {
63
- // 跟随重定向
64
- if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
65
- res.resume(); // 消费响应体
66
- return httpsGet(res.headers.location, headers, redirectCount + 1)
67
- .then(resolve, reject);
68
- }
69
-
70
- const chunks = [];
71
- res.on('data', chunk => chunks.push(chunk));
72
- res.on('end', () => {
73
- resolve({
74
- statusCode: res.statusCode,
75
- headers: res.headers,
76
- body: Buffer.concat(chunks),
77
- });
78
- });
79
- res.on('error', reject);
80
- }).on('error', reject);
81
- });
82
- }
83
-
84
- /**
85
- * 下载文件到指定路径。
86
- */
87
- async function downloadToFile(url, destPath) {
88
- const res = await httpsGet(url);
89
- if (res.statusCode !== 200) {
90
- throw new Error(`HTTP ${res.statusCode}: ${url}`);
91
- }
92
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
93
- fs.writeFileSync(destPath, res.body);
94
- return res;
95
- }
96
-
97
- // ── 版本管理 ──────────────────────────────────────────────────
98
-
99
- /**
100
- * 获取 GitHub 上 main 分支最新的 commit SHA。
101
- * @returns {Promise<string>}
102
- */
103
- async function fetchLatestCommitSha() {
104
- const res = await httpsGet(GITHUB_API_COMMIT_URL, {
105
- 'Accept': 'application/vnd.github.v3+json',
106
- });
107
- if (res.statusCode !== 200) {
108
- throw new Error(`无法获取最新 commit: HTTP ${res.statusCode}`);
109
- }
110
- const data = JSON.parse(res.body.toString('utf-8'));
111
- return data.sha;
112
- }
113
-
114
- /**
115
- * 读取本地 .version 文件中的 commit SHA。
116
- * @returns {string|null}
117
- */
118
- function getLocalVersion() {
119
- if (!fs.existsSync(VERSION_FILE)) return null;
120
- try {
121
- const content = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
122
- // 格式:commit SHA(纯字符串)或 JSON { sha, updatedAt }
123
- if (content.startsWith('{')) {
124
- const data = JSON.parse(content);
125
- return data.sha || null;
126
- }
127
- return content || null;
128
- } catch (_) {
129
- return null;
130
- }
131
- }
132
-
133
- /**
134
- * 写入版本信息到 .version 文件。
135
- * @param {string} sha
136
- */
137
- function writeVersion(sha) {
138
- const data = {
139
- sha,
140
- branch: GITHUB_BRANCH,
141
- repo: GITHUB_REPO,
142
- updatedAt: new Date().toISOString(),
143
- };
144
- fs.writeFileSync(VERSION_FILE, JSON.stringify(data, null, 2) + '\n');
145
- }
146
-
147
- // ── 下载与解压 ──────────────────────────────────────────────────
148
-
149
- /**
150
- * 从 GitHub 下载 tarball 并解压 skills 目录到 superpowers/skills/。
151
- * @returns {Promise<{sha: string, skillCount: number}>}
152
- */
153
- async function fetchLatestSkills() {
154
- // 1. 获取最新 commit SHA
155
- log('正在获取 superpowers 最新版本信息...', 'info');
156
- const sha = await fetchLatestCommitSha();
157
- const localSha = getLocalVersion();
158
-
159
- if (sha === localSha) {
160
- log(`已是最新版本 (${sha.slice(0, 8)})`, 'success');
161
- return { sha, skillCount: getSkillCount(), skipped: true };
162
- }
163
-
164
- // 2. 下载 tarball
165
- log(`正在下载 superpowers (${GITHUB_BRANCH}@${sha.slice(0, 8)})...`, 'info');
166
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cbb-sp-'));
167
- const tarballPath = path.join(tmpDir, 'superpowers.tar.gz');
168
-
169
- try {
170
- await downloadToFile(GITHUB_TARBALL_URL, tarballPath);
171
-
172
- // 3. 解压 tarball
173
- log('正在解压...', 'info');
174
- execSync(`tar -xzf "${tarballPath}" -C "${tmpDir}"`, { stdio: 'pipe' });
175
-
176
- // 4. 找到解压后的目录(GitHub tarball 解压后目录名格式:obra-superpowers-<short-sha>)
177
- const entries = fs.readdirSync(tmpDir).filter(f => {
178
- const p = path.join(tmpDir, f);
179
- return fs.statSync(p).isDirectory() && f.startsWith('obra-superpowers');
180
- });
181
- if (entries.length === 0) {
182
- throw new Error('解压后未找到 superpowers 目录');
183
- }
184
- const extractedDir = path.join(tmpDir, entries[0]);
185
- const extractedSkills = path.join(extractedDir, 'skills');
186
-
187
- if (!fs.existsSync(extractedSkills)) {
188
- throw new Error('解压后未找到 skills 目录');
189
- }
190
-
191
- // 5. 清空并复制 skills
192
- if (fs.existsSync(SUPERPOWERS_SKILLS_DIR)) {
193
- fs.rmSync(SUPERPOWERS_SKILLS_DIR, { recursive: true });
194
- }
195
- fs.mkdirSync(SUPERPOWERS_SKILLS_DIR, { recursive: true });
196
- fs.cpSync(extractedSkills, SUPERPOWERS_SKILLS_DIR, { recursive: true });
197
-
198
- // 6. 记录版本
199
- writeVersion(sha);
200
-
201
- const skillCount = getSkillCount();
202
- log(`已更新 superpowers skills: ${localSha ? localSha.slice(0, 8) : '(无)'} → ${sha.slice(0, 8)} (${skillCount} 个 skills)`, 'success');
203
-
204
- return { sha, skillCount, skipped: false };
205
- } finally {
206
- // 清理临时目录
207
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) { /* ignore */ }
208
- }
209
- }
210
-
211
- // ── Skills 校验 ──────────────────────────────────────────────────
212
-
213
- /**
214
- * 获取本地 skills 目录中的 skill 数量。
215
- */
216
- function getSkillCount() {
217
- if (!fs.existsSync(SUPERPOWERS_SKILLS_DIR)) return 0;
218
- return fs.readdirSync(SUPERPOWERS_SKILLS_DIR)
219
- .filter(f => {
220
- const p = path.join(SUPERPOWERS_SKILLS_DIR, f);
221
- return fs.statSync(p).isDirectory();
222
- }).length;
223
- }
224
-
225
- /**
226
- * 校验本地 skills 完整性:所有预期的 skill 目录存在且包含 SKILL.md。
227
- * @returns {{valid: boolean, missing: string[], incomplete: string[]}}
228
- */
229
- function validateSkills() {
230
- const missing = [];
231
- const incomplete = [];
232
-
233
- for (const skill of EXPECTED_SKILLS) {
234
- const skillDir = path.join(SUPERPOWERS_SKILLS_DIR, skill);
235
- if (!fs.existsSync(skillDir)) {
236
- missing.push(skill);
237
- } else if (!fs.existsSync(path.join(skillDir, 'SKILL.md'))) {
238
- incomplete.push(skill);
239
- }
240
- }
241
-
242
- return {
243
- valid: missing.length === 0 && incomplete.length === 0,
244
- missing,
245
- incomplete,
246
- };
247
- }
248
-
249
- // ── 导出 ──────────────────────────────────────────────────────────
250
-
251
- module.exports = {
252
- // 常量
253
- SUPERPOWERS_DIR,
254
- SUPERPOWERS_SKILLS_DIR,
255
- VERSION_FILE,
256
- EXPECTED_SKILLS,
257
- GITHUB_REPO,
258
- GITHUB_BRANCH,
259
-
260
- // 核心函数
261
- fetchLatestSkills,
262
- getLocalVersion,
263
- validateSkills,
264
- getSkillCount,
265
- };
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Superpowers Skills 管理模块
5
+ *
6
+ * 负责按 pin 的 release tag 从 GitHub 下载 obra/superpowers 的 skills 并部署到本项目。
7
+ * 提供 cbbm update --upstream-superpowers 选项,以及 init 时的自动部署能力。
8
+ *
9
+ * 网络:GitHub 直连可能被墙,走 config/upstream-mirrors.json 的镜像前缀列表
10
+ * (按顺序尝试,首个成功即用;空字符串 = 直连)。
11
+ *
12
+ * 升级方式:cbbm update --upstream-superpowers(按 .version 的 tag 幂等重拉)
13
+ * 版本检查:cbbm check-upstream
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+
20
+ const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
21
+
22
+ // Superpowers 本地存储目录
23
+ const SUPERPOWERS_DIR = path.join(SHARED_STANDARDS_DIR, 'superpowers');
24
+ const SUPERPOWERS_SKILLS_DIR = path.join(SUPERPOWERS_DIR, 'skills');
25
+ const VERSION_FILE = path.join(SUPERPOWERS_DIR, '.version');
26
+
27
+ // GitHub 配置
28
+ const GITHUB_REPO = 'obra/superpowers';
29
+
30
+ // 预期的 skill 列表(15 个)
31
+ const EXPECTED_SKILLS = [
32
+ 'brainstorming',
33
+ 'diagnosing-superpowers',
34
+ 'dispatching-parallel-agents',
35
+ 'executing-plans',
36
+ 'finishing-a-development-branch',
37
+ 'receiving-code-review',
38
+ 'requesting-code-review',
39
+ 'subagent-driven-development',
40
+ 'systematic-debugging',
41
+ 'test-driven-development',
42
+ 'using-git-worktrees',
43
+ 'using-superpowers',
44
+ 'verification-before-completion',
45
+ 'writing-plans',
46
+ 'writing-skills',
47
+ ];
48
+
49
+ const { log } = require('../utils/output');
50
+ const { extractTarGz } = require('../utils/tar');
51
+ const { loadMirrors, fetchWithMirrors } = require('../utils/upstream');
52
+
53
+ // ── 版本管理(pin)────────────────────────────────────────────
54
+
55
+ /**
56
+ * 读取本地 pin(.version 的 tag 字段)。
57
+ * 兼容旧格式:无 tag 时回退 sha。
58
+ * @returns {string|null}
59
+ */
60
+ function getLocalVersion() {
61
+ if (!fs.existsSync(VERSION_FILE)) return null;
62
+ try {
63
+ const content = fs.readFileSync(VERSION_FILE, 'utf-8').trim();
64
+ if (content.startsWith('{')) {
65
+ const data = JSON.parse(content);
66
+ return data.tag || data.sha || null;
67
+ }
68
+ return content || null;
69
+ } catch (_) {
70
+ return null;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 写入版本信息到 .version 文件。
76
+ * @param {string} tag
77
+ * @param {string} sha
78
+ */
79
+ function writeVersion(tag, sha) {
80
+ const data = {
81
+ tag,
82
+ sha,
83
+ repo: GITHUB_REPO,
84
+ updatedAt: new Date().toISOString(),
85
+ };
86
+ fs.writeFileSync(VERSION_FILE, JSON.stringify(data, null, 2) + '\n');
87
+ }
88
+
89
+ // ── 上游拉取 ──────────────────────────────────────────────────
90
+
91
+ /** tarball 下载地址(按镜像前缀拼接) */
92
+ function tarballUrl(mirror, tag) {
93
+ return `${mirror}https://api.github.com/repos/${GITHUB_REPO}/tarball/${tag}`;
94
+ }
95
+
96
+ /** tag → commit sha(走镜像) */
97
+ async function fetchTagSha(tag) {
98
+ const mirrors = loadMirrors('superpowers');
99
+ const { buffer } = await fetchWithMirrors(
100
+ mirrors,
101
+ m => `${m}https://api.github.com/repos/${GITHUB_REPO}/git/ref/tags/${tag}`
102
+ );
103
+ const data = JSON.parse(buffer.toString('utf-8'));
104
+ // annotated tag 指向 tag 对象(需再解一层);lightweight tag 直接是 commit
105
+ return (data.object && data.object.sha) || null;
106
+ }
107
+
108
+ /** 最新 release tag(走镜像,用于 check-upstream) */
109
+ async function fetchLatestTag() {
110
+ const mirrors = loadMirrors('superpowers');
111
+ const { buffer } = await fetchWithMirrors(
112
+ mirrors,
113
+ m => `${m}https://api.github.com/repos/${GITHUB_REPO}/releases/latest`
114
+ );
115
+ const data = JSON.parse(buffer.toString('utf-8'));
116
+ return data.tag_name || null;
117
+ }
118
+
119
+ // ── 下载与部署 ────────────────────────────────────────────────
120
+
121
+ /**
122
+ * 按 pin 的 tag 下载 tarball,解压出 skills/ 覆盖到 superpowers/skills/。
123
+ * 幂等:重复执行结果一致。
124
+ * @returns {Promise<{tag: string, skillCount: number, skipped: boolean}>}
125
+ */
126
+ async function fetchLatestSkills() {
127
+ const pin = getLocalVersion();
128
+ if (!pin) {
129
+ throw new Error(
130
+ `未找到 superpowers 版本 pin(${path.relative(SHARED_STANDARDS_DIR, VERSION_FILE)} 缺失或损坏)。\n` +
131
+ `请先写入 { "tag": "vX.Y.Z" } 再执行。`
132
+ );
133
+ }
134
+
135
+ // pin 一致且 skills 齐全 → 幂等跳过
136
+ const validation = validateSkills();
137
+ if (validation.valid) {
138
+ log(`superpowers 内容已按 pin ${pin} 就位,跳过重拉`, 'success');
139
+ return { tag: pin, skillCount: getSkillCount(), skipped: true };
140
+ }
141
+ log(`检测到 skills 不完整,按 pin ${pin} 重新拉取...`, 'info');
142
+
143
+ const mirrors = loadMirrors('superpowers');
144
+ log(`正在获取 superpowers ${pin}(${mirrors.map(m => m || '直连').join(' → ')})...`, 'info');
145
+ const { buffer, mirror } = await fetchWithMirrors(mirrors, m => tarballUrl(m, pin));
146
+ log(`已从 ${mirror || '(直连)'} 下载 ${(buffer.length / 1024).toFixed(0)} KB`, 'info');
147
+
148
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cbb-sp-'));
149
+ try {
150
+ log('正在解压...', 'info');
151
+ extractTarGz(buffer, tmpDir);
152
+
153
+ // GitHub tarball 顶层目录名格式:obra-superpowers-<short-sha>
154
+ const entries = fs.readdirSync(tmpDir).filter(f => {
155
+ const p = path.join(tmpDir, f);
156
+ return fs.statSync(p).isDirectory() && f.startsWith('obra-superpowers');
157
+ });
158
+ if (entries.length === 0) throw new Error('解压后未找到 superpowers 目录');
159
+
160
+ const extractedSkills = path.join(tmpDir, entries[0], 'skills');
161
+ if (!fs.existsSync(extractedSkills)) throw new Error('解压后未找到 skills 目录');
162
+
163
+ // 覆盖 skills/
164
+ fs.rmSync(SUPERPOWERS_SKILLS_DIR, { recursive: true, force: true });
165
+ fs.mkdirSync(SUPERPOWERS_SKILLS_DIR, { recursive: true });
166
+ fs.cpSync(extractedSkills, SUPERPOWERS_SKILLS_DIR, { recursive: true });
167
+
168
+ // 记录版本(tag + 其指向的 sha)
169
+ let sha = null;
170
+ try { sha = await fetchTagSha(pin); } catch (_) { /* sha 记录失败不阻塞 */ }
171
+ writeVersion(pin, sha);
172
+
173
+ const skillCount = getSkillCount();
174
+ log(`superpowers skills 已更新: ${pin}(${skillCount} 个)`, 'success');
175
+ return { tag: pin, skillCount, skipped: false };
176
+ } finally {
177
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) { /* ignore */ }
178
+ }
179
+ }
180
+
181
+ /**
182
+ * 检查上游是否有新版本(不改动任何文件)。
183
+ * @returns {Promise<{current: string|null, latest: string|null, outdated: boolean}>}
184
+ */
185
+ async function checkUpstream() {
186
+ const current = getLocalVersion();
187
+ const latest = await fetchLatestTag();
188
+ const outdated = Boolean(current && latest && current !== latest);
189
+ return { current, latest, outdated };
190
+ }
191
+
192
+ // ── Skills 校验 ───────────────────────────────────────────────
193
+
194
+ /**
195
+ * 获取本地 skills 目录中的 skill 数量。
196
+ */
197
+ function getSkillCount() {
198
+ if (!fs.existsSync(SUPERPOWERS_SKILLS_DIR)) return 0;
199
+ return fs.readdirSync(SUPERPOWERS_SKILLS_DIR)
200
+ .filter(f => {
201
+ const p = path.join(SUPERPOWERS_SKILLS_DIR, f);
202
+ return fs.statSync(p).isDirectory();
203
+ }).length;
204
+ }
205
+
206
+ /**
207
+ * 校验本地 skills 完整性:所有预期的 skill 目录存在且包含 SKILL.md。
208
+ * @returns {{valid: boolean, missing: string[], incomplete: string[]}}
209
+ */
210
+ function validateSkills() {
211
+ const missing = [];
212
+ const incomplete = [];
213
+
214
+ for (const skill of EXPECTED_SKILLS) {
215
+ const skillDir = path.join(SUPERPOWERS_SKILLS_DIR, skill);
216
+ if (!fs.existsSync(skillDir)) {
217
+ missing.push(skill);
218
+ } else if (!fs.existsSync(path.join(skillDir, 'SKILL.md'))) {
219
+ incomplete.push(skill);
220
+ }
221
+ }
222
+
223
+ return {
224
+ valid: missing.length === 0 && incomplete.length === 0,
225
+ missing,
226
+ incomplete,
227
+ };
228
+ }
229
+
230
+ // ── 导出 ──────────────────────────────────────────────────────
231
+
232
+ module.exports = {
233
+ // 常量
234
+ SUPERPOWERS_DIR,
235
+ SUPERPOWERS_SKILLS_DIR,
236
+ VERSION_FILE,
237
+ EXPECTED_SKILLS,
238
+ GITHUB_REPO,
239
+
240
+ // 核心函数
241
+ fetchLatestSkills,
242
+ checkUpstream,
243
+ getLocalVersion,
244
+ validateSkills,
245
+ getSkillCount,
246
+ };
@@ -23,12 +23,27 @@ const CACHE_FILE = path.join(os.homedir(), '.cbb', '.update-check-cache.json');
23
23
  const CACHE_TTL = 24 * 60 * 60 * 1000;
24
24
 
25
25
  function getProjectDir() {
26
- return process.env.QODER_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
26
+ return process.env.WORKBUDDY_PROJECT_DIR
27
+ || process.env.CODEBUDDY_PROJECT_DIR
28
+ || process.env.TRAE_CN_PROJECT_DIR
29
+ || process.env.TRAE_PROJECT_DIR
30
+ || process.env.QODER_PROJECT_DIR
31
+ // MiniMax 同时注入 MINIMAX_PROJECT_DIR 与 CLAUDE_PROJECT_DIR,专属变量优先
32
+ || process.env.MINIMAX_PROJECT_DIR
33
+ || process.env.CLAUDE_PROJECT_DIR
34
+ || process.cwd();
27
35
  }
28
36
 
29
37
  /** 通过环境变量检测当前调用方对应的适配器名称 */
30
38
  function detectAdapterName() {
39
+ // 兼容变量(CLAUDE_PROJECT_DIR)由 Trae / WorkBuddy / MiniMax 一并注入,故优先判定各自专属变量;
40
+ // CODEBUDDY_PROJECT_DIR 为 WorkBuddy 沿用的历史变量名,一并识别为 workbuddy;
41
+ // TRAE_CN_PROJECT_DIR / TRAE_PROJECT_DIR 为中国版 Trae 的变量名(不同版本注入不一),均识别为 trae;
42
+ // MINIMAX_PROJECT_DIR 为 MiniMax Code 专属变量(插件 hook 注入,与 CLAUDE_PROJECT_DIR 同时存在)
43
+ if (process.env.WORKBUDDY_PROJECT_DIR || process.env.CODEBUDDY_PROJECT_DIR) return 'workbuddy';
44
+ if (process.env.TRAE_CN_PROJECT_DIR || process.env.TRAE_PROJECT_DIR) return 'trae';
31
45
  if (process.env.QODER_PROJECT_DIR) return 'qoder';
46
+ if (process.env.MINIMAX_PROJECT_DIR) return 'minimax';
32
47
  if (process.env.CLAUDE_PROJECT_DIR) return 'claude-code';
33
48
  return null;
34
49
  }
@@ -134,7 +149,7 @@ async function main() {
134
149
  process.stdout.write(Buffer.from(JSON.stringify({
135
150
  hookSpecificOutput: {
136
151
  hookEventName: 'UserPromptSubmit',
137
- additionalContext: `[系统提示] 检测到 ${message}\n请用一句话友好地提醒用户当前安装的开发规范有新版本,并给出上面的升级命令。`,
152
+ additionalContext: `[系统提示] 检测到 ${message}\n请用一句话友好地提醒用户,且必须以「你正在使用的团队AI开发规范CBB有新版本可升级」开头,并给出上面的升级命令。`,
138
153
  },
139
154
  }) + '\n', 'utf8'));
140
155
  cache.notifiedSessions[notifiedKey] = sessionId;