@jspg-ai/coding-bb 0.0.3-beta.14 → 0.0.3-beta.16

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.
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * WorkBuddy / CodeBuddy 适配器
3
3
  *
4
- * 腾讯 WorkBuddy 桌面工作台与 CodeBuddy IDE / CLI 共用项目级配置目录 `.codebuddy/`,
5
- * 故一个适配器同时覆盖两者。
4
+ * 腾讯 WorkBuddy 桌面工作台的项目级配置目录为 `.workbuddy/`(实测:skill 只有
5
+ * 放在 `.workbuddy/skills/` 才生效;旧版假设「与 CodeBuddy 共用 .codebuddy/」不成立)。
6
+ * 0.0.3-beta.16 起目录与用户级 settings 全部切到 `.workbuddy`,`.codebuddy` 作为
7
+ * legacy 由安装流程迁移清理(legacyDirs / legacySettingsFile)。
6
8
  *
7
9
  * 与 Claude Code / Qoder 的映射差异:
8
- * - rules:`.codebuddy/rules/*.md`(扁平文件、递归加载),frontmatter 用
10
+ * - rules:`.workbuddy/rules/*.md`(扁平文件、递归加载),frontmatter 用
9
11
  * `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
12
+ * - skills:`.workbuddy/skills/<name>/SKILL.md`,结构与 Claude 同构
13
+ * - commands:`.workbuddy/commands/`,支持目录嵌套(`/group:command`),与 Claude 同风格
14
+ * - hooks:兼容 Claude Code Hooks 规范,settings 结构一致,直接复用 settingsManager
13
15
  */
14
16
 
15
17
  const path = require('path');
@@ -18,13 +20,16 @@ const os = require('os');
18
20
  module.exports = {
19
21
  name: 'codebuddy',
20
22
  displayName: 'WorkBuddy(CodeBuddy)',
21
- rulesDir: '.codebuddy/rules',
22
- skillsDir: '.codebuddy/skills',
23
- commandsDir: '.codebuddy/commands/opsx',
23
+ rulesDir: '.workbuddy/rules',
24
+ skillsDir: '.workbuddy/skills',
25
+ commandsDir: '.workbuddy/commands/opsx',
24
26
  // 规则 frontmatter 改写:Qoder 风格 → CodeBuddy 风格
25
27
  ruleRewrites: [['trigger: always_on', 'alwaysApply: true']],
26
28
  // 用户级 settings,写入全局生效,不污染项目目录
27
- settingsFile: path.join(os.homedir(), '.codebuddy', 'settings.json'),
29
+ settingsFile: path.join(os.homedir(), '.workbuddy', 'settings.json'),
28
30
  settingsFileIsUserLevel: true,
29
31
  hookEvents: ['UserPromptSubmit'],
32
+ // 旧版目录/文件(beta.15 及以前写入 .codebuddy):安装时迁移清理
33
+ legacyDirs: ['.codebuddy'],
34
+ legacySettingsFile: path.join(os.homedir(), '.codebuddy', 'settings.json'),
30
35
  };
@@ -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/CodeBuddy (.workbuddy/rules/)、TraeWork/TraeCode (.trae/rules/)
9
9
  *
10
10
  * 使用方式:
11
11
  * npm install -g @jspg-ai/coding-bb # 首次使用
@@ -40,6 +40,7 @@ function execCmd(file, args, opts = {}) {
40
40
  }
41
41
 
42
42
  const SHARED_STANDARDS_DIR = path.join(__dirname, '..', '..', '..');
43
+ const setupDecision = require('./setup-decision');
43
44
 
44
45
  // 目标项目根目录:默认取当前 cwd,init() 可在调用前覆盖。
45
46
  // 注意:使用 let 而非 const 是为了让 cbb setup 能在拉好仓库后把目标目录
@@ -93,6 +94,16 @@ function hookOpts(adapter) {
93
94
  return adapter.hookDialect ? { dialect: adapter.hookDialect } : {};
94
95
  }
95
96
 
97
+ /**
98
+ * 迁移清理:移除本包写在旧用户级 settings 里的 hook(如 WorkBuddy 适配修正:
99
+ * ~/.codebuddy/settings.json → ~/.workbuddy/settings.json)。
100
+ * removeHooks 只删 _managedBy: cbb 标记的条目,文件清空则删除。返回 true 表示有改动。
101
+ */
102
+ function cleanLegacySettings(adapter) {
103
+ if (!adapter.legacySettingsFile || !adapter.hookEvents) return false;
104
+ return settingsManager.removeHooks(adapter.legacySettingsFile, adapter.hookEvents, hookOpts(adapter));
105
+ }
106
+
96
107
  // 清单驱动的文件清理决策(保护用户文档 CLAUDE.md / AGENTS.md 等)
97
108
  const { pathsToDelete, protectedKeptPaths } = require('./cleanup');
98
109
 
@@ -1152,6 +1163,10 @@ async function init(targetDir, options = {}) {
1152
1163
  if (settingsManager.removeLegacyHooks(settingsPath, hookOpts(adapter))) {
1153
1164
  log(`清理 ${adapter.displayName} 遗留的 SessionStart hook`, 'success');
1154
1165
  }
1166
+ // 迁移:清理旧用户级 settings(如 ~/.codebuddy → ~/.workbuddy)
1167
+ if (cleanLegacySettings(adapter)) {
1168
+ log(`迁移清理旧用户级配置 ${adapter.legacySettingsFile}`, 'success');
1169
+ }
1155
1170
  if (!adapter.settingsFileIsUserLevel) newPaths.add(adapter.settingsFile);
1156
1171
  });
1157
1172
  log(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join(', ')} (${hookAdapters[0].hookEvents.join(', ')})`, 'success');
