@jspg-ai/coding-bb 0.0.1

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 (183) hide show
  1. package/README.md +41 -0
  2. package/cbb/dev-standards/rules/cbb-ai-behavior.md +104 -0
  3. package/cbb/dev-standards/rules/cbb-coding-rule.md +50 -0
  4. package/cbb/dev-standards/rules/cbb-priority.md +56 -0
  5. package/cbb/dev-standards/skills/architecture-specs/SKILL.md +129 -0
  6. package/cbb/dev-standards/skills/coding-specs/SKILL.md +376 -0
  7. package/cbb/dev-standards/skills/coding-specs/references/concurrency.md +53 -0
  8. package/cbb/dev-standards/skills/coding-specs/references/config-center.md +16 -0
  9. package/cbb/dev-standards/skills/coding-specs/references/distributed.md +42 -0
  10. package/cbb/dev-standards/skills/coding-specs/references/es-coding.md +60 -0
  11. package/cbb/dev-standards/skills/coding-specs/references/scheduled-task.md +25 -0
  12. package/cbb/dev-standards/skills/coding-specs/references/security.md +11 -0
  13. package/cbb/dev-standards/skills/coding-specs/references/unit-testing.md +43 -0
  14. package/cbb/dev-standards/skills/es-design-specs/SKILL.md +104 -0
  15. package/cbb/dev-standards/skills/mysql-design-specs/SKILL.md +89 -0
  16. package/cbb/lib/install/claude-code.js +20 -0
  17. package/cbb/lib/install/cleanup.js +64 -0
  18. package/cbb/lib/install/init.js +1875 -0
  19. package/cbb/lib/install/qoder.js +20 -0
  20. package/cbb/lib/install/workspaces.js +232 -0
  21. package/cbb/lib/openspec/index.js +554 -0
  22. package/cbb/lib/superpowers/index.js +265 -0
  23. package/cbb/lib/utils/check-update.js +147 -0
  24. package/cbb/lib/utils/checkbox.js +383 -0
  25. package/cbb/lib/utils/gitignore.js +69 -0
  26. package/cbb/lib/utils/output.js +64 -0
  27. package/cbb/lib/utils/settings.js +119 -0
  28. package/cbb/lib/utils/version.js +135 -0
  29. package/cbb/lib/wiki/api-client.js +358 -0
  30. package/cbb/lib/wiki/cli.js +427 -0
  31. package/cbb/lib/wiki/convert.js +464 -0
  32. package/cbb/lib/wiki/index.js +218 -0
  33. package/cbb/lib/wiki/mermaid-guard.js +103 -0
  34. package/cbb/lib/wiki/split.js +131 -0
  35. package/cbb/tools/cbb-decompile-jar/SKILL.md +220 -0
  36. package/cbb/tools/cbb-decompile-jar/scripts/decompile.py +863 -0
  37. package/cbb/tools/cbb-design-to-wiki/SKILL.md +180 -0
  38. package/cbb/tools/cbb-mvn-guide/SKILL.md +281 -0
  39. package/cbb/tools/cbb-mvn-guide/scripts/mvn_jdk_manager.py +378 -0
  40. package/cbb/tools/cbb-mvn-guide/scripts/mvn_pom_jdk_reader.py +197 -0
  41. package/cbb/tools/cbb-plantuml-authoring/SKILL.md +448 -0
  42. package/cbb/tools/cbb-plantuml-authoring/references/drawing-templates.md +479 -0
  43. package/cbb/tools/cbb-wiki-ops/SKILL.md +134 -0
  44. package/cbb/tools/cbb-wiki-ops/scripts/check-auth.js +44 -0
  45. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +96 -0
  46. package/cbb/worktrees/_shared/scripts/find-workspace-root.js +67 -0
  47. package/cbb/worktrees/_shared/scripts/push-core.js +136 -0
  48. package/cbb/worktrees/_shared/scripts/silence-popup.js +23 -0
  49. package/cbb/worktrees/commands/close.md +64 -0
  50. package/cbb/worktrees/commands/extend.md +65 -0
  51. package/cbb/worktrees/commands/init.md +55 -0
  52. package/cbb/worktrees/commands/push.md +42 -0
  53. package/cbb/worktrees/skills/openspec-close-worktree/SKILL.md +449 -0
  54. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-env.js +76 -0
  55. package/cbb/worktrees/skills/openspec-close-worktree/scripts/check-unarchived.js +48 -0
  56. package/cbb/worktrees/skills/openspec-close-worktree/scripts/delete-branches.js +136 -0
  57. package/cbb/worktrees/skills/openspec-close-worktree/scripts/discover-apps.js +76 -0
  58. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +96 -0
  59. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-workspace-root.js +67 -0
  60. package/cbb/worktrees/skills/openspec-close-worktree/scripts/remove-worktrees.js +124 -0
  61. package/cbb/worktrees/skills/openspec-close-worktree/scripts/safety-check.js +174 -0
  62. package/cbb/worktrees/skills/openspec-close-worktree/scripts/silence-popup.js +23 -0
  63. package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +390 -0
  64. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +95 -0
  65. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +98 -0
  66. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +135 -0
  67. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-target-worktree.js +96 -0
  68. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/find-workspace-root.js +67 -0
  69. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +150 -0
  70. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +88 -0
  71. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/push-core.js +136 -0
  72. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/silence-popup.js +23 -0
  73. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +93 -0
  74. package/cbb/worktrees/skills/openspec-init-worktree/SKILL.md +539 -0
  75. package/cbb/worktrees/skills/openspec-init-worktree/scripts/auto-open.js +136 -0
  76. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env-deep.js +87 -0
  77. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-env.js +90 -0
  78. package/cbb/worktrees/skills/openspec-init-worktree/scripts/check-repos.js +171 -0
  79. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js +103 -0
  80. package/cbb/worktrees/skills/openspec-init-worktree/scripts/create-worktrees.js +154 -0
  81. package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +67 -0
  82. package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +93 -0
  83. package/cbb/worktrees/skills/openspec-init-worktree/scripts/install-ai.js +177 -0
  84. package/cbb/worktrees/skills/openspec-init-worktree/scripts/parse-config.js +67 -0
  85. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-branches.js +101 -0
  86. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +136 -0
  87. package/cbb/worktrees/skills/openspec-init-worktree/scripts/silence-popup.js +23 -0
  88. package/cbb/worktrees/skills/openspec-init-worktree/scripts/sync-repos.js +95 -0
  89. package/cbb/worktrees/skills/openspec-init-worktree/scripts/update-gitignore.js +134 -0
  90. package/cbb/worktrees/skills/openspec-push-worktrees/SKILL.md +307 -0
  91. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/commit-worktrees.js +269 -0
  92. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-target-worktree.js +96 -0
  93. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/find-workspace-root.js +67 -0
  94. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-core.js +136 -0
  95. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/push-worktrees.js +159 -0
  96. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +23 -0
  97. package/config/cbb.yaml +6 -0
  98. package/config/config.sample.json +16 -0
  99. package/config/openspec/config.yaml +28 -0
  100. package/config/openspec/schemas/spec-driven/schema.yaml +247 -0
  101. package/config/openspec/schemas/spec-driven/templates/design.md +475 -0
  102. package/config/openspec/schemas/spec-driven/templates/proposal.md +58 -0
  103. package/config/openspec/schemas/spec-driven/templates/spec.md +8 -0
  104. package/config/openspec/schemas/spec-driven/templates/tasks.md +9 -0
  105. package/config/workspaces.json +15 -0
  106. package/openspec/.version +6 -0
  107. package/openspec/commands/apply.md +182 -0
  108. package/openspec/commands/archive.md +223 -0
  109. package/openspec/commands/bulk-archive.md +334 -0
  110. package/openspec/commands/continue.md +112 -0
  111. package/openspec/commands/explore.md +206 -0
  112. package/openspec/commands/ff.md +111 -0
  113. package/openspec/commands/new.md +70 -0
  114. package/openspec/commands/onboard.md +555 -0
  115. package/openspec/commands/propose.md +157 -0
  116. package/openspec/commands/sync.md +256 -0
  117. package/openspec/commands/update.md +85 -0
  118. package/openspec/commands/verify.md +169 -0
  119. package/openspec/skills/openspec-apply-change/SKILL.md +187 -0
  120. package/openspec/skills/openspec-archive-change/SKILL.md +181 -0
  121. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -0
  122. package/openspec/skills/openspec-continue-change/SKILL.md +117 -0
  123. package/openspec/skills/openspec-explore/SKILL.md +342 -0
  124. package/openspec/skills/openspec-ff-change/SKILL.md +116 -0
  125. package/openspec/skills/openspec-new-change/SKILL.md +76 -0
  126. package/openspec/skills/openspec-onboard/SKILL.md +560 -0
  127. package/openspec/skills/openspec-propose/SKILL.md +162 -0
  128. package/openspec/skills/openspec-sync-specs/SKILL.md +261 -0
  129. package/openspec/skills/openspec-update-change/SKILL.md +90 -0
  130. package/openspec/skills/openspec-verify-change/SKILL.md +174 -0
  131. package/package.json +46 -0
  132. package/superpowers/.version +6 -0
  133. package/superpowers/skills/brainstorming/SKILL.md +250 -0
  134. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -0
  135. package/superpowers/skills/brainstorming/scripts/helper.js +167 -0
  136. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -0
  137. package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -0
  138. package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -0
  139. package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  140. package/superpowers/skills/brainstorming/visual-companion.md +299 -0
  141. package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -0
  142. package/superpowers/skills/executing-plans/SKILL.md +64 -0
  143. package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -0
  144. package/superpowers/skills/receiving-code-review/SKILL.md +205 -0
  145. package/superpowers/skills/requesting-code-review/SKILL.md +95 -0
  146. package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -0
  147. package/superpowers/skills/subagent-driven-development/SKILL.md +568 -0
  148. package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -0
  149. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -0
  150. package/superpowers/skills/subagent-driven-development/scripts/review-package +46 -0
  151. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
  152. package/superpowers/skills/subagent-driven-development/scripts/task-brief +41 -0
  153. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
  154. package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
  155. package/superpowers/skills/systematic-debugging/SKILL.md +283 -0
  156. package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  157. package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
  158. package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
  159. package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -0
  160. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
  161. package/superpowers/skills/systematic-debugging/test-academic.md +14 -0
  162. package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
  163. package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
  164. package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
  165. package/superpowers/skills/test-driven-development/SKILL.md +320 -0
  166. package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -0
  167. package/superpowers/skills/using-git-worktrees/SKILL.md +167 -0
  168. package/superpowers/skills/using-superpowers/SKILL.md +63 -0
  169. package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -0
  170. package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -0
  171. package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -0
  172. package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -0
  173. package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -0
  174. package/superpowers/skills/verification-before-completion/SKILL.md +120 -0
  175. package/superpowers/skills/writing-plans/SKILL.md +171 -0
  176. package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  177. package/superpowers/skills/writing-skills/SKILL.md +679 -0
  178. package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
  179. package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  180. package/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
  181. package/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
  182. package/superpowers/skills/writing-skills/render-graphs.js +169 -0
  183. package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Superpowers Skills 管理模块
