@jspg-ai/coding-bb 0.0.1 → 0.0.2-beta.22

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 (145) hide show
  1. package/cbb/bin/cbb.js +6 -0
  2. package/cbb/bin/cbbm.js +6 -0
  3. package/cbb/dev-standards/rules/cbb-ai-behavior.md +103 -103
  4. package/cbb/dev-standards/rules/cbb-priority.md +55 -55
  5. package/cbb/lib/install/claude-code.js +2 -0
  6. package/cbb/lib/install/init.js +496 -290
  7. package/cbb/lib/install/opencode.js +31 -0
  8. package/cbb/lib/install/qoder.js +2 -0
  9. package/cbb/lib/install/workspaces.js +53 -13
  10. package/cbb/lib/openspec/index.js +3 -3
  11. package/cbb/lib/superpowers/index.js +1 -1
  12. package/cbb/lib/utils/check-update.js +16 -14
  13. package/cbb/lib/utils/gitignore.js +2 -1
  14. package/cbb/lib/utils/output.js +92 -4
  15. package/cbb/lib/utils/version.js +2 -31
  16. package/cbb/lib/wiki/cli.js +25 -25
  17. package/cbb/lib/wiki/index.js +16 -16
  18. package/cbb/tools/cbb-design-to-wiki/SKILL.md +14 -14
  19. package/cbb/tools/cbb-wiki-ops/SKILL.md +22 -22
  20. package/cbb/worktrees/_shared/scripts/find-target-worktree.js +2 -2
  21. package/cbb/worktrees/_shared/scripts/find-workspace-root.js +7 -7
  22. package/cbb/worktrees/_shared/scripts/push-core.js +18 -0
  23. package/cbb/worktrees/commands/{close.md → worktree-close.md} +8 -8
  24. package/cbb/worktrees/commands/{init.md → worktree-init.md} +9 -9
  25. package/cbb/worktrees/commands/{push.md → worktree-push.md} +3 -3
  26. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/SKILL.md +20 -20
  27. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-env.js +4 -4
  28. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-unarchived.js +1 -1
  29. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/delete-branches.js +3 -3
  30. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/discover-apps.js +6 -6
  31. package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-close}/scripts/find-target-worktree.js +2 -2
  32. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-close}/scripts/find-workspace-root.js +7 -7
  33. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/remove-worktrees.js +4 -4
  34. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/safety-check.js +4 -4
  35. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/SKILL.md +81 -107
  36. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/auto-open.js +1 -1
  37. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env-deep.js +4 -4
  38. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env.js +6 -6
  39. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-repos.js +4 -4
  40. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-branches.js +4 -4
  41. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-worktrees.js +8 -7
  42. package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-init}/scripts/find-workspace-root.js +7 -7
  43. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/install-ai.js +33 -33
  44. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/parse-config.js +7 -7
  45. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/push-branches.js +9 -7
  46. package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/push-core.js +18 -0
  47. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/sync-repos.js +10 -12
  48. package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/update-gitignore.js +19 -6
  49. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/SKILL.md +16 -16
  50. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/commit-worktrees.js +2 -2
  51. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/find-target-worktree.js +2 -2
  52. package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-push}/scripts/find-workspace-root.js +7 -7
  53. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-core.js +18 -0
  54. package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-worktrees.js +7 -5
  55. package/config/workspace-config.sample.json +18 -0
  56. package/config/workspaces.json +11 -6
  57. package/openspec/.version +6 -6
  58. package/openspec/commands/apply.md +7 -7
  59. package/openspec/commands/archive.md +7 -7
  60. package/openspec/commands/bulk-archive.md +7 -7
  61. package/openspec/commands/continue.md +7 -7
  62. package/openspec/commands/explore.md +7 -7
  63. package/openspec/commands/ff.md +7 -7
  64. package/openspec/commands/new.md +7 -7
  65. package/openspec/commands/onboard.md +7 -7
  66. package/openspec/commands/propose.md +7 -7
  67. package/openspec/commands/sync.md +7 -7
  68. package/openspec/commands/update.md +7 -7
  69. package/openspec/commands/verify.md +7 -7
  70. package/openspec/skills/openspec-apply-change/SKILL.md +187 -187
  71. package/openspec/skills/openspec-archive-change/SKILL.md +181 -181
  72. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -338
  73. package/openspec/skills/openspec-continue-change/SKILL.md +117 -117
  74. package/openspec/skills/openspec-explore/SKILL.md +342 -342
  75. package/openspec/skills/openspec-ff-change/SKILL.md +116 -116
  76. package/openspec/skills/openspec-new-change/SKILL.md +76 -76
  77. package/openspec/skills/openspec-onboard/SKILL.md +560 -560
  78. package/openspec/skills/openspec-propose/SKILL.md +162 -162
  79. package/openspec/skills/openspec-sync-specs/SKILL.md +261 -261
  80. package/openspec/skills/openspec-update-change/SKILL.md +90 -90
  81. package/openspec/skills/openspec-verify-change/SKILL.md +174 -174
  82. package/package.json +4 -3
  83. package/superpowers/.version +6 -6
  84. package/superpowers/skills/brainstorming/SKILL.md +250 -250
  85. package/superpowers/skills/brainstorming/scripts/helper.js +167 -167
  86. package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -209
  87. package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -120
  88. package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -49
  89. package/superpowers/skills/brainstorming/visual-companion.md +299 -299
  90. package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -167
  91. package/superpowers/skills/executing-plans/SKILL.md +64 -64
  92. package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -225
  93. package/superpowers/skills/receiving-code-review/SKILL.md +205 -205
  94. package/superpowers/skills/requesting-code-review/SKILL.md +95 -95
  95. package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -181
  96. package/superpowers/skills/subagent-driven-development/SKILL.md +568 -568
  97. package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -154
  98. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -115
  99. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -207
  100. package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -119
  101. package/superpowers/skills/systematic-debugging/SKILL.md +283 -283
  102. package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  103. package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -115
  104. package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -122
  105. package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -72
  106. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -169
  107. package/superpowers/skills/systematic-debugging/test-academic.md +14 -14
  108. package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -58
  109. package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -68
  110. package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -69
  111. package/superpowers/skills/test-driven-development/SKILL.md +320 -320
  112. package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -198
  113. package/superpowers/skills/using-git-worktrees/SKILL.md +167 -167
  114. package/superpowers/skills/using-superpowers/SKILL.md +63 -63
  115. package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -23
  116. package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -108
  117. package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -63
  118. package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -56
  119. package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -16
  120. package/superpowers/skills/verification-before-completion/SKILL.md +120 -120
  121. package/superpowers/skills/writing-plans/SKILL.md +171 -171
  122. package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -49
  123. package/superpowers/skills/writing-skills/SKILL.md +679 -679
  124. package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -1150
  125. package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
  126. package/superpowers/skills/writing-skills/persuasion-principles.md +187 -187
  127. package/superpowers/skills/writing-skills/render-graphs.js +169 -169
  128. package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -384
  129. package/cbb/worktrees/commands/extend.md +0 -65
  130. package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +0 -96
  131. package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +0 -390
  132. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +0 -95
  133. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +0 -98
  134. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +0 -135
  135. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +0 -150
  136. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +0 -88
  137. package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +0 -93
  138. package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +0 -67
  139. package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +0 -93
  140. package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +0 -136
  141. package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +0 -23
  142. package/config/config.sample.json +0 -16
  143. /package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/silence-popup.js +0 -0
  144. /package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/silence-popup.js +0 -0
  145. /package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-push}/scripts/silence-popup.js +0 -0
@@ -4,21 +4,25 @@
4
4
  * OpenSpec Shared Standards 安装脚本
5
5
  *
6
6
  * 将AI 编码规范与工作流套件安装到目标项目中。
7
- * 支持工具:Claude Code (.claude/rules/)、Qoder (.qoder/rules/)
7
+ * 支持工具:Claude Code (.claude/rules/)、Qoder (.qoder/rules/)、opencode (.opencode/rules/)
8
8
  *
9
9
  * 使用方式:
10
10
  * npm install -g @jspg-ai/coding-bb # 首次使用
11
11
  * npm install -g @jspg-ai/coding-bb
12
- * cd your-project && cbb init
12
+ * cd your-project && cbbm init
13
13
  */
14
14
 
15
15
  const fs = require('fs');
16
16
  const os = require('os');
17
17
  const path = require('path');
18
18
  const readline = require('readline');
19
- const { execSync } = require('child_process');
19
+ const { execSync, execFile } = require('child_process');
20
+ const { promisify } = require('util');
20
21
  const yaml = require('js-yaml');
21
22
 
23
+ // 异步执行外部命令:长耗时 git 操作不阻塞事件循环,进度行才能持续刷新
24
+ const execFileAsync = promisify(execFile);
25
+
22
26
  const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
23
27
 
24
28
  // 目标项目根目录:默认取当前 cwd,init() / initWorkspace() 可在调用前覆盖。
@@ -35,6 +39,7 @@ const LAST_TOOLS_FILE = path.join(MANIFEST_DIR, '.last-tools');
35
39
  const adapters = [
36
40
  require('./claude-code'),
37
41
  require('./qoder'),
42
+ require('./opencode'),
38
43
  ];
39
44
 
40
45
  // superpowers 部署白名单:仅装 openspec 工作流硬依赖 + 方法论基底(源仓库仍保留全量 14 个,
@@ -48,11 +53,12 @@ const SUPERPOWERS_WHITELIST = [
48
53
  'test-driven-development', // TDD 计划结构 + TDD Evidence 方法论基底
49
54
  ];
50
55
 
51
- // worktree 扩展 skills 部署到沙箱的白名单:init/extend 只在 workspace 根执行
56
+ // worktree 扩展 skills 部署到沙箱的白名单:init 只在 workspace 根执行
52
57
  // (由 installWorkspaceConfig 部署),沙箱内只需 push(推送)与 close(关闭)
53
- const SANDBOX_WORKTREE_SKILLS = ['openspec-push-worktrees', 'openspec-close-worktree'];
58
+ const SANDBOX_WORKTREE_SKILLS = ['cbb-worktree-push', 'cbb-worktree-close'];
54
59
 
55
- // 装到沙箱的 tools skillscbb-wiki-ops 装用户级(与项目无关),Java 工具不装
60
+ // 装到沙箱的 tools skills:仅 cbb-design-to-wiki(openspec 设计阶段动作)。
61
+ // cbb-wiki-ops 当前不分发(保留在源仓库,有用户需要时再加入 USER_LEVEL_SKILLS);Java 工具不装。
56
62
  const SANDBOX_TOOLS_SKILLS = ['cbb-design-to-wiki'];
