@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.
Files changed (100) hide show
  1. package/cbb/dev-standards/rules/cbb-ai-behavior.md +118 -116
  2. package/cbb/lib/install/claude-code.js +1 -3
  3. package/cbb/lib/install/codebuddy.js +30 -0
  4. package/cbb/lib/install/init.js +295 -119
  5. package/cbb/lib/install/opencode.js +35 -31
  6. package/cbb/lib/install/qoder.js +1 -3
  7. package/cbb/lib/install/rule-rewrite.js +27 -0
  8. package/cbb/lib/install/trae.js +33 -0
  9. package/cbb/lib/openspec/index.js +337 -554
  10. package/cbb/lib/superpowers/index.js +246 -265
  11. package/cbb/lib/utils/check-update.js +8 -1
  12. package/cbb/lib/utils/gitignore.js +2 -0
  13. package/cbb/lib/utils/settings.js +17 -4
  14. package/cbb/lib/utils/tar.js +92 -0
  15. package/cbb/lib/utils/upstream.js +90 -0
  16. package/cbb/worktrees/skills/cbb-worktree-close/SKILL.md +32 -23
  17. package/cbb/worktrees/skills/cbb-worktree-close/scripts/remove-worktrees.js +2 -2
  18. package/cbb/worktrees/skills/cbb-worktree-init/SKILL.md +36 -28
  19. package/cbb/worktrees/skills/cbb-worktree-init/scripts/create-worktrees.js +15 -37
  20. package/cbb/worktrees/skills/cbb-worktree-init/scripts/update-gitignore.js +25 -9
  21. package/cbb/worktrees/skills/cbb-worktree-push/SKILL.md +108 -31
  22. package/cbb/worktrees/skills/cbb-worktree-push/scripts/make-mr-urls.js +211 -0
  23. package/config/openspec/schemas/spec-driven/schema.yaml +38 -30
  24. package/config/openspec/schemas/spec-driven/templates/design.md +0 -18
  25. package/config/openspec/schemas/spec-driven/templates/proposal.md +4 -3
  26. package/config/upstream-mirrors.json +12 -0
  27. package/config/workspace-agents.sample.md +43 -39
  28. package/openspec/.version +2 -3
  29. package/openspec/commands/apply.md +189 -175
  30. package/openspec/commands/archive.md +237 -216
  31. package/openspec/commands/bulk-archive.md +355 -327
  32. package/openspec/commands/continue.md +116 -105
  33. package/openspec/commands/explore.md +230 -199
  34. package/openspec/commands/ff.md +115 -104
  35. package/openspec/commands/new.md +74 -63
  36. package/openspec/commands/onboard.md +557 -548
  37. package/openspec/commands/propose.md +161 -150
  38. package/openspec/commands/sync.md +277 -249
  39. package/openspec/commands/update.md +92 -80
  40. package/openspec/commands/verify.md +175 -162
  41. package/openspec/skills/openspec-apply-change/SKILL.md +20 -5
  42. package/openspec/skills/openspec-archive-change/SKILL.md +30 -8
  43. package/openspec/skills/openspec-bulk-archive-change/SKILL.md +36 -6
  44. package/openspec/skills/openspec-continue-change/SKILL.md +14 -2
  45. package/openspec/skills/openspec-explore/SKILL.md +21 -9
  46. package/openspec/skills/openspec-ff-change/SKILL.md +14 -2
  47. package/openspec/skills/openspec-new-change/SKILL.md +13 -1
  48. package/openspec/skills/openspec-onboard/SKILL.md +49 -39
  49. package/openspec/skills/openspec-propose/SKILL.md +15 -3
  50. package/openspec/skills/openspec-sync-specs/SKILL.md +31 -2
  51. package/openspec/skills/openspec-update-change/SKILL.md +27 -14
  52. package/openspec/skills/openspec-verify-change/SKILL.md +17 -3
  53. package/package.json +2 -2
  54. package/superpowers/.version +4 -4
  55. package/superpowers/skills/brainstorming/SKILL.md +47 -12
  56. package/superpowers/skills/brainstorming/scripts/frame-template.html +213 -213
  57. package/superpowers/skills/brainstorming/scripts/server.cjs +723 -723
  58. package/superpowers/skills/brainstorming/visual-companion.md +6 -6
  59. package/superpowers/skills/diagnosing-superpowers/SKILL.md +120 -0
  60. package/superpowers/skills/diagnosing-superpowers/prompts/analyst-common.md +38 -0
  61. package/superpowers/skills/diagnosing-superpowers/prompts/cost-and-time.md +28 -0
  62. package/superpowers/skills/diagnosing-superpowers/prompts/plan-adherence.md +29 -0
  63. package/superpowers/skills/diagnosing-superpowers/prompts/quality-evidence.md +26 -0
  64. package/superpowers/skills/diagnosing-superpowers/prompts/repeated-work.md +30 -0
  65. package/superpowers/skills/diagnosing-superpowers/prompts/request-conflicts.md +20 -0
  66. package/superpowers/skills/diagnosing-superpowers/prompts/scrub-audit.md +33 -0
  67. package/superpowers/skills/diagnosing-superpowers/prompts/scrub.md +29 -0
  68. package/superpowers/skills/diagnosing-superpowers/prompts/similar-session.md +38 -0
  69. package/superpowers/skills/diagnosing-superpowers/prompts/skill-timeline.md +30 -0
  70. package/superpowers/skills/diagnosing-superpowers/prompts/stumbles.md +28 -0
  71. package/superpowers/skills/diagnosing-superpowers/references/context-safety.md +22 -0
  72. package/superpowers/skills/diagnosing-superpowers/references/github-issues.md +47 -0
  73. package/superpowers/skills/diagnosing-superpowers/references/redaction-policy.md +34 -0
  74. package/superpowers/skills/diagnosing-superpowers/references/session-discovery.md +31 -0
  75. package/superpowers/skills/diagnosing-superpowers/templates/bundle-README.md +77 -0
  76. package/superpowers/skills/diagnosing-superpowers/templates/case.md +64 -0
  77. package/superpowers/skills/diagnosing-superpowers/templates/issue.md +51 -0
  78. package/superpowers/skills/diagnosing-superpowers/templates/report.md +82 -0
  79. package/superpowers/skills/executing-plans/SKILL.md +350 -41
  80. package/superpowers/skills/executing-plans/scripts/task-done +52 -0
  81. package/superpowers/skills/executing-plans/scripts/task-start +28 -0
  82. package/superpowers/skills/requesting-code-review/SKILL.md +1 -1
  83. package/superpowers/skills/requesting-code-review/code-reviewer.md +17 -0
  84. package/superpowers/skills/subagent-driven-development/SKILL.md +18 -18
  85. package/superpowers/skills/subagent-driven-development/re-review-prompt.md +1 -1
  86. package/superpowers/skills/subagent-driven-development/scripts/review-package +53 -46
  87. package/superpowers/skills/subagent-driven-development/scripts/sdd-workspace +82 -40
  88. package/superpowers/skills/subagent-driven-development/scripts/task-brief +43 -41
  89. package/superpowers/skills/subagent-driven-development/task-reviewer-prompt.md +2 -2
  90. package/superpowers/skills/systematic-debugging/root-cause-tracing.md +1 -1
  91. package/superpowers/skills/test-driven-development/SKILL.md +10 -0
  92. package/superpowers/skills/using-superpowers/SKILL.md +2 -0
  93. package/superpowers/skills/using-superpowers/references/claude-code-tools.md +29 -0
  94. package/superpowers/skills/using-superpowers/references/muse-tools.md +35 -0
  95. package/superpowers/skills/writing-plans/SKILL.md +30 -9
  96. package/superpowers/skills/writing-skills/SKILL.md +4 -2
  97. package/superpowers/skills/writing-skills/graphviz-conventions.dot +171 -171
  98. package/cbb/worktrees/commands/worktree-close.md +0 -64
  99. package/cbb/worktrees/commands/worktree-init.md +0 -51
  100. package/cbb/worktrees/commands/worktree-push.md +0 -42