@@ -1246,28 +1261,41 @@ async function init(targetDir, options = {}) {
1246
1261
  // ── cbb setup 命令 ──────────────────────────────────────
1247
1262
 
1248
1263
  /**
1249
- * 若 targetDir 已安装,比较已装版本与当前包版本决定是否继续安装。
1250
- * - 版本一致 → 返回 'skip'(不询问、不重复安装)
1251
- * - 版本不同 → 返回 'install'(setup 已整体确认过,直接升级到当前包版本,不再二次询问)
1252
- * - 未安装 返回 'install'(不提示,直接继续)
1264
+ * 若 targetDir 已安装,比较「已装版本 vs 当前包版本」与「本次工具选择 vs .last-tools」
1265
+ * 决定是否继续安装(判定纯逻辑见 setup-decision.js)。
1266
+ * - 版本不同 → 'install'(setup 已整体确认过,直接升级到当前包版本,不再二次询问)
1267
+ * - 版本一致但本次选择含 .last-tools 之外的新工具(或无记录)→ 'install'
1268
+ * (beta.14 及以前的 bug:版本一致即整体跳过,新工具的空间级目录永不安装)
1269
+ * - 版本一致且所选 ⊆ .last-tools → 'skip'(不询问、不重复安装)
1253
1270
  *
1254
1271
  * 注:只比较「已装版本 vs 当前包版本」——安装动作只能落盘当前包的产物,
1255
1272
  * registry 是否有更新由用户级版本检查 hook 提醒,不在此处联网查询。
1273
+ * @param {string} targetDir
1274
+ * @param {Array<{name:string}>} [selectedAdapters] 本次 setup 选中的工具
1256
1275
  * @returns {'install'|'skip'}
1257
1276
  */
1258
- function decideUpgradeIfInstalled(targetDir) {
1277
+ function decideUpgradeIfInstalled(targetDir, selectedAdapters) {
1259
1278
  const installed = version.readInstalledVersion(targetDir);
1260
1279
  if (!installed) return 'install'; // 未安装:不提示,直接继续
1261
1280
 
1262
1281
  const pkg = require(path.join(SHARED_STANDARDS_DIR, 'package.json'));
1282
+ const decision = setupDecision.decideSetupAction({
1283
+ sameVersion: version.compareVersions(installed, pkg.version) === 0,
1284
+ lastTools: setupDecision.readLastToolsFrom(targetDir),
1285
+ selectedNames: (selectedAdapters || []).map(a => a.name),
1286
+ });
1263
1287
 
1264
- // 版本未变化 不处理(不询问、不重复安装)
1265
- if (version.compareVersions(installed, pkg.version) === 0) {
1288
+ if (decision.action === 'skip') {
1266
1289
  step('✓', '版本', `v${installed}(已是最新,跳过)`);
1267
1290
  return 'skip';
1268
1291
  }
1269
-
1270
- step('✓', '版本', `v${installed} → v${pkg.version}`);
1292
+ if (decision.reason === 'new-tools') {
1293
+ step('✓', '版本', `v${installed}(版本一致,新增工具 ${decision.newTools.join('、')} → 继续安装)`);
1294
+ } else if (decision.reason === 'no-tools-record') {
1295
+ step('✓', '版本', `v${installed}(无工具选择记录 → 继续安装)`);
1296
+ } else {
1297
+ step('✓', '版本', `v${installed} → v${pkg.version}`);
1298
+ }
1271
1299
  return 'install';
1272
1300
  }
1273
1301
 
@@ -1558,8 +1586,8 @@ async function initSingleWorkspace(w, opts = {}) {
1558
1586
  console.log(` ${c('gray', `${names} → 编辑 workspace-config.json 填入真实仓库地址`)}`);
1559
1587
  }
1560
1588
 
1561
- // 5. 若已安装:版本未变化直接跳过;有新版本直接升级(setup 已整体确认过,不再二次询问)
1562
- const decision = decideUpgradeIfInstalled(targetDir);
1589
+ // 5. 若已安装:版本一致且无新增工具才跳过;否则继续安装/升级(setup 已整体确认过,不再二次询问)
1590
+ const decision = decideUpgradeIfInstalled(targetDir, opts.selectedAdapters);
1563
1591
  if (decision === 'skip') {
1564
1592
  return { ok: true, subdir: subdirName, upToDate: true };
1565
1593
  }
@@ -1762,6 +1790,7 @@ async function setupUserLevelComponents(targetAdapters = adapters) {
1762
1790
  hookAdapters.forEach(adapter => {
1763
1791
  settingsManager.addHooks(adapter.settingsFile, command, adapter.hookEvents, hookOpts(adapter));
1764
1792
  settingsManager.removeLegacyHooks(adapter.settingsFile, hookOpts(adapter));
1793
+ cleanLegacySettings(adapter);
1765
1794
  });
1766
1795
  summaryParts.push(`版本检查 hook → ${hookAdapters.map(a => a.displayName).join('、')}`);
1767
1796
  }
@@ -1880,6 +1909,9 @@ async function uninstallUserLevelComponents() {
1880
1909
  if (settingsManager.removeHooks(adapter.settingsFile, adapter.hookEvents, hookOpts(adapter))) {
1881
1910
  log(`${adapter.settingsFile}: 移除版本检查 hook,保留用户配置`, 'success');
1882
1911
  }
1912
+ if (cleanLegacySettings(adapter)) {
1913
+ log(`${adapter.legacySettingsFile}: 移除版本检查 hook(旧路径),保留用户配置`, 'success');
1914
+ }
1883
1915
  });
1884
1916
 
1885
1917
  removePath(USER_MANIFEST_FILE);
@@ -1908,7 +1940,7 @@ async function uninstall() {
1908
1940
  // 是否同时卸载用户级组件(工具 skills + 版本检查 hook,作用于 ~/.claude 等用户目录)
1909
1941
  let removeUserLevel = false;
1910
1942
  if (fs.existsSync(USER_MANIFEST_FILE)) {
1911
- const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.codebuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
1943
+ const answerUser = await ask('是否同时卸载用户级组件(~/.claude、~/.qoder、~/.workbuddy、~/.trae-cn 的版本检查 hook)?(y/N): ');
1912
1944
  removeUserLevel = answerUser.toLowerCase() === 'y' || answerUser.toLowerCase() === 'yes';
1913
1945
  }
1914
1946
 
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * cbb setup 空间级安装的跳过判定(纯逻辑,可单测)。
5
+ *
6
+ * 背景 bug(0.0.3-beta.15 修复):旧版 decideUpgradeIfInstalled 只比较
7
+ * 「已装版本 vs 当前包版本」,版本一致即 skip——导致「空间已是当前版本 +
8
+ * 本次新选了工具(如 WorkBuddy)」被整体跳过,新工具的空间级目录
9
+ * (.workbuddy/ 等)永不生成,.last-tools 也不更新。
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ /**
16
+ * 读取 <targetDir>/.cbb/.last-tools 的已装工具集合。
17
+ * @param {string} targetDir 业务空间根
18
+ * @returns {Set<string>|null} 工具名集合;无文件或解析失败返回 null
19
+ */
20
+ function readLastToolsFrom(targetDir) {
21
+ const filePath = path.join(targetDir, '.cbb', '.last-tools');
22
+ if (!fs.existsSync(filePath)) return null;
23
+ try {
24
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
25
+ return new Set(data.tools || []);
26
+ } catch (_) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 判定已安装空间本次 setup 应跳过还是继续安装。
33
+ * @param {object} p
34
+ * @param {boolean} p.sameVersion 已装版本 == 当前包版本
35
+ * @param {Set<string>|null} p.lastTools .last-tools 记录(null = 无记录)
36
+ * @param {string[]} p.selectedNames 本次选中的工具名
37
+ * @returns {{action:'skip'|'install', newTools:string[], reason:string}}
38
+ * reason: 'version-changed' | 'no-tools-record' | 'new-tools' | 'up-to-date'
39
+ */
40
+ function decideSetupAction({ sameVersion, lastTools, selectedNames }) {
41
+ if (!sameVersion) return { action: 'install', newTools: [], reason: 'version-changed' };
42
+ if (lastTools === null) return { action: 'install', newTools: [], reason: 'no-tools-record' };
43
+ const newTools = (selectedNames || []).filter(n => !lastTools.has(n));
44
+ if (newTools.length > 0) return { action: 'install', newTools, reason: 'new-tools' };
45
+ return { action: 'skip', newTools: [], reason: 'up-to-date' };
46
+ }
47
+
48
+ module.exports = { decideSetupAction, readLastToolsFrom };
@@ -20,6 +20,7 @@ const GITIGNORE_RULES = [
20
20
  { add: '.codespace/', accept: ['.codespace/', '.codespace'] },
21
21
  { add: '.opencode', accept: ['.opencode', '.opencode/'] },
22
22
  { add: '.codebuddy', accept: ['.codebuddy', '.codebuddy/'] },
23
+ { add: '.workbuddy', accept: ['.workbuddy', '.workbuddy/'] },
23
24
  { add: '.trae', accept: ['.trae', '.trae/'] },
24
25
  ];
25
26
 
@@ -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.14",
3
+ "version": "0.0.3-beta.16",
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/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/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/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/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/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",