57
63
 
58
64
  // settings.json hook 工具
@@ -81,7 +87,7 @@ const openspec = require('../openspec');
81
87
 
82
88
  // ── 颜色与输出辅助 ──────────────────────────────────────
83
89
 
84
- const { COLOR, COLOR_ENABLED, c, log, section, field, LOG_STYLES } = require('../utils/output');
90
+ const { COLOR, COLOR_ENABLED, c, log, section, step, padVisual, progress, LOG_STYLES } = require('../utils/output');
85
91
 
86
92
  // 检测是否已安装(清单文件存在即视为已安装)
87
93
  function detectExistingInstall() {
@@ -275,6 +281,63 @@ function deployCoreRules(adapter, isUpgrade, managedPaths) {
275
281
 
276
282
  }
277
283
 
284
+ // ── opencode instructions 登记 ────────────────────────────────
285
+
286
+ /**
287
+ * opencode 无原生 rules 目录:把 .opencode/rules/*.md 的 glob 登记进项目根
288
+ * opencode.json 的 instructions(官方支持的加载方式)。
289
+ * - 文件不存在:创建(记入清单,卸载时整体删除)
290
+ * - 文件存在:仅追加 pattern(幂等);若清单显示该文件本由本包创建,则续登
291
+ * newPaths 防止升级清理误删;JSONC(含注释)解析失败则只提示不改写
292
+ */
293
+ function injectInstructions(adapter, newPaths, managedPaths) {
294
+ const cfgPath = path.join(TARGET_DIR, 'opencode.json');
295
+ const pattern = adapter.instructionsPattern;
296
+ if (!fs.existsSync(cfgPath)) {
297
+ const cfg = { $schema: 'https://opencode.ai/config.json', instructions: [pattern] };
298
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
299
+ newPaths.add('opencode.json');
300
+ log(`生成 opencode.json 并登记 rules 加载(instructions: ${pattern})`, 'success');
301
+ return;
302
+ }
303
+ let cfg;
304
+ try {
305
+ cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
306
+ } catch (_) {
307
+ log(`opencode.json 解析失败(可能含注释),未自动登记。请手动在 instructions 中加入 "${pattern}"`, 'warn');
308
+ return;
309
+ }
310
+ const list = Array.isArray(cfg.instructions) ? cfg.instructions : [];
311
+ if (!list.includes(pattern)) {
312
+ cfg.instructions = [...list, pattern];
313
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
314
+ log(`已在 opencode.json instructions 中登记 ${pattern}`, 'success');
315
+ }
316
+ // 本包此前创建的文件:续登清单,避免升级清理误删
317
+ if (managedPaths.has('opencode.json')) newPaths.add('opencode.json');
318
+ }
319
+
320
+ /** 卸载时移除本包登记的 instructions pattern(用户创建的 opencode.json 只删 pattern 不删文件) */
321
+ function removeInstructionsRegistrations() {
322
+ adapters.forEach(adapter => {
323
+ if (!adapter.instructionsPattern) return;
324
+ const cfgPath = path.join(TARGET_DIR, 'opencode.json');
325
+ if (!fs.existsSync(cfgPath)) return;
326
+ let cfg;
327
+ try {
328
+ cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
329
+ } catch (_) {
330
+ return; // 不可解析的文件不动
331
+ }
332
+ if (Array.isArray(cfg.instructions) && cfg.instructions.includes(adapter.instructionsPattern)) {
333
+ cfg.instructions = cfg.instructions.filter(p => p !== adapter.instructionsPattern);
334
+ if (cfg.instructions.length === 0) delete cfg.instructions;
335
+ fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
336
+ log('opencode.json: 移除本包登记的 instructions,保留用户配置', 'success');
337
+ }
338
+ });
339
+ }
340
+
278
341
  // ── YAML 配置合并 ─────────────────────────────────────────
279
342
 
280
343
  function mergeConfig(ourYaml, userYaml) {
@@ -337,6 +400,29 @@ function generateBackupPath(filePath) {
337
400
 
338
401
  // ── 部署 Skills ──────────────────────────────────────────────
339
402
 
403
+ /**
404
+ * 按适配器的 contentRewrites 改写文本(opencode 侧把 /opsx:xxx 引用
405
+ * 改写为其扁平命令名 /opsx-xxx)。无改写规则的适配器原样返回。
406
+ */
407
+ function applyContentRewrites(adapter, text) {
408
+ if (!adapter.contentRewrites) return text;
409
+ return adapter.contentRewrites.reduce((t, [from, to]) => t.split(from).join(to), text);
410
+ }
411
+
412
+ /** 递归改写目录内所有 .md 文件的适配器引用(opencode skills 部署后调用) */
413
+ function rewriteSkillRefs(adapter, dir) {
414
+ if (!adapter.contentRewrites) return;
415
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
416
+ const p = path.join(dir, e.name);
417
+ if (e.isDirectory()) rewriteSkillRefs(adapter, p);
418
+ else if (e.name.endsWith('.md')) {
419
+ const content = fs.readFileSync(p, 'utf-8');
420
+ const rewritten = applyContentRewrites(adapter, content);
421
+ if (rewritten !== content) fs.writeFileSync(p, rewritten);
422
+ }
423
+ }
424
+ }
425
+
340
426
  function deploySkills(adapter, isUpgrade, managedPaths, srcDir, filter) {
341
427
  const skillsDir = adapter.skillsDir;
342
428
  if (!skillsDir || !fs.existsSync(srcDir)) return { count: 0, names: [] };
@@ -355,6 +441,7 @@ function deploySkills(adapter, isUpgrade, managedPaths, srcDir, filter) {
355
441
  const destDir = path.join(skillsDest, skillName);
356
442
  removePath(destDir);
357
443
  fs.cpSync(path.join(srcDir, skillName), destDir, { recursive: true });
444
+ rewriteSkillRefs(adapter, destDir);
358
445
  });
359
446
 
360
447
  return { count: skillDirs.length, names: skillDirs };
@@ -362,13 +449,15 @@ function deploySkills(adapter, isUpgrade, managedPaths, srcDir, filter) {
362
449
 
363
450
  // ── 部署 Commands ──────────────────────────────────────────────
364
451
 
365
- function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
366
- const commandsDir = adapter.commandsDir;
367
- if (!commandsDir || !fs.existsSync(srcDir)) return { count: 0, names: [] };
368
-
369
- const commandsDest = path.join(TARGET_DIR, commandsDir);
452
+ /**
453
+ * 部署命令文件(扁平 .md)到指定目录并加文件名前缀。
454
+ * opencode 侧:命令名 = 文件名,加适配器前缀扁平化(opsx-propose.md /opsx-propose)。
455
+ * @returns {{count:number, names:string[]}} names 为落盘文件名(含前缀)
456
+ */
457
+ function deployCommandFiles(adapter, srcDir, destDir, prefix, filter) {
458
+ if (!destDir || !fs.existsSync(srcDir)) return { count: 0, names: [] };
370
459
 
371
- fs.mkdirSync(commandsDest, { recursive: true });
460
+ fs.mkdirSync(destDir, { recursive: true });
372
461
 
373
462
  // 读取所有 .md 文件(command 是扁平文件,不像 skill 是子目录)
374
463
  const commandFiles = fs.readdirSync(srcDir)
@@ -377,17 +466,37 @@ function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
377
466
 
378
467
  commandFiles.forEach(f => {
379
468
  const srcPath = path.join(srcDir, f);
380
- const destPath = path.join(commandsDest, f);
469
+ const destPath = path.join(destDir, prefix + f);
381
470
  removePath(destPath);
382
- fs.copyFileSync(srcPath, destPath);
471
+ const content = applyContentRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'));
472
+ fs.writeFileSync(destPath, content);
383
473
  });
384
474
 
385
- return { count: commandFiles.length, names: commandFiles };
475
+ return { count: commandFiles.length, names: commandFiles.map(f => prefix + f) };
476
+ }
477
+
478
+ /** 官方 OpenSpec 命令(openspec/commands/ → adapter.commandsDir) */
479
+ function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
480
+ if (!adapter.commandsDir) return { count: 0, names: [] };
481
+ return deployCommandFiles(
482
+ adapter, srcDir, path.join(TARGET_DIR, adapter.commandsDir), adapter.commandPrefix || '', filter);
483
+ }
484
+
485
+ /** worktree 管理命令(cbb/worktrees/commands/ → adapter.worktreeCommandsDir) */
486
+ function deployWorktreeCommands(adapter, srcDir, filter) {
487
+ if (!adapter.worktreeCommandsDir) return { count: 0, names: [] };
488
+ return deployCommandFiles(
489
+ adapter, srcDir, path.join(TARGET_DIR, adapter.worktreeCommandsDir), adapter.worktreeCommandPrefix || '', filter);
386
490
  }
387
491
 
388
492
  // ── 设置 OpenSpec 工作流 ──────────────────────────────────
389
493
 
494
+ /**
495
+ * 安装 openspec/schemas/* 与 openspec/config.yaml。
496
+ * 不打印结果(由调用方汇总输出),返回 { schemas, config, installed, defaultSchema, removed }。
497
+ */
390
498
  function setupOpenSpec(isUpgrade, managedPaths) {
499
+ const result = { schemas: [], config: null, installed: [], defaultSchema: null, removed: [] };
391
500
  const schemasSrcs = [
392
501
  path.join(SHARED_STANDARDS_DIR, 'config', 'openspec', 'schemas'),
393
502
  ];
@@ -421,7 +530,7 @@ function setupOpenSpec(isUpgrade, managedPaths) {
421
530
  } else if (managedPaths.has(relativePath)) {
422
531
  // 清单中有但本包不再提供 → 清理旧版本
423
532
  removePath(dest);
424
- log(`移除已废弃的 openspec/schemas/${name}/`, 'success');
533
+ result.removed.push(`openspec/schemas/${name}`);
425
534
  }
426
535
  // 非本包且不在清单中 → 不动(用户自建)
427
536
  });
@@ -435,7 +544,7 @@ function setupOpenSpec(isUpgrade, managedPaths) {
435
544
  const srcPath = path.join(src, name);
436
545
  const dest = path.join(schemasDest, name);
437
546
  fs.cpSync(srcPath, dest, { recursive: true });
438
- log(`安装 openspec/schemas/${name}/`, 'success');
547
+ result.schemas.push(name);
439
548
  });
440
549
  });
