@jspg-ai/coding-bb 0.0.3-beta.29 → 0.0.3-beta.30

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.
@@ -2,6 +2,11 @@
2
2
  * Claude Code 适配器
3
3
  *
4
4
  * 将编码规范分发到 .claude/rules/ 目录
5
+ *
6
+ * 退市(selectable: false):团队当前不使用 Claude Code,不在安装面板提供。
7
+ * 适配器代码与全部能力保留(含用户级 settings hook);先前已安装的空间按
8
+ * .last-tools 记录**粘性维护**(见 tool-selection.js),内容不被清理。
9
+ * 恢复入口:删掉 selectable 即可重新出现在面板(决策:不留参数开关)。
5
10
  */
6
11
 
7
12
  const path = require('path');
@@ -13,8 +18,10 @@ module.exports = {
13
18
  rulesDir: '.claude/rules',
14
19
  skillsDir: '.claude/skills',
15
20
  commandsDir: '.claude/commands/opsx',
16
- // 用户级 settings,写入全局生效,不污染项目目录
21
+ // 用户级 settings,写入全局生效,不污染项目目录
17
22
  settingsFile: path.join(os.homedir(), '.claude', 'settings.json'),
18
23
  settingsFileIsUserLevel: true,
19
24
  hookEvents: ['UserPromptSubmit'],
25
+ // 退市:不在安装面板提供(代码保留,存量空间粘性维护)
26
+ selectable: false,
20
27
  };
@@ -100,6 +100,9 @@ const agentsNav = require('./agents-nav');
100
100
  // 命令 → skill 形态转换(commandsAsSkills 适配器,当前仅 MiniMax Code)
101
101
  const commandSkill = require('./command-skill');
102
102
 
103
+ // 工具选择策略(退市工具不在面板提供 + 存量粘性保留)
104
+ const toolSelection = require('./tool-selection');
105
+
103
106
  /** 适配器的 hook 方言参数(Trae 需顶层 version 字段等差异) */
