@jspg-ai/coding-bb 0.0.3-beta.1 → 0.0.3-beta.13
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/dev-standards/rules/cbb-ai-behavior.md +118 -116
- package/cbb/lib/install/claude-code.js +1 -3
- package/cbb/lib/install/codebuddy.js +30 -0
- package/cbb/lib/install/init.js +295 -119
- 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/openspec/index.js +337 -554
- package/cbb/lib/superpowers/index.js +246 -265
- package/cbb/lib/utils/check-update.js +8 -1
- 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 +32 -23
- package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +2 -2
- package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +36 -28
- 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 +108 -31
- package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
- package/config/openspec/schemas/spec-driven/schema.yaml +38 -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 +43 -39
- 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 -51
- package/cbb/worktrees/commands/worktree-push.md +0 -42
package/cbb/lib/install/init.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
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 # 首次使用
|
|
@@ -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
|
-
//
|
|
75
|
+
// worktree 管理 skill 清单:init / close / push 三件套(SKILL.md + scripts)。
|
|
76
|
+
// 按 skill 形态部署到各工具的 skillsDir,使这三个能力可被 AI 在对话中自动触发
|
|
77
|
+
// (description 驱动),同时工具也会把它们暴露为斜杠命令供用户手动指定。
|
|
73
78
|
const WORKTREE_SKILLS = ['cbb-worktree-init', 'cbb-worktree-close', 'cbb-worktree-push'];
|
|
74
79
|
|
|
75
|
-
// 装到空间的 tools skills
|
|
76
|
-
// cbb-wiki-ops
|
|
77
|
-
const TOOLS_SKILLS = [
|
|
80
|
+
// 装到空间的 tools skills:当前不分发。
|
|
81
|
+
// cbb-design-to-wiki(Wiki 发布)与 cbb-wiki-ops 保留在源仓库,需要时加入本清单即恢复分发;Java 工具不装。
|
|
82
|
+
const TOOLS_SKILLS = [];
|
|
83
|
+
|
|
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
|
|
|
@@ -205,6 +222,39 @@ function removePath(targetPath) {
|
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
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
|
+
|
|
208
258
|
/**
|
|
209
259
|
* 安全删除目标路径(文件或目录)
|
|
210
260
|
* 在清单中 → 直接删除(本包旧版本)
|
|
@@ -302,7 +352,8 @@ function deployCoreRules(adapter, isUpgrade, managedPaths) {
|
|
|
302
352
|
// 同名文件存在时直接覆盖
|
|
303
353
|
removePath(destPath);
|
|
304
354
|
|
|
305
|
-
|
|
355
|
+
const content = applyRuleRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'));
|
|
356
|
+
fs.writeFileSync(destPath, content);
|
|
306
357
|
}
|
|
307
358
|
});
|
|
308
359
|
|
|
@@ -311,56 +362,117 @@ function deployCoreRules(adapter, isUpgrade, managedPaths) {
|
|
|
311
362
|
// ── opencode instructions 登记 ────────────────────────────────
|
|
312
363
|
|
|
313
364
|
/**
|
|
314
|
-
* opencode 无原生 rules 目录:把 .opencode/
|
|
315
|
-
* opencode.
|
|
365
|
+
* opencode 无原生 rules 目录:把 rules glob 登记进 .opencode/opencode.json 的
|
|
366
|
+
* instructions(opencode V2 支持的配置位置;随 .opencode gitignore,按机器生成)。
|
|
367
|
+
* instructions 相对路径的解析基准(配置文件目录 vs 项目根)文档未写死,
|
|
368
|
+
* 故登记双 glob 对冲:任一基准下恰有一个命中,另一个空匹配。
|
|
316
369
|
* - 文件不存在:创建(记入清单,卸载时整体删除)
|
|
317
|
-
* -
|
|
318
|
-
*
|
|
370
|
+
* - 文件存在:幂等补齐缺失的 glob(保留用户已有 instructions)
|
|
371
|
+
* - JSONC(含注释)解析失败则只提示不改写
|
|
372
|
+
* 旧版登记在项目根 opencode.json,由 migrateLegacyInstructions 负责迁移清理。
|
|
319
373
|
*/
|
|
320
374
|
function injectInstructions(adapter, newPaths, managedPaths) {
|
|
321
|
-
const cfgPath = path.join(TARGET_DIR,
|
|
322
|
-
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
|
+
});
|
|
323
396
|
if (!fs.existsSync(cfgPath)) {
|
|
324
|
-
|
|
397
|
+
cfg.instructions = merged;
|
|
398
|
+
if (!cfg.$schema) cfg.$schema = 'https://opencode.ai/config.json';
|
|
399
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
325
400
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
326
|
-
newPaths.add(
|
|
327
|
-
log(`生成
|
|
328
|
-
|
|
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');
|
|
329
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;
|
|
330
422
|
let cfg;
|
|
331
423
|
try {
|
|
332
424
|
cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
333
425
|
} catch (_) {
|
|
334
|
-
log(
|
|
426
|
+
log('根 opencode.json 解析失败(可能含注释),未自动迁移旧登记。可手动移除 instructions 中的 "' + legacyPattern + '"', 'warn');
|
|
335
427
|
return;
|
|
336
428
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
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;
|
|
340
432
|
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
341
|
-
log(
|
|
433
|
+
log('已从根 opencode.json 移除旧版 rules 登记(迁移至 .opencode/opencode.json)', 'success');
|
|
342
434
|
}
|
|
343
|
-
// 本包此前创建的文件:续登清单,避免升级清理误删
|
|
344
|
-
if (managedPaths.has('opencode.json')) newPaths.add('opencode.json');
|
|
345
435
|
}
|
|
346
436
|
|
|
347
|
-
/** 卸载时移除本包登记的 instructions
|
|
437
|
+
/** 卸载时移除本包登记的 instructions(用户自建配置只摘本包 glob,不删文件) */
|
|
348
438
|
function removeInstructionsRegistrations() {
|
|
349
439
|
adapters.forEach(adapter => {
|
|
350
|
-
if (!adapter.
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
cfg
|
|
356
|
-
|
|
357
|
-
|
|
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
|
+
}
|
|
358
460
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
+
}
|
|
364
476
|
}
|
|
365
477
|
});
|
|
366
478
|
}
|
|
@@ -495,13 +607,59 @@ function deployCommandFiles(adapter, srcDir, destDir, prefix, filter) {
|
|
|
495
607
|
const srcPath = path.join(srcDir, f);
|
|
496
608
|
const destPath = path.join(destDir, prefix + f);
|
|
497
609
|
removePath(destPath);
|
|
498
|
-
const content = applyContentRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'));
|
|
610
|
+
const content = rewriteCommandName(adapter, sanitizeCommandFrontmatter(applyContentRewrites(adapter, fs.readFileSync(srcPath, 'utf-8'))));
|
|
499
611
|
fs.writeFileSync(destPath, content);
|
|
500
612
|
});
|
|
501
613
|
|
|
502
614
|
return { count: commandFiles.length, names: commandFiles.map(f => prefix + f) };
|
|
503
615
|
}
|
|
504
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
|
+
|
|
505
663
|
/** 官方 OpenSpec 命令(openspec/commands/ → adapter.commandsDir) */
|
|
506
664
|
function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
|
|
507
665
|
if (!adapter.commandsDir) return { count: 0, names: [] };
|
|
@@ -509,13 +667,6 @@ function deployCommands(adapter, isUpgrade, managedPaths, srcDir, filter) {
|
|
|
509
667
|
adapter, srcDir, path.join(TARGET_DIR, adapter.commandsDir), adapter.commandPrefix || '', filter);
|
|
510
668
|
}
|
|
511
669
|
|
|
512
|
-
/** worktree 管理命令(cbb/worktrees/commands/ → adapter.worktreeCommandsDir) */
|
|
513
|
-
function deployWorktreeCommands(adapter, srcDir, filter) {
|
|
514
|
-
if (!adapter.worktreeCommandsDir) return { count: 0, names: [] };
|
|
515
|
-
return deployCommandFiles(
|
|
516
|
-
adapter, srcDir, path.join(TARGET_DIR, adapter.worktreeCommandsDir), adapter.worktreeCommandPrefix || '', filter);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
670
|
// ── 设置 OpenSpec 工作流 ──────────────────────────────────
|
|
520
671
|
|
|
521
672
|
/**
|
|
@@ -867,9 +1018,10 @@ async function init(targetDir, options = {}) {
|
|
|
867
1018
|
newPaths.add(`${adapter.rulesDir}/cbb-coding-rule.md`);
|
|
868
1019
|
newPaths.add(`${adapter.rulesDir}/cbb-priority.md`);
|
|
869
1020
|
newPaths.add(`${adapter.rulesDir}/cbb-ai-behavior.md`);
|
|
870
|
-
// opencode:rules glob
|
|
871
|
-
if (adapter.
|
|
1021
|
+
// opencode:rules glob 登记进 .opencode/opencode.json instructions,并迁移根文件旧登记
|
|
1022
|
+
if (adapter.instructionsFile) {
|
|
872
1023
|
injectInstructions(adapter, newPaths, managedPaths);
|
|
1024
|
+
migrateLegacyInstructions(adapter);
|
|
873
1025
|
}
|
|
874
1026
|
});
|
|
875
1027
|
log(`核心规则 → ${selectedAdapters.map(a => a.rulesDir).join(', ')} (2 个文件)`, 'success');
|
|
@@ -879,11 +1031,10 @@ async function init(targetDir, options = {}) {
|
|
|
879
1031
|
if (skillsAdapters.length > 0) {
|
|
880
1032
|
console.log(`\n 🔧 ${action}按需加载 Skills...`);
|
|
881
1033
|
const standardsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'dev-standards', 'skills');
|
|
882
|
-
const openspecExtendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'skills');
|
|
883
1034
|
const extendsSkillsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'tools');
|
|
884
1035
|
|
|
885
1036
|
const skillsDests = skillsAdapters.map(a => a.skillsDir);
|
|
886
|
-
let stdCount = 0,
|
|
1037
|
+
let stdCount = 0, extCount = 0;
|
|
887
1038
|
skillsAdapters.forEach(adapter => {
|
|
888
1039
|
// 通用编码规范 Skills(cbb/dev-standards/skills)
|
|
889
1040
|
const stdResult = deploySkills(adapter, isUpgrade, managedPaths, standardsSkillsSrc);
|
|
@@ -893,13 +1044,7 @@ async function init(targetDir, options = {}) {
|
|
|
893
1044
|
}
|
|
894
1045
|
// 官方 OpenSpec Skills(openspec/skills/)不部署——工作流入口统一为 /opsx:* 斜杠命令
|
|
895
1046
|
//(openspec/commands/ 在下方 Commands 段部署),避免双形态重复
|
|
896
|
-
//
|
|
897
|
-
const osExtResult = deploySkills(adapter, isUpgrade, managedPaths, openspecExtendsSkillsSrc, WORKTREE_SKILLS);
|
|
898
|
-
if (osExtResult.count > 0) {
|
|
899
|
-
osCount = osExtResult.count;
|
|
900
|
-
osExtResult.names.forEach(name => newPaths.add(`${adapter.skillsDir}/${name}`));
|
|
901
|
-
}
|
|
902
|
-
// 工具 Skills:仅 cbb-design-to-wiki(设计文档发布属 openspec 设计阶段动作)
|
|
1047
|
+
// 工具 Skills:当前不分发(恢复方式见 TOOLS_SKILLS 注释)
|
|
903
1048
|
const extResult = deploySkills(adapter, isUpgrade, managedPaths, extendsSkillsSrc, TOOLS_SKILLS);
|
|
904
1049
|
if (extResult.count > 0) {
|
|
905
1050
|
extCount = extResult.count;
|
|
@@ -907,9 +1052,22 @@ async function init(targetDir, options = {}) {
|
|
|
907
1052
|
}
|
|
908
1053
|
});
|
|
909
1054
|
if (stdCount > 0) log(`规范 Skills → ${skillsDests.join(', ')} (${stdCount} 个)`, 'success');
|
|
910
|
-
if (osCount > 0) log(`worktree Skills → ${skillsDests.join(', ')} (${osCount} 个)`, 'success');
|
|
911
1055
|
if (extCount > 0) log(`扩展 Skills → ${skillsDests.join(', ')} (${extCount} 个)`, 'success');
|
|
912
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
|
+
|
|
913
1071
|
// 部署 Superpowers Skills(白名单:openspec 工作流依赖 + 方法论基底)
|
|
914
1072
|
const superpowersSkillsSrc = superpowers.SUPERPOWERS_SKILLS_DIR;
|
|
915
1073
|
let spCount = 0;
|
|
@@ -931,32 +1089,26 @@ async function init(targetDir, options = {}) {
|
|
|
931
1089
|
await detectUserLevelSuperpowers(selectedAdapters, yes);
|
|
932
1090
|
}
|
|
933
1091
|
|
|
934
|
-
// 安装 Commands(官方
|
|
1092
|
+
// 安装 Commands(官方 11 个 → /opsx:*,onboard 不分发)
|
|
935
1093
|
const commandsAdapters = selectedAdapters.filter(a => a.commandsDir);
|
|
936
1094
|
if (commandsAdapters.length > 0) {
|
|
937
1095
|
console.log(`\n ⚡ ${action} Commands...`);
|
|
938
1096
|
const commandsBaseSrc = path.join(SHARED_STANDARDS_DIR, 'openspec', 'commands');
|
|
939
|
-
const
|
|
940
|
-
|
|
1097
|
+
const openspecCmdInclude = fs.readdirSync(commandsBaseSrc)
|
|
1098
|
+
.filter(f => f.endsWith('.md') && !OPENSPEC_COMMANDS_EXCLUDE.includes(f));
|
|
941
1099
|
|
|
942
1100
|
const cmdDests = [...new Set(commandsAdapters
|
|
943
|
-
.
|
|
1101
|
+
.map(a => a.commandsDir)
|
|
944
1102
|
.filter(Boolean))];
|
|
945
1103
|
let cmdCount = 0;
|
|
946
1104
|
commandsAdapters.forEach(adapter => {
|
|
947
1105
|
let adapterCmdCount = 0;
|
|
948
|
-
// 官方 Commands(openspec/commands/)—
|
|
949
|
-
const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc);
|
|
1106
|
+
// 官方 Commands(openspec/commands/)— 部署除不分发清单外的全部
|
|
1107
|
+
const baseResult = deployCommands(adapter, isUpgrade, managedPaths, commandsBaseSrc, openspecCmdInclude);
|
|
950
1108
|
if (baseResult.count > 0) {
|
|
951
1109
|
adapterCmdCount += baseResult.count;
|
|
952
1110
|
baseResult.names.forEach(name => newPaths.add(`${adapter.commandsDir}/${name}`));
|
|
953
1111
|
}
|
|
954
|
-
// worktree 管理 Commands(cbb/worktrees/commands/)
|
|
955
|
-
const extResult = deployWorktreeCommands(adapter, commandsExtendsSrc, cmdFilter);
|
|
956
|
-
if (extResult.count > 0) {
|
|
957
|
-
adapterCmdCount += extResult.count;
|
|
958
|
-
extResult.names.forEach(name => newPaths.add(`${adapter.worktreeCommandsDir}/${name}`));
|
|
959
|
-
}
|
|
960
1112
|
cmdCount = Math.max(cmdCount, adapterCmdCount);
|
|
961
1113
|
});
|
|
962
1114
|
if (cmdCount > 0) log(`Commands → ${cmdDests.join(', ')} (${cmdCount} 个)`, 'success');
|
|
@@ -995,9 +1147,9 @@ async function init(targetDir, options = {}) {
|
|
|
995
1147
|
const settingsPath = adapter.settingsFileIsUserLevel
|
|
996
1148
|
? adapter.settingsFile
|
|
997
1149
|
: path.join(TARGET_DIR, adapter.settingsFile);
|
|
998
|
-
settingsManager.addHooks(settingsPath, command, adapter.hookEvents);
|
|
1150
|
+
settingsManager.addHooks(settingsPath, command, adapter.hookEvents, hookOpts(adapter));
|
|
999
1151
|
// 清理旧版本遗留的 SessionStart hook(现已仅安装 UserPromptSubmit)
|
|
1000
|
-
if (settingsManager.removeLegacyHooks(settingsPath)) {
|
|
1152
|
+
if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
|
|
1001
1153
|
log(`清理 ${adapter.displayName} 遗留的 SessionStart hook`, 'success');
|
|
1002
1154
|
}
|
|
1003
1155
|
if (!adapter.settingsFileIsUserLevel) newPaths.add(adapter.settingsFile);
|
|
@@ -1022,6 +1174,8 @@ async function init(targetDir, options = {}) {
|
|
|
1022
1174
|
if (oldPaths.length > MAX_DISPLAY) {
|
|
1023
1175
|
log(` ... 还有 ${oldPaths.length - MAX_DISPLAY} 个`, 'dim');
|
|
1024
1176
|
}
|
|
1177
|
+
// 收尾:清掉因此变空的祖先目录(如改名后残留的空 .cbb/skills/)
|
|
1178
|
+
cleanupEmptyParentDirs(oldPaths).forEach(dir => log(` 清理空目录 ${dir}/`, 'dim'));
|
|
1025
1179
|
}
|
|
1026
1180
|
|
|
1027
1181
|
const kept = protectedKeptPaths(managedPaths, newPaths);
|
|
@@ -1606,8 +1760,8 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
|
|
|
1606
1760
|
if (hookAdapters.length > 0) {
|
|
1607
1761
|
const command = settingsManager.getCheckUpdateCommand();
|
|
1608
1762
|
hookAdapters.forEach(adapter => {
|
|
1609
|
-
settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents);
|
|
1610
|
-
settingsManager.removeLegacyHooks(adapter.settingsFile);
|
|
1763
|
+
settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents, hookOpts(adapter));
|
|
1764
|
+
settingsManager.removeLegacyHooks(adapter.settingsFile, hookOpts(adapter));
|
|
1611
1765
|
});
|
|
1612
1766
|
summaryParts.push(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join('、')}`);
|
|
1613
1767
|
}
|
|
@@ -1723,7 +1877,7 @@ async function uninstallUserLevelComponents() {
|
|
|
1723
1877
|
// 移除版本检查 hook(保留用户其余配置)
|
|
1724
1878
|
adapters.forEach(adapter => {
|
|
1725
1879
|
if (!adapter.settingsFile || !adapter.hookEvents) return;
|
|
1726
|
-
if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents)) {
|
|
1880
|
+
if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents, hookOpts(adapter))) {
|
|
1727
1881
|
log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
|
|
1728
1882
|
}
|
|
1729
1883
|
});
|
|
@@ -1754,7 +1908,7 @@ async function uninstall() {
|
|
|
1754
1908
|
// 是否同时卸载用户级组件(工具 skills + 版本检查 hook,作用于 ~/.claude 等用户目录)
|
|
1755
1909
|
let removeUserLevel = false;
|
|
1756
1910
|
if (fs.existsSync(USER_MANIFEST_FILE)) {
|
|
1757
|
-
const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder 的版本检查 hook)?(y/N): ');
|
|
1911
|
+
const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.codebuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
|
|
1758
1912
|
removeUserLevel = answerUser.toLowerCase() === 'y' || answerUser.toLowerCase() === 'yes';
|
|
1759
1913
|
}
|
|
1760
1914
|
|
|
@@ -1773,11 +1927,11 @@ async function uninstall() {
|
|
|
1773
1927
|
: path.join(TARGET_DIR, adapter.settingsFile);
|
|
1774
1928
|
// 项目级:需在清单中才处理
|
|
1775
1929
|
if (!adapter.settingsFileIsUserLevel && !managedPaths.has(adapter.settingsFile)) return;
|
|
1776
|
-
if (removeUserLevel && settingsManager.removeHooks(settingsPath, adapter.hookEvents)) {
|
|
1930
|
+
if (removeUserLevel && settingsManager.removeHooks(settingsPath, adapter.hookEvents, hookOpts(adapter))) {
|
|
1777
1931
|
log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
|
|
1778
1932
|
}
|
|
1779
1933
|
// 清理旧版本遗留的 SessionStart hook(无版本差异,无条件清理)
|
|
1780
|
-
if (settingsManager.removeLegacyHooks(settingsPath)) {
|
|
1934
|
+
if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
|
|
1781
1935
|
log(`${adapter.settingsFile}: 清理遗留 SessionStart hook`, 'success');
|
|
1782
1936
|
}
|
|
1783
1937
|
skippedPaths.add(adapter.settingsFile);
|
|
@@ -1802,22 +1956,7 @@ async function uninstall() {
|
|
|
1802
1956
|
});
|
|
1803
1957
|
|
|
1804
1958
|
// 清理可能变空的父目录(从深到浅)
|
|
1805
|
-
|
|
1806
|
-
managedPaths.forEach(relativePath => {
|
|
1807
|
-
let dir = path.dirname(relativePath);
|
|
1808
|
-
while (dir && dir !== '.') {
|
|
1809
|
-
dirsToCheck.add(dir);
|
|
1810
|
-
dir = path.dirname(dir);
|
|
1811
|
-
}
|
|
1812
|
-
});
|
|
1813
|
-
// 按深度排序,深层先处理
|
|
1814
|
-
[...dirsToCheck].sort((a, b) => b.split('/').length - a.split('/').length).forEach(dir => {
|
|
1815
|
-
const fullPath = path.join(TARGET_DIR, dir);
|
|
1816
|
-
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length === 0) {
|
|
1817
|
-
removePath(fullPath);
|
|
1818
|
-
log(`清理空目录 ${dir}/`, 'success');
|
|
1819
|
-
}
|
|
1820
|
-
});
|
|
1959
|
+
cleanupEmptyParentDirs(managedPaths).forEach(dir => log(`清理空目录 ${dir}/`, 'success'));
|
|
1821
1960
|
|
|
1822
1961
|
// 删除清单文件和 .va 目录
|
|
1823
1962
|
removePath(manifestDir);
|
|
@@ -1835,11 +1974,50 @@ async function uninstall() {
|
|
|
1835
1974
|
console.log(`\n✅ 卸载完成!共移除 ${removed} 个文件/目录\n`);
|
|
1836
1975
|
}
|
|
1837
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
|
+
|
|
1838
2016
|
// ── CLI 入口 ──────────────────────────────────────────────
|
|
1839
2017
|
|
|
1840
2018
|
// 入口身份:cbb(用户命令) / cbbm(内部命令,由流程、hook、Agent 技能调用)
|
|
1841
2019
|
const ENTRY = process.env.CBB_ENTRY === 'cbbm' ? 'cbbm' : 'cbb';
|
|
1842
|
-
const INTERNAL_COMMANDS = ['check-update', 'wiki', 'wp'];
|
|
2020
|
+
const INTERNAL_COMMANDS = ['check-update', 'check-upstream', 'wiki', 'wp'];
|
|
1843
2021
|
const UPSTREAM_FLAGS = ['--upstream-superpowers', '--upstream-openspec'];
|
|
1844
2022
|
|
|
1845
2023
|
const args = process.argv.slice(2);
|
|
@@ -1878,11 +2056,11 @@ switch (command) {
|
|
|
1878
2056
|
const upstreamSuperpowers = restArgs.includes('--upstream-superpowers');
|
|
1879
2057
|
const upstreamOpenspec = restArgs.includes('--upstream-openspec');
|
|
1880
2058
|
if (upstreamSuperpowers || upstreamOpenspec) {
|
|
1881
|
-
//
|
|
2059
|
+
// 维护者选项:按 .version 的 pin 重新拉取上游原生内容到本包源仓库(发布流程用)
|
|
1882
2060
|
if (upstreamSuperpowers) {
|
|
1883
2061
|
superpowers.fetchLatestSkills().then(result => {
|
|
1884
2062
|
if (result.skipped) {
|
|
1885
|
-
log(`superpowers skills 已是最新 (${result.
|
|
2063
|
+
log(`superpowers skills 已是最新 (${result.tag})`, 'success');
|
|
1886
2064
|
}
|
|
1887
2065
|
const validation = superpowers.validateSkills();
|
|
1888
2066
|
if (!validation.valid) {
|
|
@@ -1898,22 +2076,13 @@ switch (command) {
|
|
|
1898
2076
|
}
|
|
1899
2077
|
if (upstreamOpenspec) {
|
|
1900
2078
|
openspec.fetchLatestOpenSpec().then(result => {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
}
|
|
1907
|
-
log(`openspec 工作流完整性校验通过`, 'success');
|
|
1908
|
-
} else {
|
|
1909
|
-
const validation = openspec.validateDeployed();
|
|
1910
|
-
if (!validation.valid) {
|
|
1911
|
-
log(`本地 openspec 工作流不完整,请重新运行 cbb update --upstream-openspec:`, 'error');
|
|
1912
|
-
for (const m of validation.missing) log(` - 缺失 ${m}`, 'error');
|
|
1913
|
-
process.exit(1);
|
|
1914
|
-
}
|
|
1915
|
-
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);
|
|
1916
2084
|
}
|
|
2085
|
+
log(`openspec 工作流完整性校验通过`, 'success');
|
|
1917
2086
|
}).catch(err => {
|
|
1918
2087
|
log(`更新失败: ${err.message}`, 'error');
|
|
1919
2088
|
process.exit(1);
|
|
@@ -1936,6 +2105,12 @@ switch (command) {
|
|
|
1936
2105
|
case 'check-update':
|
|
1937
2106
|
require('../utils/check-update.js');
|
|
1938
2107
|
break;
|
|
2108
|
+
case 'check-upstream':
|
|
2109
|
+
checkUpstream().catch(err => {
|
|
2110
|
+
log(err.message, 'error');
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
});
|
|
2113
|
+
break;
|
|
1939
2114
|
case 'wp':
|
|
1940
2115
|
require('../wiki/index.js').publish(restArgs);
|
|
1941
2116
|
break;
|
|
@@ -1967,11 +2142,12 @@ function showHelp() {
|
|
|
1967
2142
|
@jspg-ai/coding-bb v${pkg.version} — cbbm:内部命令(由流程 / hook / Agent 技能调用)
|
|
1968
2143
|
|
|
1969
2144
|
cbbm check-update 版本检查(用户级 hook 每日自动触发)
|
|
2145
|
+
cbbm check-upstream 检查 openspec / superpowers 是否有上游新版本
|
|
1970
2146
|
cbbm wiki / cbbm wp Wiki 查询与发布(AI 通过技能调用)
|
|
1971
2147
|
|
|
1972
|
-
|
|
1973
|
-
cbbm update --upstream-superpowers
|
|
1974
|
-
cbbm update --upstream-openspec
|
|
2148
|
+
维护者选项(按 .version 的 pin 重新拉取上游原生内容到本包源仓库):
|
|
2149
|
+
cbbm update --upstream-superpowers 按 pin tag 拉取 superpowers skills(走镜像)
|
|
2150
|
+
cbbm update --upstream-openspec 按 pin 版本从 npmmirror 渲染 OpenSpec 工作流
|
|
1975
2151
|
`);
|
|
1976
2152
|
return;
|
|
1977
2153
|
}
|