5
+ *
6
+ * 负责从 GitHub (obra/superpowers) 下载 skills 并部署到本项目。
7
+ * 提供 cbb 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
+ };
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 版本检查脚本,由 hook 调用。
5
+ *
6
+ * 通过 UserPromptSubmit 事件触发:用户发消息时,若有新版本则以 additionalContext 注入对话,
7
+ * 由 AI 助手主动告知。会话级去重,每会话只提示一次。
8
+ *
9
+ * 每天最多请求一次 registry;任何异常静默退出 0,绝不打断会话。
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // 共享版本工具(readInstalledVersion / fetchLatest / compareVersions / 常量)
16
+ const versionUtil = require('./version');
17
+
18
+ const CACHE_FILE = path.join('.cbb', '.update-check-cache.json');
19
+ const CACHE_TTL = 24 * 60 * 60 * 1000;
20
+
21
+ function getProjectDir() {
22
+ return process.env.QODER_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
23
+ }
24
+
25
+ /** 通过环境变量检测当前调用方对应的适配器名称 */
26
+ function detectAdapterName() {
27
+ if (process.env.QODER_PROJECT_DIR) return 'qoder';
28
+ if (process.env.CLAUDE_PROJECT_DIR) return 'claude-code';
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * 读取项目内已安装的版本号,优先按适配器取版本(adapterVersions 字段),
34
+ * 缺失时回退到清单的全局 version。cbb setup 的升级确认不走这个函数,
35
+ * 直接用 version.js 的 readInstalledVersion(只看全局 version)。
36
+ */
37
+ function readInstalledVersion(projectDir, adapterName) {
38
+ const manifestPath = path.join(projectDir, versionUtil.MANIFEST_FILE);
39
+ if (!fs.existsSync(manifestPath)) return null;
40
+ try {
41
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
42
+ if (adapterName && manifest.adapterVersions && manifest.adapterVersions[adapterName]) {
43
+ return manifest.adapterVersions[adapterName] || null;
44
+ }
45
+ return manifest.version || null;
46
+ } catch (_) {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function readCache(projectDir) {
52
+ const cachePath = path.join(projectDir, CACHE_FILE);
53
+ if (!fs.existsSync(cachePath)) return null;
54
+ try {
55
+ return JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
56
+ } catch (_) {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ function writeCache(projectDir, data) {
62
+ const cachePath = path.join(projectDir, CACHE_FILE);
63
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
64
+ fs.writeFileSync(cachePath, JSON.stringify(data) + '\n');
65
+ }
66
+
67
+ /** 读取 hook 传入的 stdin(session_id);TTY 或异常返回空对象 */
68
+ function readStdin() {
69
+ return new Promise(resolve => {
70
+ if (process.stdin.isTTY) { resolve({}); return; }
71
+ let data = '';
72
+ let done = false;
73
+ const finish = val => { if (!done) { done = true; resolve(val); } };
74
+ const timer = setTimeout(() => finish({}), 500);
75
+ process.stdin.setEncoding('utf-8');
76
+ process.stdin.on('data', chunk => { data += chunk; });
77
+ process.stdin.on('end', () => {
78
+ clearTimeout(timer);
79
+ try {
80
+ const json = JSON.parse(data);
81
+ finish({ sessionId: json.session_id || null });
82
+ } catch (_) { finish({}); }
83
+ });
84
+ process.stdin.on('error', () => { clearTimeout(timer); finish({}); });
85
+ });
86
+ }
87
+
88
+ function buildMessage(installed, latest, isPrerelease) {
89
+ const installTag = isPrerelease ? '@beta' : '@latest';
90
+ return `📢 coding-bb 有新版本:${installed} → ${latest}\n` +
91
+ `升级步骤:\n` +
92
+ ` 1. npm install -g ${versionUtil.PKG_NAME}${installTag}\n` +
93
+ ` 2. cd <项目目录> && cbb update`;
94
+ }
95
+
96
+ async function main() {
97
+ const { sessionId } = await readStdin();
98
+ const projectDir = getProjectDir();
99
+ const adapterName = detectAdapterName();
100
+
101
+ const installed = readInstalledVersion(projectDir, adapterName);
102
+ if (!installed) return; // 本项目未安装本规范(或当前适配器无版本记录)
103
+
104
+ const isPrerelease = versionUtil.detectIsPrerelease(installed);
105
+ const tag = isPrerelease ? 'beta' : 'latest';
106
+ const now = Date.now();
107
+ let cache = readCache(projectDir) || {};
108
+
109
+ // 决定最新版本:命中每日缓存且 tag 一致则复用,否则请求 registry
110
+ let latest = cache.latest;
111
+ const cacheFresh = cache.lastCheck && (now - cache.lastCheck) < CACHE_TTL && cache.latest && cache.tag === tag;
112
+ if (!cacheFresh) {
113
+ try {
114
+ latest = await versionUtil.fetchLatest(tag);
115
+ } catch (_) {
116
+ return; // 网络失败静默
117
+ }
118
+ cache = { ...cache, lastCheck: now, latest, tag };
119
+ writeCache(projectDir, cache);
120
+ }
121
+
122
+ if (!latest || versionUtil.compareVersions(latest, installed) <= 0) return; // 已是最新
123
+
124
+ const message = buildMessage(installed, latest, isPrerelease);
125
+
126
+ // Hook 调用(有 sessionId):以 additionalContext 注入,由 AI 助手提醒用户
127
+ if (sessionId) {
128
+ // 按适配器分别去重,同一会话同一适配器只提示一次
129
+ const notifiedKey = adapterName || '_default';
130
+ if (!cache.notifiedSessions) cache.notifiedSessions = {};
131
+ if (cache.notifiedSessions[notifiedKey] === sessionId) return; // 本会话本适配器已提示过
132
+ process.stdout.write(Buffer.from(JSON.stringify({
133
+ hookSpecificOutput: {
134
+ hookEventName: 'UserPromptSubmit',
135
+ additionalContext: `[系统提示] 检测到 ${message}\n请用一句话友好地提醒用户当前安装的开发规范有新版本,并给出上面的升级命令。`,
136
+ },
137
+ }) + '\n', 'utf8'));
138
+ cache.notifiedSessions[notifiedKey] = sessionId;
139
+ writeCache(projectDir, cache);
140
+ return;
141
+ }
142
+
143
+ // 手动运行(无 sessionId):stderr 黄色输出
144
+ process.stderr.write(Buffer.from(`\n\x1b[33m${message}\x1b[0m\n\n`, 'utf8'));
145
+ }
146
+
147
+ main().catch(() => {}).finally(() => process.exit(0));