@jspg-ai/coding-bb 0.0.2-beta.29 → 0.0.3-beta.12
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/cbbm.js +1 -1
- package/cbb/dev-standards/rules/cbb-ai-behavior.md +118 -103
- package/cbb/lib/install/claude-code.js +1 -3
- package/cbb/lib/install/codebuddy.js +30 -0
- package/cbb/lib/install/init.js +354 -305
- package/cbb/lib/install/opencode.js +35 -31
- package/cbb/lib/install/qoder.js +1 -3
- package/cbb/lib/install/rule-rewrite.js +27 -0
- package/cbb/lib/install/trae.js +33 -0
- package/cbb/lib/install/workspaces.js +24 -0
- package/cbb/lib/openspec/index.js +337 -554
- package/cbb/lib/superpowers/index.js +246 -265
- package/cbb/lib/utils/check-update.js +9 -2
- package/cbb/lib/utils/gitignore.js +2 -0
- package/cbb/lib/utils/settings.js +17 -4
- package/cbb/lib/utils/tar.js +92 -0
- package/cbb/lib/utils/upstream.js +90 -0
- package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +33 -24
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/check-env.js +4 -4
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +61 -78
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env-deep.js +3 -33
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/check-env.js +5 -14
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +15 -37
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +25 -9
- package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +67 -31
- package/config/openspec/schemas/spec-driven/schema.yaml +29 -30
- package/config/openspec/schemas/spec-driven/templates/design.md +0 -18
- package/config/openspec/schemas/spec-driven/templates/proposal.md +4 -3
- package/config/upstream-mirrors.json +12 -0
- package/config/workspace-agents.sample.md +17 -13
- package/config/workspaces.json +5 -0
- package/openspec/.version +2 -3
- package/openspec/commands/apply.md +189 -175
- package/openspec/commands/archive.md +237 -216
- package/openspec/commands/bulk-archive.md +355 -327
- package/openspec/commands/continue.md +116 -105
- package/openspec/commands/explore.md +230 -199
- package/openspec/commands/ff.md +115 -104
- package/openspec/commands/new.md +74 -63
- package/openspec/commands/onboard.md +557 -548
- package/openspec/commands/propose.md +161 -150
- package/openspec/commands/sync.md +277 -249
- package/openspec/commands/update.md +92 -80
- package/openspec/commands/verify.md +175 -162
- package/openspec/skills/openspec-apply-change/SKILL.md +20 -5
- package/openspec/skills/openspec-archive-change/SKILL.md +30 -8
- package/openspec/skills/openspec-bulk-archive-change/SKILL.md +36 -6
- package/openspec/skills/openspec-continue-change/SKILL.md +14 -2
- package/openspec/skills/openspec-explore/SKILL.md +21 -9
- package/openspec/skills/openspec-ff-change/SKILL.md +14 -2
- package/openspec/skills/openspec-new-change/SKILL.md +13 -1
- package/openspec/skills/openspec-onboard/SKILL.md +49 -39
- package/openspec/skills/openspec-propose/SKILL.md +15 -3
- package/openspec/skills/openspec-sync-specs/SKILL.md +31 -2
- package/openspec/skills/openspec-update-change/SKILL.md +27 -14
- package/openspec/skills/openspec-verify-change/SKILL.md +17 -3
- package/package.json +2 -2
- package/superpowers/.version +4 -4
- package/superpowers/skills/brainstorming/SKILL.md +47 -12
- package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -213
- package/superpowers/skills/brainstorming/scripts/server.cjs +723 -723
- package/superpowers/skills/brainstorming/visual-companion.md +6 -6
- package/superpowers/skills/diagnosing-superpowers/SKILL.md +120 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/analyst-common.md +38 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/cost-and-time.md +28 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/plan-adherence.md +29 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/quality-evidence.md +26 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/repeated-work.md +30 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/request-conflicts.md +20 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/scrub-audit.md +33 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/scrub.md +29 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/similar-session.md +38 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/skill-timeline.md +30 -0
- package/superpowers/skills/diagnosing-superpowers/prompts/stumbles.md +28 -0
- package/superpowers/skills/diagnosing-superpowers/references/context-safety.md +22 -0
- package/superpowers/skills/diagnosing-superpowers/references/github-issues.md +47 -0
- package/superpowers/skills/diagnosing-superpowers/references/redaction-policy.md +34 -0
- package/superpowers/skills/diagnosing-superpowers/references/session-discovery.md +31 -0
- package/superpowers/skills/diagnosing-superpowers/templates/bundle-README.md +77 -0
- package/superpowers/skills/diagnosing-superpowers/templates/case.md +64 -0
- package/superpowers/skills/diagnosing-superpowers/templates/issue.md +51 -0
- package/superpowers/skills/diagnosing-superpowers/templates/report.md +82 -0
- package/superpowers/skills/executing-plans/SKILL.md +350 -41
- package/superpowers/skills/executing-plans/scripts/task-done +52 -0
- package/superpowers/skills/executing-plans/scripts/task-start +28 -0
- package/superpowers/skills/requesting-code-review/SKILL.md +1 -1
- package/superpowers/skills/requesting-code-review/code-reviewer.md +17 -0
- package/superpowers/skills/subagent-driven-development/SKILL.md +18 -18
- package/superpowers/skills/subagent-driven-development/re-review-prompt.md +1 -1
- package/superpowers/skills/subagent-driven-development/scripts/review-package +53 -46
- package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +82 -40
- package/superpowers/skills/subagent-driven-development/scripts/task-brief +43 -41
- package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
- package/superpowers/skills/systematic-debugging/root-cause-tracing.md +1 -1
- package/superpowers/skills/test-driven-development/SKILL.md +10 -0
- package/superpowers/skills/using-superpowers/SKILL.md +2 -0
- package/superpowers/skills/using-superpowers/references/claude-code-tools.md +29 -0
- package/superpowers/skills/using-superpowers/references/muse-tools.md +35 -0
- package/superpowers/skills/writing-plans/SKILL.md +30 -9
- package/superpowers/skills/writing-skills/SKILL.md +4 -2
- package/superpowers/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/cbb/worktrees/commands/worktree-close.md +0 -64
- package/cbb/worktrees/commands/worktree-init.md +0 -55
- package/cbb/worktrees/commands/worktree-push.md +0 -42
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/auto-open.js +0 -136
- package/cbb/worktrees/skills/cbb-worktree-init/scripts/install-ai.js +0 -177
package/cbb/lib/install/init.js
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* OpenSpec Shared Standards 安装脚本
|
|
5
5
|
*
|
|
6
6
|
* 将AI 编码规范与工作流套件安装到目标项目中。
|
|
7
|
-
* 支持工具:Claude Code (.claude/rules/)、Qoder (.qoder/rules/)、opencode (.opencode/rules/)
|
|
7
|
+
* 支持工具:Claude Code (.claude/rules/)、Qoder (.qoder/rules/)、opencode (.opencode/rules/)、
|
|
8
|
+
* WorkBuddy/CodeBuddy (.codebuddy/rules/)、TraeWork/TraeCode (.trae/rules/)
|
|
8
9
|
*
|
|
9
10
|
* 使用方式:
|
|
10
11
|
* npm install -g @jspg-ai/coding-bb # 首次使用
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* cbb setup # 初始化业务空间(空间根全量安装)
|
|
13
|
+
* cbb update # 就地升级已安装目录
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const fs = require('fs');
|
|
@@ -40,7 +41,7 @@ function execCmd(file, args, opts = {}) {
|
|
|
40
41
|
|
|
41
42
|
const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
|
|
42
43
|
|
|
43
|
-
// 目标项目根目录:默认取当前 cwd,init()
|
|
44
|
+
// 目标项目根目录:默认取当前 cwd,init() 可在调用前覆盖。
|
|
44
45
|
// 注意:使用 let 而非 const 是为了让 cbb setup 能在拉好仓库后把目标目录
|
|
45
46
|
// 切到仓库根,再复用 init() 的完整安装流程,而无需把 TARGET_DIR 透传到所有 helper。
|
|
46
47
|
let TARGET_DIR = process.cwd();
|
|
@@ -51,14 +52,17 @@ const MANIFEST_FILE = path.join(MANIFEST_DIR, '.managed-by-cbb');
|
|
|
51
52
|
const LAST_TOOLS_FILE = path.join(MANIFEST_DIR, '.last-tools');
|
|
52
53
|
|
|
53
54
|
// 适配器
|
|
55
|
+
// 适配器(数组顺序 = 工具选择面板的显示顺序)
|
|
54
56
|
const adapters = [
|
|
55
|
-
require('./claude-code'),
|
|
56
|
-
require('./qoder'),
|
|
57
57
|
require('./opencode'),
|
|
58
|
+
require('./codebuddy'),
|
|
59
|
+
require('./trae'),
|
|
60
|
+
require('./qoder'),
|
|
61
|
+
require('./claude-code'),
|
|
58
62
|
];
|
|
59
63
|
|
|
60
|
-
// superpowers 部署白名单:仅装 openspec 工作流硬依赖 + 方法论基底(源仓库仍保留全量
|
|
61
|
-
// 供
|
|
64
|
+
// superpowers 部署白名单:仅装 openspec 工作流硬依赖 + 方法论基底(源仓库仍保留全量 15 个,
|
|
65
|
+
// 供 cbbm update --upstream-superpowers 维护;砍掉的与 openspec 工作流重叠或属低频场景)
|
|
62
66
|
const SUPERPOWERS_WHITELIST = [
|
|
63
67
|
'writing-plans', // openspec apply:生成 plan.md(schema.yaml 引用)
|
|
64
68
|
'subagent-driven-development', // openspec apply:执行 plan.md(schema.yaml 引用)
|
|
@@ -68,20 +72,33 @@ const SUPERPOWERS_WHITELIST = [
|
|
|
68
72
|
'test-driven-development', // TDD 计划结构 + TDD Evidence 方法论基底
|
|
69
73
|
];
|
|
70
74
|
|
|
71
|
-
// worktree
|
|
72
|
-
//
|
|
73
|
-
|
|
75
|
+
// worktree 管理 skill 清单:init / close / push 三件套(SKILL.md + scripts)。
|
|
76
|
+
// 按 skill 形态部署到各工具的 skillsDir,使这三个能力可被 AI 在对话中自动触发
|
|
77
|
+
// (description 驱动),同时工具也会把它们暴露为斜杠命令供用户手动指定。
|
|
78
|
+
const WORKTREE_SKILLS = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
|
|
79
|
+
|
|
80
|
+
// 装到空间的 tools skills:当前不分发。
|
|
81
|
+
// cbb-design-to-wiki(Wiki 发布)与 cbb-wiki-ops 保留在源仓库,需要时加入本清单即恢复分发;Java 工具不装。
|
|
82
|
+
const TOOLS_SKILLS = [];
|
|
74
83
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
const
|
|
84
|
+
// 不随安装分发的官方 OpenSpec 命令(源仓库保留,仍由 CLI 接管升级):
|
|
85
|
+
// onboard 为新手引导教程,团队入门已由文档覆盖;sync 是 archive 流程承重件,保留分发。
|
|
86
|
+
const OPENSPEC_COMMANDS_EXCLUDE = ['onboard.md'];
|
|
78
87
|
|
|
79
88
|
// settings.json hook 工具
|
|
80
89
|
const settingsManager = require('../utils/settings');
|
|
81
90
|
|
|
91
|
+
/** 适配器的 hook 方言参数(Trae 需顶层 version 字段等差异) */
|
|
92
|
+
function hookOpts(adapter) {
|
|
93
|
+
return adapter.hookDialect ? { dialect: adapter.hookDialect } : {};
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
// 清单驱动的文件清理决策(保护用户文档 CLAUDE.md / AGENTS.md 等)
|
|
83
97
|
const { pathsToDelete, protectedKeptPaths } = require('./cleanup');
|
|
84
98
|
|
|
99
|
+
// 规则 frontmatter 改写(Trae/CodeBuddy 需 alwaysApply: true)
|
|
100
|
+
const { applyRuleRewrites } = require('./rule-rewrite');
|
|
101
|
+
|
|
85
102
|
// .gitignore 条目决策(纯函数:检测/写入/卸载移除)
|
|
86
103
|
const gitignore = require('../utils/gitignore');
|
|
87
104
|
|
|
@@ -109,6 +126,18 @@ function detectExistingInstall() {
|
|
|
109
126
|
return fs.existsSync(path.join(TARGET_DIR, MANIFEST_FILE));
|
|
110
127
|
}
|
|
111
128
|
|
|
129
|
+
/**
|
|
130
|
+
* 判定目标目录是否为业务空间根:workspace-config.json 存在且 .git 是目录。
|
|
131
|
+
* 需求工作树内也有 workspace-config.json(tracked 副本),但其 .git 是文件(gitdir 指针),
|
|
132
|
+
* 必须以 .git 形态排除——避免「在 worktree 里误跑 cbb update」被当作空间根
|
|
133
|
+
* (误装 worktree 管理入口、在错误位置报旧导航提示)。
|
|
134
|
+
*/
|
|
135
|
+
function detectWorkspaceRoot() {
|
|
136
|
+
if (!fs.existsSync(path.join(TARGET_DIR, 'workspace-config.json'))) return false;
|
|
137
|
+
const gitPath = path.join(TARGET_DIR, '.git');
|
|
138
|
+
return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
// ── 清单管理 ──────────────────────────────────────────────
|
|
113
142
|
|
|
114
143
|
/** 读取清单,返回 Set<path> */
|
|
@@ -193,6 +222,39 @@ function removePath(targetPath) {
|
|
|
193
222
|
}
|
|
194
223
|
}
|
|
195
224
|
|
|
225
|
+
/**
|
|
226
|
+
* 清理因删除文件/目录而变空的祖先目录(从深到浅,止于 TARGET_DIR)。
|
|
227
|
+
*
|
|
228
|
+
* 删除清单路径只删到该路径本身,其父目录会留空(如改名后残留的空 `.cbb/skills/`)。
|
|
229
|
+
* 此处统一收尾,升级与卸载共用。仅删除「存在且为空」的目录,非空目录(含用户文件)
|
|
230
|
+
* 一律保留。
|
|
231
|
+
*
|
|
232
|
+
* @param {Iterable<string>} relativePaths 已删除的相对路径(正斜杠)
|
|
233
|
+
* @returns {string[]} 实际删除的空目录相对路径
|
|
234
|
+
*/
|
|
235
|
+
function cleanupEmptyParentDirs(relativePaths) {
|
|
236
|
+
const dirsToCheck = new Set();
|
|
237
|
+
for (const relativePath of relativePaths) {
|
|
238
|
+
let dir = path.dirname(relativePath);
|
|
239
|
+
while (dir && dir !== '.') {
|
|
240
|
+
dirsToCheck.add(dir);
|
|
241
|
+
dir = path.dirname(dir);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const removed = [];
|
|
246
|
+
[...dirsToCheck]
|
|
247
|
+
.sort((a, b) => b.split('/').length - a.split('/').length)
|
|
248
|
+
.forEach(dir => {
|
|
249
|
+
const fullPath = path.join(TARGET_DIR, dir);
|
|
250
|
+
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length === 0) {
|
|
251
|
+
removePath(fullPath);
|
|
252
|
+
removed.push(dir);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
return removed;
|
|
256
|
+
}
|
|
257
|
+
|
|
196
258
|
/**
|
|
197
259
|
* 安全删除目标路径(文件或目录)
|
|
198
260
|
* 在清单中 → 直接删除(本包旧版本)
|
|
@@ -290,7 +352,8 @@ function deployCoreRules(adapter, isUpgrade, managedPaths) {
|
|
|
290
352
|
// 同名文件存在时直接覆盖
|
|
291
353
|
removePath(destPath);
|
|
292
354
|
|
|
293
|
-
|
|
355
|
+
const content = applyRuleRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'));
|
|
356
|
+
fs.writeFileSync(destPath, content);
|
|
294
357
|
}
|
|
295
358
|
});
|
|
296
359
|
|
|
@@ -299,56 +362,117 @@ function deployCoreRules(adapter, isUpgrade, managedPaths) {
|
|
|
299
362
|
// ── opencode instructions 登记 ────────────────────────────────
|
|
300
363
|
|
|
301
364
|
/**
|
|
302
|
-
* opencode 无原生 rules 目录:把 .opencode/
|
|
303
|
-
* opencode.
|
|
365
|
+
* opencode 无原生 rules 目录:把 rules glob 登记进 .opencode/opencode.json 的
|
|
366
|
+
* instructions(opencode V2 支持的配置位置;随 .opencode gitignore,按机器生成)。
|
|
367
|
+
* instructions 相对路径的解析基准(配置文件目录 vs 项目根)文档未写死,
|
|
368
|
+
* 故登记双 glob 对冲:任一基准下恰有一个命中,另一个空匹配。
|
|
304
369
|
* - 文件不存在:创建(记入清单,卸载时整体删除)
|
|
305
|
-
* -
|
|
306
|
-
*
|
|
370
|
+
* - 文件存在:幂等补齐缺失的 glob(保留用户已有 instructions)
|
|
371
|
+
* - JSONC(含注释)解析失败则只提示不改写
|
|
372
|
+
* 旧版登记在项目根 opencode.json,由 migrateLegacyInstructions 负责迁移清理。
|
|
307
373
|
*/
|
|
308
374
|
function injectInstructions(adapter, newPaths, managedPaths) {
|
|
309
|
-
const cfgPath = path.join(TARGET_DIR,
|
|
310
|
-
const
|
|
375
|
+
const cfgPath = path.join(TARGET_DIR, adapter.instructionsFile);
|
|
376
|
+
const patterns = adapter.instructionsPatterns;
|
|
377
|
+
let cfg = {};
|
|
378
|
+
if (fs.existsSync(cfgPath)) {
|
|
379
|
+
try {
|
|
380
|
+
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
381
|
+
} catch (_) {
|
|
382
|
+
log(`${adapter.instructionsFile} 解析失败(可能含注释),未自动登记。请手动在 instructions 中加入 "${patterns.join('", "')}"`, 'warn');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!cfg || typeof cfg !== 'object') cfg = {};
|
|
386
|
+
}
|
|
387
|
+
const list = Array.isArray(cfg.instructions) ? cfg.instructions : [];
|
|
388
|
+
const merged = [...list];
|
|
389
|
+
let added = false;
|
|
390
|
+
patterns.forEach(p => {
|
|
391
|
+
if (!merged.includes(p)) {
|
|
392
|
+
merged.push(p);
|
|
393
|
+
added = true;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
311
396
|
if (!fs.existsSync(cfgPath)) {
|
|
312
|
-
|
|
397
|
+
cfg.instructions = merged;
|
|
398
|
+
if (!cfg.$schema) cfg.$schema = 'https://opencode.ai/config.json';
|
|
399
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
313
400
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
314
|
-
newPaths.add(
|
|
315
|
-
log(`生成
|
|
316
|
-
|
|
401
|
+
newPaths.add(adapter.instructionsFile);
|
|
402
|
+
log(`生成 ${adapter.instructionsFile} 并登记 rules 加载(instructions: ${patterns.join(' | ')})`, 'success');
|
|
403
|
+
} else if (added) {
|
|
404
|
+
cfg.instructions = merged;
|
|
405
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
406
|
+
log(`已在 ${adapter.instructionsFile} instructions 中登记 rules glob`, 'success');
|
|
317
407
|
}
|
|
408
|
+
// 本包此前创建的文件:续登清单,避免升级清理误删
|
|
409
|
+
if (managedPaths.has(adapter.instructionsFile)) newPaths.add(adapter.instructionsFile);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 旧版(≤0.0.3-beta.2)把 rules glob 登记在项目根 opencode.json;迁移为
|
|
414
|
+
* .opencode/opencode.json 后需把旧 pattern 从根文件摘除(用户其余配置保留)。
|
|
415
|
+
* 根文件若由本包创建(在清单中),升级时由清单差集自动整体删除,此处只摘 pattern。
|
|
416
|
+
*/
|
|
417
|
+
function migrateLegacyInstructions(adapter) {
|
|
418
|
+
const legacyPattern = adapter.legacyInstructionsPattern;
|
|
419
|
+
if (!legacyPattern) return;
|
|
420
|
+
const cfgPath = path.join(TARGET_DIR, 'opencode.json');
|
|
421
|
+
if (!fs.existsSync(cfgPath)) return;
|
|
318
422
|
let cfg;
|
|
319
423
|
try {
|
|
320
424
|
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
321
425
|
} catch (_) {
|
|
322
|
-
log(
|
|
426
|
+
log('根 opencode.json 解析失败(可能含注释),未自动迁移旧登记。可手动移除 instructions 中的 "' + legacyPattern + '"', 'warn');
|
|
323
427
|
return;
|
|
324
428
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
cfg.instructions
|
|
429
|
+
if (Array.isArray(cfg.instructions) && cfg.instructions.includes(legacyPattern)) {
|
|
430
|
+
cfg.instructions = cfg.instructions.filter(p => p !== legacyPattern);
|
|
431
|
+
if (cfg.instructions.length === 0) delete cfg.instructions;
|
|
328
432
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
329
|
-
log(
|
|
433
|
+
log('已从根 opencode.json 移除旧版 rules 登记(迁移至 .opencode/opencode.json)', 'success');
|
|
330
434
|
}
|
|
331
|
-
// 本包此前创建的文件:续登清单,避免升级清理误删
|
|
332
|
-
if (managedPaths.has('opencode.json')) newPaths.add('opencode.json');
|
|
333
435
|
}
|
|
334
436
|
|
|
335
|
-
/** 卸载时移除本包登记的 instructions
|
|
437
|
+
/** 卸载时移除本包登记的 instructions(用户自建配置只摘本包 glob,不删文件) */
|
|
336
438
|
function removeInstructionsRegistrations() {
|
|
337
439
|
adapters.forEach(adapter => {
|
|
338
|
-
if (!adapter.
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
cfg
|
|
344
|
-
|
|
345
|
-
|
|
440
|
+
if (!adapter.instructionsFile) return;
|
|
441
|
+
const patterns = adapter.instructionsPatterns;
|
|
442
|
+
// 新位置:.opencode/opencode.json(cbb 创建的整体文件由清单删除兜底)
|
|
443
|
+
const cfgPath = path.join(TARGET_DIR, adapter.instructionsFile);
|
|
444
|
+
if (fs.existsSync(cfgPath)) {
|
|
445
|
+
let cfg;
|
|
446
|
+
try {
|
|
447
|
+
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
448
|
+
} catch (_) {
|
|
449
|
+
cfg = null; // 不可解析的文件不动
|
|
450
|
+
}
|
|
451
|
+
if (cfg && Array.isArray(cfg.instructions)) {
|
|
452
|
+
const filtered = cfg.instructions.filter(p => !patterns.includes(p));
|
|
453
|
+
if (filtered.length !== cfg.instructions.length) {
|
|
454
|
+
if (filtered.length === 0) delete cfg.instructions;
|
|
455
|
+
else cfg.instructions = filtered;
|
|
456
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
457
|
+
log(`${adapter.instructionsFile}: 移除本包登记的 instructions,保留用户配置`, 'success');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
346
460
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
461
|
+
// 旧位置:项目根 opencode.json(未升级就卸载的老空间,或迁移残留)
|
|
462
|
+
const legacyPath = path.join(TARGET_DIR, 'opencode.json');
|
|
463
|
+
if (adapter.legacyInstructionsPattern && fs.existsSync(legacyPath)) {
|
|
464
|
+
let legacyCfg;
|
|
465
|
+
try {
|
|
466
|
+
legacyCfg = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
|
|
467
|
+
} catch (_) {
|
|
468
|
+
return; // 不可解析的文件不动
|
|
469
|
+
}
|
|
470
|
+
if (Array.isArray(legacyCfg.instructions) && legacyCfg.instructions.includes(adapter.legacyInstructionsPattern)) {
|
|
471
|
+
legacyCfg.instructions = legacyCfg.instructions.filter(p => p !== adapter.legacyInstructionsPattern);
|
|
472
|
+
if (legacyCfg.instructions.length === 0) delete legacyCfg.instructions;
|
|
473
|
+
fs.writeFileSync(legacyPath, JSON.stringify(legacyCfg, null, 2) + '\n');
|
|
474
|
+
log('opencode.json: 移除本包登记的旧版 instructions,保留用户配置', 'success');
|
|
475
|
+
}
|
|
352
476
|
}
|
|
353
477
|
});
|
|
354
478
|
}
|
|
@@ -483,13 +607,59 @@ function deployCommandFiles(adapter, srcDir, destDir, prefix, filter) {
|
|
|
483
607
|
const srcPath = path.join(srcDir, f);
|
|
484
608
|
const destPath = path.join(destDir, prefix + f);
|
|
485
609
|
removePath(destPath);
|
|
486
|
-
const content = applyContentRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'));
|
|
610
|
+
const content = rewriteCommandName(adapter, sanitizeCommandFrontmatter(applyContentRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'))));
|
|
487
611
|
fs.writeFileSync(destPath, content);
|
|
488
612
|
});
|
|
489
613
|
|
|
490
614
|
return { count: commandFiles.length, names: commandFiles.map(f => prefix + f) };
|
|
491
615
|
}
|
|
492
616
|
|
|
617
|
+
/**
|
|
618
|
+
* 命令 frontmatter 消毒:opencode 新版校验命令配置(description 必须为 string | undefined),
|
|
619
|
+
* 上游个别命令(openspec/commands/update.md)的 description 为空值(YAML 解析为 null),
|
|
620
|
+
* 会导致 opencode 拒绝加载整个项目配置。此处对部署副本回填正文首句作为描述
|
|
621
|
+
* (超过 120 字截断),上游源文件不动(CLI 接管区)。
|
|
622
|
+
*/
|
|
623
|
+
function sanitizeCommandFrontmatter(content) {
|
|
624
|
+
const lines = content.split('\n');
|
|
625
|
+
if ((lines[0] || '').trim() !== '---') return content;
|
|
626
|
+
let end = -1;
|
|
627
|
+
for (let i = 1; i < lines.length; i++) {
|
|
628
|
+
if ((lines[i] || '').trim() === '---') { end = i; break; }
|
|
629
|
+
}
|
|
630
|
+
if (end === -1) return content;
|
|
631
|
+
let sentence = '';
|
|
632
|
+
for (let i = end + 1; i < lines.length; i++) {
|
|
633
|
+
const t = (lines[i] || '').trim();
|
|
634
|
+
if (t) { sentence = t; break; }
|
|
635
|
+
}
|
|
636
|
+
let changed = false;
|
|
637
|
+
for (let i = 1; i < end; i++) {
|
|
638
|
+
if (/^description:\s*(null|~)?\s*\r?$/.test(lines[i])) {
|
|
639
|
+
if (sentence) {
|
|
640
|
+
if (sentence.length > 120) sentence = sentence.slice(0, 117) + '...';
|
|
641
|
+
lines[i] = 'description: ' + JSON.stringify(sentence) + (lines[i].endsWith('\r') ? '\r' : '');
|
|
642
|
+
} else {
|
|
643
|
+
lines[i] = null; // 无正文可回填则移除(undefined 同样合法)
|
|
644
|
+
}
|
|
645
|
+
changed = true;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return changed ? lines.filter(l => l !== null).join('\n') : content;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* opencode 命令显示名取自 frontmatter name,上游为大写空格风格("OPSX: Propose"),
|
|
653
|
+
* 与文档写法(/opsx-propose)不一致;部署副本统一重写为 kebab-case。
|
|
654
|
+
*/
|
|
655
|
+
function rewriteCommandName(adapter, content) {
|
|
656
|
+
if (!adapter.commandNameKebab) return content;
|
|
657
|
+
return content.replace(/^name:\s*"?([A-Za-z]+):\s*([^"\r\n]+?)"?\s*\r?$/m, (m0, ns, rest) => {
|
|
658
|
+
const kebab = (ns + '-' + rest).toLowerCase().replace(/\s+/g, '-');
|
|
659
|
+
return `name: "${kebab}"`;
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
493
663
|
/** 官方 OpenSpec 命令(openspec/commands/ → adapter.commandsDir) */
|
|
494
664
|
function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
|
|
495
665
|
if (!adapter.commandsDir) return { count: 0, names: [] };
|
|
@@ -497,13 +667,6 @@ function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
|
|
|
497
667
|
adapter, srcDir, path.join(TARGET_DIR, adapter.commandsDir), adapter.commandPrefix || '', filter);
|
|
498
668
|
}
|
|
499
669
|
|
|
500
|
-
/** worktree 管理命令(cbb/worktrees/commands/ → adapter.worktreeCommandsDir) */
|
|
501
|
-
function deployWorktreeCommands(adapter, srcDir, filter) {
|
|
502
|
-
if (!adapter.worktreeCommandsDir) return { count: 0, names: [] };
|
|
503
|
-
return deployCommandFiles(
|
|
504
|
-
adapter, srcDir, path.join(TARGET_DIR, adapter.worktreeCommandsDir), adapter.worktreeCommandPrefix || '', filter);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
670
|
// ── 设置 OpenSpec 工作流 ──────────────────────────────────
|
|
508
671
|
|
|
509
672
|
/**
|
|
@@ -801,7 +964,7 @@ async function detectUserLevelSuperpowers(selectedAdapters, yes = false) {
|
|
|
801
964
|
// ── 主流程 ────────────────────────────────────────────────
|
|
802
965
|
|
|
803
966
|
async function init(targetDir, options = {}) {
|
|
804
|
-
const { yes = false } = options;
|
|
967
|
+
const { yes = false, selectedAdapters: presetAdapters = null } = options;
|
|
805
968
|
|
|
806
969
|
// 允许调用方指定目标目录(cbb setup 在拉好仓库后切到仓库根调用)。
|
|
807
970
|
// 不传则保持模块级 TARGET_DIR(即调用 init 时的 cwd)。
|
|
@@ -812,16 +975,33 @@ async function init(targetDir, options = {}) {
|
|
|
812
975
|
const action = isUpgrade ? '升级' : '安装';
|
|
813
976
|
const managedPaths = readManifest();
|
|
814
977
|
const newPaths = new Set();
|
|
978
|
+
const isWorkspaceRoot = detectWorkspaceRoot();
|
|
815
979
|
|
|
816
980
|
console.log(`\n 🚀 ${action} @jspg-ai/coding-bb...\n`);
|
|
817
981
|
|
|
818
982
|
// 先选适配器:后续的前置依赖检测仅针对所选适配器,避免误导(如选了 Qoder 却提示安装 Claude Code 的 Superpowers)
|
|
819
983
|
// 非交互模式判断:显式 -y/--yes 或 stdin 非 TTY(pipe/ignore)均视为非交互
|
|
984
|
+
// 选择优先级:调用方预设(cbb setup)> .last-tools(非交互沿用)> 交互选择;无记录才回退全选
|
|
820
985
|
const isNonInteractive = yes || !process.stdin.isTTY;
|
|
821
|
-
|
|
822
|
-
|
|
986
|
+
let selectedAdapters;
|
|
987
|
+
if (presetAdapters && presetAdapters.length > 0) {
|
|
988
|
+
selectedAdapters = presetAdapters;
|
|
989
|
+
} else if (isNonInteractive) {
|
|
990
|
+
const lastTools = readLastTools();
|
|
991
|
+
const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
|
|
992
|
+
selectedAdapters = reused.length > 0 ? reused : adapters;
|
|
993
|
+
if (yes && process.stdin.isTTY) {
|
|
994
|
+
log(reused.length > 0
|
|
995
|
+
? `--yes 非交互模式,沿用上次工具选择:${reused.map(a => a.displayName).join('、')}`
|
|
996
|
+
: '--yes 非交互模式,无上次选择记录,已默认选中所有适配器', 'info');
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
selectedAdapters = await selectTools(isUpgrade);
|
|
1000
|
+
}
|
|
1001
|
+
// 业务空间根记录本次选择,供 cbb update 等非交互场景沿用
|
|
1002
|
+
if (isWorkspaceRoot && presetAdapters && presetAdapters.length > 0) {
|
|
1003
|
+
saveLastTools(selectedAdapters);
|
|
823
1004
|
}
|
|
824
|
-
const selectedAdapters = isNonInteractive ? adapters : await selectTools(isUpgrade);
|
|
825
1005
|
|
|
826
1006
|
// 前置依赖检测(放在选适配器之后,只检测与所选工具相关的依赖)
|
|
827
1007
|
const depProgress = progress('依赖');
|
|
@@ -838,9 +1018,10 @@ async function init(targetDir, options = {}) {
|
|
|
838
1018
|
newPaths.add(`${adapter.rulesDir}/cbb-coding-rule.md`);
|
|
839
1019
|
newPaths.add(`${adapter.rulesDir}/cbb-priority.md`);
|
|
840
1020
|
newPaths.add(`${adapter.rulesDir}/cbb-ai-behavior.md`);
|
|
841
|
-
// opencode:rules glob
|
|
842
|
-
if (adapter.
|
|
1021
|
+
// opencode:rules glob 登记进 .opencode/opencode.json instructions,并迁移根文件旧登记
|
|
1022
|
+
if (adapter.instructionsFile) {
|
|
843
1023
|
injectInstructions(adapter, newPaths, managedPaths);
|
|
1024
|
+
migrateLegacyInstructions(adapter);
|
|
844
1025
|
}
|
|
845
1026
|
});
|
|
846
1027
|
log(`核心规则 → ${selectedAdapters.map(a => a.rulesDir).join(', ')} (2 个文件)`, 'success');
|
|
@@ -850,11 +1031,10 @@ async function init(targetDir, options = {}) {
|
|
|
850
1031
|
if (skillsAdapters.length > 0) {
|
|
851
1032
|
console.log(`\n 🔧 ${action}按需加载 Skills...`);
|
|
852
1033
|
const standardsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'dev-standards', 'skills');
|
|
853
|
-
const openspecExtendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
|
|
854
1034
|
const extendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'tools');
|
|
855
1035
|
|
|
856
1036
|
const skillsDests = skillsAdapters.map(a => a.skillsDir);
|
|
857
|
-
let stdCount = 0,
|
|
1037
|
+
let stdCount = 0, extCount = 0;
|
|
858
1038
|
skillsAdapters.forEach(adapter => {
|
|
859
1039
|
// 通用编码规范 Skills(cbb/dev-standards/skills)
|
|
860
1040
|
const stdResult = deploySkills(adapter, isUpgrade, managedPaths, standardsSkillsSrc);
|
|
@@ -864,23 +1044,30 @@ async function init(targetDir, options = {}) {
|
|
|
864
1044
|
}
|
|
865
1045
|
// 官方 OpenSpec Skills(openspec/skills/)不部署——工作流入口统一为 /opsx:* 斜杠命令
|
|
866
1046
|
//(openspec/commands/ 在下方 Commands 段部署),避免双形态重复
|
|
867
|
-
//
|
|
868
|
-
const
|
|
869
|
-
if (osExtResult.count > 0) {
|
|
870
|
-
osCount = osExtResult.count;
|
|
871
|
-
osExtResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
872
|
-
}
|
|
873
|
-
// 工具 Skills:仅 cbb-design-to-wiki(设计文档发布属 openspec 设计阶段动作)
|
|
874
|
-
const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, SANDBOX_TOOLS_SKILLS);
|
|
1047
|
+
// 工具 Skills:当前不分发(恢复方式见 TOOLS_SKILLS 注释)
|
|
1048
|
+
const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, TOOLS_SKILLS);
|
|
875
1049
|
if (extResult.count > 0) {
|
|
876
1050
|
extCount = extResult.count;
|
|
877
1051
|
extResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
878
1052
|
}
|
|
879
1053
|
});
|
|
880
1054
|
if (stdCount > 0) log(`规范 Skills → ${skillsDests.join(', ')} (${stdCount} 个)`, 'success');
|
|
881
|
-
if (osCount > 0) log(`worktree Skills → ${skillsDests.join(', ')} (${osCount} 个)`, 'success');
|
|
882
1055
|
if (extCount > 0) log(`扩展 Skills → ${skillsDests.join(', ')} (${extCount} 个)`, 'success');
|
|
883
1056
|
|
|
1057
|
+
// 部署 worktree 管理 Skills(init / close / push;skill 形态,可被对话自动触发)
|
|
1058
|
+
const worktreeSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
|
|
1059
|
+
let wtCount = 0;
|
|
1060
|
+
if (fs.existsSync(worktreeSkillsSrc)) {
|
|
1061
|
+
skillsAdapters.forEach(adapter => {
|
|
1062
|
+
const wtResult = deploySkills(adapter, isUpgrade, managedPaths, worktreeSkillsSrc, WORKTREE_SKILLS);
|
|
1063
|
+
if (wtResult.count > 0) {
|
|
1064
|
+
wtCount = wtResult.count;
|
|
1065
|
+
wtResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
if (wtCount > 0) log(`Worktree Skills → ${skillsDests.join(', ')} (${wtCount} 个)`, 'success');
|
|
1069
|
+
}
|
|
1070
|
+
|
|
884
1071
|
// 部署 Superpowers Skills(白名单:openspec 工作流依赖 + 方法论基底)
|
|
885
1072
|
const superpowersSkillsSrc = superpowers.SUPERPOWERS_SKILLS_DIR;
|
|
886
1073
|
let spCount = 0;
|
|
@@ -902,33 +1089,26 @@ async function init(targetDir, options = {}) {
|
|
|
902
1089
|
await detectUserLevelSuperpowers(selectedAdapters, yes);
|
|
903
1090
|
}
|
|
904
1091
|
|
|
905
|
-
// 安装 Commands(官方
|
|
906
|
-
// 仅装 close/push,init 只在 workspace 根使用,由 installWorkspaceConfig 部署)
|
|
1092
|
+
// 安装 Commands(官方 11 个 → /opsx:*,onboard 不分发)
|
|
907
1093
|
const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
|
|
908
1094
|
if (commandsAdapters.length > 0) {
|
|
909
1095
|
console.log(`\n ⚡ ${action} Commands...`);
|
|
910
1096
|
const commandsBaseSrc = path.join(SHARED_STANDARDS_DIR, 'openspec', 'commands');
|
|
911
|
-
const
|
|
912
|
-
|
|
1097
|
+
const openspecCmdInclude = fs.readdirSync(commandsBaseSrc)
|
|
1098
|
+
.filter(f => f.endsWith('.md') && !OPENSPEC_COMMANDS_EXCLUDE.includes(f));
|
|
913
1099
|
|
|
914
1100
|
const cmdDests = [...new Set(commandsAdapters
|
|
915
|
-
.
|
|
1101
|
+
.map(a => a.commandsDir)
|
|
916
1102
|
.filter(Boolean))];
|
|
917
1103
|
let cmdCount = 0;
|
|
918
1104
|
commandsAdapters.forEach(adapter => {
|
|
919
1105
|
let adapterCmdCount = 0;
|
|
920
|
-
// 官方 Commands(openspec/commands/)—
|
|
921
|
-
const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc);
|
|
1106
|
+
// 官方 Commands(openspec/commands/)— 部署除不分发清单外的全部
|
|
1107
|
+
const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc, openspecCmdInclude);
|
|
922
1108
|
if (baseResult.count > 0) {
|
|
923
1109
|
adapterCmdCount += baseResult.count;
|
|
924
1110
|
baseResult.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
|
|
925
1111
|
}
|
|
926
|
-
// worktree 管理 Commands(cbb/worktrees/commands/)
|
|
927
|
-
const extResult = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
|
|
928
|
-
if (extResult.count > 0) {
|
|
929
|
-
adapterCmdCount += extResult.count;
|
|
930
|
-
extResult.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
|
|
931
|
-
}
|
|
932
1112
|
cmdCount = Math.max(cmdCount, adapterCmdCount);
|
|
933
1113
|
});
|
|
934
1114
|
if (cmdCount > 0) log(`Commands → ${cmdDests.join(', ')} (${cmdCount} 个)`, 'success');
|
|
@@ -948,6 +1128,15 @@ async function init(targetDir, options = {}) {
|
|
|
948
1128
|
setupOpenSpec(isUpgrade, managedPaths);
|
|
949
1129
|
newPaths.add('openspec/config.yaml');
|
|
950
1130
|
|
|
1131
|
+
// 业务空间根专属:空间导航 AGENTS.md(仅缺失时生成,永不覆盖)+ 旧版导航文案提示
|
|
1132
|
+
let agentsMdState = null;
|
|
1133
|
+
if (isWorkspaceRoot) {
|
|
1134
|
+
agentsMdState = workspaces.ensureWorkspaceAgentsMd(TARGET_DIR);
|
|
1135
|
+
if (workspaces.detectLegacyAgentsMd(TARGET_DIR)) {
|
|
1136
|
+
log('空间根 AGENTS.md 仍是旧版导航文案(单层安装改造前模板)。建议删除该文件后重跑 cbb update 重新生成(仅缺失时生成,不会覆盖你的增补内容)', 'warn');
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
951
1140
|
// 写入自动版本检查 hook
|
|
952
1141
|
const hookAdapters = selectedAdapters.filter(a => a.settingsFile && a.hookEvents);
|
|
953
1142
|
if (hookAdapters.length > 0) {
|
|
@@ -958,9 +1147,9 @@ async function init(targetDir, options = {}) {
|
|
|
958
1147
|
const settingsPath = adapter.settingsFileIsUserLevel
|
|
959
1148
|
? adapter.settingsFile
|
|
960
1149
|
: path.join(TARGET_DIR, adapter.settingsFile);
|
|
961
|
-
settingsManager.addHooks(settingsPath, command, adapter.hookEvents);
|
|
1150
|
+
settingsManager.addHooks(settingsPath, command, adapter.hookEvents, hookOpts(adapter));
|
|
962
1151
|
// 清理旧版本遗留的 SessionStart hook(现已仅安装 UserPromptSubmit)
|
|
963
|
-
if (settingsManager.removeLegacyHooks(settingsPath)) {
|
|
1152
|
+
if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
|
|
964
1153
|
log(`清理 ${adapter.displayName} 遗留的 SessionStart hook`, 'success');
|
|
965
1154
|
}
|
|
966
1155
|
if (!adapter.settingsFileIsUserLevel) newPaths.add(adapter.settingsFile);
|
|
@@ -985,6 +1174,8 @@ async function init(targetDir, options = {}) {
|
|
|
985
1174
|
if (oldPaths.length > MAX_DISPLAY) {
|
|
986
1175
|
log(` ... 还有 ${oldPaths.length - MAX_DISPLAY} 个`, 'dim');
|
|
987
1176
|
}
|
|
1177
|
+
// 收尾:清掉因此变空的祖先目录(如改名后残留的空 .cbb/skills/)
|
|
1178
|
+
cleanupEmptyParentDirs(oldPaths).forEach(dir => log(` 清理空目录 ${dir}/`, 'dim'));
|
|
988
1179
|
}
|
|
989
1180
|
|
|
990
1181
|
const kept = protectedKeptPaths(managedPaths, newPaths);
|
|
@@ -1015,7 +1206,7 @@ async function init(targetDir, options = {}) {
|
|
|
1015
1206
|
console.log(`\n ✅ ${action}完成!\n`);
|
|
1016
1207
|
|
|
1017
1208
|
if (yes) {
|
|
1018
|
-
console.log(c('dim',
|
|
1209
|
+
console.log(c('dim', ` (使用 --yes 非交互模式,工具:${selectedAdapters.map(a => a.displayName).join('、')})`));
|
|
1019
1210
|
}
|
|
1020
1211
|
|
|
1021
1212
|
// 适配器映射(单行)
|
|
@@ -1045,161 +1236,13 @@ async function init(targetDir, options = {}) {
|
|
|
1045
1236
|
if (hasSkills) extras.push('Skills');
|
|
1046
1237
|
if (hasCommands) extras.push('Commands');
|
|
1047
1238
|
if (hasHooks) extras.push('版本检查 hook');
|
|
1239
|
+
if (agentsMdState === 'created') extras.push('AGENTS.md(空间导航)');
|
|
1048
1240
|
if (extras.length > 0) {
|
|
1049
1241
|
console.log(` ${extras.join(' / ')} 已配置`);
|
|
1050
1242
|
}
|
|
1051
1243
|
console.log('');
|
|
1052
1244
|
}
|
|
1053
1245
|
|
|
1054
|
-
/**
|
|
1055
|
-
* 为 workspace 根目录安装 AI 配置子集(由 cbb setup 调用)。
|
|
1056
|
-
*
|
|
1057
|
-
* 只装 worktree 生命周期管理(init/close/push)+ openspec 运行时配置 + 空间导航
|
|
1058
|
-
* AGENTS.md(仅缺失时生成,永不覆盖),不装 base 工作流、编码规范、superpowers、
|
|
1059
|
-
* 版本检查 hook--这些属于 sandbox worktree 内的开发配置,由 worktree 内
|
|
1060
|
-
* `cbbm init --yes` 完整模式安装。在 workspace 根目录
|
|
1061
|
-
* (主分支)装 base 工作流会诱导绕过 worktree 直接在主分支做需求开发。
|
|
1062
|
-
*/
|
|
1063
|
-
async function installWorkspaceConfig(targetDir, options = {}) {
|
|
1064
|
-
const { yes = false, selectedAdapters: presetAdapters = null } = options;
|
|
1065
|
-
if (targetDir) TARGET_DIR = targetDir;
|
|
1066
|
-
|
|
1067
|
-
const isUpgrade = detectExistingInstall();
|
|
1068
|
-
const managedPaths = readManifest();
|
|
1069
|
-
const newPaths = new Set();
|
|
1070
|
-
|
|
1071
|
-
let selectedAdapters;
|
|
1072
|
-
if (presetAdapters && presetAdapters.length > 0) {
|
|
1073
|
-
// cbb setup 场景:上层已交互选定适配器,持久化供 cbb update 预选
|
|
1074
|
-
selectedAdapters = presetAdapters;
|
|
1075
|
-
saveLastTools(selectedAdapters);
|
|
1076
|
-
} else {
|
|
1077
|
-
const isNonInteractive = yes || !process.stdin.isTTY;
|
|
1078
|
-
if (isNonInteractive) {
|
|
1079
|
-
// 非交互(cbb update / 管道运行):沿用上次工具选择,避免误装未使用的适配器
|
|
1080
|
-
const lastTools = readLastTools();
|
|
1081
|
-
const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
|
|
1082
|
-
if (reused.length > 0) {
|
|
1083
|
-
selectedAdapters = reused;
|
|
1084
|
-
} else {
|
|
1085
|
-
selectedAdapters = adapters;
|
|
1086
|
-
}
|
|
1087
|
-
} else {
|
|
1088
|
-
selectedAdapters = await selectTools(isUpgrade);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
// 只装 worktree 生命周期管理 Skills(init/close/push)
|
|
1092
|
-
const skillsAdapters = selectedAdapters.filter(a => a.skillsDir);
|
|
1093
|
-
const commandsAdapters = selectedAdapters.filter(a => a.worktreeCommandsDir);
|
|
1094
|
-
let skillCount = 0;
|
|
1095
|
-
let cmdCount = 0;
|
|
1096
|
-
let adapterRoots = [];
|
|
1097
|
-
|
|
1098
|
-
// 「AI 配置」阶段心跳:检查依赖 → 部署 skills/commands → 写入 openspec 配置 → 生成导航。
|
|
1099
|
-
// 其中 npm 查询/安装最长可达分钟级,必须让用户看到进程仍活着;任何异常先清掉进度行。
|
|
1100
|
-
const cfgProgress = progress('AI 配置');
|
|
1101
|
-
let agentsMdState = 'exists';
|
|
1102
|
-
let openspecRes;
|
|
1103
|
-
try {
|
|
1104
|
-
await ensureOpenSpecCli(t => cfgProgress.update(t));
|
|
1105
|
-
|
|
1106
|
-
if (skillsAdapters.length > 0) {
|
|
1107
|
-
cfgProgress.update('部署 skills');
|
|
1108
|
-
const openspecExtendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
|
|
1109
|
-
const osExtFilter = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
|
|
1110
|
-
skillsAdapters.forEach(adapter => {
|
|
1111
|
-
const r = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, osExtFilter);
|
|
1112
|
-
if (r.count > 0) {
|
|
1113
|
-
skillCount = Math.max(skillCount, r.count);
|
|
1114
|
-
r.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
1115
|
-
}
|
|
1116
|
-
});
|
|
1117
|
-
adapterRoots = skillsAdapters.map(a => a.skillsDir.split('/')[0]);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// 只装 worktree 管理 Commands(cbb:worktree-init/close/push)
|
|
1121
|
-
if (commandsAdapters.length > 0) {
|
|
1122
|
-
cfgProgress.update('部署 worktree 命令');
|
|
1123
|
-
const commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
|
|
1124
|
-
const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
|
|
1125
|
-
commandsAdapters.forEach(adapter => {
|
|
1126
|
-
const r = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
|
|
1127
|
-
if (r.count > 0) {
|
|
1128
|
-
cmdCount = Math.max(cmdCount, r.count);
|
|
1129
|
-
r.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
if (adapterRoots.length === 0) adapterRoots = commandsAdapters.map(a => a.worktreeCommandsDir.split('/')[0]);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// 设置 OpenSpec 工作流(config.yaml + schemas)
|
|
1136
|
-
cfgProgress.update('写入 openspec 配置');
|
|
1137
|
-
const schemasSrcs = [
|
|
1138
|
-
path.join(SHARED_STANDARDS_DIR, 'config', 'openspec', 'schemas'),
|
|
1139
|
-
];
|
|
1140
|
-
schemasSrcs.forEach(src => {
|
|
1141
|
-
if (fs.existsSync(src)) {
|
|
1142
|
-
fs.readdirSync(src).filter(f2 => fs.statSync(path.join(src, f2)).isDirectory())
|
|
1143
|
-
.forEach(name => newPaths.add(`openspec/schemas/${name}`));
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
openspecRes = setupOpenSpec(isUpgrade, managedPaths);
|
|
1147
|
-
newPaths.add('openspec/config.yaml');
|
|
1148
|
-
|
|
1149
|
-
// 空间导航文件 AGENTS.md:仅缺失时生成,永不覆盖(不登记清单,卸载不删)
|
|
1150
|
-
cfgProgress.update('生成空间导航');
|
|
1151
|
-
agentsMdState = workspaces.ensureWorkspaceAgentsMd(TARGET_DIR);
|
|
1152
|
-
} finally {
|
|
1153
|
-
cfgProgress.done();
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// 升级时清理旧路径(workspace 曾装过的 base 工作流等不属于此子集,删除)
|
|
1157
|
-
const cleaned = [];
|
|
1158
|
-
const keptDocs = [];
|
|
1159
|
-
if (isUpgrade) {
|
|
1160
|
-
const oldPaths = pathsToDelete(managedPaths, newPaths);
|
|
1161
|
-
oldPaths.forEach(oldPath => {
|
|
1162
|
-
const targetPath = path.join(TARGET_DIR, oldPath);
|
|
1163
|
-
if (fs.existsSync(targetPath)) {
|
|
1164
|
-
removePath(targetPath);
|
|
1165
|
-
cleaned.push(oldPath);
|
|
1166
|
-
}
|
|
1167
|
-
});
|
|
1168
|
-
keptDocs.push(...protectedKeptPaths(managedPaths, newPaths));
|
|
1169
|
-
(skillsAdapters || []).forEach(adapter => {
|
|
1170
|
-
const legacyNodeModules = path.join(TARGET_DIR, adapter.skillsDir, 'node_modules');
|
|
1171
|
-
if (fs.existsSync(legacyNodeModules)) {
|
|
1172
|
-
removePath(legacyNodeModules);
|
|
1173
|
-
cleaned.push(`${adapter.skillsDir}/node_modules`);
|
|
1174
|
-
}
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
writeManifest(newPaths, selectedAdapters);
|
|
1179
|
-
updateGitignore();
|
|
1180
|
-
|
|
1181
|
-
// 汇总输出(一行主结果 + 可选清理/保留明细)
|
|
1182
|
-
const rootLabel = [...new Set(adapterRoots)].join('/');
|
|
1183
|
-
const parts = [];
|
|
1184
|
-
if (skillCount > 0) parts.push(`${rootLabel} skills×${skillCount}`);
|
|
1185
|
-
if (cmdCount > 0) parts.push(`commands×${cmdCount}`);
|
|
1186
|
-
if (openspecRes.installed.length > 0) {
|
|
1187
|
-
parts.push(`openspec ${openspecRes.defaultSchema || openspecRes.installed.join('/')}`);
|
|
1188
|
-
}
|
|
1189
|
-
if (agentsMdState === 'created') parts.push('AGENTS.md(空间导航)');
|
|
1190
|
-
const configLabel = 'AI 配置';
|
|
1191
|
-
step('✓', configLabel, parts.join(' · '));
|
|
1192
|
-
if (cleaned.length > 0) {
|
|
1193
|
-
const MAX_DISPLAY = 3;
|
|
1194
|
-
const preview = cleaned.slice(0, MAX_DISPLAY).join('、');
|
|
1195
|
-
step('!', '清理残留', `${cleaned.length} 个(${preview}${cleaned.length > MAX_DISPLAY ? ' 等' : ''})`,
|
|
1196
|
-
{ iconColor: 'yellow' });
|
|
1197
|
-
}
|
|
1198
|
-
if (keptDocs.length > 0) {
|
|
1199
|
-
step('!', '保留文档', `${keptDocs.join('、')}(本包已不再管理)`, { iconColor: 'yellow' });
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
1246
|
// ── cbb setup 命令 ──────────────────────────────────────
|
|
1204
1247
|
|
|
1205
1248
|
/**
|
|
@@ -1521,9 +1564,9 @@ async function initSingleWorkspace(w, opts = {}) {
|
|
|
1521
1564
|
return { ok: true, subdir: subdirName, upToDate: true };
|
|
1522
1565
|
}
|
|
1523
1566
|
|
|
1524
|
-
// 6.
|
|
1567
|
+
// 6. 安装空间 AI 配置(单层全量:开发配置 + worktree 管理,统一安装器自动识别空间根)
|
|
1525
1568
|
try {
|
|
1526
|
-
await
|
|
1569
|
+
await init(targetDir, { yes: true, selectedAdapters: opts.selectedAdapters });
|
|
1527
1570
|
return { ok: true, subdir: subdirName };
|
|
1528
1571
|
} catch (e) {
|
|
1529
1572
|
step('✗', 'AI 配置', e.message, { iconColor: 'red', labelColor: 'red', valueColor: 'red' });
|
|
@@ -1717,8 +1760,8 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
|
|
|
1717
1760
|
if (hookAdapters.length > 0) {
|
|
1718
1761
|
const command = settingsManager.getCheckUpdateCommand();
|
|
1719
1762
|
hookAdapters.forEach(adapter => {
|
|
1720
|
-
settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents);
|
|
1721
|
-
settingsManager.removeLegacyHooks(adapter.settingsFile);
|
|
1763
|
+
settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents, hookOpts(adapter));
|
|
1764
|
+
settingsManager.removeLegacyHooks(adapter.settingsFile, hookOpts(adapter));
|
|
1722
1765
|
});
|
|
1723
1766
|
summaryParts.push(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join('、')}`);
|
|
1724
1767
|
}
|
|
@@ -1733,13 +1776,16 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
|
|
|
1733
1776
|
}
|
|
1734
1777
|
|
|
1735
1778
|
/**
|
|
1736
|
-
* cbb update
|
|
1779
|
+
* cbb update:就地升级当前目录内容 + 用户级组件。
|
|
1737
1780
|
*
|
|
1738
|
-
*
|
|
1739
|
-
*
|
|
1781
|
+
* 单层安装:统一安装器按空间根判定(workspace-config.json + .git 目录)补齐
|
|
1782
|
+
* worktree 管理入口与空间导航;旧子集清单升级时由清单差集机制自动补齐全量内容。
|
|
1740
1783
|
*/
|
|
1741
1784
|
async function updateInstalled() {
|
|
1742
|
-
|
|
1785
|
+
// 用户级组件沿用本目录的工具选择(.last-tools),无记录或为空才回退全量
|
|
1786
|
+
const lastTools = readLastTools();
|
|
1787
|
+
const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
|
|
1788
|
+
await setupUserLevelComponents(reused.length > 0 ? reused : adapters);
|
|
1743
1789
|
|
|
1744
1790
|
const manifest = readManifest();
|
|
1745
1791
|
if (manifest.size === 0) {
|
|
@@ -1747,17 +1793,7 @@ async function updateInstalled() {
|
|
|
1747
1793
|
process.exit(1);
|
|
1748
1794
|
}
|
|
1749
1795
|
|
|
1750
|
-
|
|
1751
|
-
adapters.some(a => p.startsWith(`${a.rulesDir}/`))
|
|
1752
|
-
);
|
|
1753
|
-
|
|
1754
|
-
if (isFullInstall) {
|
|
1755
|
-
log('检测到完整模式安装,升级开发配置(rules + skills + commands + 工作流)...', 'info');
|
|
1756
|
-
await init(undefined, {});
|
|
1757
|
-
} else {
|
|
1758
|
-
log('检测到 workspace 子集模式,升级 worktree 管理 + openspec 配置...', 'info');
|
|
1759
|
-
await installWorkspaceConfig(undefined, {});
|
|
1760
|
-
}
|
|
1796
|
+
await init(undefined, {});
|
|
1761
1797
|
}
|
|
1762
1798
|
|
|
1763
1799
|
// ── 卸载 ──────────────────────────────────────────────────
|
|
@@ -1841,7 +1877,7 @@ async function uninstallUserLevelComponents() {
|
|
|
1841
1877
|
// 移除版本检查 hook(保留用户其余配置)
|
|
1842
1878
|
adapters.forEach(adapter => {
|
|
1843
1879
|
if (!adapter.settingsFile || !adapter.hookEvents) return;
|
|
1844
|
-
if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents)) {
|
|
1880
|
+
if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents, hookOpts(adapter))) {
|
|
1845
1881
|
log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
|
|
1846
1882
|
}
|
|
1847
1883
|
});
|
|
@@ -1872,7 +1908,7 @@ async function uninstall() {
|
|
|
1872
1908
|
// 是否同时卸载用户级组件(工具 skills + 版本检查 hook,作用于 ~/.claude 等用户目录)
|
|
1873
1909
|
let removeUserLevel = false;
|
|
1874
1910
|
if (fs.existsSync(USER_MANIFEST_FILE)) {
|
|
1875
|
-
const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder 的版本检查 hook)?(y/N): ');
|
|
1911
|
+
const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.codebuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
|
|
1876
1912
|
removeUserLevel = answerUser.toLowerCase() === 'y' || answerUser.toLowerCase() === 'yes';
|
|
1877
1913
|
}
|
|
1878
1914
|
|
|
@@ -1891,11 +1927,11 @@ async function uninstall() {
|
|
|
1891
1927
|
: path.join(TARGET_DIR, adapter.settingsFile);
|
|
1892
1928
|
// 项目级:需在清单中才处理
|
|
1893
1929
|
if (!adapter.settingsFileIsUserLevel && !managedPaths.has(adapter.settingsFile)) return;
|
|
1894
|
-
if (removeUserLevel && settingsManager.removeHooks(settingsPath, adapter.hookEvents)) {
|
|
1930
|
+
if (removeUserLevel && settingsManager.removeHooks(settingsPath, adapter.hookEvents, hookOpts(adapter))) {
|
|
1895
1931
|
log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
|
|
1896
1932
|
}
|
|
1897
1933
|
// 清理旧版本遗留的 SessionStart hook(无版本差异,无条件清理)
|
|
1898
|
-
if (settingsManager.removeLegacyHooks(settingsPath)) {
|
|
1934
|
+
if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
|
|
1899
1935
|
log(`${adapter.settingsFile}: 清理遗留 SessionStart hook`, 'success');
|
|
1900
1936
|
}
|
|
1901
1937
|
skippedPaths.add(adapter.settingsFile);
|
|
@@ -1920,22 +1956,7 @@ async function uninstall() {
|
|
|
1920
1956
|
});
|
|
1921
1957
|
|
|
1922
1958
|
// 清理可能变空的父目录(从深到浅)
|
|
1923
|
-
|
|
1924
|
-
managedPaths.forEach(relativePath => {
|
|
1925
|
-
let dir = path.dirname(relativePath);
|
|
1926
|
-
while (dir && dir !== '.') {
|
|
1927
|
-
dirsToCheck.add(dir);
|
|
1928
|
-
dir = path.dirname(dir);
|
|
1929
|
-
}
|
|
1930
|
-
});
|
|
1931
|
-
// 按深度排序,深层先处理
|
|
1932
|
-
[...dirsToCheck].sort((a, b) => b.split('/').length - a.split('/').length).forEach(dir => {
|
|
1933
|
-
const fullPath = path.join(TARGET_DIR, dir);
|
|
1934
|
-
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length === 0) {
|
|
1935
|
-
removePath(fullPath);
|
|
1936
|
-
log(`清理空目录 ${dir}/`, 'success');
|
|
1937
|
-
}
|
|
1938
|
-
});
|
|
1959
|
+
cleanupEmptyParentDirs(managedPaths).forEach(dir => log(`清理空目录 ${dir}/`, 'success'));
|
|
1939
1960
|
|
|
1940
1961
|
// 删除清单文件和 .va 目录
|
|
1941
1962
|
removePath(manifestDir);
|
|
@@ -1953,11 +1974,50 @@ async function uninstall() {
|
|
|
1953
1974
|
console.log(`\n✅ 卸载完成!共移除 ${removed} 个文件/目录\n`);
|
|
1954
1975
|
}
|
|
1955
1976
|
|
|
1977
|
+
// ── 上游版本检查 ──────────────────────────────────────────────
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* 检查 openspec / superpowers 是否有上游新版本(只读,不改动任何文件)。
|
|
1981
|
+
* 供 cbbm check-upstream 调用。
|
|
1982
|
+
*/
|
|
1983
|
+
async function checkUpstream() {
|
|
1984
|
+
const results = await Promise.allSettled([
|
|
1985
|
+
openspec.checkUpstream().then(r => ({ name: 'openspec', ...r })),
|
|
1986
|
+
superpowers.checkUpstream().then(r => ({ name: 'superpowers', ...r })),
|
|
1987
|
+
]);
|
|
1988
|
+
|
|
1989
|
+
let outdated = false;
|
|
1990
|
+
for (const r of results) {
|
|
1991
|
+
if (r.status === 'rejected') {
|
|
1992
|
+
log(`检查失败: ${r.reason.message}`, 'error');
|
|
1993
|
+
continue;
|
|
1994
|
+
}
|
|
1995
|
+
const { name, current, latest, outdated: isOutdated } = r.value;
|
|
1996
|
+
if (!latest) {
|
|
1997
|
+
log(`${name}: 无法获取上游版本`, 'warn');
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
if (!current) {
|
|
2001
|
+
log(`${name}: 本地无 pin(上游最新 ${latest})`, 'warn');
|
|
2002
|
+
outdated = true;
|
|
2003
|
+
} else if (isOutdated) {
|
|
2004
|
+
log(`${name}: ${current} → ${latest}(有新版本)`, 'warn');
|
|
2005
|
+
outdated = true;
|
|
2006
|
+
} else {
|
|
2007
|
+
log(`${name}: ${current} 已是最新`, 'success');
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
if (outdated) {
|
|
2012
|
+
log('升级方式:修改对应 .version 的 pin 后,执行 cbbm update --upstream-openspec / --upstream-superpowers', 'info');
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1956
2016
|
// ── CLI 入口 ──────────────────────────────────────────────
|
|
1957
2017
|
|
|
1958
2018
|
// 入口身份:cbb(用户命令) / cbbm(内部命令,由流程、hook、Agent 技能调用)
|
|
1959
2019
|
const ENTRY = process.env.CBB_ENTRY === 'cbbm' ? 'cbbm' : 'cbb';
|
|
1960
|
-
const INTERNAL_COMMANDS = ['
|
|
2020
|
+
const INTERNAL_COMMANDS = ['check-update', 'check-upstream', 'wiki', 'wp'];
|
|
1961
2021
|
const UPSTREAM_FLAGS = ['--upstream-superpowers', '--upstream-openspec'];
|
|
1962
2022
|
|
|
1963
2023
|
const args = process.argv.slice(2);
|
|
@@ -1996,11 +2056,11 @@ switch (command) {
|
|
|
1996
2056
|
const upstreamSuperpowers = restArgs.includes('--upstream-superpowers');
|
|
1997
2057
|
const upstreamOpenspec = restArgs.includes('--upstream-openspec');
|
|
1998
2058
|
if (upstreamSuperpowers || upstreamOpenspec) {
|
|
1999
|
-
//
|
|
2059
|
+
// 维护者选项:按 .version 的 pin 重新拉取上游原生内容到本包源仓库(发布流程用)
|
|
2000
2060
|
if (upstreamSuperpowers) {
|
|
2001
2061
|
superpowers.fetchLatestSkills().then(result => {
|
|
2002
2062
|
if (result.skipped) {
|
|
2003
|
-
log(`superpowers skills 已是最新 (${result.
|
|
2063
|
+
log(`superpowers skills 已是最新 (${result.tag})`, 'success');
|
|
2004
2064
|
}
|
|
2005
2065
|
const validation = superpowers.validateSkills();
|
|
2006
2066
|
if (!validation.valid) {
|
|
@@ -2016,22 +2076,13 @@ switch (command) {
|
|
|
2016
2076
|
}
|
|
2017
2077
|
if (upstreamOpenspec) {
|
|
2018
2078
|
openspec.fetchLatestOpenSpec().then(result => {
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2025
|
-
log(`openspec 工作流完整性校验通过`, 'success');
|
|
2026
|
-
} else {
|
|
2027
|
-
const validation = openspec.validateDeployed();
|
|
2028
|
-
if (!validation.valid) {
|
|
2029
|
-
log(`本地 openspec 工作流不完整,请重新运行 cbb update --upstream-openspec:`, 'error');
|
|
2030
|
-
for (const m of validation.missing) log(` - 缺失 ${m}`, 'error');
|
|
2031
|
-
process.exit(1);
|
|
2032
|
-
}
|
|
2033
|
-
log(`openspec 工作流完整性校验通过`, 'success');
|
|
2079
|
+
const validation = openspec.validateDeployed();
|
|
2080
|
+
if (!validation.valid) {
|
|
2081
|
+
log(`部署不完整,缺失: ${validation.missing.join(', ')}`, 'error');
|
|
2082
|
+
for (const m of validation.missing) log(` - 缺失 ${m}`, 'error');
|
|
2083
|
+
process.exit(1);
|
|
2034
2084
|
}
|
|
2085
|
+
log(`openspec 工作流完整性校验通过`, 'success');
|
|
2035
2086
|
}).catch(err => {
|
|
2036
2087
|
log(`更新失败: ${err.message}`, 'error');
|
|
2037
2088
|
process.exit(1);
|
|
@@ -2045,14 +2096,6 @@ switch (command) {
|
|
|
2045
2096
|
}
|
|
2046
2097
|
break;
|
|
2047
2098
|
}
|
|
2048
|
-
case 'init': {
|
|
2049
|
-
const yes = restArgs.includes('-y') || restArgs.includes('--yes');
|
|
2050
|
-
init(undefined, { yes }).catch(err => {
|
|
2051
|
-
log(err.message, 'error');
|
|
2052
|
-
process.exit(1);
|
|
2053
|
-
});
|
|
2054
|
-
break;
|
|
2055
|
-
}
|
|
2056
2099
|
case 'uninstall':
|
|
2057
2100
|
uninstall().catch(err => {
|
|
2058
2101
|
log(err.message, 'error');
|
|
@@ -2062,6 +2105,12 @@ switch (command) {
|
|
|
2062
2105
|
case 'check-update':
|
|
2063
2106
|
require('../utils/check-update.js');
|
|
2064
2107
|
break;
|
|
2108
|
+
case 'check-upstream':
|
|
2109
|
+
checkUpstream().catch(err => {
|
|
2110
|
+
log(err.message, 'error');
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
});
|
|
2113
|
+
break;
|
|
2065
2114
|
case 'wp':
|
|
2066
2115
|
require('../wiki/index.js').publish(restArgs);
|
|
2067
2116
|
break;
|
|
@@ -2092,13 +2141,13 @@ function showHelp() {
|
|
|
2092
2141
|
console.log(`
|
|
2093
2142
|
@jspg-ai/coding-bb v${pkg.version} — cbbm:内部命令(由流程 / hook / Agent 技能调用)
|
|
2094
2143
|
|
|
2095
|
-
cbbm init --yes worktree 沙箱内安装开发配置(/cbb:worktree-init Step 4 自动调用)
|
|
2096
2144
|
cbbm check-update 版本检查(用户级 hook 每日自动触发)
|
|
2145
|
+
cbbm check-upstream 检查 openspec / superpowers 是否有上游新版本
|
|
2097
2146
|
cbbm wiki / cbbm wp Wiki 查询与发布(AI 通过技能调用)
|
|
2098
2147
|
|
|
2099
|
-
|
|
2100
|
-
cbbm update --upstream-superpowers
|
|
2101
|
-
cbbm update --upstream-openspec
|
|
2148
|
+
维护者选项(按 .version 的 pin 重新拉取上游原生内容到本包源仓库):
|
|
2149
|
+
cbbm update --upstream-superpowers 按 pin tag 拉取 superpowers skills(走镜像)
|
|
2150
|
+
cbbm update --upstream-openspec 按 pin 版本从 npmmirror 渲染 OpenSpec 工作流
|
|
2102
2151
|
`);
|
|
2103
2152
|
return;
|
|
2104
2153
|
}
|