@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.
- package/cbb/bin/cbb.js +6 -0
- package/cbb/bin/cbbm.js +6 -0
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +103 -103
- package/cbb/dev-standards/rules/cbb-priority.md +55 -55
- package/cbb/lib/install/claude-code.js +2 -0
- package/cbb/lib/install/init.js +496 -290
- package/cbb/lib/install/opencode.js +31 -0
- package/cbb/lib/install/qoder.js +2 -0
- package/cbb/lib/install/workspaces.js +53 -13
- package/cbb/lib/openspec/index.js +3 -3
- package/cbb/lib/superpowers/index.js +1 -1
- package/cbb/lib/utils/check-update.js +16 -14
- package/cbb/lib/utils/gitignore.js +2 -1
- package/cbb/lib/utils/output.js +92 -4
- package/cbb/lib/utils/version.js +2 -31
- package/cbb/lib/wiki/cli.js +25 -25
- package/cbb/lib/wiki/index.js +16 -16
- package/cbb/tools/cbb-design-to-wiki/SKILL.md +14 -14
- package/cbb/tools/cbb-wiki-ops/SKILL.md +22 -22
- package/cbb/worktrees/_shared/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/_shared/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/_shared/scripts/push-core.js +18 -0
- package/cbb/worktrees/commands/{close.md → worktree-close.md} +8 -8
- package/cbb/worktrees/commands/{init.md → worktree-init.md} +9 -9
- package/cbb/worktrees/commands/{push.md → worktree-push.md} +3 -3
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/SKILL.md +20 -20
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-env.js +4 -4
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/check-unarchived.js +1 -1
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/delete-branches.js +3 -3
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/discover-apps.js +6 -6
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-close}/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-close}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/remove-worktrees.js +4 -4
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/safety-check.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/SKILL.md +81 -107
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/auto-open.js +1 -1
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env-deep.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-env.js +6 -6
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/check-repos.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-branches.js +4 -4
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/create-worktrees.js +8 -7
- package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-init}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/install-ai.js +33 -33
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/parse-config.js +7 -7
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/push-branches.js +9 -7
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/push-core.js +18 -0
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/sync-repos.js +10 -12
- package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-init}/scripts/update-gitignore.js +19 -6
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/SKILL.md +16 -16
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/commit-worktrees.js +2 -2
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/find-target-worktree.js +2 -2
- package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-push}/scripts/find-workspace-root.js +7 -7
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-core.js +18 -0
- package/cbb/worktrees/skills/{openspec-push-worktrees → cbb-worktree-push}/scripts/push-worktrees.js +7 -5
- package/config/workspace-config.sample.json +18 -0
- package/config/workspaces.json +11 -6
- package/openspec/.version +6 -6
- package/openspec/commands/apply.md +7 -7
- package/openspec/commands/archive.md +7 -7
- package/openspec/commands/bulk-archive.md +7 -7
- package/openspec/commands/continue.md +7 -7
- package/openspec/commands/explore.md +7 -7
- package/openspec/commands/ff.md +7 -7
- package/openspec/commands/new.md +7 -7
- package/openspec/commands/onboard.md +7 -7
- package/openspec/commands/propose.md +7 -7
- package/openspec/commands/sync.md +7 -7
- package/openspec/commands/update.md +7 -7
- package/openspec/commands/verify.md +7 -7
- package/openspec/skills/openspec-apply-change/SKILL.md +187 -187
- package/openspec/skills/openspec-archive-change/SKILL.md +181 -181
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +338 -338
- package/openspec/skills/openspec-continue-change/SKILL.md +117 -117
- package/openspec/skills/openspec-explore/SKILL.md +342 -342
- package/openspec/skills/openspec-ff-change/SKILL.md +116 -116
- package/openspec/skills/openspec-new-change/SKILL.md +76 -76
- package/openspec/skills/openspec-onboard/SKILL.md +560 -560
- package/openspec/skills/openspec-propose/SKILL.md +162 -162
- package/openspec/skills/openspec-sync-specs/SKILL.md +261 -261
- package/openspec/skills/openspec-update-change/SKILL.md +90 -90
- package/openspec/skills/openspec-verify-change/SKILL.md +174 -174
- package/package.json +4 -3
- package/superpowers/.version +6 -6
- package/superpowers/skills/brainstorming/SKILL.md +250 -250
- package/superpowers/skills/brainstorming/scripts/helper.js +167 -167
- package/superpowers/skills/brainstorming/scripts/start-server.sh +209 -209
- package/superpowers/skills/brainstorming/scripts/stop-server.sh +120 -120
- package/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -49
- package/superpowers/skills/brainstorming/visual-companion.md +299 -299
- package/superpowers/skills/dispatching-parallel-agents/SKILL.md +167 -167
- package/superpowers/skills/executing-plans/SKILL.md +64 -64
- package/superpowers/skills/finishing-a-development-branch/SKILL.md +225 -225
- package/superpowers/skills/receiving-code-review/SKILL.md +205 -205
- package/superpowers/skills/requesting-code-review/SKILL.md +95 -95
- package/superpowers/skills/requesting-code-review/code-reviewer.md +181 -181
- package/superpowers/skills/subagent-driven-development/SKILL.md +568 -568
- package/superpowers/skills/subagent-driven-development/implementer-prompt.md +154 -154
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +115 -115
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +207 -207
- package/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/superpowers/skills/systematic-debugging/SKILL.md +283 -283
- package/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/superpowers/skills/systematic-debugging/find-polluter.sh +72 -72
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/superpowers/skills/systematic-debugging/test-academic.md +14 -14
- package/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/superpowers/skills/test-driven-development/SKILL.md +320 -320
- package/superpowers/skills/test-driven-development/writing-good-tests.md +198 -198
- package/superpowers/skills/using-git-worktrees/SKILL.md +167 -167
- package/superpowers/skills/using-superpowers/SKILL.md +63 -63
- package/superpowers/skills/using-superpowers/references/antigravity-tools.md +23 -23
- package/superpowers/skills/using-superpowers/references/codex-tools.md +108 -108
- package/superpowers/skills/using-superpowers/references/gemini-tools.md +63 -63
- package/superpowers/skills/using-superpowers/references/hermes-tools.md +56 -56
- package/superpowers/skills/using-superpowers/references/pi-tools.md +16 -16
- package/superpowers/skills/verification-before-completion/SKILL.md +120 -120
- package/superpowers/skills/writing-plans/SKILL.md +171 -171
- package/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -49
- package/superpowers/skills/writing-skills/SKILL.md +679 -679
- package/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/superpowers/skills/writing-skills/persuasion-principles.md +187 -187
- package/superpowers/skills/writing-skills/render-graphs.js +169 -169
- package/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/cbb/worktrees/commands/extend.md +0 -65
- package/cbb/worktrees/skills/openspec-close-worktree/scripts/find-target-worktree.js +0 -96
- package/cbb/worktrees/skills/openspec-extend-worktree/SKILL.md +0 -390
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-env.js +0 -95
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/check-repos.js +0 -98
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/create-branches-and-worktrees.js +0 -135
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/install-ai.js +0 -150
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/list-available-apps.js +0 -88
- package/cbb/worktrees/skills/openspec-extend-worktree/scripts/sync-repos.js +0 -93
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/find-workspace-root.js +0 -67
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/generate-app-options.js +0 -93
- package/cbb/worktrees/skills/openspec-init-worktree/scripts/push-core.js +0 -136
- package/cbb/worktrees/skills/openspec-push-worktrees/scripts/silence-popup.js +0 -23
- package/config/config.sample.json +0 -16
- /package/cbb/worktrees/skills/{openspec-close-worktree → cbb-worktree-close}/scripts/silence-popup.js +0 -0
- /package/cbb/worktrees/skills/{openspec-extend-worktree → cbb-worktree-init}/scripts/silence-popup.js +0 -0
- /package/cbb/worktrees/skills/{openspec-init-worktree → cbb-worktree-push}/scripts/silence-popup.js +0 -0
package/cbb/lib/install/init.js
CHANGED
|
@@ -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 &&
|
|
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
|
|
56
|
+
// worktree 扩展 skills 部署到沙箱的白名单:init 只在 workspace 根执行
|
|
52
57
|
// (由 installWorkspaceConfig 部署),沙箱内只需 push(推送)与 close(关闭)
|
|
53
|
-
const SANDBOX_WORKTREE_SKILLS = ['
|
|
58
|
+
const SANDBOX_WORKTREE_SKILLS = ['cbb-worktree-push', 'cbb-worktree-close'];
|
|
54
59
|
|
|
55
|
-
// 装到沙箱的 tools skills
|
|
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,
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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(
|
|
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(
|
|
469
|
+
const destPath = path.join(destDir, prefix + f);
|
|
381
470
|
removePath(destPath);
|
|
382
|
-
fs.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
566
|
+
result.config = 'merged';
|
|
458
567
|
} else {
|
|
459
568
|
fs.writeFileSync(configDest, ourContent);
|
|
460
|
-
|
|
569
|
+
result.config = 'created';
|
|
461
570
|
}
|
|
462
571
|
|
|
463
|
-
//
|
|
572
|
+
// 汇总已安装的 schema 与默认工作流
|
|
464
573
|
if (fs.existsSync(schemasDest)) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 个
|
|
765
|
-
// init
|
|
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
|
|
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
|
|
784
|
-
const extResult =
|
|
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.
|
|
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/
|
|
1027
|
+
* 只装 worktree 生命周期管理(init/close/push)+ openspec 运行时配置,
|
|
915
1028
|
* 不装 base 工作流、编码规范、superpowers、版本检查 hook--这些属于 sandbox worktree
|
|
916
|
-
* 内的开发配置,由 worktree 内 `
|
|
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
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
|
935
|
-
await ensureOpenSpecCli();
|
|
1060
|
+
const openspecCli = await ensureOpenSpecCli();
|
|
936
1061
|
|
|
937
|
-
// 只装 worktree 生命周期管理 Skills(init/
|
|
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 = ['
|
|
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
|
-
|
|
1075
|
+
skillCount = Math.max(skillCount, r.count);
|
|
949
1076
|
r.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
950
1077
|
}
|
|
951
1078
|
});
|
|
952
|
-
|
|
1079
|
+
adapterRoots = skillsAdapters.map(a => a.skillsDir.split('/')[0]);
|
|
953
1080
|
}
|
|
954
1081
|
|
|
955
|
-
// 只装 worktree 管理 Commands(init/
|
|
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', '
|
|
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 =
|
|
1087
|
+
const r = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
|
|
965
1088
|
if (r.count > 0) {
|
|
966
|
-
|
|
967
|
-
r.names.forEach(name => newPaths.add(`${adapter.
|
|
1089
|
+
cmdCount = Math.max(cmdCount, r.count);
|
|
1090
|
+
r.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
|
|
968
1091
|
}
|
|
969
1092
|
});
|
|
970
|
-
if (
|
|
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
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
*
|
|
1029
|
-
* -
|
|
1030
|
-
* - 未安装
|
|
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
|
|
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
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
// 网络失败静默降级:latest 保持 null
|
|
1173
|
+
// 版本未变化 → 不处理(不询问、不重复安装)
|
|
1174
|
+
if (version.compareVersions(installed, pkg.version) === 0) {
|
|
1175
|
+
step('✓', '版本', `v${installed}(已是最新,跳过)`);
|
|
1176
|
+
return 'skip';
|
|
1051
1177
|
}
|
|
1052
1178
|
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1075
|
-
return
|
|
1182
|
+
step('!', '版本', '已跳过安装/升级', { iconColor: 'yellow' });
|
|
1183
|
+
return 'cancel';
|
|
1076
1184
|
}
|
|
1077
|
-
return
|
|
1185
|
+
return 'install';
|
|
1078
1186
|
}
|
|
1079
1187
|
|
|
1080
1188
|
/**
|
|
1081
|
-
* 检查业务空间根目录下是否有
|
|
1082
|
-
* 不存在时,将包内样例文件复制为
|
|
1189
|
+
* 检查业务空间根目录下是否有 workspace-config.json(init-worktree 技能依赖)。
|
|
1190
|
+
* 不存在时,将包内样例文件复制为 workspace-config.json。
|
|
1191
|
+
* 返回 'exists' | 'created'(不打印,由调用方汇总输出)。
|
|
1083
1192
|
*/
|
|
1084
|
-
function ensureWorkspaceConfig(targetDir
|
|
1085
|
-
const configPath = path.join(targetDir, '
|
|
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
|
-
|
|
1095
|
-
log('请编辑 CONFIG.json,将示例 apps 替换为实际关联应用', 'warn');
|
|
1199
|
+
return 'created';
|
|
1096
1200
|
}
|
|
1097
1201
|
|
|
1098
1202
|
/**
|
|
1099
|
-
* 读取
|
|
1203
|
+
* 读取 workspace-config.json 中的应用列表,将关联应用代码 clone/fetch 到 .codespace/ 目录。
|
|
1100
1204
|
* 首次 clone(耗时较长),后续 fetch --all(秒级更新)。
|
|
1101
1205
|
* 这是基准代码,init-worktree 技能后续只做轻量 fetch 检查。
|
|
1102
1206
|
*/
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1118
|
-
return;
|
|
1230
|
+
summary.empty = true;
|
|
1231
|
+
return summary;
|
|
1119
1232
|
}
|
|
1120
1233
|
|
|
1121
|
-
const codeDir = path.join(targetDir, '.
|
|
1234
|
+
const codeDir = path.join(targetDir, '.codespace');
|
|
1122
1235
|
fs.mkdirSync(codeDir, { recursive: true });
|
|
1123
1236
|
|
|
1124
|
-
|
|
1125
|
-
|
|
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
|
-
|
|
1128
|
-
fail++;
|
|
1129
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1147
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 => `
|
|
1281
|
-
: `
|
|
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(`
|
|
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
|
-
|
|
1416
|
+
const cloneProgress = progress('仓库');
|
|
1417
|
+
cloneProgress.update(`克隆 ${subdirName}/(首次较慢)`);
|
|
1294
1418
|
try {
|
|
1295
1419
|
// 在 parentDir 下克隆,git 会自动创建 subdirName 子目录
|
|
1296
|
-
|
|
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
|
-
|
|
1432
|
+
step('✓', '仓库', `已克隆 ${subdirName}/`);
|
|
1308
1433
|
}
|
|
1309
1434
|
|
|
1310
|
-
//
|
|
1311
|
-
ensureWorkspaceConfig(targetDir
|
|
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
|
-
|
|
1318
|
-
|
|
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}/),请配好
|
|
1445
|
+
log(`已撤销本次克隆(${subdirName}/),请配好 workspace-config.json 后重新执行 cbb setup`, 'warn');
|
|
1322
1446
|
} else {
|
|
1323
|
-
log('请修正
|
|
1447
|
+
log('请修正 workspace-config.json 后重新执行 cbb setup', 'warn');
|
|
1324
1448
|
}
|
|
1325
|
-
|
|
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
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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
|
-
//
|
|
1335
|
-
const
|
|
1336
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
//
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
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
|
-
//
|
|
1445
|
-
section('📁', '业务空间将创建在以下目录');
|
|
1446
|
-
console.log(` ${c('cyan', parentDir)}`);
|
|
1599
|
+
// 5. 目录确认
|
|
1447
1600
|
console.log('');
|
|
1448
|
-
const answer = await ask(`${c('yellow', '?')}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1640
|
+
* - 内容:check-update hook(settings 由 settingsManager 幂等管理)
|
|
1476
1641
|
*/
|
|
1477
1642
|
const USER_MANIFEST_FILE = path.join(os.homedir(), '.cbb', '.user-components');
|
|
1478
|
-
// 装到用户级的工具 skills
|
|
1479
|
-
|
|
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
|
-
|
|
1662
|
+
const summaryParts = [];
|
|
1497
1663
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
-
|
|
1676
|
+
summaryParts.push(`工具 skills×${USER_LEVEL_SKILLS.length}`);
|
|
1677
|
+
}
|
|
1510
1678
|
|
|
1511
1679
|
// check-update hook(settingsManager 幂等:已有 _managedBy: cbb 标记则不重复写入)
|
|
1512
|
-
const hookAdapters =
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
`);
|