@jspg-ai/coding-bb 0.0.3-beta.15 → 0.0.3-beta.17

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.
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 将AI 编码规范与工作流套件安装到目标项目中。
7
7
  * 支持工具:Claude Code (.claude/rules/)、Qoder (.qoder/rules/)、opencode (.opencode/rules/)、
8
- * WorkBuddy/CodeBuddy (.codebuddy/rules/)、TraeWork/TraeCode (.trae/rules/)
8
+ * WorkBuddy (.workbuddy/rules/)、TraeWork/TraeCode (.trae/rules/)
9
9
  *
10
10
  * 使用方式:
11
11
  * npm install -g @jspg-ai/coding-bb # 首次使用
@@ -56,7 +56,7 @@ const LAST_TOOLS_FILE = path.join(MANIFEST_DIR, '.last-tools');
56
56
  // 适配器(数组顺序 = 工具选择面板的显示顺序)
57
57
  const adapters = [
58
58
  require('./opencode'),
59
- require('./codebuddy'),
59
+ require('./workbuddy'),
60
60
  require('./trae'),
61
61
  require('./qoder'),
62
62
  require('./claude-code'),
@@ -94,10 +94,31 @@ function hookOpts(adapter) {
94
94
  return adapter.hookDialect ? { dialect: adapter.hookDialect } : {};
95
95
  }
96
96
 
97
+ // 工具历史名迁移:beta.16 及以前为 'codebuddy'(CodeBuddy 目录模型),
98
+ // beta.17 起统一为 'workbuddy'。存量 .last-tools / 清单里的旧名读取时归一,
99
+ // 下次安装由 saveLastTools 以新名落盘。
100
+ const LEGACY_TOOL_NAMES = { codebuddy: 'workbuddy' };
101
+
102
+ /** 工具名集合归一(旧名 → 新名);null 透传 */
103
+ function normalizeToolSet(set) {
104
+ if (!set) return set;
105
+ return new Set([...set].map(t => LEGACY_TOOL_NAMES[t] || t));
106
+ }
107
+
108
+ /**
109
+ * 迁移清理:移除本包写在旧用户级 settings 里的 hook(如 WorkBuddy 适配修正:
110
+ * ~/.codebuddy/settings.json → ~/.workbuddy/settings.json)。
111
+ * removeHooks 只删 _managedBy: cbb 标记的条目,文件清空则删除。返回 true 表示有改动。
112
+ */
113
+ function cleanLegacySettings(adapter) {
114
+ if (!adapter.legacySettingsFile || !adapter.hookEvents) return false;
115
+ return settingsManager.removeHooks(adapter.legacySettingsFile, adapter.hookEvents, hookOpts(adapter));
116
+ }
117
+
97
118
  // 清单驱动的文件清理决策(保护用户文档 CLAUDE.md / AGENTS.md 等)
98
119
  const { pathsToDelete, protectedKeptPaths } = require('./cleanup');
99
120
 
100
- // 规则 frontmatter 改写(Trae/CodeBuddy 需 alwaysApply: true)
121
+ // 规则 frontmatter 改写(Trae/WorkBuddy 需 alwaysApply: true)
101
122
  const { applyRuleRewrites } = require('./rule-rewrite');
102
123
 
103
124
  // .gitignore 条目决策(纯函数:检测/写入/卸载移除)
@@ -198,7 +219,7 @@ function readLastTools() {
198
219
  if (!fs.existsSync(filePath)) return null;
199
220
  try {
200
221
  const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
201
- return new Set(data.tools || []);
222
+ return normalizeToolSet(new Set(data.tools || []));
202
223
  } catch (_) {
203
224
  return null;
204
225
  }
@@ -1153,6 +1174,10 @@ async function init(targetDir, options = {}) {
1153
1174
  if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
1154
1175
  log(`清理 ${adapter.displayName} 遗留的 SessionStart hook`, 'success');
1155
1176
  }
1177
+ // 迁移:清理旧用户级 settings(如 ~/.codebuddy → ~/.workbuddy)
1178
+ if (cleanLegacySettings(adapter)) {
1179
+ log(`迁移清理旧用户级配置 ${adapter.legacySettingsFile}`, 'success');
1180
+ }
1156
1181
  if (!adapter.settingsFileIsUserLevel) newPaths.add(adapter.settingsFile);
1157
1182
  });