104
107
  function hookOpts(adapter) {
105
108
  return adapter.hookDialect ? { dialect: adapter.hookDialect } : {};
@@ -341,19 +344,22 @@ function ask(question) {
341
344
  async function selectTools(isUpgrade) {
342
345
  const action = isUpgrade ? '升级' : '安装';
343
346
 
347
+ // 面板可选项 = 未退市适配器(Claude Code / MiniMax Code 已退市,见 tool-selection.js)
348
+ const selectable = toolSelection.selectableAdapters(adapters);
349
+
344
350
  // 读取上次选择的工具:有记录则预选上次选择,首次安装则全部不选
345
351
  const lastTools = readLastTools();
346
352
  const preSelected = lastTools
347
- ? adapters.map((a, i) => lastTools.has(a.name) ? i : -1).filter(i => i >= 0)
353
+ ? selectable.map((a, i) => lastTools.has(a.name) ? i : -1).filter(i => i >= 0)
348
354
  : [];
349
355
 
350
356
  // checkbox 多选:
351
357
  // - 不切换 → 确认当前选择
352
358
  // - 取消某项 → 只装剩下的
353
- // - 全部不选 → 提示后回退全部
359
+ // - 全部不选 → 提示后回退全部可选项
354
360
  const selectedIdx = await checkbox.promptCheckbox({
355
361
  header: `🔧 选择要${action}的 AI 工具(空格切换,回车确认)`,
356
- items: adapters.map(a => ({
362
+ items: selectable.map(a => ({
357
363
  label: c('cyan', a.displayName),
358
364
  })),
359
365
  preSelected,
@@ -362,15 +368,18 @@ async function selectTools(isUpgrade) {
362
368
  let result;
363
369
  if (selectedIdx.size === 0) {
364
370
  log(`未选择任何工具,默认全部${action}`, 'warn');
365
- result = adapters;
371
+ result = selectable;
366
372
  } else {
367
373
  // 转成有序适配器数组(按原始顺序)
368
374
  result = [...selectedIdx]
369
375
  .sort((a, b) => a - b)
370
- .map(i => adapters[i]);
376
+ .map(i => selectable[i]);
371
377
  }
372
378
 
373
- // 保存本次选择,供下次运行时预选
379
+ // 退市但该空间旧记录里有的工具:粘性保留(继续维护,不因退市清删其内容)
380
+ result = toolSelection.carryOverRetired(adapters, result, lastTools);
381
+
382
+ // 保存本次选择(含粘性保留项,避免记录丢失导致下次被清理),供下次运行时预选
374
383
  saveLastTools(result);
375
384
  return result;
376
385
  }
@@ -1038,16 +1047,27 @@ async function init(targetDir, options = {}) {
1038
1047
  } else if (isNonInteractive) {
1039
1048
  const lastTools = readLastTools();
1040
1049
  const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
1041
- selectedAdapters = reused.length > 0 ? reused : adapters;
1050
+ selectedAdapters = reused.length > 0 ? reused : toolSelection.selectableAdapters(adapters);
1042
1051
  fromRecord = reused.length > 0;
1043
1052
  if (yes && process.stdin.isTTY) {
1044
1053
  log(reused.length > 0
1045
1054
  ? `--yes 非交互模式,沿用上次工具选择:${reused.map(a => a.displayName).join('、')}`
1046
- : '--yes 非交互模式,无上次选择记录,已默认选中所有适配器', 'info');
1055
+ : '--yes 非交互模式,无上次选择记录,已默认选中全部可选工具', 'info');
1047
1056
  }
1048
1057
  } else {
1049
1058
  selectedAdapters = await selectTools(isUpgrade);
1050
1059
  }
1060
+
1061
+ // 退市工具粘性保留(面板不出现,但本空间旧记录里有 → 继续维护,内容不被清理)。
1062
+ // 覆盖面板外入口:setup 预设(presetAdapters)与非交互记录沿用;交互面板已在 selectTools 内并过。
1063
+ const lastToolsForDir = readLastTools();
1064
+ const withRetired = toolSelection.carryOverRetired(adapters, selectedAdapters, lastToolsForDir);
1065
+ const retiredKept = toolSelection.retiredNames(withRetired);
1066
+ if (retiredKept.length > 0) {
1067
+ log(`保留上次选择的退市工具:${retiredKept.join('、')}(当前不在安装面板提供,内容继续维护)`, 'dim');
1068
+ }
1069
+ selectedAdapters = withRetired;
1070
+
1051
1071
  // 业务空间根记录本次选择,供 cbb update 等非交互场景沿用;
1052
1072
  // 沿用旧记录时同样落盘,使历史工具名(codebuddy → workbuddy)归一化自愈
1053
1073
  if (isWorkspaceRoot && fromRecord) {
@@ -1765,20 +1785,21 @@ async function setupWorkspace() {
1765
1785
  .sort((a, b) => a - b)
1766
1786
  .map(i => list[i]);
1767
1787
 
1768
- // 2.5 选择 AI 工具适配器(一次选择,作用于用户级组件与全部空间;非交互默认全选)
1788
+ // 2.5 选择 AI 工具适配器(一次选择,作用于用户级组件与全部空间;非交互默认全选可选工具)
1789
+ const selectable = toolSelection.selectableAdapters(adapters);
1769
1790
  let selectedAdapters;
1770
1791
  if (!process.stdin.isTTY) {
1771
- selectedAdapters = adapters;
1792
+ selectedAdapters = selectable;
1772
1793
  } else {
1773
1794
  const toolIdx = await checkbox.promptCheckbox({
1774
1795
  header: '🔧 选择要安装的 AI 工具(空格切换,回车确认)',
1775
- items: adapters.map(a => ({ label: c('cyan', a.displayName) })),
1796
+ items: selectable.map(a => ({ label: c('cyan', a.displayName) })),
1776
1797
  // 默认只预选 opencode(团队当前主用),可空格加选其他工具
1777
- preSelected: adapters.reduce((acc, a, i) => (a.name === 'opencode' ? acc.concat(i) : acc), []),
1798
+ preSelected: selectable.reduce((acc, a, i) => (a.name === 'opencode' ? acc.concat(i) : acc), []),
1778
1799
  });
1779
1800
  selectedAdapters = toolIdx.size === 0
1780
- ? adapters
1781
- : [...toolIdx].sort((a, b) => a - b).map(i => adapters[i]);
1801
+ ? selectable
1802
+ : [...toolIdx].sort((a, b) => a - b).map(i => selectable[i]);
1782
1803
  }
1783
1804
 
1784
1805
  // 3. 配置摘要(业务空间 / 目标目录 / AI 工具)
@@ -1834,7 +1855,7 @@ const USER_MANIFEST_FILE = path.join(os.homedir(), '.cbb', '.user-components');
1834
1855
  // 当前为空:cbb-wiki-ops 保留在源仓库(cbb/tools/)但不分发,有用户需要时再加入此清单。
1835
1856
  const USER_LEVEL_SKILLS = [];
1836
1857
 
1837
- async function setupUserLevelComponents(targetAdapters = adapters) {
1858
+ async function setupUserLevelComponents(targetAdapters = toolSelection.selectableAdapters(adapters)) {
1838
1859
  const home = os.homedir();
1839
1860
  const srcDir = path.join(SHARED_STANDARDS_DIR, 'cbb', 'tools');
1840
1861
  if (!fs.existsSync(srcDir)) return;
@@ -1913,7 +1934,7 @@ async function updateInstalled() {
1913
1934
  // 用户级组件沿用本目录的工具选择(.last-tools),无记录或为空才回退全量
1914
1935
  const lastTools = readLastTools();
1915
1936
  const reused = lastTools ? adapters.filter(a => lastTools.has(a.name)) : [];
1916
- await setupUserLevelComponents(reused.length > 0 ? reused : adapters);
1937
+ await setupUserLevelComponents(reused.length > 0 ? reused : toolSelection.selectableAdapters(adapters));
1917
1938
 
1918
1939
  const manifest = readManifest();
1919
1940
  if (manifest.size === 0) {
@@ -15,6 +15,11 @@
15
15
  *
16
16
  * 数据目录:MINIMAX_DATA_DIR(优先)> MAVIS_DATA_DIR > ~/.minimax,与 mcode
17
17
  * 运行时一致;用户若改过数据目录,需重跑 cbb update 以重装插件到新位置。
18
+ *
19
+ * 退市(selectable: false):实测 MiniMax Code 不支持目录级 skill 与 command
20
+ * (无项目级 commands 目录,skill 亦需插件形态承载),当前分发形态不可用。
21
+ * 适配器代码与全部能力保留(备将来改走插件形态),但不在安装面板提供;
22
+ * 已安装过(beta.29)的空间按 .last-tools 记录**粘性维护**,内容不被清理。
18
23
  */
19
24
 
20
25
  const path = require('path');
@@ -50,4 +55,6 @@ module.exports = {
50
55
  timeout: 10,
51
56
  },
52
57
  resolveDataDir,
58
+ // 退市:不在安装面板提供(代码保留,存量空间粘性维护)
59
+ selectable: false,
53
60
  };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 工具选择策略:安装面板的可见集合 + 退市工具的粘性保留。
5
+ *
6
+ * 背景(2026-09-28 决策):MiniMax Code 实测不支持目录级 skill 与 command、
7
+ * Claude Code 团队当前未用,两者**退市**——适配器与全部能力保留(见各适配器
8
+ * 文件头),仅不在 `cbb setup` / `cbb update` 的安装面板提供。
9
+ *
10
+ * 两条规则:
11
+ * 1. **可选项**(selectableAdapters):面板项、非交互全选兜底、setup 默认预选、
12
+ * 用户级组件新装,一律只用未退市适配器(`selectable !== false`);
13
+ * 2. **粘性保留**(carryOverRetired):某空间的 `.last-tools` 记录过退市工具
14
+ * (即确曾安装),则继续纳入本次安装/升级集合——面板不出现,内容不因退市
15
+ * 被清单差集清删;`.last-tools` 一并保持该记录,避免记录丢失导致下次清理。
16
+ *
17
+ * 恢复入口:删掉适配器的 `selectable: false` 即回到面板(决策:不留参数开关)。
18
+ */
19
+
20
+ /** 面板/默认可选适配器(未标 selectable: false 的全部) */
21
+ function selectableAdapters(adapters) {
22
+ return adapters.filter(a => a.selectable !== false);
23
+ }
24
+
25
+ /**
26
+ * 把「本空间旧记录中的退市工具」并入选择结果(按注册表顺序追加,去重)。
27
+ * @param {Array} adapters 注册表全量
28
+ * @param {Array} selected 本次选择(面板 / 预设 / 记录沿用)
29
+ * @param {Set<string>|null} lastTools 该空间的旧工具记录(.last-tools)
30
+ * @returns {Array} 并入后的有序数组(无退市工具需保留时原样返回)
31
+ */
32
+ function carryOverRetired(adapters, selected, lastTools) {
33
+ if (!lastTools || lastTools.size === 0) return selected;
34
+ const selectedNames = new Set(selected.map(a => a.name));
35
+ const carried = adapters.filter(a =>
36
+ a.selectable === false && lastTools.has(a.name) && !selectedNames.has(a.name));
37
+ return carried.length > 0 ? selected.concat(carried) : selected;
38
+ }
39
+
40
+ /** 结果集中退市工具的显示名(供安装输出提示,如「保留上次选择的 Claude Code」) */
41
+ function retiredNames(selected) {
42
+ return selected.filter(a => a.selectable === false).map(a => a.displayName);
43
+ }
44
+
45
+ module.exports = { selectableAdapters, carryOverRetired, retiredNames };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jspg-ai/coding-bb",
3
- "version": "0.0.3-beta.29",
3
+ "version": "0.0.3-beta.30",
4
4
  "description": "整合业界热门且高价值的工具、框架与技能,为 AI CODING AGENT 提供统一的行为准则与工作流,辅助开发者将需求高效落地为符合规范的代码",
5
5
  "main": "cbb/lib/install/init.js",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "scripts": {
20
20
  "sync:shared": "node scripts/sync-shared.js",
21
21
  "pretest": "node scripts/sync-shared.js --check",
22
- "test": "node test/lib/install/cleanup.test.js && node test/lib/utils/settings.test.js && node test/lib/utils/gitignore.test.js && node test/lib/utils/githook.test.js && node test/lib/utils/version.test.js && node test/lib/install/workspaces.test.js && node test/lib/utils/checkbox.test.js && node test/lib/utils/output.test.js && node test/lib/install/cli-help.test.js && node test/lib/install/adapters.test.js && node test/lib/install/agents-nav.test.js && node test/lib/install/command-skill.test.js && node test/lib/utils/plugin-hook.test.js && node test/lib/wiki/wiki-publish.test.js && node test/lib/wiki/wiki-parse-args.test.js && node test/lib/wiki/wiki-split.test.js && node test/lib/superpowers/superpowers.test.js && node test/worktrees/push-branches.test.js && node test/worktrees/commit-worktrees.test.js && node test/worktrees/push-worktrees.test.js && node test/worktrees/check-env-deep.test.js && node test/worktrees/create-worktrees.test.js && node test/worktrees/make-mr-urls.test.js && node test/lib/install/setup-decision.test.js && node test/lib/openspec/protected-paths.test.js"
22
+ "test": "node test/lib/install/cleanup.test.js && node test/lib/utils/settings.test.js && node test/lib/utils/gitignore.test.js && node test/lib/utils/githook.test.js && node test/lib/utils/version.test.js && node test/lib/install/workspaces.test.js && node test/lib/utils/checkbox.test.js && node test/lib/utils/output.test.js && node test/lib/install/cli-help.test.js && node test/lib/install/adapters.test.js && node test/lib/install/tool-selection.test.js && node test/lib/install/agents-nav.test.js && node test/lib/install/command-skill.test.js && node test/lib/utils/plugin-hook.test.js && node test/lib/wiki/wiki-publish.test.js && node test/lib/wiki/wiki-parse-args.test.js && node test/lib/wiki/wiki-split.test.js && node test/lib/superpowers/superpowers.test.js && node test/worktrees/push-branches.test.js && node test/worktrees/commit-worktrees.test.js && node test/worktrees/push-worktrees.test.js && node test/worktrees/check-env-deep.test.js && node test/worktrees/create-worktrees.test.js && node test/worktrees/make-mr-urls.test.js && node test/lib/install/setup-decision.test.js && node test/lib/openspec/protected-paths.test.js"
23
23
  },
24
24
  "keywords": [
25
25
  "cbb",