441
550
  }
@@ -444,7 +553,7 @@ function setupOpenSpec(isUpgrade, managedPaths) {
444
553
  const configSrc = path.join(SHARED_STANDARDS_DIR, 'config', 'openspec', 'config.yaml');
445
554
  const configDest = path.join(TARGET_DIR, 'openspec', 'config.yaml');
446
555
 
447
- if (!fs.existsSync(configSrc)) return;
556
+ if (!fs.existsSync(configSrc)) return result;
448
557
 
449
558
  fs.mkdirSync(path.dirname(configDest), { recursive: true });
450
559
  const ourContent = fs.readFileSync(configSrc, 'utf-8');
@@ -454,21 +563,19 @@ function setupOpenSpec(isUpgrade, managedPaths) {
454
563
  const userContent = fs.readFileSync(configDest, 'utf-8');
455
564
  const merged = mergeConfig(ourContent, userContent);
456
565
  fs.writeFileSync(configDest, merged);
457
- log('合并 openspec/config.yaml', 'success');
566
+ result.config = 'merged';
458
567
  } else {
459
568
  fs.writeFileSync(configDest, ourContent);
460
- log('创建 openspec/config.yaml', 'success');
569
+ result.config = 'created';
461
570
  }
462
571
 
463
- // 输出已安装的 schema 和可用命令
572
+ // 汇总已安装的 schema 与默认工作流
464
573
  if (fs.existsSync(schemasDest)) {
465
- const defaultSchema = yaml.load(fs.readFileSync(configDest, 'utf-8')).schema;
466
- const installedSchemas = fs.readdirSync(schemasDest).filter(name => {
467
- return fs.statSync(path.join(schemasDest, name)).isDirectory();
468
- });
469
-
470
- log(`已安装工作流:${installedSchemas.join(', ')}(默认: ${defaultSchema})`);
574
+ result.defaultSchema = yaml.load(fs.readFileSync(configDest, 'utf-8')).schema;
575
+ result.installed = fs.readdirSync(schemasDest)
576
+ .filter(name => fs.statSync(path.join(schemasDest, name)).isDirectory());
471
577
  }
578
+ return result;
472
579
  }
473
580
 
474
581
  // ── .gitignore 管理 ───────────────────────────────────────
@@ -540,26 +647,26 @@ async function ensureOpenSpecCli() {
540
647
  && version.compareVersions(latestVersion, currentVersion) > 0;
541
648
 
542
649
  if (!needInstall && !needUpgrade) {
543
- log(`OpenSpec CLI: v${currentVersion} (最新)`, 'success');
544
- return;
650
+ return { state: 'ok', version: currentVersion };
545
651
  }
546
652
 
547
- // 4. 执行安装/升级
653
+ // 4. 执行安装/升级(耗时操作,打印进行中提示)
548
654
  const action = needInstall ? '安装' : '升级';
549
655
  const detail = needInstall
550
656
  ? `→ v${latestVersion || 'latest'}`
551
657
  : `v${currentVersion} → v${latestVersion}`;
552
- log(`${action} OpenSpec CLI (${detail})...`, 'info');
658
+ console.log(` ${c('gray', '⏳')} ${c('gray', `${action} OpenSpec CLI(${detail})...`)}`);
553
659
 
554
660
  try {
555
661
  execSync(
556
662
  `npm install -g ${OPENSPEC_PKG}@latest --registry ${OPENSPEC_REGISTRY}`,
557
663
  { stdio: 'pipe', timeout: 120000 }
558
664
  );
559
- log(`OpenSpec CLI ${action}完成`, 'success');
665
+ return { state: needInstall ? 'installed' : 'upgraded', version: latestVersion || currentVersion };
560
666
  } catch (err) {
561
667
  log(`OpenSpec CLI ${action}失败: ${err.message}`, 'error');
562
668
  log(`请手动执行: npm install -g ${OPENSPEC_PKG}@latest --registry ${OPENSPEC_REGISTRY}`, 'warn');
669
+ return { state: 'failed', version: currentVersion };
563
670
  }
564
671
  }
565
672
 
@@ -701,6 +808,10 @@ async function init(targetDir, options = {}) {
701
808
  newPaths.add(`${adapter.rulesDir}/cbb-coding-rule.md`);
702
809
  newPaths.add(`${adapter.rulesDir}/cbb-priority.md`);
703
810
  newPaths.add(`${adapter.rulesDir}/cbb-ai-behavior.md`);
811
+ // opencode:rules glob 登记进项目根 opencode.json instructions
812
+ if (adapter.instructionsPattern) {
813
+ injectInstructions(adapter, newPaths, managedPaths);
814
+ }
704
815
  });
705
816
  log(`核心规则 → ${selectedAdapters.map(a => a.rulesDir).join(', ')} (2 个文件)`, 'success');
706
817
 
@@ -723,7 +834,7 @@ async function init(targetDir, options = {}) {
723
834
  }
724
835
  // 官方 OpenSpec Skills(openspec/skills/)不部署——工作流入口统一为 /opsx:* 斜杠命令
725
836
  //(openspec/commands/ 在下方 Commands 段部署),避免双形态重复
726
- // worktree 扩展 Skills:沙箱内只需 push / close(init/extend 由 workspace 根的子集安装)
837
+ // worktree 扩展 Skills:沙箱内只需 push / close(init 由 workspace 根的子集安装)
727
838
  const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, SANDBOX_WORKTREE_SKILLS);