1158
1183
  log(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join(', ')} (${hookAdapters[0].hookEvents.join(', ')})`, 'success');
@@ -1267,7 +1292,7 @@ function decideUpgradeIfInstalled(targetDir, selectedAdapters) {
1267
1292
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1268
1293
  const decision = setupDecision.decideSetupAction({
1269
1294
  sameVersion: version.compareVersions(installed, pkg.version) === 0,
1270
- lastTools: setupDecision.readLastToolsFrom(targetDir),
1295
+ lastTools: normalizeToolSet(setupDecision.readLastToolsFrom(targetDir)),
1271
1296
  selectedNames: (selectedAdapters || []).map(a => a.name),
1272
1297
  });
1273
1298
 
@@ -1776,6 +1801,7 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
1776
1801
  hookAdapters.forEach(adapter => {
1777
1802
  settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents, hookOpts(adapter));
1778
1803
  settingsManager.removeLegacyHooks(adapter.settingsFile, hookOpts(adapter));
1804
+ cleanLegacySettings(adapter);
1779
1805
  });
1780
1806
  summaryParts.push(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join('、')}`);
1781
1807
  }
@@ -1894,6 +1920,9 @@ async function uninstallUserLevelComponents() {
1894
1920
  if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents, hookOpts(adapter))) {
1895
1921
  log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
1896
1922
  }
1923
+ if (cleanLegacySettings(adapter)) {
1924
+ log(`${adapter.legacySettingsFile}: 移除版本检查 hook(旧路径),保留用户配置`, 'success');
1925
+ }
1897
1926
  });
1898
1927
 
1899
1928
  removePath(USER_MANIFEST_FILE);