@@ -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 工作流硬依赖 + 方法论基底(源仓库仍保留全量 14 个,
61
- // 供 cbb update --upstream-superpowers 维护;砍掉的与 openspec 工作流重叠或属低频场景)
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 扩展 skills 部署白名单:init / close / push 三件套
72
- // (单层安装:业务空间根是唯一安装点,worktree 管理入口 init 同样部署在根)
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:仅 cbb-design-to-wiki(openspec 设计阶段动作)。
76
- // cbb-wiki-ops 当前不分发(保留在源仓库,有用户需要时再加入 USER_LEVEL_SKILLS);Java 工具不装。
77
- const TOOLS_SKILLS = ['cbb-design-to-wiki'];
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
- fs.writeFileSync(destPath, fs.readFileSync(srcPath, 'utf-8'));
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/rules/*.md glob 登记进项目根
315
- * opencode.json 的 instructions(官方支持的加载方式)。
365
+ * opencode 无原生 rules 目录:把 rules glob 登记进 .opencode/opencode.json
366
+ * instructions(opencode V2 支持的配置位置;随 .opencode gitignore,按机器生成)。
367
+ * instructions 相对路径的解析基准(配置文件目录 vs 项目根)文档未写死,
368
+ * 故登记双 glob 对冲:任一基准下恰有一个命中,另一个空匹配。
316
369
  * - 文件不存在:创建(记入清单,卸载时整体删除)
317
- * - 文件存在:仅追加 pattern(幂等);若清单显示该文件本由本包创建,则续登
318
- * newPaths 防止升级清理误删;JSONC(含注释)解析失败则只提示不改写
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, 'opencode.json');
322
- const pattern = adapter.instructionsPattern;
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
- const cfg = { $schema: 'https://opencode.ai/config.json', instructions: [pattern] };
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('opencode.json');
327
- log(`生成 opencode.json 并登记 rules 加载(instructions: ${pattern})`, 'success');
328
- return;
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(`opencode.json 解析失败(可能含注释),未自动登记。请手动在 instructions 中加入 "${pattern}"`, 'warn');
426
+ log('根 opencode.json 解析失败(可能含注释),未自动迁移旧登记。可手动移除 instructions 中的 "' + legacyPattern + '"', 'warn');
335
427
  return;
336
428
  }
337
- const list = Array.isArray(cfg.instructions) ? cfg.instructions : [];
338
- if (!list.includes(pattern)) {
339
- cfg.instructions = [...list, pattern];
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(`已在 opencode.json instructions 中登记 ${pattern}`, 'success');
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 pattern(用户创建的 opencode.json 只删 pattern 不删文件) */
437
+ /** 卸载时移除本包登记的 instructions(用户自建配置只摘本包 glob,不删文件) */
348
438
  function removeInstructionsRegistrations() {
349
439
  adapters.forEach(adapter => {
350
- if (!adapter.instructionsPattern) return;
351
- const cfgPath = path.join(TARGET_DIR, 'opencode.json');
352
- if (!fs.existsSync(cfgPath)) return;
353
- let cfg;
354
- try {
355
- cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
356
- } catch (_) {
357
- return; // 不可解析的文件不动
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
- if (Array.isArray(cfg.instructions) && cfg.instructions.includes(adapter.instructionsPattern)) {
360
- cfg.instructions = cfg.instructions.filter(p => p !== adapter.instructionsPattern);
361
- if (cfg.instructions.length === 0) delete cfg.instructions;
362
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
363
- log('opencode.json: 移除本包登记的 instructions,保留用户配置', 'success');
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 登记进项目根 opencode.json instructions
871
- if (adapter.instructionsPattern) {
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, osCount = 0, extCount = 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
- // worktree 扩展 Skills:init / close / push 三件套(单层安装,空间根是唯一安装点)
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(官方 12 个 → /opsx:*;worktree 管理 → /cbb:worktree-*,init/close/push 三件套)
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 commandsExtendsSrc = path.join(SHARED_STANDARDS_DIR, 'cbb', 'worktrees', 'commands');
940
- const cmdFilter = ['worktree-init.md', 'worktree-close.md', 'worktree-push.md'];
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
- .flatMap(a => [a.commandsDir, a.worktreeCommandsDir])
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
- const dirsToCheck = new Set();
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
- // 维护者选项:从 GitHub 拉取上游原生内容到本包源仓库(发布流程用)
2059
+ // 维护者选项:按 .version 的 pin 重新拉取上游原生内容到本包源仓库(发布流程用)
1882
2060
  if (upstreamSuperpowers) {
1883
2061
  superpowers.fetchLatestSkills().then(result => {
1884
2062
  if (result.skipped) {
1885
- log(`superpowers skills 已是最新 (${result.sha.slice(0, 8)})`, 'success');
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
- if (!result.skipped) {
1902
- const validation = openspec.validateDeployed();
1903
- if (!validation.valid) {
1904
- log(`部署不完整,缺失: ${validation.missing.join(', ')}`, 'error');
1905
- process.exit(1);
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 GitHub 拉取最新 superpowers skills
1974
- cbbm update --upstream-openspec GitHub 拉取最新 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
  }