728
839
  if (osExtResult.count > 0) {
729
840
  osCount = osExtResult.count;
@@ -761,16 +872,18 @@ async function init(targetDir, options = {}) {
761
872
  await detectUserLevelSuperpowers(selectedAdapters, yes);
762
873
  }
763
874
 
764
- // 安装 Commands(官方 12 个 + worktree 扩展;worktree 命令仅装 close/push,
765
- // init/extend 只在 workspace 根使用,由 installWorkspaceConfig 部署)
875
+ // 安装 Commands(官方 12 个 /opsx:*;worktree 管理 /cbb:worktree-*,
876
+ // 仅装 close/push,init 只在 workspace 根使用,由 installWorkspaceConfig 部署)
766
877
  const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
767
878
  if (commandsAdapters.length > 0) {
768
879
  console.log(`\n ⚡ ${action} Commands...`);
769
880
  const commandsBaseSrc = path.join(SHARED_STANDARDS_DIR, 'openspec', 'commands');
770
881
  const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
771
- const cmdFilter = ['close.md', 'push.md'];
882
+ const cmdFilter = ['worktree-close.md', 'worktree-push.md'];
772
883
 
773
- const cmdDests = commandsAdapters.map(a => a.commandsDir);
884
+ const cmdDests = [...new Set(commandsAdapters
885
+ .flatMap(a => [a.commandsDir, a.worktreeCommandsDir])
886
+ .filter(Boolean))];
774
887
  let cmdCount = 0;
775
888
  commandsAdapters.forEach(adapter => {
776
889
  let adapterCmdCount = 0;
@@ -780,11 +893,11 @@ async function init(targetDir, options = {}) {
780
893
  adapterCmdCount += baseResult.count;
781
894
  baseResult.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
782
895
  }
783
- // worktree 扩展 Commands(cbb/worktrees/commands/)
784
- const extResult = deployCommands(adapter, isUpgrade, managedPaths, commandsExtendsSrc, cmdFilter);
896
+ // worktree 管理 Commands(cbb/worktrees/commands/)
897
+ const extResult = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
785
898
  if (extResult.count > 0) {
786
899
  adapterCmdCount += extResult.count;
787
- extResult.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
900
+ extResult.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
788
901
  }
789
902
  cmdCount = Math.max(cmdCount, adapterCmdCount);
790
903
  });
@@ -911,67 +1024,76 @@ async function init(targetDir, options = {}) {
911
1024
  /**
912
1025
  * 为 workspace 根目录安装 AI 配置子集(由 cbb setup 调用)。
913
1026
  *
914
- * 只装 worktree 生命周期管理(init/extend/close/push)+ openspec 运行时配置,
1027
+ * 只装 worktree 生命周期管理(init/close/push)+ openspec 运行时配置,
915
1028
  * 不装 base 工作流、编码规范、superpowers、版本检查 hook--这些属于 sandbox worktree
916
- * 内的开发配置,由 worktree 内 `cbb init --yes` 完整模式安装。在 workspace 根目录
1029
+ * 内的开发配置,由 worktree 内 `cbbm init --yes` 完整模式安装。在 workspace 根目录
917
1030
  * (主分支)装 base 工作流会诱导绕过 worktree 直接在主分支做需求开发。
918
1031
  */
919
1032
  async function installWorkspaceConfig(targetDir, options = {}) {
920
- const { yes = false } = options;
1033
+ const { yes = false, selectedAdapters: presetAdapters = null } = options;
921
1034
  if (targetDir) TARGET_DIR = targetDir;
922
1035
 
923
1036
  const isUpgrade = detectExistingInstall();
924
- const action = isUpgrade ? '升级' : '安装';
925
1037
  const managedPaths = readManifest();
926
1038
  const newPaths = new Set();
927
1039
 
928
- console.log(`\n 🚀 ${action} workspace AI 配置(worktree 管理子集)...\n`);
929
-
930
- const isNonInteractive = yes || !process.stdin.isTTY;
931
- if (yes && process.stdin.isTTY) {
932
- log('--yes 非交互模式,已默认选中所有适配器', 'info');
1040
+ let selectedAdapters;
1041
+ if (presetAdapters && presetAdapters.length > 0) {
1042
+ // cbb setup 场景:上层已交互选定适配器,持久化供 cbb update 预选
1043
+ selectedAdapters = presetAdapters;
1044
+ saveLastTools(selectedAdapters);
1045
+ } else {
1046
+ const isNonInteractive = yes || !process.stdin.isTTY;
1047
+ if (isNonInteractive) {
1048
+ // 非交互(cbb update / 管道运行):沿用上次工具选择,避免误装未使用的适配器
1049
+ const lastTools = readLastTools();
1050
+ const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
1051
+ if (reused.length > 0) {
1052
+ selectedAdapters = reused;
1053
+ } else {
1054
+ selectedAdapters = adapters;
1055
+ }
1056
+ } else {
1057
+ selectedAdapters = await selectTools(isUpgrade);
1058
+ }
933
1059
  }
934
- const selectedAdapters = isNonInteractive ? adapters : await selectTools(isUpgrade);
935
- await ensureOpenSpecCli();
1060
+ const openspecCli = await ensureOpenSpecCli();
936
1061
 
937
- // 只装 worktree 生命周期管理 Skills(init/extend/close/push)
1062
+ // 只装 worktree 生命周期管理 Skills(init/close/push)
938
1063
  const skillsAdapters = selectedAdapters.filter(a => a.skillsDir);
1064
+ const commandsAdapters = selectedAdapters.filter(a => a.worktreeCommandsDir);
1065
+ let skillCount = 0;
1066
+ let cmdCount = 0;
1067
+ let adapterRoots = [];
1068
+
939
1069
  if (skillsAdapters.length > 0) {
940
- console.log(`\n 🔧 ${action} worktree 管理 Skills...`);
941
1070
  const openspecExtendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
942
- const osExtFilter = ['openspec-init-worktree', 'openspec-extend-worktree', 'openspec-close-worktree', 'openspec-push-worktrees'];
943
- const skillsDests = skillsAdapters.map(a => a.skillsDir);
944
- let count = 0;
1071
+ const osExtFilter = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
945
1072
  skillsAdapters.forEach(adapter => {
946
1073
  const r = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, osExtFilter);
947
1074
  if (r.count > 0) {
948
- count += r.count;
1075
+ skillCount = Math.max(skillCount, r.count);
949
1076
  r.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
950
1077
  }
951
1078
  });
952
- if (count > 0) log(`worktree 管理 Skills → ${skillsDests.join(', ')} (${count} 个)`, 'success');
1079
+ adapterRoots = skillsAdapters.map(a => a.skillsDir.split('/')[0]);
953
1080
  }
954
1081
 
955
- // 只装 worktree 管理 Commands(init/extend/close/push)
956
- const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
1082
+ // 只装 worktree 管理 Commands(cbb:worktree-init/close/push)
957
1083
  if (commandsAdapters.length > 0) {
958
- console.log(`\n ⚡ ${action} worktree 管理 Commands...`);
959
1084
  const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
960
- const cmdFilter = ['init.md', 'extend.md', 'close.md', 'push.md'];
961
- const cmdDests = commandsAdapters.map(a => a.commandsDir);
962
- let count = 0;
1085
+ const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
963
1086
  commandsAdapters.forEach(adapter => {
964
- const r = deployCommands(adapter, isUpgrade, managedPaths, commandsExtendsSrc, cmdFilter);
1087
+ const r = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
965
1088
  if (r.count > 0) {
966
- count += r.count;
967
- r.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
1089
+ cmdCount = Math.max(cmdCount, r.count);
1090
+ r.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
968
1091
  }
969
1092
  });
970
- if (count > 0) log(`worktree 管理 Commands ${cmdDests.join(', ')} (${count} 个)`, 'success');
1093
+ if (adapterRoots.length === 0) adapterRoots = commandsAdapters.map(a => a.worktreeCommandsDir.split('/')[0]);
971
1094
  }
972
1095
 
973
1096
  // 设置 OpenSpec 工作流(config.yaml + schemas)
974
- console.log(`\n 📋 设置 OpenSpec 工作流...`);
975
1097
  const schemasSrcs = [
976
1098
  path.join(SHARED_STANDARDS_DIR, 'config', 'openspec', 'schemas'),
977
1099
  ];
@@ -981,32 +1103,27 @@ async function installWorkspaceConfig(targetDir, options = {}) {
981
1103
  .forEach(name => newPaths.add(`openspec/schemas/${name}`));
982
1104
  }
983
1105
  });
984
- setupOpenSpec(isUpgrade, managedPaths);
1106
+ const openspecRes = setupOpenSpec(isUpgrade, managedPaths);
985
1107
  newPaths.add('openspec/config.yaml');
986
1108
 
987
1109
  // 升级时清理旧路径(workspace 曾装过的 base 工作流等不属于此子集,删除)
1110
+ const cleaned = [];
1111
+ const keptDocs = [];
988
1112
  if (isUpgrade) {
989
1113
  const oldPaths = pathsToDelete(managedPaths, newPaths);
990
- if (oldPaths.length > 0) {
991
- section('🧹', `清理上一版本残留(${oldPaths.length} 个)`);
992
- oldPaths.forEach(oldPath => {
993
- const targetPath = path.join(TARGET_DIR, oldPath);
994
- if (fs.existsSync(targetPath)) removePath(targetPath);
995
- });
996
- const MAX_DISPLAY = 5;
997
- oldPaths.slice(0, MAX_DISPLAY).forEach(p => log(` ${p}`, 'dim'));
998
- if (oldPaths.length > MAX_DISPLAY) log(` ... 还有 ${oldPaths.length - MAX_DISPLAY} 个`, 'dim');
999
- }
1000
- const kept = protectedKeptPaths(managedPaths, newPaths);
1001
- if (kept.length > 0) {
1002
- section('🛡️', '保留用户文档');
1003
- kept.forEach(k => log(` ${k}(本包已不再管理,文件可能含用户内容,未删除)`, 'warn'));
1004
- }
1114
+ oldPaths.forEach(oldPath => {
1115
+ const targetPath = path.join(TARGET_DIR, oldPath);
1116
+ if (fs.existsSync(targetPath)) {
1117
+ removePath(targetPath);
1118
+ cleaned.push(oldPath);
1119
+ }
1120
+ });
1121
+ keptDocs.push(...protectedKeptPaths(managedPaths, newPaths));
1005
1122
  (skillsAdapters || []).forEach(adapter => {
1006
1123
  const legacyNodeModules = path.join(TARGET_DIR, adapter.skillsDir, 'node_modules');
1007
1124
  if (fs.existsSync(legacyNodeModules)) {
1008
1125
  removePath(legacyNodeModules);
1009
- log(`清理遗留 ${adapter.skillsDir}/node_modules/`, 'success');
1126
+ cleaned.push(`${adapter.skillsDir}/node_modules`);
1010
1127
  }
1011
1128
  });
1012
1129
  }
@@ -1014,156 +1131,158 @@ async function installWorkspaceConfig(targetDir, options = {}) {
1014
1131
  writeManifest(newPaths, selectedAdapters);
1015
1132
  updateGitignore();
1016
1133
 
1017
- console.log(`\n ✅ ${action}完成!\n`);
1018
- const adapterSummary = selectedAdapters.map(a => `${a.displayName} → ${a.skillsDir}/`).join(' | ');
1019
- console.log(` ${adapterSummary}`);
1020
- console.log(' workspace 子集:worktree 管理 Skills/Commands + openspec 配置(不含 base 工作流/编码规范/superpowers)');
1021
- console.log('');
1134
+ // 汇总输出(一行主结果 + 可选清理/保留明细)
1135
+ const rootLabel = [...new Set(adapterRoots)].join('/');
1136
+ const parts = [];
1137
+ if (skillCount > 0) parts.push(`${rootLabel} skills×${skillCount}`);
1138
+ if (cmdCount > 0) parts.push(`commands×${cmdCount}`);
1139
+ if (openspecRes.installed.length > 0) {
1140
+ parts.push(`openspec ${openspecRes.defaultSchema || openspecRes.installed.join('/')}`);
1141
+ }
1142
+ const configLabel = 'AI 配置';
1143
+ step('✓', configLabel, parts.join(' · '));
1144
+ if (cleaned.length > 0) {
1145
+ const MAX_DISPLAY = 3;
1146
+ const preview = cleaned.slice(0, MAX_DISPLAY).join('、');
1147
+ step('🧹', '清理残留', `${cleaned.length} 个(${preview}${cleaned.length > MAX_DISPLAY ? ' 等' : ''})`,
1148
+ { iconColor: 'yellow' });
1149
+ }
1150
+ if (keptDocs.length > 0) {
1151
+ step('🛡️', '保留文档', `${keptDocs.join('、')}(本包已不再管理)`, { iconColor: 'yellow' });
1152
+ }
1022
1153
  }
1023
1154
 
1024
1155
  // ── cbb setup 命令 ──────────────────────────────────────
1025
1156
 
1026
1157
  /**
1027
- * 若 targetDir 已安装(.cbb/.managed-by-cbb 存在),输出当前版本号
1028
- * 并尝试从 registry 查询最新版本,让用户确认是否继续。
1029
- * - 用户输入 n / no 返回 false(调用方决定是跳过还是退出)
1030
- * - 未安装 / 查询失败 / 用户确认 → 返回 true
1158
+ * 若 targetDir 已安装(.cbb/.managed-by-cbb 存在),比较已装版本与当前包版本决定是否继续。
1159
+ * - 版本一致 返回 'skip'(不询问、不重复安装)
1160
+ * - 版本不同询问是否升级到当前包版本,用户拒绝返回 'cancel'
1161
+ * - 未安装 → 返回 'install'(不提示,直接继续)
1162
+ *
1163
+ * 注:只比较「已装版本 vs 当前包版本」——安装动作只能落盘当前包的产物,
1164
+ * registry 是否有更新由用户级版本检查 hook 提醒,不在此处联网查询。
1165
+ * @returns {Promise<'install'|'skip'|'cancel'>}
1031
1166
  */
1032
1167
  async function confirmUpgradeIfInstalled(targetDir) {
1033
1168
  const installed = version.readInstalledVersion(targetDir);
1034
- if (!installed) return true; // 未安装:不提示,直接继续
1169
+ if (!installed) return 'install'; // 未安装:不提示,直接继续
1035
1170
 
1036
- // 根据当前包版本(而非已安装版本)决定查询哪个 tag:
1037
- // 反查当前版本所属的 dist-tag(如 zjc / beta / latest),不依赖版本号格式
1038
1171
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1039
- let tag = 'latest';
1040
- try {
1041
- tag = await version.findTagForVersion(pkg.version) || 'latest';
1042
- } catch (_) {
1043
- // 网络失败静默降级:使用 latest
1044
- }
1045
1172
 
1046
- let latest = null;
1047
- try {
1048
- latest = await version.fetchLatest(tag);
1049
- } catch (_) {
1050
- // 网络失败静默降级:latest 保持 null
1173
+ // 版本未变化 不处理(不询问、不重复安装)
1174
+ if (version.compareVersions(installed, pkg.version) === 0) {
1175
+ step('✓', '版本', `v${installed}(已是最新,跳过)`);
1176
+ return 'skip';
1051
1177
  }
1052
1178
 
1053
- section('📦', '检测到已安装版本');
1054
- field('当前', `v${installed}`, 'cyan');
1055
- if (latest && version.compareVersions(latest, installed) > 0) {
1056
- field('最新', `v${latest}`, 'green');
1057
- console.log('');
1058
- } else if (latest) {
1059
- field('最新', `v${latest}`, 'gray');
1060
- console.log(c('gray', ' (已是最新,无需升级)'));
1061
- console.log('');
1062
- // 已是最新,但仍询问一次确认,避免用户误以为没生效
1063
- latest = null;
1064
- } else {
1065
- field('最新', c('yellow', '无法检查(网络异常)'));
1066
- console.log('');
1067
- }
1068
-
1069
- const prompt = latest
1070
- ? `是否升级到 v${latest}?(Y/n): `
1071
- : '是否重新安装?(Y/n): ';
1072
- const answer = await ask(prompt);
1179
+ step('', '版本', `v${installed} → v${pkg.version}`, { iconColor: 'cyan' });
1180
+ const answer = await ask(`? 升级到 v${pkg.version}?(Y/n) `);
1073
1181
  if (answer && answer.toLowerCase() === 'n') {
1074
- log('已取消安装/升级', 'warn');
1075
- return false;
1182
+ step('!', '版本', '已跳过安装/升级', { iconColor: 'yellow' });
1183
+ return 'cancel';
1076
1184
  }
1077
- return true;
1185
+ return 'install';
1078
1186
  }
1079
1187
 
1080
1188
  /**
1081
- * 检查业务空间根目录下是否有 CONFIG.json(init-worktree 技能依赖)。
1082
- * 不存在时,将包内样例文件复制为 CONFIG.json,提示用户编辑为实际配置。
1189
+ * 检查业务空间根目录下是否有 workspace-config.json(init-worktree 技能依赖)。
1190
+ * 不存在时,将包内样例文件复制为 workspace-config.json
1191
+ * 返回 'exists' | 'created'(不打印,由调用方汇总输出)。
1083
1192
  */
1084
- function ensureWorkspaceConfig(targetDir, workspaceName) {
1085
- const configPath = path.join(targetDir, 'CONFIG.json');
1086
- if (fs.existsSync(configPath)) {
1087
- log(`工作空间「${workspaceName}」的 CONFIG.json 已存在,跳过初始化`, 'success');
1088
- return;
1089
- }
1193
+ function ensureWorkspaceConfig(targetDir) {
1194
+ const configPath = path.join(targetDir, 'workspace-config.json');
1195
+ if (fs.existsSync(configPath)) return 'exists';
1090
1196
 
1091
- // 复制样例文件作为 CONFIG.json
1092
1197
  const samplePath = path.join(SHARED_STANDARDS_DIR, workspaces.SAMPLE_CONFIG);
1093
1198
  fs.copyFileSync(samplePath, configPath);
1094
- log(`工作空间「${workspaceName}」未找到 CONFIG.json,已从样例文件复制`, 'warn');
1095
- log('请编辑 CONFIG.json,将示例 apps 替换为实际关联应用', 'warn');
1199
+ return 'created';
1096
1200
  }
1097
1201
 
1098
1202
  /**
1099
- * 读取 CONFIG.json 中的应用列表,将关联应用代码 clone/fetch 到 .code/ 目录。
1203
+ * 读取 workspace-config.json 中的应用列表,将关联应用代码 clone/fetch 到 .codespace/ 目录。
1100
1204
  * 首次 clone(耗时较长),后续 fetch --all(秒级更新)。
1101
1205
  * 这是基准代码,init-worktree 技能后续只做轻量 fetch 检查。
1102
1206
  */
1103
- function syncAppRepos(targetDir) {
1104
- const configPath = path.join(targetDir, 'CONFIG.json');
1105
- if (!fs.existsSync(configPath)) return;
1207
+ /**
1208
+ * 读取 workspace-config.json,将关联应用代码 clone/fetch 到 .codespace/ 目录。
1209
+ * 不打印逐条日志,返回汇总对象由调用方渲染:
1210
+ * { ok, fail, updated[], cloned[], fetchFailed[], sampleApps[], invalid[] }
1211
+ * 长耗时(首次 clone),通过 onProgress 回调实时提示当前正在处理哪个应用。
1212
+ * @param {string} targetDir
1213
+ * @param {(text:string)=>void} [onProgress]
1214
+ */
1215
+ async function syncAppRepos(targetDir, onProgress = () => {}) {
1216
+ const summary = { ok: 0, fail: 0, updated: [], cloned: [], fetchFailed: [], sampleApps: [], invalid: [] };
1217
+ const configPath = path.join(targetDir, 'workspace-config.json');
1218
+ if (!fs.existsSync(configPath)) return summary;
1106
1219
 
1107
1220
  let config;
1108
1221
  try {
1109
1222
  config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
1110
1223
  } catch (e) {
1111
- log('CONFIG.json 解析失败,跳过应用代码拉取', 'warn');
1112
- return;
1224
+ summary.parseFailed = true;
1225
+ return summary;
1113
1226
  }
1114
1227
 
1115
1228
  const apps = config.apps;
1116
1229
  if (!Array.isArray(apps) || apps.length === 0) {
1117
- log('CONFIG.json apps 为空,跳过 .code/ 拉取', 'dim');
1118
- return;
1230
+ summary.empty = true;
1231
+ return summary;
1119
1232
  }
1120
1233
 
1121
- const codeDir = path.join(targetDir, '.code');
1234
+ const codeDir = path.join(targetDir, '.codespace');
1122
1235
  fs.mkdirSync(codeDir, { recursive: true });
1123
1236
 
1124
- let ok = 0, fail = 0;
1125
- apps.forEach(app => {
1237
+ const syncable = apps.filter(app => app.name && app.repo && !workspaces.isSampleRepo(app.repo));
1238
+ let index = 0;
1239
+
1240
+ for (const app of apps) {
1126
1241
  if (!app.name || !app.repo) {
1127
- log(`应用 "${app.name || '(未知)'}" 缺少 name 或 repo,跳过`, 'warn');
1128
- fail++;
1129
- return;
1242
+ summary.invalid.push(app.name || '(未知)');
1243
+ summary.fail++;
1244
+ continue;
1245
+ }
1246
+
1247
+ // 样例占位地址(example 等保留域名):clone 必然失败,逐条跳过并汇总提示
1248
+ if (workspaces.isSampleRepo(app.repo)) {
1249
+ summary.sampleApps.push(app);
1250
+ continue;
1130
1251
  }
1131
1252
 
1253
+ index++;
1132
1254
  const appDir = path.join(codeDir, app.name);
1133
1255
  if (fs.existsSync(path.join(appDir, '.git'))) {
1134
1256
  // 已存在:fetch 更新
1257
+ onProgress(`更新 ${app.name}(${index}/${syncable.length})`);
1135
1258
  try {
1136
- execSync('git fetch --all -q', { cwd: appDir, stdio: 'pipe', timeout: 60000 });
1137
- log(`${app.name}: 代码已更新(fetch --all)`, 'success');
1138
- ok++;
1259
+ await execFileAsync('git', ['fetch', '--all', '-q'], { cwd: appDir, timeout: 60000 });
1260
+ summary.updated.push(app.name);
1261
+ summary.ok++;
1139
1262
  } catch (e) {
1140
- log(`${app.name}: fetch 失败(离线或无权限,使用本地已有代码)`, 'warn');
1141
- ok++; // 不算失败,用本地代码
1263
+ summary.fetchFailed.push(app.name);
1264
+ summary.ok++; // 不算失败,用本地代码
1142
1265
  }
1143
1266
  } else {
1144
- // 不存在:clone
1267
+ // 不存在:clone(首次较慢)
1268
+ onProgress(`克隆 ${app.name}(${index}/${syncable.length},首次较慢)`);
1145
1269
  try {
1146
- execSync(`git clone -q "${app.repo}" "${appDir}"`, { stdio: 'pipe', timeout: 180000 });
1147
- log(`${app.name}: 代码已拉取(clone)`, 'success');
1148
- ok++;
1270
+ await execFileAsync('git', ['clone', '-q', app.repo, appDir], { timeout: 180000 });
1271
+ summary.cloned.push(app.name);
1272
+ summary.ok++;
1149
1273
  } catch (e) {
1150
- log(`${app.name}: clone 失败 — ${app.repo}`, 'error');
1151
- fail++;
1274
+ summary.fail++;
1152
1275
  }
1153
1276
  }
1154
- });
1155
-
1156
- if (fail > 0) {
1157
- log(`应用代码拉取完成(成功 ${ok},失败 ${fail})`, 'warn');
1158
- } else {
1159
- log(`应用代码已就绪(${ok} 个应用 → .code/)`, 'success');
1160
1277
  }
1278
+
1279
+ return summary;
1161
1280
  }
1162
1281
 
1163
1282
  /**
1164
1283
  * 读取远程默认分支名(如 "main" / "master" / "develop")。
1165
1284
  * 用 symbolic-ref refs/remotes/origin/HEAD 探测;取不到返回 null。
1166
- * 参照 cbb/worktrees/skills/openspec-init-worktree/scripts/create-branches.js:47。
1285
+ * 参照 cbb/worktrees/skills/cbb-worktree-init/scripts/create-branches.js:47。
1167
1286
  */
1168
1287
  function getRemoteDefaultBranch(cwd) {
1169
1288
  try {
@@ -1185,13 +1304,17 @@ function getRemoteDefaultBranch(cwd) {
1185
1304
  * 在「子目录已存在且匹配」分支同步工作空间仓库到远程默认分支的最新。
1186
1305
  * 流程:fetch → 检测默认分支 → 必要时切换分支 → fast-forward merge。
1187
1306
  * 任一步失败都抛出,由调用方走错误路径中止整个 cbb setup。
1307
+ * fetch 可能耗时,通过 onProgress 回调实时提示。
1308
+ * @param {string} targetDir
1309
+ * @param {(text:string)=>void} [onProgress]
1310
+ * @returns {Promise<string>} 同步到的默认分支名
1188
1311
  */
1189
- function syncWorkspaceRepo(targetDir) {
1190
- // 1. fetch
1312
+ async function syncWorkspaceRepo(targetDir, onProgress = () => {}) {
1313
+ // 1. fetch(耗时步:大型仓库首次 fetch 可能数十秒)
1314
+ onProgress(`同步 ${path.basename(targetDir)}/(git fetch)`);
1191
1315
  try {
1192
- execSync('git fetch --all -q', {
1316
+ await execFileAsync('git', ['fetch', '--all', '-q'], {
1193
1317
  cwd: targetDir,
1194
- stdio: 'pipe',
1195
1318
  timeout: 60000,
1196
1319
  });
1197
1320
  } catch (e) {
@@ -1237,112 +1360,135 @@ function syncWorkspaceRepo(targetDir) {
1237
1360
  stdio: 'pipe',
1238
1361
  timeout: 60000,
1239
1362
  });
1240
- log(`已同步 ${path.basename(targetDir)}/ 到 origin/${defaultBranch} 最新(fast-forward)`, 'success');
1241
1363
  } catch (e) {
1242
1364
  log(`git merge --ff-only origin/${defaultBranch} 失败:${e.message}`, 'error');
1243
1365
  log(`本地 ${targetDir}/ 与远程分叉或有未提交改动,请手动处理后重试`, 'error');
1244
1366
  throw e;
1245
1367
  }
1368
+
1369
+ return defaultBranch;
1246
1370
  }
1247
1371
 
1248
1372
  /**
1249
- * 处理单个业务空间:展示详情计算子目录 → 检测/克隆 → CONFIG.json → 拉取应用代码 → 升级询问 → init()。
1250
- * 失败时不抛出也不退出,返回结果对象供调用方汇总。
1373
+ * 处理单个业务空间:计算子目录 → 检测/克隆 → workspace-config.json → 拉取应用代码 → 升级询问 → 安装 AI 配置。
1374
+ * 输出为紧凑步进行(✓ 标签 值),失败时不抛出也不退出,返回结果对象供调用方汇总。
1375
+ * @param {object} [opts] - { selectedAdapters } cbb setup 上层选定的适配器(透传给空间级安装)
1251
1376
  * @returns {Promise<{ok: boolean, error?: string, message?: string, subdir?: string, skipped?: boolean}>}
1252
1377
  */
1253
- async function initSingleWorkspace(w) {
1254
- // 1. 展示所选业务空间(单行摘要)
1255
- console.log(`${c('cyan', '开始配置')} ${c('yellow', w.businessLine)}/${c('bold', w.name)}...`);
1256
-
1257
- // 2. 计算仓库子目录名(来自 repo URL 的最后一段)
1378
+ async function initSingleWorkspace(w, opts = {}) {
1379
+ // 1. 计算仓库子目录名(来自 repo URL 的最后一段)
1258
1380
  const subdirName = workspaces.extractRepoBasename(w.repo);
1259
1381
  if (!subdirName) {
1260
- log(`无法从仓库地址 "${w.repo}" 提取目录名`, 'error');
1382
+ step('✗', '仓库', `无法从仓库地址提取目录名:${w.repo}`, { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
1261
1383
  return { ok: false, error: 'EXTRACT_FAILED' };
1262
1384
  }
1263
1385
 
1264
1386
  const parentDir = process.cwd();
1265
1387
  const targetDir = path.join(parentDir, subdirName);
1266
1388
 
1267
- // 3. 检查子目录:复用 / 报错 / 克隆
1389
+ // 2. 检查子目录:复用(同步)/ 报错 / 克隆
1268
1390
  let wasCloned = false;
1269
1391
  if (fs.existsSync(targetDir)) {
1270
1392
  const detection = workspaces.detectRepo(targetDir, w.repo);
1271
1393
  if (detection.state === 'matched') {
1272
- log(`子目录 ${c('cyan', subdirName + '/')} 已存在且匹配,正在同步远程最新...`, 'info');
1394
+ let branch;
1395
+ const repoProgress = progress('仓库');
1273
1396
  try {
1274
- syncWorkspaceRepo(targetDir);
1397
+ branch = await syncWorkspaceRepo(targetDir, t => repoProgress.update(t));
1275
1398
  } catch (e) {
1399
+ repoProgress.done();
1276
1400
  return { ok: false, error: 'PULL_FAILED', message: e.message };
1277
1401
  }
1402
+ repoProgress.done();
1403
+ step('✓', '仓库', `${subdirName} → origin/${branch}(fast-forward)`);
1278
1404
  } else {
1405
+ step('✗', '仓库', `${subdirName}/ 已存在,但不是业务空间「${w.name}」的仓库`,
1406
+ { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
1279
1407
  const remoteList = detection.remotes.length
1280
- ? detection.remotes.map(r => ` ${c('gray', r.name + ':')} ${r.url}`).join('\n')
1281
- : ` ${c('gray', '(该目录尚未配置 remote)')}`;
1282
- log(
1283
- `子目录 ${c('cyan', subdirName + '/')} 已存在但不是所选业务空间 "${w.name}" 的仓库:`,
1284
- 'error',
1285
- );
1408
+ ? detection.remotes.map(r => ` ${c('gray', r.name + ':')} ${r.url}`).join('\n')
1409
+ : ` ${c('gray', '(该目录尚未配置 remote)')}`;
1286
1410
  console.log(remoteList);
1287
- console.log(` ${c('gray', '期望 remote:')} ${w.repo}`);
1288
- console.log('');
1411
+ console.log(` ${c('gray', '期望 remote:')} ${w.repo}`);
1289
1412
  log('请删除该子目录后重试,或在另一个父目录中运行本命令。', 'warn');
1290
1413
  return { ok: false, error: 'DIR_MISMATCH' };
1291
1414
  }
1292
1415
  } else {
1293
- console.log(`⏳ 正在克隆仓库到 ${c('cyan', subdirName + '/')} ...`);
1416
+ const cloneProgress = progress('仓库');
1417
+ cloneProgress.update(`克隆 ${subdirName}/(首次较慢)`);
1294
1418
  try {
1295
1419
  // 在 parentDir 下克隆,git 会自动创建 subdirName 子目录
1296
- execSync(`git clone "${w.repo}"`, {
1420
+ await execFileAsync('git', ['clone', '-q', w.repo], {
1297
1421
  cwd: parentDir,
1298
- stdio: 'pipe',
1299
1422
  timeout: 180000,
1300
1423
  });
1301
1424
  } catch (e) {
1425
+ cloneProgress.done();
1302
1426
  log(`git clone 失败:${e.message}`, 'error');
1303
1427
  log('请检查:1) 仓库地址是否正确 2) 是否已配置 SSH key 或 GitLab 访问凭证', 'warn');
1304
1428
  return { ok: false, error: 'CLONE_FAILED', message: e.message };
1305
1429
  }
1430
+ cloneProgress.done();
1306
1431
  wasCloned = true;
1307
- log(`已克隆到 ${c('cyan', subdirName + '/')}`, 'success');
1432
+ step('✓', '仓库', `已克隆 ${subdirName}/`);
1308
1433
  }
1309
1434
 
1310
- // 4. 检查 CONFIG.json(init-worktree 技能依赖),不存在则复制样例文件
1311
- ensureWorkspaceConfig(targetDir, w.name);
1312
-
1313
- // 5. 校验 CONFIG.json 格式,不合法则中断并撤销本空间初始化
1314
- const configPath = path.join(targetDir, 'CONFIG.json');
1435
+ // 3. workspace-config.json:缺失则从样例生成,随后校验格式
1436
+ const cfgState = ensureWorkspaceConfig(targetDir);
1437
+ const configPath = path.join(targetDir, 'workspace-config.json');
1315
1438
  const configValidation = workspaces.validateWorkspaceConfig(configPath);
1316
1439
  if (!configValidation.valid) {
1317
- log(`CONFIG.json 校验失败:${configValidation.message}`, 'error');
1318
- // 撤销本次克隆,使工作空间回到初始状态,用户配好 CONFIG.json 后可重新执行
1440
+ step('✗', '配置', `workspace-config.json 校验失败:${configValidation.message}`,
1441
+ { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
1442
+ // 撤销本次克隆,使工作空间回到初始状态,用户配好 workspace-config.json 后可重新执行
1319
1443
  if (wasCloned && fs.existsSync(targetDir)) {
1320
1444
  removePath(targetDir);
1321
- log(`已撤销本次克隆(${subdirName}/),请配好 CONFIG.json 后重新执行 cbb setup`, 'warn');
1445
+ log(`已撤销本次克隆(${subdirName}/),请配好 workspace-config.json 后重新执行 cbb setup`, 'warn');
1322
1446
  } else {
1323
- log('请修正 CONFIG.json 后重新执行 cbb setup', 'warn');
1447
+ log('请修正 workspace-config.json 后重新执行 cbb setup', 'warn');
1324
1448
  }
1325
- const samplePath = path.join(SHARED_STANDARDS_DIR, workspaces.SAMPLE_CONFIG);
1326
- log(`参考格式:${samplePath}`, 'info');
1449
+ log(`参考格式:${path.join(SHARED_STANDARDS_DIR, workspaces.SAMPLE_CONFIG)}`, 'info');
1327
1450
  return { ok: false, error: 'CONFIG_INVALID', message: configValidation.message };
1328
1451
  }
1329
- log('CONFIG.json 校验通过', 'success');
1330
-
1331
- // 6. 拉取关联应用代码到 .code/(基准代码,首次 clone,后续 fetch 更新)
1332
- syncAppRepos(targetDir);
1452
+ if (cfgState === 'created') {
1453
+ step('!', '配置', 'workspace-config.json 已从样例生成,请编辑 apps 后重跑 cbb setup',
1454
+ { iconColor: 'yellow', valueColor: 'yellow' });
1455
+ } else {
1456
+ step('✓', '配置', `workspace-config.json(${configValidation.config.apps.length} 个应用)`);
1457
+ }
1333
1458
 
1334
- // 7. 若已安装,提示当前版本并询问是否继续;用户拒绝则跳过本 workspace
1335
- const shouldInstall = await confirmUpgradeIfInstalled(targetDir);
1336
- if (!shouldInstall) {
1459
+ // 4. 拉取关联应用代码到 .codespace/(基准代码,首次 clone 较慢,实时提示当前应用)
1460
+ const appProgress = progress('应用');
1461
+ let apps;
1462
+ try {
1463
+ apps = await syncAppRepos(targetDir, t => appProgress.update(t));
1464
+ } finally {
1465
+ appProgress.done();
1466
+ }
1467
+ const appParts = [`${apps.ok}/${apps.ok + apps.fail} 个就绪`];
1468
+ if (apps.fetchFailed.length > 0) appParts.push(`${apps.fetchFailed.length} 个 fetch 失败(用本地代码)`);
1469
+ if (apps.sampleApps.length > 0) appParts.push(`${apps.sampleApps.length} 个样例占位已跳过`);
1470
+ const appWarn = apps.fail > 0 || apps.sampleApps.length > 0 || apps.fetchFailed.length > 0;
1471
+ step(appWarn ? '!' : '✓', '应用', appParts.join(' · '), { iconColor: appWarn ? 'yellow' : 'green' });
1472
+ if (apps.sampleApps.length > 0) {
1473
+ const names = apps.sampleApps.map(a => a.name).join('、');
1474
+ console.log(` ${c('gray', `${names} → 编辑 workspace-config.json 填入真实仓库地址`)}`);
1475
+ }
1476
+
1477
+ // 5. 若已安装:版本未变化直接跳过;有新版本需用户确认;用户拒绝则跳过本 workspace
1478
+ const decision = await confirmUpgradeIfInstalled(targetDir);
1479
+ if (decision === 'cancel') {
1337
1480
  return { ok: false, error: 'USER_CANCELLED', skipped: true };
1338
1481
  }
1482
+ if (decision === 'skip') {
1483
+ return { ok: true, subdir: subdirName, upToDate: true };
1484
+ }
1339
1485
 
1340
- // 7. 安装 workspace AI 配置子集(worktree 管理技能/命令 + openspec 配置,不含 base 工作流)
1486
+ // 6. 安装 workspace AI 配置子集(worktree 管理技能/命令 + openspec 配置,不含 base 工作流)
1341
1487
  try {
1342
- await installWorkspaceConfig(targetDir, { yes: true });
1488
+ await installWorkspaceConfig(targetDir, { yes: true, selectedAdapters: opts.selectedAdapters });
1343
1489
  return { ok: true, subdir: subdirName };
1344
1490
  } catch (e) {
1345
- log(`初始化 ${subdirName} 失败:${e.message}`, 'error');
1491
+ step('✗', 'AI 配置', e.message, { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
1346
1492
  return { ok: false, error: 'INIT_FAILED', message: e.message };
1347
1493
  }
1348
1494
  }
@@ -1354,42 +1500,37 @@ function describeBatchError(r) {
1354
1500
  case 'DIR_MISMATCH': return '子目录已存在但不是该业务空间的仓库';
1355
1501
  case 'CLONE_FAILED': return `git clone 失败:${r.message || ''}`.trim();
1356
1502
  case 'PULL_FAILED': return `工作空间仓库同步失败:${r.message || ''}`.trim();
1357
- case 'CONFIG_INVALID': return `CONFIG.json 不合法:${r.message || ''}`.trim();
1503
+ case 'CONFIG_INVALID': return `workspace-config.json 不合法:${r.message || ''}`.trim();
1358
1504
  case 'USER_CANCELLED': return '用户取消升级';
1359
1505
  case 'INIT_FAILED': return `初始化失败:${r.message || ''}`.trim();
1360
1506
  default: return r.error || '未知错误';
1361
1507
  }
1362
1508
  }
1363
1509
 
1364
- /** 批量处理结束后输出汇总报告(成功项绿色、失败项红色) */
1510
+ /** 批量处理结束后的紧凑汇总(一行完成/计数 + 失败项明细) */
1365
1511
  function printBatchSummary(results) {
1366
1512
  const ok = results.filter(r => r.ok);
1367
1513
  const failed = results.filter(r => !r.ok);
1368
1514
 
1369
- section('📊', `处理汇总(成功 ${ok.length}/${results.length})`);
1370
- ok.forEach(r => {
1371
- log(`${c('cyan', (r.subdir || '?') + '/')} ${r.w.name}(${r.w.businessLine})`, 'success');
1372
- });
1515
+ if (failed.length === 0) {
1516
+ const dirs = ok.map(r => (r.subdir || r.w.name) + '/').join('、');
1517
+ step('', '完成', `${ok.length} 个业务空间已就绪(${dirs})`);
1518
+ return;
1519
+ }
1520
+ step('!', '完成', `成功 ${ok.length}/${results.length},失败项见下`, { iconColor: 'yellow', valueColor: 'yellow' });
1373
1521
  failed.forEach(r => {
1374
- log(`${r.w.name}(${r.w.businessLine}):${describeBatchError(r)}`, 'error');
1522
+ step('✗', r.w.name, describeBatchError(r), { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
1375
1523
  });
1376
- if (failed.length > 0) {
1377
- console.log('');
1378
- log(`${failed.length} 个业务空间处理失败,请按上述提示排查`, 'warn');
1379
- }
1380
1524
  }
1381
1525
 
1382
1526
  /**
1383
1527
  * 初始化业务空间工作目录(cbb setup 命令)。
1384
1528
  *
1385
- * 流程:安装用户级组件(幂等)→ 检测当前目录 → 加载配置 → checkbox 多选
1386
- * → 确认目标目录 → 依次处理每个选中的 workspace。
1529
+ * 流程:检测当前目录 → 加载配置 → checkbox 多选业务空间 → 选择 AI 工具适配器
1530
+ * → 安装用户级组件 → 确认目标目录 → 依次处理每个选中的 workspace。
1387
1531
  * 单个 workspace 失败不影响后续。
1388
1532
  */
1389
1533
  async function setupWorkspace() {
1390
- // 首次运行:安装用户级组件(hook / cbb-wiki-ops,幂等,已装则跳过)
1391
- await setupUserLevelComponents();
1392
-
1393
1534
  // 0. 检测当前目录是否为 git 仓库
1394
1535
  const parentDir = process.cwd();
1395
1536
  try {
@@ -1433,37 +1574,61 @@ async function setupWorkspace() {
1433
1574
  .sort((a, b) => a - b)
1434
1575
  .map(i => list[i]);
1435
1576
 
1436
- // 3. 批量展示所选业务空间
1437
- section('✓', `已选择 ${selected.length} 个业务空间`);
1438
- selected.forEach((w, i) => {
1439
- console.log(` ${c('cyan', String(i + 1) + '.')} ${c('yellow', '[' + w.businessLine + ']')} ${w.name}`);
1440
- console.log(` ${c('gray', w.repo)}`);
1441
- });
1442
- console.log('');
1577
+ // 2.5 选择 AI 工具适配器(一次选择,作用于用户级组件与全部空间;非交互默认全选)
1578
+ let selectedAdapters;
1579
+ if (!process.stdin.isTTY) {
1580
+ selectedAdapters = adapters;
1581
+ } else {
1582
+ const toolIdx = await checkbox.promptCheckbox({
1583
+ header: '🔧 选择要安装的 AI 工具(空格切换,回车确认)',
1584
+ items: adapters.map(a => ({ label: c('cyan', a.displayName) })),
1585
+ // 默认只预选 opencode(团队当前主用),可空格加选其他工具
1586
+ preSelected: adapters.reduce((acc, a, i) => (a.name === 'opencode' ? acc.concat(i) : acc), []),
1587
+ });
1588
+ selectedAdapters = toolIdx.size === 0
1589
+ ? adapters
1590
+ : [...toolIdx].sort((a, b) => a - b).map(i => adapters[i]);
1591
+ }
1592
+
1593
+ // 3. 配置摘要(业务空间 / 目标目录 / AI 工具)
1594
+ printSetupSummary(selected, selectedAdapters, parentDir);
1595
+
1596
+ // 4. 用户级组件(版本检查 hook,幂等)→ 一行结果
1597
+ await setupUserLevelComponents(selectedAdapters);
1443
1598
 
1444
- // 4. 展示目标目录并让用户确认
1445
- section('📁', '业务空间将创建在以下目录');
1446
- console.log(` ${c('cyan', parentDir)}`);
1599
+ // 5. 目录确认
1447
1600
  console.log('');
1448
- const answer = await ask(`${c('yellow', '?')} 是否将上述业务空间创建在此目录?(Y/n) `);
1601
+ const answer = await ask(`${c('yellow', '?')} 在以上目录初始化上述业务空间?(Y/n) `);
1449
1602
  if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
1450
1603
  log('已取消,未创建任何业务空间。', 'warn');
1451
1604
  process.exit(0);
1452
1605
  }
1453
- console.log('');
1454
1606
 
1455
1607
  // 5. 依次处理每个 workspace;任一失败不影响后续
1456
1608
  const results = [];
1457
1609
  for (let i = 0; i < selected.length; i++) {
1458
1610
  const w = selected[i];
1459
- section('📦', `[${i + 1}/${selected.length}] ${w.name}`);
1460
1611
  console.log('');
1461
- const result = await initSingleWorkspace(w);
1612
+ console.log(' ' + c('cyan', '▸') + ' ' + c('gray', `[${i + 1}/${selected.length}]`) + ' ' + c('bold', w.name));
1613
+ const result = await initSingleWorkspace(w, { selectedAdapters });
1462
1614
  results.push({ w, ...result });
1463
- console.log('');
1464
1615
  }
1616
+ console.log('');
1617
+ printBatchSummary(results);
1618
+ }
1465
1619
 
1466
- // 5. 汇总报告(已移除)
1620
+ /** 展示本次 setup 的配置摘要:业务空间清单 + 目标目录 + AI 工具 */
1621
+ function printSetupSummary(selected, selectedAdapters, parentDir) {
1622
+ const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1623
+ console.log('');
1624
+ console.log(' ' + c('bold', 'cbb setup') + ' ' + c('gray', `v${pkg.version}`));
1625
+ console.log('');
1626
+ selected.forEach(w => {
1627
+ console.log(` ${c('yellow', '[' + w.businessLine + ']')} ${w.name}`);
1628
+ console.log(` ${c('gray', w.repo)}`);
1629
+ });
1630
+ console.log(' ' + c('gray', padVisual('目标目录', 10)) + ' ' + c('cyan', parentDir));
1631
+ console.log(' ' + c('gray', padVisual('AI 工具', 10)) + ' ' + selectedAdapters.map(a => a.displayName).join('、'));
1467
1632
  }
1468
1633
 
1469
1634
  // ── 用户级组件 ────────────────────────────────────────────
@@ -1472,13 +1637,14 @@ async function setupWorkspace() {
1472
1637
  * 安装用户级组件(与项目无关的内容,一次安装全局可用,幂等)。
1473
1638
  *
1474
1639
  * - 清单:~/.cbb/.user-components 记录本包管理的用户级路径
1475
- * - 内容:check-update hook(settings 由 settingsManager 幂等管理)+ 工具 skills(cbb-wiki-ops)
1640
+ * - 内容:check-update hook(settings 由 settingsManager 幂等管理)
1476
1641
  */
1477
1642
  const USER_MANIFEST_FILE = path.join(os.homedir(), '.cbb', '.user-components');
1478
- // 装到用户级的工具 skills(与具体项目无关,任何会话都可能用到)
1479
- const USER_LEVEL_SKILLS = ['cbb-wiki-ops'];
1643
+ // 装到用户级的工具 skills(与具体项目无关,任何会话都可能用到)。
1644
+ // 当前为空:cbb-wiki-ops 保留在源仓库(cbb/tools/)但不分发,有用户需要时再加入此清单。
1645
+ const USER_LEVEL_SKILLS = [];
1480
1646
 
1481
- async function setupUserLevelComponents() {
1647
+ async function setupUserLevelComponents(targetAdapters = adapters) {
1482
1648
  const home = os.homedir();
1483
1649
  const srcDir = path.join(SHARED_STANDARDS_DIR, 'cbb', 'tools');
1484
1650
  if (!fs.existsSync(srcDir)) return;
@@ -1493,32 +1659,36 @@ async function setupUserLevelComponents() {
1493
1659
  } catch (_) { /* ignore parse errors */ }
1494
1660
  }
1495
1661
 
1496
- section('🏠', '检查用户级组件(~/.claude/、~/.qoder/)');
1662
+ const summaryParts = [];
1497
1663
 
1498
- const skillAdapters = adapters.filter(a => a.skillsDir);
1499
- skillAdapters.forEach(adapter => {
1500
- USER_LEVEL_SKILLS.forEach(skillName => {
1501
- const src = path.join(srcDir, skillName);
1502
- if (!fs.existsSync(src)) return;
1503
- const dest = path.join(home, adapter.skillsDir, skillName);
1504
- removePath(dest);
1505
- fs.cpSync(src, dest, { recursive: true });
1506
- managed.add(`${adapter.skillsDir}/${skillName}`);
1664
+ if (USER_LEVEL_SKILLS.length > 0) {
1665
+ const skillAdapters = targetAdapters.filter(a => a.skillsDir);
1666
+ skillAdapters.forEach(adapter => {
1667
+ USER_LEVEL_SKILLS.forEach(skillName => {
1668
+ const src = path.join(srcDir, skillName);
1669
+ if (!fs.existsSync(src)) return;
1670
+ const dest = path.join(home, adapter.skillsDir, skillName);
1671
+ removePath(dest);
1672
+ fs.cpSync(src, dest, { recursive: true });
1673
+ managed.add(`${adapter.skillsDir}/${skillName}`);
1674
+ });
1507
1675
  });
1508
- });
1509
- log(`工具 Skills → ${skillAdapters.map(a => `~/${a.skillsDir}/`).join(', ')} (${USER_LEVEL_SKILLS.length} 个)`, 'success');
1676
+ summaryParts.push(`工具 skills×${USER_LEVEL_SKILLS.length}`);
1677
+ }
1510
1678
 
1511
1679
  // check-update hook(settingsManager 幂等:已有 _managedBy: cbb 标记则不重复写入)
1512
- const hookAdapters = adapters.filter(a => a.settingsFile && a.hookEvents);
1680
+ const hookAdapters = targetAdapters.filter(a => a.settingsFile && a.hookEvents);
1513
1681
  if (hookAdapters.length > 0) {
1514
1682
  const command = settingsManager.getCheckUpdateCommand();
1515
1683
  hookAdapters.forEach(adapter => {
1516
1684
  settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents);
1517
1685
  settingsManager.removeLegacyHooks(adapter.settingsFile);
1518
1686
  });
1519
- log(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join(', ')}`, 'success');
1687
+ summaryParts.push(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join('')}`);
1520
1688
  }
1521
1689
 
1690
+ if (summaryParts.length > 0) step('✓', '用户级组件', summaryParts.join(' · '));
1691
+
1522
1692
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1523
1693
  fs.writeFileSync(USER_MANIFEST_FILE, JSON.stringify({
1524
1694
  version: pkg.version,
@@ -1608,7 +1778,7 @@ function uninstallConfigYaml(managedPaths) {
1608
1778
  }
1609
1779
 
1610
1780
  /**
1611
- * 卸载用户级组件(cbb-wiki-ops 等工具 skills + 版本检查 hook)。
1781
+ * 卸载用户级组件(工具 skills + 版本检查 hook,按清单精确删除)。
1612
1782
  * 按 ~/.cbb/.user-components 清单精确删除,不影响用户自装内容;
1613
1783
  * ~/.cbb 目录不动(可能含用户自己的 wiki-config.json)。
1614
1784
  */
@@ -1666,7 +1836,7 @@ async function uninstall() {
1666
1836
  // 是否同时卸载用户级组件(工具 skills + 版本检查 hook,作用于 ~/.claude 等用户目录)
1667
1837
  let removeUserLevel = false;
1668
1838
  if (fs.existsSync(USER_MANIFEST_FILE)) {
1669
- const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder 下的工具 skills + 版本检查 hook)?(y/N): ');
1839
+ const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder 的版本检查 hook)?(y/N): ');
1670
1840
  removeUserLevel = answerUser.toLowerCase() === 'y' || answerUser.toLowerCase() === 'yes';
1671
1841
  }
1672
1842
 
@@ -1695,6 +1865,9 @@ async function uninstall() {
1695
1865
  skippedPaths.add(adapter.settingsFile);
1696
1866
  });
1697
1867
 
1868
+ // opencode.json:移除本包登记的 instructions pattern(cbb 创建的整体文件由清单删除兜底)
1869
+ removeInstructionsRegistrations();
1870
+
1698
1871
  // 删除清单中记录的所有路径。
1699
1872
  // 注意:CLAUDE.md / AGENTS.md 等用户文档受保护,即使旧清单里有也不删除。
1700
1873
  let removed = 0;
@@ -1746,9 +1919,34 @@ async function uninstall() {
1746
1919
 
1747
1920
  // ── CLI 入口 ──────────────────────────────────────────────
1748
1921
 
1922
+ // 入口身份:cbb(用户命令) / cbbm(内部命令,由流程、hook、Agent 技能调用)
1923
+ const ENTRY = process.env.CBB_ENTRY === 'cbbm' ? 'cbbm' : 'cbb';
1924
+ const INTERNAL_COMMANDS = ['init', 'check-update', 'wiki', 'wp'];
1925
+ const UPSTREAM_FLAGS = ['--upstream-superpowers', '--upstream-openspec'];
1926
+
1749
1927
  const args = process.argv.slice(2);
1750
1928
  const command = args[0];
1751
1929
  const restArgs = args.slice(1);
1930
+ const hasUpstreamFlag = restArgs.some(a => UPSTREAM_FLAGS.includes(a));
1931
+
1932
+ if (ENTRY === 'cbb' && command === 'update' && hasUpstreamFlag) {
1933
+ log('维护者拉源选项已迁移到 cbbm:cbbm update --upstream-superpowers / --upstream-openspec', 'error');
1934
+ process.exit(1);
1935
+ }
1936
+ if (ENTRY === 'cbbm' && command === 'update' && !hasUpstreamFlag) {
1937
+ log('cbbm update 仅用于维护者拉源(--upstream-superpowers / --upstream-openspec)', 'error');
1938
+ log('升级已安装内容请使用 cbb update', 'warn');
1939
+ process.exit(1);
1940
+ }
1941
+ if (ENTRY === 'cbbm' && (command === 'setup' || command === 'uninstall')) {
1942
+ log(`cbbm 仅承载内部命令;请使用 cbb ${command}`, 'error');
1943
+ process.exit(1);
1944
+ }
1945
+ if (ENTRY === 'cbb' && INTERNAL_COMMANDS.includes(command)) {
1946
+ log(`内部命令已迁移为 cbbm ${command}`, 'error');
1947
+ log('若调用方是未升级的流程文件,请先在项目根执行 cbb update', 'warn');
1948
+ process.exit(1);
1949
+ }
1752
1950
 
1753
1951
  switch (command) {
1754
1952
  case 'setup': {
@@ -1840,6 +2038,11 @@ switch (command) {
1840
2038
  case undefined:
1841
2039
  showHelp();
1842
2040
  break;
2041
+ case 'version':
2042
+ case '-v':
2043
+ case '--version':
2044
+ console.log(require(path.join(SHARED_STANDARDS_DIR, 'package.json')).version);
2045
+ break;
1843
2046
  default:
1844
2047
  log(`未知命令: ${command}`, 'error');
1845
2048
  showHelp();
@@ -1848,6 +2051,22 @@ switch (command) {
1848
2051
 
1849
2052
  function showHelp() {
1850
2053
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
2054
+
2055
+ if (ENTRY === 'cbbm') {
2056
+ console.log(`
2057
+ @jspg-ai/coding-bb v${pkg.version} — cbbm:内部命令(由流程 / hook / Agent 技能调用)
2058
+
2059
+ cbbm init --yes worktree 沙箱内安装开发配置(/cbb:worktree-init Step 4 自动调用)
2060
+ cbbm check-update 版本检查(用户级 hook 每日自动触发)
2061
+ cbbm wiki / cbbm wp Wiki 查询与发布(AI 通过技能调用)
2062
+
2063
+ 维护者选项(拉取上游原生内容到本包源仓库):
2064
+ cbbm update --upstream-superpowers 从 GitHub 拉取最新 superpowers skills
2065
+ cbbm update --upstream-openspec 从 GitHub 拉取最新 OpenSpec 工作流
2066
+ `);
2067
+ return;
2068
+ }
2069
+
1851
2070
  console.log(`
1852
2071
  @jspg-ai/coding-bb v${pkg.version} — AI Coding Agent 统一行为准则与工作流
1853
2072
 
@@ -1856,19 +2075,6 @@ function showHelp() {
1856
2075
  cbb update 更新已安装内容(用户级 + 当前空间/项目)
1857
2076
  cbb uninstall 卸载(交互确认范围)
1858
2077
 
1859
- 内部命令(由流程自动调用,无需手动执行):
1860
- cbb init --yes worktree 沙箱内安装开发配置(init-worktree Step 4 自动调用)
1861
- cbb check-update 版本检查(hook 每日自动触发)
1862
-
1863
- Agent 命令(AI 通过技能调用,请勿手动执行):
1864
- cbb wiki / cbb wp Wiki 查询与发布
1865
-
1866
- 维护者选项(发布流程用):
1867
- cbb update --upstream-superpowers 从 GitHub 拉取最新 superpowers skills 到源仓库
1868
- cbb update --upstream-openspec 从 GitHub 拉取最新 OpenSpec 工作流到源仓库
1869
-
1870
- 90% 的场景只需要:cbb setup
1871
-
1872
2078
  前提条件(首次使用执行一次):
1873
2079
  npm install -g @jspg-ai/coding-bb
1874
2080
  `);