@@ -1922,7 +1951,7 @@ async function uninstall() {
1922
1951
  // 是否同时卸载用户级组件(工具 skills + 版本检查 hook,作用于 ~/.claude 等用户目录)
1923
1952
  let removeUserLevel = false;
1924
1953
  if (fs.existsSync(USER_MANIFEST_FILE)) {
1925
- const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.codebuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
1954
+ const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.workbuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
1926
1955
  removeUserLevel = answerUser.toLowerCase() === 'y' || answerUser.toLowerCase() === 'yes';
1927
1956
  }
1928
1957
 
@@ -7,7 +7,7 @@
7
7
  * `trigger: always_on`。各工具对"始终生效"的字段名不同:
8
8
  * - Qoder:trigger: always_on(源格式)
9
9
  * - Claude Code / opencode:无该字段,靠各自机制加载
10
- * - WorkBuddy/CodeBuddy、Trae:alwaysApply: true
10
+ * - WorkBuddy、Trae:alwaysApply: true
11
11
  *
12
12
  * 适配器通过 ruleRewrites([[from, to], ...])声明改写规则,部署时套用;
13
13
  * 无 ruleRewrites 的适配器原样返回。
@@ -6,7 +6,7 @@
6
6
  * 背景 bug(0.0.3-beta.15 修复):旧版 decideUpgradeIfInstalled 只比较
7
7
  * 「已装版本 vs 当前包版本」,版本一致即 skip——导致「空间已是当前版本 +
8
8
  * 本次新选了工具(如 WorkBuddy)」被整体跳过,新工具的空间级目录
9
- * (.codebuddy/ 等)永不生成,.last-tools 也不更新。
9
+ * (.workbuddy/ 等)永不生成,.last-tools 也不更新。
10
10
  */
11
11
 
12
12
  const fs = require('fs');
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WorkBuddy 适配器
3
+ *
4
+ * 腾讯 WorkBuddy 桌面工作台的项目级配置目录为 `.workbuddy/`(业务空间实测:
5
+ * skill 只有放在 `.workbuddy/skills/` 才生效;旧版假设「与 CodeBuddy 共用
6
+ * .codebuddy/」不成立)。0.0.3-beta.16 起目录与用户级 settings 切到
7
+ * `.workbuddy`;0.0.3-beta.17 起不再支持 CodeBuddy,`.codebuddy` 仅作为
8
+ * legacy 由安装流程迁移清理(legacyDirs / legacySettingsFile)。
9
+ *
10
+ * 与 Claude Code / Qoder 的映射差异:
11
+ * - rules:`.workbuddy/rules/*.md`(扁平文件、递归加载),frontmatter 用
12
+ * `alwaysApply: true`(Qoder 的 `trigger: always_on` 需改写,见 ruleRewrites)
13
+ * - skills:`.workbuddy/skills/<name>/SKILL.md`,结构与 Claude 同构
14
+ * - commands:`.workbuddy/commands/`,支持目录嵌套(`/group:command`),与 Claude 同风格
15
+ * - hooks:兼容 Claude Code Hooks 规范,settings 结构一致,直接复用 settingsManager
16
+ */
17
+
18
+ const path = require('path');
19
+ const os = require('os');
20
+
21
+ module.exports = {
22
+ name: 'workbuddy',
23
+ displayName: 'WorkBuddy',
24
+ rulesDir: '.workbuddy/rules',
25
+ skillsDir: '.workbuddy/skills',
26
+ commandsDir: '.workbuddy/commands/opsx',
27
+ // 规则 frontmatter 改写:Qoder 风格 → WorkBuddy 风格
28
+ ruleRewrites: [['trigger: always_on', 'alwaysApply: true']],
29
+ // 用户级 settings,写入全局生效,不污染项目目录
30
+ settingsFile: path.join(os.homedir(), '.workbuddy', 'settings.json'),
31
+ settingsFileIsUserLevel: true,
32
+ hookEvents: ['UserPromptSubmit'],
33
+ // 旧版(beta.15 及以前按 CodeBuddy 装的 .codebuddy):安装时迁移清理
34
+ legacyDirs: ['.codebuddy'],
35
+ legacySettingsFile: path.join(os.homedir(), '.codebuddy', 'settings.json'),
36
+ };
@@ -23,7 +23,8 @@ const CACHE_FILE = path.join(os.homedir(), '.cbb', '.update-check-cache.json');
23
23
  const CACHE_TTL = 24 * 60 * 60 * 1000;
24
24
 
25
25
  function getProjectDir() {
26
- return process.env.CODEBUDDY_PROJECT_DIR
26
+ return process.env.WORKBUDDY_PROJECT_DIR
27
+ || process.env.CODEBUDDY_PROJECT_DIR
27
28
  || process.env.TRAE_PROJECT_DIR
28
29
  || process.env.QODER_PROJECT_DIR
29
30
  || process.env.CLAUDE_PROJECT_DIR
@@ -32,8 +33,9 @@ function getProjectDir() {
32
33
 
33
34
  /** 通过环境变量检测当前调用方对应的适配器名称 */
34
35
  function detectAdapterName() {
35
- // 兼容变量(CLAUDE_PROJECT_DIR)由 Trae / CodeBuddy 一并注入,故优先判定各自专属变量
36
- if (process.env.CODEBUDDY_PROJECT_DIR) return 'codebuddy';
36
+ // 兼容变量(CLAUDE_PROJECT_DIR)由 Trae / WorkBuddy 一并注入,故优先判定各自专属变量;
37
+ // CODEBUDDY_PROJECT_DIR WorkBuddy 沿用的历史变量名,一并识别为 workbuddy
38
+ if (process.env.WORKBUDDY_PROJECT_DIR || process.env.CODEBUDDY_PROJECT_DIR) return 'workbuddy';
37
39
  if (process.env.TRAE_PROJECT_DIR) return 'trae';
38
40
  if (process.env.QODER_PROJECT_DIR) return 'qoder';
39
41
  if (process.env.CLAUDE_PROJECT_DIR) return 'claude-code';
@@ -19,7 +19,9 @@ const GITIGNORE_RULES = [
19
19
  { add: '.cbb/', accept: ['.cbb/'] },
20
20
  { add: '.codespace/', accept: ['.codespace/', '.codespace'] },
21
21
  { add: '.opencode', accept: ['.opencode', '.opencode/'] },
22
- { add: '.codebuddy', accept: ['.codebuddy', '.codebuddy/'] },
22
+ // .codebuddy:beta.15 及以前写入的旧目录,不再新增(add: null),但卸载/清理时仍移除其行
23
+ { add: null, accept: ['.codebuddy', '.codebuddy/'] },
24
+ { add: '.workbuddy', accept: ['.workbuddy', '.workbuddy/'] },
23
25
  { add: '.trae', accept: ['.trae', '.trae/'] },
24
26
  ];
25
27
 
@@ -31,12 +33,12 @@ function toLines(content) {
31
33
  /**
32
34
  * 给定 .gitignore 内容,返回需要新增的条目。
33
35
  * 已含任一可接受形式(带/不带尾斜杠)的目录会被跳过;全部已存在时返回空数组。
34
- * 返回的是写入形式(.claude / .qoder 无尾斜杠,.cbb/ 带尾斜杠)。
36
+ * 返回的是写入形式(.claude / .qoder 无尾斜杠,.cbb/ 带尾斜杠;add 为 null 的纯遗留规则不新增)。
35
37
  */
36
38
  function entriesToAdd(content) {
37
39
  const lines = toLines(content).map(l => l.trim());
38
40
  return GITIGNORE_RULES
39
- .filter(r => !r.accept.some(a => lines.includes(a)))
41
+ .filter(r => r.add && !r.accept.some(a => lines.includes(a)))
40
42
  .map(r => r.add);
41
43
  }
42
44
 
@@ -20,7 +20,7 @@
20
20
  | `.codespace/` | 各关联应用的基准代码(每个应用一个子目录) | `cbb setup` 与 worktree 流程自动 clone / fetch;**勿手动编辑**;已 gitignore,不提交 |
21
21
  | `.worktrees/` | 需求隔离开发区(每个需求一个 `worktree-<需求名>/` 目录) | worktree 命令自动创建 / 清理;不提交(首次执行 worktree 流程时自动加入 .gitignore) |
22
22
  | `openspec/` | OpenSpec 工作流配置(`config.yaml` + `schemas/`) | 由 cbb 安装;变更产物(`openspec/changes/` 等)worktree 模式在**需求工作树内**生成随需求分支提交,轻量模式直接在空间根生成提交 main |
23
- | `.claude/` `.qoder/` `.opencode/` `.codebuddy/` `.trae/` | AI 工具适配目录:编码规范规则、OpenSpec 命令、worktree 管理技能(skill 形态) | 由 cbb 按 setup 时选择的工具安装;已 gitignore,不提交,勿手动改 |
23
+ | `.claude/` `.qoder/` `.opencode/` `.workbuddy/` `.trae/` | AI 工具适配目录:编码规范规则、OpenSpec 命令、worktree 管理技能(skill 形态) | 由 cbb 按 setup 时选择的工具安装;已 gitignore,不提交,勿手动改 |
24
24
  | `.cbb/` | cbb 安装清单与状态(`.managed-by-cbb`、`.last-tools`) | 由 cbb 管理;**勿手动编辑** |
25
25
 
26
26
  ## 典型流程
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jspg-ai/coding-bb",
3
- "version": "0.0.3-beta.15",
3
+ "version": "0.0.3-beta.17",
4
4
  "description": "整合业界热门且高价值的工具、框架与技能,为 AI CODING AGENT 提供统一的行为准则与工作流,辅助开发者将需求高效落地为符合规范的代码",
5
5
  "main": "cbb/lib/install/init.js",
6
6
  "bin": {
@@ -1,30 +0,0 @@
1
- /**
2
- * WorkBuddy / CodeBuddy 适配器
3
- *
4
- * 腾讯 WorkBuddy 桌面工作台与 CodeBuddy IDE / CLI 共用项目级配置目录 `.codebuddy/`,
5
- * 故一个适配器同时覆盖两者。
6
- *
7
- * 与 Claude Code / Qoder 的映射差异:
8
- * - rules:`.codebuddy/rules/*.md`(扁平文件、递归加载),frontmatter 用
9
- * `alwaysApply: true`(Qoder 的 `trigger: always_on` 需改写,见 ruleRewrites)
10
- * - skills:`.codebuddy/skills/<name>/SKILL.md`,结构与 Claude 同构
11
- * - commands:`.codebuddy/commands/`,支持目录嵌套(`/group:command`),与 Claude 同风格
12
- * - hooks:完全兼容 Claude Code Hooks 规范,settings 结构一致,直接复用 settingsManager
13
- */
14
-
15
- const path = require('path');
16
- const os = require('os');
17
-
18
- module.exports = {
19
- name: 'codebuddy',
20
- displayName: 'WorkBuddy(CodeBuddy)',
21
- rulesDir: '.codebuddy/rules',
22
- skillsDir: '.codebuddy/skills',
23
- commandsDir: '.codebuddy/commands/opsx',
24
- // 规则 frontmatter 改写:Qoder 风格 → CodeBuddy 风格
25
- ruleRewrites: [['trigger: always_on', 'alwaysApply: true']],
26
- // 用户级 settings,写入全局生效,不污染项目目录
27
- settingsFile: path.join(os.homedir(), '.codebuddy', 'settings.json'),
28
- settingsFileIsUserLevel: true,
29
- hookEvents: ['UserPromptSubmit'],
30
- };